您的位置:首页 > 其它

A cheap way to hit test unregular area

2013-10-21 22:26 253 查看
Hit-testing an unregular area is important when you're creating some game UI. Here is a cheap way. When I say cheap, it's extremely fast, and has very tiny memory foot print.

Here is the idea:

1. Write a program to process the image which represents the unregular area, and produce some binary data for easy hit-testing.

2. In the app, when hit-testing is required, simply check the corresponding bit.

Preparing the data is simple. I used C# to complete this task.

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;

namespace MakeHitArea
{
class Program
{
static void Main(string[] args)
{
foreach (string s in args)
{
using (HitAreaBuilder builder = new HitAreaBuilder(s))
{
builder.Build();
}
}
}
}

public class HitAreaBuilder : IDisposable
{
Bitmap thePicture = null;
string theOutName = string.Empty;
int theWidth = 0;
int theHeight = 0;
byte[] theBaked = null;

public HitAreaBuilder(string file)
{
try
{
using (Image image = Image.FromFile(file))
{
thePicture = new Bitmap(image);
}

string path = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(path))
{
path = Environment.CurrentDirectory;
}

string filename = Path.GetFileNameWithoutExtension(file);
theOutName = string.Format("{0}\\{1}.hit", path, filename);
}
catch
{
Console.Error.WriteLine("Can't load image {0}", file);
}
}

public void Dispose()
{
if (thePicture != null)
{
thePicture.Dispose();
thePicture = null;
}
}

public void Build()
{
if (PrepareData())
{
SaveData();
}
}

bool PrepareData()
{
bool succ = false;

if (thePicture != null)
{
theWidth = thePicture.Width;
theHeight = thePicture.Height;

// Align the width for easier processing
int linebytes = (theWidth + 7) / 8;
theWidth = linebytes * 8;

theBaked = new byte[linebytes * theHeight];

for (int y = 0; y < theHeight; y++)
{
for (int x = 0; x < linebytes; x++)
{
byte dat = 0;

for (int bit = 0; bit < 8; bit++)
{
if (x * 8 + bit < thePicture.Width)
{
// If the pixel is visible enough, we set the bit to hit-able.

Color c = thePicture.GetPixel(x * 8 + bit, y);
if (c.A >= 200)
{
dat = (byte)(dat | (1 << bit));
}
}
}

theBaked[y * linebytes + x] = dat;
}
}

succ = true;
}

return succ;
}

void SaveData()
{
try
{
using (FileStream fs = new FileStream(theOutName, FileMode.Create, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(fs))
{
writer.Write((short)theWidth);
writer.Write((short)theHeight);
writer.Write(theBaked);
}
}
}
catch
{
Console.Error.WriteLine("Write to file failed.");
}
}
}
}


This little program reads the picture file, check every pixel's transparency, and set visible enough pixel's bit to 1.

Now in the game app, we can check it very easily. Here is the HitArea class:

class HitArea
{
public:

HitArea(const std::string& file);
~HitArea();

bool test(int x, int y);

private:

HitArea();
HitArea(const HitArea&);
HitArea& operator= (const HitArea&);

protected:

short _width;
short _height;
unsigned char* _area;
};

HitArea::HitArea(const std::string& file)
: _width(0)
, _height(0)
, _area(NULL)
{
std::ifstream fs(file.c_str(), std::ios::in | std::ios::binary);

if (fs)
{
fs.read((char*)&_width, sizeof(_width));
fs.read((char*)&_height, sizeof(_height));

int size = _width / 8 * _height;
_area = new unsigned char[size];

fs.read((char*)_area, size);
}
}

HitArea::~HitArea()
{
if (_area)
delete[] _area;
}

bool HitArea::test(int x, int y)
{
if (x < 0 || x >= _width || y < 0 || y > _height)
return false;

int bytesPerLine = _width / 8;
int hitByte = bytesPerLine * y + x / 8;
int hitBit = x % 8;

return !!((_area[hitByte] >> hitBit) & 1);
}


Very easy and extremely fast. Isn't it?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: