0

I've been trying to create a dynamic light effect with cocos2d-x, but I cannot find a good tutorial for the 4.0 version.

For the context, I'm developping a top-down game for as a personnal project. The light would spread directly in front of the player like a flash-light, lighting every object in front of him and "colliding" with the walls and entities

The Shader hint

I've found multiple repos creating a custom light effect with cocos2d-x, but they all use the 3.0 version. The 4.0 version changed how the OpenGL code is managed in the Cocos backend.

Here's the repos I've found :

I've also read the Cocos2d-x Gitbook which explains what exactly changed in the CCSprite file. Since I'm a beginner to Cocos2d-x and shader rendering, I did not understand what I had to change to make the old repos work ..

The polygon hint

I tried to create a VisionZone that would be a Cocos2d-x Node. The node would have a physic body colliding with the walls. On collision, the shape would update and adapt its shape.

Here's what the code would look like :

VisionZone.h

class VisionZone : public Node {

public:

    static std::vector<Vec2> sm_shapeCollisionPoints;

    static VisionZone *create(Vec2 origin, int visionDetail);

    bool init() override;

    void update(float delta) override;

    void UpdateWithPlayer(Vec2 playerPos);

private:

    void CreateNewPhysicBody(std::vector<Vec2> &points);

    void UpdateShapeAndRedraw();

    static bool IsInLine(Vec2 segmentStart, Vec2 segmentEnd, Vec2 point);

    void CompareWithContactPoints();

    void Rotate();

    std::vector<Vec2> m_nonCollidedSegmentsEnds;
    std::vector<Vec2> m_collidedSegmentsEnds;
    Vec2 m_origin;

    DrawNode *m_pVisionZoneDrawer;

    int m_visionDetail;

VisionZone.cpp

std::vector<Vec2> VisionZone::sm_shapeCollisionPoints = {};

VisionZone *VisionZone::create(Vec2 origin, int visionDetail) {
    auto *_obj = new(std::nothrow) VisionZone();
    if (_obj && _obj->init()) {
        _obj->m_origin = origin;
        _obj->m_visionDetail = visionDetail;

        int mid = std::floor(_obj->m_visionDetail * 0.5);
        for (int i = -(mid); i <= mid; i++) {
            _obj->m_nonCollidedSegmentsEnds.emplace_back(
                    static_cast<float>(_obj->m_origin.x + float(i) * 1.3f), _obj->m_origin.y + 300
            );
        }
        _obj->m_collidedSegmentsEnds = _obj->m_nonCollidedSegmentsEnds;

        std::vector<Vec2> _points = _obj->m_nonCollidedSegmentsEnds;
        _points.emplace_back(_obj->m_origin);
        _obj->CreateNewPhysicBody(_points);
    } else
        CC_SAFE_DELETE(_obj);
    return _obj;
}

bool VisionZone::init() {
    if (!Node::init()) return false;

    m_pVisionZoneDrawer = DrawNode::create();
    m_pVisionZoneDrawer->setPosition(m_origin);
    assert(m_pVisionZoneDrawer);
    addChild(m_pVisionZoneDrawer);

    return true;
}

void VisionZone::update(float delta) {
    Node::update(delta);

    CompareWithContactPoints();

    Rotate();

    UpdateShapeAndRedraw();
}

void VisionZone::UpdateWithPlayer(Vec2 playerPos) {
    m_origin = playerPos;
    setPosition(m_origin);
}

void VisionZone::CreateNewPhysicBody(std::vector<Vec2> &points) {
    points.push_back(m_origin);
    PhysicsBody *_body = PhysicsBody::createEdgePolygon(
            &points.front(),
            int(points.size()),
            PHYSICSBODY_MATERIAL_DEFAULT,
            1
    );
    _body->setCategoryBitmask(vision_zone_collision_bitmask);
    _body->setCollisionBitmask(map_collision_bitmask);
    _body->setContactTestBitmask(true);
    _body->setDynamic(false);
    setPhysicsBody(_body);
}

bool VisionZone::IsInLine(Vec2 segmentStart, Vec2 segmentEnd, Vec2 point) {
    float _startToPoint = sqrt(pow((segmentStart.x - point.x), 2) + pow((segmentStart.y - point.y), 2));
    float _pointToEnd = sqrt(pow((point.x - segmentEnd.x), 2) + pow((point.y - segmentEnd.y), 2));
    float _startToEnd = sqrt(pow((segmentStart.x - segmentEnd.x), 2) + pow((segmentStart.y - segmentEnd.y), 2));
    return (_startToPoint + _pointToEnd == _startToEnd);
}

void VisionZone::CompareWithContactPoints() {
    if (sm_shapeCollisionPoints.empty()) {
        m_collidedSegmentsEnds = {m_origin, m_nonCollidedSegmentsEnds.front(), m_nonCollidedSegmentsEnds.back()};
        return;
    }

    for (Vec2 &_nonCollidedEnd: m_nonCollidedSegmentsEnds) {

        for (Vec2 &_contactPoint: sm_shapeCollisionPoints) {

            if (IsInLine(m_origin, _nonCollidedEnd, _contactPoint)) {

                Vec2 _midPoint = (m_nonCollidedSegmentsEnds.front() + m_nonCollidedSegmentsEnds.back()) / 2;

                if (IsInLine(m_nonCollidedSegmentsEnds.front(), _midPoint, _contactPoint)) {
                    m_collidedSegmentsEnds = {
                            m_origin, sm_shapeCollisionPoints.front(), sm_shapeCollisionPoints.back(),
                            m_nonCollidedSegmentsEnds.back()
                    };
                } else if (IsInLine(_midPoint, m_nonCollidedSegmentsEnds.back(), _contactPoint)) {
                    m_collidedSegmentsEnds = {
                            m_origin, m_nonCollidedSegmentsEnds.front(), sm_shapeCollisionPoints.front(),
                            sm_shapeCollisionPoints.back()
                    };
                }
            }
        }
    }
}

void VisionZone::Rotate() {
    float _distanceX = InputManager::GetCursorPosX() - getPositionX();
    float _distanceY = InputManager::GetCursorPosY() - getPositionY();
    float _angle = atan2(_distanceY, _distanceX) * 180.f / static_cast<float>(M_PI);
    setRotation(_angle);
}

void VisionZone::UpdateShapeAndRedraw() {
    PhysicsShape *_newShape = getPhysicsBody()->addShape(PhysicsShapePolygon::create(
            m_collidedSegmentsEnds.data(), int(m_collidedSegmentsEnds.size()))
    );
    assert(_newShape);

    m_pVisionZoneDrawer->clear();
    m_pVisionZoneDrawer->drawSolidPoly(
            &m_collidedSegmentsEnds.front(),
            m_collidedSegmentsEnds.size(),
            Color4F(1.f, 0.94, 0.7, 1)
    );
}

CollisionManager.cpp

#include "CollisionManager.h"
#include "Utility/Bitmasks.h"

USING_NS_CC;

EventListenerPhysicsContact *CollisionManager::m_contactListener = nullptr;

void CollisionManager::Init() {
    m_contactListener = EventListenerPhysicsContact::create();
    m_contactListener->onContactBegin = [](PhysicsContact &contact) { return ContactBeginCallback(contact); };
    m_contactListener->onContactSeparate = [](PhysicsContact &contact) { return ContactSeparateCallback(contact); };
}

bool CollisionManager::ContactBeginCallback(PhysicsContact &contact) {
    PhysicsBody *_bodyA = contact.getShapeA()->getBody();
    PhysicsBody *_bodyB = contact.getShapeB()->getBody();
    const bool visionAndWallCondition = ((_bodyA->getCategoryBitmask() == vision_zone_collision_bitmask &&
                                          _bodyB->getCategoryBitmask() == map_collision_bitmask) ||
                                         (_bodyB->getCategoryBitmask() == vision_zone_collision_bitmask &&
                                          _bodyA->getCategoryBitmask() == map_collision_bitmask));

    if (visionAndWallCondition) {
        for (Vec2 _p: contact.getContactData()->points) VisionZone::sm_shapeCollisionPoints.push_back(_p);
        return true;
    }
    return false;
}

bool CollisionManager::ContactSeparateCallback(PhysicsContact &contact) {
    PhysicsBody *_bodyA = contact.getShapeA()->getBody();
    PhysicsBody *_bodyB = contact.getShapeB()->getBody();
    const bool visionAndWallCondition = ((_bodyA->getCategoryBitmask() == vision_zone_collision_bitmask &&
                                          _bodyB->getCategoryBitmask() == map_collision_bitmask) ||
                                         (_bodyB->getCategoryBitmask() == vision_zone_collision_bitmask &&
                                          _bodyA->getCategoryBitmask() == map_collision_bitmask));

    if (visionAndWallCondition) {
        VisionZone::sm_shapeCollisionPoints.clear();
        return true;
    }
    return false;
}

GameLayer.cpp

bool GameLayer::init() {
    if (!Layer::init()) return false;

    CollisionManager::Init();
    _eventDispatcher->addEventListenerWithSceneGraphPriority(CollisionManager::GetContactListener(), this);

   // ... SOME CODE ... //

    m_visionZone = VisionZone::create(player->getPosition(), 100);
    addChild(m_visionZone, 1);

    return true;
}

void GameLayer::update(float delta) {
    Node::update(delta);
    m_visionZone->UpdateWithPlayer(m_player->getPosition());
}

I am not sure about this method though ... I feels like it is not optimal at all ?

Really curious to have some feedback on this, thanks in advance, sorry for the quantity of code :)

Let me know if you need more informations, code or links

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Thomas
  • 1

0 Answers0