cocos2d-x 4.0 El camino hacia el aprendizaje (5) El primer juego pequeño: Ninja Attack

Lo más importante sobre el desarrollo del juego de aprendizaje es que puedes hacer un juego pequeño al principio, lo que puede inspirar interés.
Bueno, hoy haremos un pequeño juego: Ninja Attack. Hay muchos artículos relacionados en Internet (me refiero a este artículo ), pero muchos de ellos no son aplicables a V4.0. Así que lo volví a escribir en V4.0.

Sin más preámbulos, vaya al tema.
Primer vistazo al efecto final:
Inserte la descripción de la imagen aquí
este es nuestro objetivo, ¡comencemos!

Reescribiremos el código en HelloWorldScene.cpp y HelloWorldScene.h.
En primer lugar, se deja la función init en HelloWorldScene.cpp y se eliminan los demás.

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

1. Configuremos el fondo del escenario en gris, que se ve más claro.

    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. Por favor salga nuestro protagonista-Ninja Elf.
Inserte la descripción de la imagen aquí
Puede descargar esta imagen y guardarla en Recursos.
Hay 3 pasos para crear un asistente, recuerda. (Definir, establecer posición, agregar a escena)
Debido a que nuestro reproductor no solo se usa en la función init (), también se usarán otras funciones, por lo que HelloWorldScene.hse nos define _playercomo una variable privada.

//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);

De esta forma, nuestro jugador está en una buena posición.
Inserte la descripción de la imagen aquí
3. Agregar enemigos
Inserte la descripción de la imagen aquí
Hacemos un método específicamente para generar. No olvides declararlo en el archivo de encabezado.

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));
}

El punto de conocimiento aquí es el movimiento de objetos, hay dos tipos de movimiento:
MoveTo: es cuánto tiempo moverse al punto especificado.
MoveBy: cuántos pasos mover en la posición actual.
MoveSelf: es liberar los recursos de este sprite. Aunque el sprite sale de la pantalla, si no lo eliminas todo el tiempo, habrá más y más sprites y el sistema se bloqueará.
runAction: es hacer que el objeto se mueva.

Después de eso, necesitamos generar Player y luego llamar a este método:

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

Schedule nos proporciona una función de acción continua, que es muy conveniente de usar.
De esta manera aparecerán nuestros enemigos:
Inserte la descripción de la imagen aquí
4. Lanzar dardos
Inserte la descripción de la imagen aquí
Lanzamos dardos haciendo clic en la pantalla. Esto implica el concepto de eventos. Cocos usa EventDispatcher para manejar varios eventos, como el tacto y otros eventos del teclado. Para obtener eventos del EventDispatcher, debe registrar un EventListener, que tiene dos tipos de oyentes para eventos táctiles:

  1. EventListenerTouchOneByOne: este tipo llama al método de devolución de llamada una vez para cada evento táctil.
  2. EventListenerTouchAllAtOnce: este tipo llama a un método de devolución de llamada una vez para todos los eventos táctiles.

Cada escucha de eventos admite 4 devoluciones de llamada, pero solo necesita vincular métodos a los eventos que le interesan.

  1. onTouchBegan: se llama cuando el dedo toca la pantalla por primera vez. Si está utilizando EventListenerTouchOneByOne, debe devolver true para obtener los otros 3 eventos táctiles.
  2. onTouchMoved: se llama cuando el dedo toca la pantalla y se mueve (sigue tocando).
  3. onTouchEnded: se llama cuando el dedo sale de la pantalla.
  4. onTouchCancelled: llamado en el contexto de un procesamiento de evento final específico, como cuando toca la pantalla, ingresó una llamada para interrumpir el proceso de la aplicación. En este juego, solo debes preocuparte cuando se produce el toque.

Primero declaramos la función de devolución de llamada en el archivo de encabezado:

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

Luego impleméntelo:

// 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;
}

Los comentarios son muy claros, así que no los explicaré mucho. Su generación, movimiento y liberación de recursos son los mismos que los del enemigo. Presta atención realDesta cómo se calcula el punto objetivo de MoveTo .
Bien, ejecútalo con Ctrl + F5, los dardos están volando por todas partes, y es muy suave.
Inserte la descripción de la imagen aquí
5. Detección de colisión (este capítulo no se encuentra en el artículo al que me refiero)
Por supuesto , lo que queremos no es que el enemigo pueda avanzar incluso si el dardo está volando por todo el cielo. Debemos dejar que el enemigo golpeado por el dardo desaparezca.
La detección de colisiones es una parte indispensable del desarrollo del juego, es decir, lo que sucede cuando dos elfos se encuentran.
Cocos nos proporciona la clase de Física, que tiene una serie de métodos para detectar colisiones físicas. Para nuestro programa, necesitamos agregar las siguientes funciones:
a. Inicializar la clase de Física. Este es un requisito difícil, ya que desea utilizar la colisión física. Cárguelo detrás de init.

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

b) Agregue PhysicsBody tanto al enemigo como a los dardos. De esta manera, nuestros objetos sprite llevan sus propiedades físicas. Podemos usar la detección de colisión física.
Agregue el siguiente código antes del addChild del enemigo y el dardo, respectivamente:

    // 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. La colisión también es un evento, entonces, ¿qué tipo de oyente deberíamos registrar esta vez? EventListenerPhysicsContact(Contacto significa contacto, podemos entenderlo como contacto aquí). También hay 4 funciones de devolución de llamada en este oyente:

  1. onContactBegin : Se llamará en dos formas para comenzar a contactar, y solo se llamará una vez.
  2. onContactPreSolve : Dos formas se tocan durante este paso.
  3. onContactPostSolve : Dos formas se tocan y su respuesta de colisión ha sido procesada.
  4. onContactSeparate: llamará a dos formas separadas, y solo lo llamará una vez.

Después de procesar los dardos, agregue el siguiente código:

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

d. Elegimos la primera función de devolución de llamada y la implementamos. El contenido es eliminar al enemigo cuando el dardo golpea al enemigo.

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;
}

El código anterior explica un poco. Cuando dos objetos chocan, se llama a este método. Primero obtenga dos nodos, ya que es una función de devolución de llamada, no puede determinar cuál es A y cuál es B. Entonces, cuando los hice PhysicsBody, solo el dart setTag (10), por lo que getTag es 10, que es el dardo, el otro es el enemigo, y luego eliminar el enemigo está bien.
Para la aplicación de BitMask (máscara de bits) en la colisión, puede aprender del sitio web oficial Collision . No se introducirá mucho aquí.

De hecho, hay más de un método para detectar colisiones, esta es la detección de colisión física. Otro método es la detección de bordes de gráficos, el que se usaba ampliamente antes intersectsRect(). Ahora esto también se puede usar, pero debe realizar más cálculos para cumplir con las condiciones que desea, lo cual es más engorroso.

Bueno, esta es la primera versión del juego. Ejecútalo de nuevo y prueba tu efecto asesino.
Inserte la descripción de la imagen aquí
Código completo (Gitee)

104 artículos originales publicados · Me gusta8 · Visita 210,000+

Supongo que te gusta

Origin blog.csdn.net/sunnyboychina/article/details/104837384
Recomendado
Clasificación