需求介绍
- 触摸虚线内的不规则区域,触发响应逻辑
问题分析
- 一般游戏引擎提供给我们的按钮一般都是矩型或者其他的规则形状,无法完成上述需求
- 透明区域,图片的 alpha 通道值为 0(), 非透明区域 alpha 值不为 0,根据这个特征实现不规则边缘检测,从而判断响应事件是否触发响应逻辑
- 实时点击扫描运算量较高,在本项目场景中没有运行效率没有问题,但是在实时高频场景性能吃紧,为了做到一次封装万年使用,因此需要在初始化过程存储 alpha 信息备用。
- 美术工作: 提供不通的不规则按钮,N个按钮拼接成图中的不规则按钮
原理拓展
- 图片存储原理
一个 4×3 像素的图片,以 RGBA8888 色彩模式加载到内存中后,那么会以一大串连续的数据存储在内存中,例如:
序号 1、2、3、4、5 各自表示一个像素点的信息,每一个像素点中的四个值分别代表 R(红色)、G(绿色)、B、(蓝色)、A(透明度),每个值的范围为 0 ~ 255(图中以 16 进制表示即范围 00~FF),每个值可用 8 个 bit 进行表示。
实现方案
0、头文件:
#ifndef __irregularButton_h__
#define __irregularButton_h__
#include "header.h"
class irregularButton : public Button {
public:
irregularButton();
irregularButton(int alphaCheckValue, bool isOptimized);
virtual ~irregularButton();
virtual bool init(const std::string& normalImage, const std::string& selectedImage = "", const std::string& disableImage = "", Widget::TextureResType texType = Widget::TextureResType::LOCAL) override;
virtual bool hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const override;
/* @param alphaCheckValue if image's alphaValue larger than alphaCheckValue,the click will happen @param isOptimized if use bit to store the alpha data, it will use smaller memory */
static irregularButton* create(const std::string& normalImage, const std::string& selectedImage = "", const std::string& disableImage = "",int alphaCheckValue = 0, bool isOptimized = false, Widget::TextureResType texType = Widget::TextureResType::LOCAL);
/* @param alphaCheckValue will use "btn" normalImage,selectedImage and disableImage to init the irregularButton,will not change the "btn" @param alphaCheckValue if image's alphaValue larger than alphaCheckValue,the click will happen @param isOptimized if use bit to store the alpha data, it will use smaller memory */
static irregularButton* createWithButton(Button * btn, int alphaCheckValue = 0, bool isOptimized = false);
protected:
bool getIsTransparentAtPoint(cocos2d::Vec2 point) const;//获取点击到的像素数据是否大于checkAlphaValue
void loadNormalTransparentInfo(std::string normalImage); //初始化按钮
private:
int _alphaCheckValue;
int normalImageWidth_;
int normalImageHeight_;
bool _isOptimized;
int _alphaDataWidth;
int _alphaDataHeight;
unsigned char * _imageAlpha;
unsigned int _alphaDataLength;
};
#endif
1、读取像素值并数据判断每个像素点的 alpha 值,采取 bool 类型进行存储。
-
内存分析:
首先 RGBA(32bit)我们只取了 Alpha(8bit)通道进行判断,判断结果按 bool 值进行存储,一个 bool 值类型在内存中占 8bit,因此此时内存为图形内存的 1/4。
-
优化
为了进一步减少内存,增加了另一种按位存储的方式。
倘若我们结合位的或运算将每 8 个判断结果存入一个 char 的每一个 Bit 中,该 bit 为 1 则不透明,为 0 则透明,那么内存又为原来 1/8,总的算起来就是图形内存的 1/32 拉。
-
内存示例
一张 1024×1024 的图,内存占用大小为 1024x1024x(32bit/8)= 4194304byte = 4096kb = 4m,那么我们的存储数组内存大小为 4096/32 = 128kb,应该可以应付绝大多数的变态内存需求了。
-
先扫描进行存储的目的是避免用户进行操作时再进行扫描,这样可能在平凡操作中用户会有卡顿感以及手感不舒服。
-
此处实现不一定判断值为 0,可为用户指定临界值。
void irregularButton::loadNormalTransparentInfo(std::string sName) {
Image* normalImage = new Image();
normalImage->initWithImageFile(sName);
normalImageWidth_ = normalImage->getWidth();
normalImageHeight_ = normalImage->getHeight();
this->setContentSize(Size(normalImageWidth_, normalImageHeight_));
if (_imageAlpha != nullptr) {
delete[] _imageAlpha;
}
unsigned char* imageData = normalImage->getData();
_alphaDataWidth = normalImageWidth_;//default
_alphaDataHeight = normalImageHeight_;
if (_isOptimized) {
_alphaDataWidth = (normalImageWidth_ + 7) >> 3;//char == 8 bit,
_alphaDataLength = _alphaDataWidth * normalImageHeight_ * sizeof(unsigned char);
_imageAlpha = new unsigned char[_alphaDataLength];
memset(_imageAlpha, 0, _alphaDataLength);
for (int i = 0; i < normalImageHeight_; i++)
{
for (int j = 0; j < normalImageWidth_; j += 8)
{
int aj = j >> 3;
for (int k = 0; k < 8; k++)
{
if (j + k >= normalImageWidth_)
{
break;
}
unsigned char alpha = imageData[((i*normalImageWidth_ + j + k) << 2) + 3];
if (alpha > _alphaCheckValue)
{//set 1 in the corresponding bit position
int index = i * _alphaDataWidth + aj;
_imageAlpha[index] = _imageAlpha[index] | (1 << k);
}
}
}
}
}
else {
_alphaDataLength = normalImageWidth_ * normalImageHeight_ * sizeof(unsigned char);
_imageAlpha = new unsigned char[_alphaDataLength];
for (int i = 0; i < normalImageHeight_; i++)
{
for (int j = 0; j < normalImageWidth_; j++)
{
int index = i * normalImageWidth_ + j;
_imageAlpha[index] = imageData[(index << 2) + 3];
}
}
}
delete normalImage;
}
2、拦截触摸,获得触摸位置。
我们通过继承 cocos2dx 中的 button 组件来封装我们的组件,通过阅读 Button 源代码,我们发现父类的虚函数 hitTest()会在触摸触发时拦截触摸,并触发我们注册的回调, 我们可以在该函数中调用判断 alpha 的函数。
bool irregularButton::hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const {
Vec2 localLocation = _buttonNormalRenderer->convertToNodeSpace(pt);
Rect validTouchedRect;
validTouchedRect.size = _buttonNormalRenderer->getContentSize();
if (validTouchedRect.containsPoint(localLocation) && getIsTransparentAtPoint(localLocation))
{
//NotificationCenter::getInstance()->postNotification("NotifyIrregularBtn", (Ref*)m_iBtnID);
return true;
}
return false;
}
3、判断触摸点是否大于指定的 alpha 值,这里要根据两种存储方式(是否按 bit 存储)分别采用不同的判断逻辑。
bool irregularButton::getIsTransparentAtPoint(cocos2d::Vec2 point) const {
auto data = _imageAlpha;
if (data == nullptr) {
return true;
}
bool isTouchClaimed = false;
auto locationInNode = point;//this->convertToNodeSpace(point);
int x = (int)locationInNode.x;
int y = (int)locationInNode.y;
if (x >= 0 && x < normalImageWidth_ && y >= 0 && y < normalImageHeight_) {
if (_isOptimized) {
unsigned int i = (_alphaDataHeight - y - 1) * _alphaDataWidth + (x >> 3);
unsigned char mask = 1 << (x & 0x07);
if (i < _alphaDataLength && ((data[i] & mask) != 0))
{
isTouchClaimed = true;
}
}
else
{
unsigned int i = (_alphaDataHeight - y - 1) * _alphaDataWidth + x;
if (i < _alphaDataLength)
{
if ((unsigned int)data[i] > _alphaCheckValue)
{
isTouchClaimed = true;
}
}
}
}
return isTouchClaimed;
}
- 注意在对象析构时释放存储判断结果的内存,否则会造成内存泄漏。
今天的文章奇形怪状-不规则按钮实现分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/22859.html