cocos2d-x 4.0 The Road to Learning (5) The first small game-Ninja Attack

The most important thing about learning game development is that you can make a small game at the beginning, which can inspire interest.
Well, today we will make a small game-Ninja Attack. There are many related articles on the Internet (I refer to this article ), but many of them are not applicable to V4.0. So I wrote it again under V4.0.

Without further ado, go to the topic.
First look at the final effect:
Insert picture description here
this is our goal, let's get started!

We will rewrite the code in HelloWorldScene.cpp and HelloWorldScene.h.
First of all, the init function in HelloWorldScene.cpp is left, and the others are deleted.

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }
    // My code here
    
    return true;
}

1. Let's set the stage background to gray, which looks clearer.

    auto winSize = Director::getInstance()->getVisibleSize();
    auto origin = Director::getInstance()->getVisibleOrigin();

    // set Background with grey colour
    auto background = DrawNode::create();
    background->drawSolidRect(origin, winSize, cocos2d::Color4F(0.6, 0.6, 0.6, 1.0));
    this->addChild(background);

2. Please come out our protagonist-Ninja Elf.
Insert picture description here
You can download this picture and save it in Resource.
There are 3 steps to create a wizard, remember. (Define, set position, add to scene)
Because our player is not only used in the init () function, other functions will also be used, so we HelloWorldScene.hare defined _playeras a private variable in it.

//HelloWorldScene.h
private:
    cocos2d::Sprite* _player;

//HelloWorldScene.cpp
    // Add player
    _player = Sprite::create("player.png");
    _player->setPosition(Vec2(winSize.width * 0.1, winSize.height * 0.5));
    this->addChild(_player);

In this way, our _player is in a good position.
Insert picture description here
3. Add enemies
Insert picture description here
We make a method specifically to generate. Don't forget to declare it in the header file.

void HelloWorld::addMonster(float dt) {
    auto monster = Sprite::create("monster.png");

    // Add monster
    auto monsterContentSize = monster->getContentSize();
    auto selfContentSize = this->getContentSize();
    int minY = monsterContentSize.height / 2;
    int maxY = selfContentSize.height - minY;
    int rangeY = maxY - minY;
    int randomY = (rand() % rangeY) + minY;
    monster->setPosition(Vec2(selfContentSize.width + monsterContentSize.width/2, randomY));
    this->addChild(monster);

    // Let monster run
    int minDuration = 2.0;
    int maxDuration = 4.0;
    int rangeDuration = maxDuration - minDuration;
    int randomDuration = (rand() % rangeDuration) + minDuration;

    // 定义移动的object
    // 在randomDuration这个时间内(2-4秒内),让怪物从屏幕右边移动到左边。(怪物有快有慢)
    auto actionMove = MoveTo::create(randomDuration, Vec2(-monsterContentSize.width / 2, randomY));
    // 定义消除的Object。怪物移出屏幕后被消除,释放资源。
    auto actionRemove = RemoveSelf::create();
    monster->runAction(Sequence::create(actionMove, actionRemove, nullptr));
}

The knowledge point here is the movement of objects, there are two types of movement:
MoveTo: is how long to move to the specified point.
MoveBy: How many steps to move at the current position.
MoveSelf: It is to release the resources of this sprite. Although the sprite comes out of the screen, if you don't delete it all the time, there will be more and more sprites and the system will crash.
runAction: It is to make the object move.

After that, we need to generate Player and then call this method:

    // 初始化了随机数生成器。如果不执行这一步,每次运行程序都会产生一样的随机数。
    srand((unsigned int)time(nullptr));
    // 每隔1.5秒生成一个怪物
    this->schedule(CC_SCHEDULE_SELECTOR(HelloWorld::addMonster), 1.5);

Schedule provides us with a continuous action function, which is very convenient to use.
This way our enemies will appear:
Insert picture description here
4. Launching darts
Insert picture description here
We launch darts by clicking on the screen. This involves the concept of events. Cocos uses EventDispatcher to handle various events, such as touch and other keyboard events. In order to get events from EventDispatcher, you need to register an EventListener, which has two kinds of touch event listeners:

  1. EventListenerTouchOneByOne: This type calls the callback method once for each touch event.
  2. EventListenerTouchAllAtOnce: This type calls a callback method once for all touch events.

Each event listener supports 4 callbacks, but you only need to bind methods to the events you care about.

  1. onTouchBegan: Called when the finger first touches the screen. If you are using EventListenerTouchOneByOne, you must return true to get the other 3 touch events.
  2. onTouchMoved: Called when the finger touches the screen and moves (keep touching).
  3. onTouchEnded: Called when the finger leaves the screen.
  4. onTouchCancelled: Called in the context of a specific end event processing, such as when you are touching the screen, a call came in to interrupt the app process. In this game, you only need to care about when the touch occurs.

We first declare the callback function in the header file:

// HelloWorldScene.h
    bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* unused_event);

Then implement it:

// HelloWorldScene.cpp
bool HelloWorld::onTouchBegan(Touch* touch, Event* unused_event) {
    // 1 - Just an example for how to get the player object
    // 说明一下作为第二个参数传递给addEventListenerWithSceneGraphPriority(eventListener, _player)的_player对象被访问的方式。
    // auto node = unused_event->getcurrentTarget();

    // 2.获取触摸点的坐标,并计算这个点相对于_player的偏移量。
    Vec2 touchLocation = touch->getLocation();
    Vec2 offset = touchLocation - _player->getPosition();
    // 如果offset的x值是负值,这表明玩家正试图朝后射击。在本游戏中这是不允许的。
    if (offset.x < 0){
        return true;
    }

    // 3.在玩家所在的位置创建一个飞镖,将其添加到场景中。
    auto projectile = Sprite::create("Projectile.png");
    projectile->setPosition(_player->getPosition());
    this->addChild(projectile);

    // 4.将偏移量转化为单位向量,即长度为1的向量。
    offset.normalize();
    // 将其乘以1000,你就获得了一个指向用户触屏方向的长度为1000的向量。为什么是1000呢?因为长度应当足以超过当前分辨率下屏幕的边界。
    auto shootAmount = offset * 1000;
    // 将此向量添加到飞镖的位置上去,这样你就有了一个目标位置。
    auto realDest = shootAmount + projectile->getPosition();

    // 5.创建一个动作,将飞镖在2秒内移动到目标位置,然后将它从场景中移除。
    auto actionMove = MoveTo::create(2.0f, realDest);
    auto actionRemove = RemoveSelf::create();
    projectile->runAction(Sequence::create(actionMove, actionRemove, nullptr));

    return true;
}

The comments are very clear, so I wo n’t explain them much. Its generation, movement and resource release are the same as those of the enemy. Pay attention realDestto how the target point of MoveTo is calculated.
Okay, run it with Ctrl + F5, the darts are flying all over, and it is very smooth.
Insert picture description here
5. Collision detection (this chapter is not in the article I refer to)
Of course , what we want is not that the enemy can move forward even if the dart is flying all over the sky. We must let the enemy hit by the dart disappear.
Collision detection is an indispensable part of game development, that is, what happens when two elves meet.
Cocos provides us with Physics class, which has a series of methods to detect physical collisions. For our program, we need to add the following functions:
a. Initialize the Physics class. This is a hard requirement, since you want to use physical collision. Load it behind init.

    if ( !Scene::init() )
    {
        return false;
    }
    // 初始化Physics
    if (!Scene::initWithPhysics())
    {
        return false;
    }

b. Add physicsBody to both the enemy and the darts. This way our sprite objects carry their physical properties. We can use physical collision detection.
Add the following code before the addChild of the enemy and the dart, respectively:

    // Add monster's physicsBody
    auto physicsBody = PhysicsBody::createBox(monster->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));
    physicsBody->setDynamic(false);
    physicsBody->setContactTestBitmask(0xFFFFFFFF);
    monster->setPhysicsBody(physicsBody);
    
    // Add projectile's physicsBody
    auto physicsBody = PhysicsBody::createBox(projectile->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));
    physicsBody->setDynamic(false);
    physicsBody->setContactTestBitmask(0xFFFFFFFF);
    projectile->setPhysicsBody(physicsBody);
    projectile->setTag(10);

c. Collision is also an event, so what kind of Listener should we register this time? EventListenerPhysicsContact(Contact means contact, we can understand it as contact here). There are also 4 callback functions in this listener:

  1. onContactBegin:It will called at two shapes start to contact, and only call it once.
  2. onContactPreSolve:Two shapes are touching during this step.
  3. onContactPostSolve:Two shapes are touching and their collision response has been processed.
  4. onContactSeparate:It will called at two shapes separated, and only call it once.

After processing the darts, add the following code:

    // 碰撞检测
    auto contactListener = EventListenerPhysicsContact::create();
    contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

d. We choose the first callback function and implement it. The content is to delete the enemy when the dart hits the enemy.

bool HelloWorld::onContactBegin(cocos2d::PhysicsContact& contact)
{
    auto nodeA = contact.getShapeA()->getBody()->getNode();
    auto nodeB = contact.getShapeB()->getBody()->getNode();

    if (nodeA && nodeB)
    {
        if (nodeA->getTag() == 10)
        {
            nodeB->removeFromParentAndCleanup(true);
        }
        else if (nodeB->getTag() == 10)
        {
            nodeA->removeFromParentAndCleanup(true);
        }
    }

    return true;
}

The code above explains a little bit. When two objects collide, this method is called. First get two Node, because it is a callback function, you can not determine which is A and which is B. So, when I made them PhysicsBody, only the dart setTag (10), so which getTag is 10, which is the dart, the other is the enemy, and then delete the enemy is OK.
For the application of BitMask (bit mask) in the collision, you can learn from the official website Collision . Not much will be introduced here.

In fact, there is more than one method to detect collisions, this is physical collision detection. Another method is the edge detection of graphics-the one used widely before intersectsRect(). Now this can also be used, but you need to perform more calculations to meet the conditions you want, which is more cumbersome.

Well, this is the first version of the game. Run it again and try your killing effect.
Insert picture description here
Complete code (Gitee)

Published 104 original articles · Like8 · Visit 210,000+

Guess you like

Origin blog.csdn.net/sunnyboychina/article/details/104837384