重力加速度を使ったミニゲームをCocos2d-xで実装する

以下の本から、「重力加速度を使ったミニゲーム」の項目をCocos2d-xで実装してみたいと思います。

ゲームを作りながら楽しく学べるHTML5+CSS+JavaScriptプログラミング (NextPublishing)

ゲームを作りながら楽しく学べるHTML5+CSS+JavaScriptプログラミング (NextPublishing)

HTMLコード

「1-3 重力加速度」のミニゲーム、speed4.htmlを実装するとこんな感じです。

f:id:Takachan:20171223203727g:plain

コードはこんな感じになります。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>velocity</title>
    <script>
        var isFlying = false;
        var timer;
        var canvas;
        var posY = 300;
        var offcet = 0, speed = 10;
        var velocityY = -20, accelY = 5;

        function init() {
            canvas = document.getElementById("viewer").getContext("2d");
            canvas.font = "24px sans-serif";

            onkeydown = function () { isFlying = true; }
            onkeyup = function () { isFlying = false; }

            timer = setInterval(timer_tick, 100);
        }

        function timer_tick() {
            velocityY += isFlying ? -accelY : accelY;
            posY += velocityY;
            offcet += speed;

            if (offcet % 100 == 0) {
                speed += 2;
            }

            print();
        }

        function print() {
            canvas.fillStyle = "green";
            canvas.fillRect(0, 0, 600, 600);

            canvas.fillStyle = "brown";
            canvas.beginPath();
            canvas.moveTo(0, 0);
            for (var i = 0; i <= 600; i += 10) {
                var up = 200 + Math.sin((i + offcet) * Math.PI / 360) * 80;
                canvas.lineTo(i, up);
                if (i == 10 && posY < up) {
                    clearInterval(timer);
                }
            }
            canvas.lineTo(600, 0);
            canvas.fill();

            canvas.beginPath();
            canvas.moveTo(0, 600);
            for (var i = 0; i <= 600; i += 10) {
                var down = 400 + Math.sin((i + offcet) * Math.PI / 340) * 80;
                canvas.lineTo(i, down);
                if (i == 10 && posY + 10> down) {
                    clearInterval(timer);
                }
            }
            canvas.lineTo(620, 600);
            canvas.fill();

            canvas.fillStyle = "white";
            canvas.fillRect(10, posY, 10, 10);

            canvas.fillText(offcet, 500, 50);
        }
    </script>
</head>

<body onload="init()">
    <canvas id="viewer" width="600" height="600" style="width:600px; height:600px"></canvas>
</body>

</html>

Cocos2d-x コード

作ってて思ったのですが、背景をFillで連続的に塗りつぶしたりすることはCocos2d-xの標準の操作ではちょっと難しみたいですね。なので背景と当たり判定は未実装となります。

こちらは上昇がタッチしているとき、タッチしていないときは落下の動きになります。

f:id:Takachan:20171223205145g:plain

ヘッダー側の実装は以下の通り。

// HelloWorldScene.h
#pragma once

#include "cocos2d.h"

class HelloWorld : public cocos2d::Scene
{
    bool isFlying = false;

    double velocity_y = 0;
    double accel_y = 0.2;
    double score = 0;

    double elapsed_time = 0;

    static const int RECT_SPRITE_KEY;
    static const int SCORE_TEXT_KEY;
    static const double DEFAULT_VELOCITY_Y;

public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    static HelloWorld* create();

    void update(float delta);
};

実装側は以下の通りです。

// HelloWorldScene.cpp
#include "HelloWorldScene.h"

USING_NS_CC;

const int HelloWorld::RECT_SPRITE_KEY = 0;
const int HelloWorld::SCORE_TEXT_KEY = 1;
const double HelloWorld::DEFAULT_VELOCITY_Y = 0;

Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}

bool HelloWorld::init()
{
    if (!Scene::init())
    {
        return false;
    }

    double center = Director::getInstance()->getOpenGLView()->getFrameSize().height / 2;

    // 時期の表示
    Sprite* rect_sprite = Sprite::create();
    rect_sprite->setTag(RECT_SPRITE_KEY);
    rect_sprite->setTextureRect(Rect(0, 0, 10, 10));
    rect_sprite->setPosition(10, center);
    rect_sprite->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
    this->addChild(rect_sprite);

    // スコア表示
    Label* score_label = Label::createWithSystemFont("0", "Arial", 24);
    score_label->setTag(SCORE_TEXT_KEY);
    score_label->setPosition(Point(440, 290));
    score_label->setString("0");
    score_label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_RIGHT);

    this->addChild(score_label);

    this->scheduleUpdate();

    EventListenerTouchOneByOne* listner = EventListenerTouchOneByOne::create();
    listner->onTouchBegan = [this](Touch *t, Event *e)
    {
        this->isFlying = true;
        return true;
    };

    listner->onTouchEnded = [this](Touch *t, Event *e) { this->isFlying = false; };

    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listner, this);

    return true;
}

HelloWorld* HelloWorld::create()
{
    HelloWorld *pRet = new(std::nothrow) HelloWorld();
    if (pRet && pRet->init())
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = nullptr;
        return nullptr;
    }
}

void HelloWorld::update(float delta)
{
    auto sp = (Sprite*)this->getChildByTag(RECT_SPRITE_KEY);
    Point p = sp->getPosition();
    
    this->velocity_y += this->isFlying ? this->accel_y : -this->accel_y;
    p.y += this->velocity_y;

    if (p.y > Director::getInstance()->getOpenGLView()->getFrameSize().height || p.y < 0)
    {
        p.y = Director::getInstance()->getOpenGLView()->getFrameSize().height / 2;
        this->velocity_y = DEFAULT_VELOCITY_Y;

        this->score = 0;
        this->elapsed_time = 0;
    }

    this->score += delta * 10 * 2.5;

    auto score_label = (Label*)this->getChildByTag(SCORE_TEXT_KEY);
    score_label->setString(std::to_string((int)this->score));

    sp->setPosition(p);
}