前回までの、X-Y座標系と円座標系の学習の一環として、サインカーブを使って雪を降らせるデモを実装してみました。
『JavaScriptゲームプログラミング 知っておきたい数学と物理の基本』から、第2章 基礎編-2 三角関数、2-2 サインカーブを使ったサンプルの箇所になります。
実行例
実行するとこんな感じになります。実行したときの絵面は書籍の内容とほとんど同じかと思います。
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はちょっと長めになりますね。
今回は以上です。