Demand is introduced

  • Touch the irregular area inside the dotted line to trigger the response logic

Problem analysis

  • The buttons given to us by game engines are usually rectangular or other regular shapes that do not meet this requirement
  • In the transparent region, the alpha channel value of the image is 0(), while in the non-transparent region, the alpha value is not 0. According to this feature, irregular edge detection is implemented to determine whether the response event triggers the response logic
  • Real-time click-scan requires a high amount of computation, which has no operational efficiency in the scenario of this project. However, in the real-time high-frequency scenario, the performance is tight. In order to achieve a one-time encapsulation, alpha information needs to be stored in the initialization process for backup.
  • Art work: irregular buttons are provided. N buttons are spliced into irregular buttons in the picture

The principle of development

  • Picture storage Principle

When a 4×3 pixel image is loaded into memory in RGBA8888 color mode, it will be stored in memory as a large sequence of data, such as:

Serial number 1, 2, 3, 4 and 5 respectively represent the information of A pixel point, and the four values in each pixel point respectively represent R(red), G (green), B, (blue) and A (transparency), and the range of each value is 0~ 255 (represented in hexadecimal in the figure, i.e. the range is 00~FF). Each value can be represented by eight bits.

Implementation scheme

0, header file:

#ifndef __irregularButton_h__
#define __irregularButton_h__

#include "header.h"

class irregularButton : public Button {
public:
    irregularButton(a);irregularButton(int alphaCheckValue, bool isOptimized);
    virtual ~irregularButton(a);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;// Get whether the clicked pixel data is greater than checkAlphaValue
    void loadNormalTransparentInfo(std::string normalImage);   // Initialize button
private:

    int _alphaCheckValue;
    int normalImageWidth_;
    int normalImageHeight_;

    bool _isOptimized;
    int _alphaDataWidth;
    int _alphaDataHeight;
    unsigned char * _imageAlpha;
    unsigned int _alphaDataLength;
};


#endif
Copy the code

1. Read the pixel value and determine the alpha value of each pixel, which is stored as bool.

  • Memory analysis:

    First of all, RGBA (32bit) we only take the Alpha (8bit) channel for judgment, and the judgment result is stored according to bool value. A bool value type occupies 8bit in memory, so the memory at this time is 1/4 of the graphics memory.

  • To optimize the

    To further reduce memory, another bitwise storage method was added.

    If we store every 8 judgments in each Bit of a char with Bit or, and the Bit is opaque if it is 1 and transparent if it is 0, then the memory is 1/8, which adds up to 1/32 of the graphics memory.

  • Memory sample

    For a 1024×1024 image, the memory usage is 1024x1024x (32bit/8) = 4194304byte = 4096KB = 4m, so the memory size of our storage array is 4096/32 = 128KB. It should be able to handle most of the abnormal memory requirements.

  • The purpose of scanning for storage is to avoid users to scan before operation, which may cause users to feel stuck and uncomfortable in ordinary operation.

  • The implementation does not necessarily judge the value to be 0, but can specify a critical value for the user.

void irregularButton::loadNormalTransparentInfo(std::string sName) {
    Image* normalImage = new Image(a); normalImage->initWithImageFile(sName);
    normalImageWidth_ = normalImage->getWidth(a); normalImageHeight_ = normalImage->getHeight(a);this->setContentSize(Size(normalImageWidth_, normalImageHeight_));
    if(_imageAlpha ! =nullptr) {
        delete[] _imageAlpha;
    }
    unsigned char* imageData = normalImage->getData(a); _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;
}
Copy the code

2, intercept the touch, get the touch position.

We encapsulate our component by inheriting the Button component from Cocos2DX. Reading the Button source code, we found that the virtual function hitTest() of the parent class intercepts the touch when it is triggered and triggers the callback we registered, We can call the function that determines alpha in this function.

bool irregularButton::hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const {
    Vec2 localLocation = _buttonNormalRenderer->convertToNodeSpace(pt);
    Rect validTouchedRect;
    validTouchedRect.size = _buttonNormalRenderer->getContentSize(a);if (validTouchedRect.containsPoint(localLocation) && getIsTransparentAtPoint(localLocation))
    {
        //NotificationCenter::getInstance()->postNotification("NotifyIrregularBtn", (Ref*)m_iBtnID);
        return true;
    }
   return false;
}
Copy the code

3. To judge whether the touch point is greater than the specified alpha value, different judgment logic shall be used according to the two storage methods (whether it is stored by bit or not).

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;
}
Copy the code
  • Release the memory that stores the judgment results during object destruction. Otherwise, memory leaks will occur.