サインカーブを使って雪を降らせるデモ

前回までの、X-Y座標系と円座標系の学習の一環として、サインカーブを使って雪を降らせるデモを実装してみました。

『JavaScriptゲームプログラミング 知っておきたい数学と物理の基本』から、第2章 基礎編-2 三角関数、2-2 サインカーブを使ったサンプルの箇所になります。

takachan.hatenablog.com

実行例

実行するとこんな感じになります。実行したときの絵面は書籍の内容とほとんど同じかと思います。

TypeScriptでのコード

前回前々回、と同じく、そのままJavaScriptコードを転記すると丸パクリになってしまうので、TypeScriptで実装し直してみました。

// index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="app.js"></script>
</head>
<body>
    <canvas width="600" height="600" id="showfield"></canvas>
</body>
</html>
/* app.css */
#snowfield {
    width: 600px;
    height: 600px;
}
// 全体を制御するクラス
class ShowControl
{
    public static xlimit: number = 600;
    public static ylimit: number = 600;

    private ctx: CanvasRenderingContext2D = null;
    private width: number;
    private height: number;
    private showTimer: any;
    private snows: Array<Snow> = new Array<Snow>();

    init() : void
    {
        var canvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("showfield");
        this.ctx = canvas.getContext("2d");
        this.ctx.globalAlpha = .6;
        this.showTimer = setInterval(() => { this.onAddShow(); }, 200);
        setInterval(() => { this.tick(); }, 50);
    }

    onAddShow() : void
    {
        let _snow: Snow = new Snow(600);
        this.snows.push(_snow);

        if (this.snows.length >= 100)
        {
            clearInterval(this.showTimer);
        }
    }

    tick() : void
    {
        this.snows.forEach((snow) =>
        {
            snow.update();
        });

        this.paint();
    }

    paint(): void
    {
        this.ctx.fillStyle = "black";
        this.ctx.fillRect(0, 0, 600, 600);

        this.snows.forEach((s) =>
        {
            s.paintSelf(this.ctx);
        });
    }
}

// 1つの雪片を表すクラス
class Snow
{
    x: number;
    y: number;
    drift: number;
    speed: number;
    width: number;
    height: number;
    theta: number;
    radius: number;

    constructor(limit: number)
    {
        this.x = Snow.random(limit);
        this.y = -10;
        this.drift = Math.random();
        this.speed = Snow.random(5) + 1;
        this.width = Snow.random(3) + 2;
        this.height = this.width;
        this.theta = Snow.random(100);
        this.radius = Snow.random(10) + 3;
    }

    update() : void
    {
        this.y += this.speed;

        if (this.y > ShowControl.ylimit)
        {
            this.y = -5;
        }

        this.x += this.drift;

        if (this.x > ShowControl.xlimit)
        {
            this.x = 0;
        }

        this.theta += 0.1;
    }

    paintSelf(canvas: CanvasRenderingContext2D): void
    {
        canvas.fillStyle = "white";
        canvas.fillRect(this.x + Math.sin(this.theta) * this.radius, this.y, this.width, this.height);
    }

    public static random(limit: number): number
    {
        return Math.floor(Math.random() * limit);
    }
}

// 起動時の処理
var _shows = new ShowControl();
window.onload = () => {
    _shows.init();
};

C++/Cocos2d-xでの実装

次はCocos2d-xでの実装です。

実行例

実行するとこんな感じです。

Cocos2d-xのコード

コード例は以下の通りです。以下をSceneやLayerに追加すると、表示されたときに勝手に雪が降り始めます。

ヘッダー側

雪を表示するレイヤーと1つの雪の粒子を表すクラスから構成されています。

#pragma once

#include <cocos2d.h>
#include <vector>
#include <AppCommon.hpp>

/**
 * 1つの雪片を表すクラス
 */
class Snow : public cocos2d::Sprite, CreateFunc<Snow>
{
    double drift;
    double speed;
    double theta;
    double radius;

public:
    Snow();
    bool init();
    using CreateFunc::create;
    void update(float delta);
};

/**
 * サインカーブを使って雪を降らせるサンプルのレイヤーを表すクラス
 */
class SnowLayer : public cocos2d::Layer, CreateFunc<SnowLayer>
{
    std::vector<Snow*> snowList;
    double skipDelta;
    double totalDelta;

public:
    SnowLayer();
    bool init();
    using CreateFunc::create;
    void update(float delta);
};

実装側

かなり長いですが全体をいかに載せます。

#include "SnowLayer.hpp"

using namespace std;
using namespace cocos2d;

//
// Show class implements
// - - - - - - - - - - - - - - - - - - - -

Snow::Snow() : 
    drift(random<double>(0, 1)),
    speed(random<double>(1, 6)/12),
    theta(random<double>(0, 100)),
    radius(random<double>(3, 13)/24)
{
}

bool Snow::init()
{
    if (!Sprite::init())
    {
        return false;
    }

    double size = random<double>(1.25, 2.5);

    this->setTextureRect(Rect(0, 0, size, size));
    this->setColor(Color3B::WHITE);
    this->setOpacity(255 * 0.6);

    return true;
}

void Snow::update(float delta)
{
    Vec2 pos = this->getPosition();
    double newX = pos.x + sin(this->theta) * this->radius;
    double newY = pos.y - this->speed;
    this->setPosition(Vec2(newX, newY));

    this->theta += 0.02;
}

//
// ShowLayer class implements
// - - - - - - - - - - - - - - - - - - - -

SnowLayer::SnowLayer() : 
    skipDelta(0),
    totalDelta(0)
{
}

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

    this->scheduleUpdate();

    return true;
}

void SnowLayer::update(float delta)
{
    // 画面のサイズ用
    auto size = Director::getInstance()->getVisibleSize();

    // 同時に200個以下、一定間隔ごとに生成
    if (this->snowList.size() <= 200 && this->totalDelta >= this->skipDelta)
    {
        Snow* snow = Snow::create();
        
        double x = random<double>(0, size.width);
        double y = size.height - 5;

        snow->setPosition(Vec2(x, y));

        this->addChild(snow);
        this->snowList.push_back(snow);

        // 次回生成時刻を設定
        this->totalDelta = 0;
        this->skipDelta = random<double>(0.075, 0.125);
    }

    this->totalDelta += delta;

    auto it = this->snowList.begin();
    while (it != this->snowList.end())
    {
        Snow* snow = (Snow*)*it;

        if (NodeUtil::isOutScreenY(snow))
        {
            snow->removeFromParentAndCleanup(true);
            it = this->snowList.erase(it);
        }
        else if (snow->getPosition().x > size.width + 5)
        {
            snow->setPosition(-5, snow->getPosition().y);
        }
        else
        {
            snow->update(delta);
            it++;
        }
    }
}

やっぱりCocos2d-xはちょっと長めになりますね。

今回は以上です。