DXScriptでゲーム作成 その4
今回で一気に完成まで持っていきます。
完成品はこちら(sample4.zip)
長くなるので重要じゃない部分は省略。
・プレイヤーを敵クラスと同様クラス化
・プレイヤーの向きに黄色い弾を連射するように。
プレイヤーの弾クラスを敵の弾クラスから派生して作成
1.当たり判定
当たり判定を作成します。
弾はグループ化してあるので、そこから取り出して一つ一つ判定します。
高速化の工夫は無し!
// 敵の弾を格納するGroup var eb_group = new Group(); scene.addChild(eb_group); enemy.eb_group = eb_group; // プレイヤーの弾を格納するGroup var pb_group = new Group(); scene.addChild(pb_group); player.pb_group = pb_group; // 当たり判定を処理するダミーノード var colli_dummy = new Node();
弾の移動→当たり判定と処理したいので、当たり判定を受け持つダミーノードを作っています。
SceneやGroupに追加されたアイテムは先に追加されたほうから処理されるので・・・
Nodeクラスは描画部品の親クラスです。
素の状態だとなにも描画せず、イベントハンドラだけ動作します。
colli_dummy.addEventListener(ONFRAME, function() { // プレーヤーの当たり判定を作成 var padding = 9; // 左右を画像の空白分削る var pl_colli = new BoxCollision(player.width - padding * 2, player.height); pl_colli.x = player.x + padding; pl_colli.y = player.y; // 弾と一つ一つ判定 for (var i=0; i<eb_group._children.length; i++) { var bullet = eb_group._children[i]; // 弾の当たり判定が無効になっていれば判定しない if (!bullet.available) { continue; } var colli = new CircleCollision(bullet.r); colli.x = bullet.x; colli.y = bullet.y; if (Collision.collide(pl_colli, colli)) { dxs.print("player hit!"); bullet.available = false; // 弾の判定を無効に player.life--; // ライフ減少 playerLife.life = (player.life / PL_LIFE) * 100; } } // 敵の当たり判定を作成 var en_colli = new BoxCollision(enemy.width , enemy.height); en_colli.x = enemy.x; en_colli.y = enemy.y; // 弾と一つ一つ判定 for (var i=0; i<pb_group._children.length; i++) { var bullet = pb_group._children[i]; // 弾の当たり判定が無効になっていれば判定しない if (!bullet.available) { continue; } var colli = new CircleCollision(bullet.r); colli.x = bullet.x; colli.y = bullet.y; if (Collision.collide(en_colli, colli)) { dxs.print("enemy hit!"); bullet.available = false; // 弾の判定を無効に enemy.life--; // ライフ減少 enemyLife.life = (enemy.life / EN_LIFE) * 100; } } // ライフ判定 if (player.life <= 0) { // 敗北 dxs.alert("敗北・・・"); dxs.exit(); return; } if (enemy.life <= 0) { // 敗北 dxs.alert("勝利!"); dxs.exit(); return; } }); scene.addChild(colli_dummy); // ライフバー var playerLife = new LifeBar(640, 5); playerLife.x = 0; playerLife.y = 10; scene.addChild(playerLife); var enemyLife = new LifeBar(640, 5); enemyLife.x = 0; enemyLife.y = 20; enemyLife.color = dxs.GetColor(255, 0, 0); scene.addChild(enemyLife);
BoxCollision、CircleCollisionは当たり判定クラスです。
座標をセットして、Collision.collide()で判定。
当たっていたらプレイヤーか敵のライフを減少させます。
どちらかのライフが0になったらゲーム終了します。
dxs.exit()はアプリを終了させる関数です。
あと画面上部にライフバーを描画します。
2.ライフバー
ライフバーを描画するクラスです。
// ライフバー var LifeBar = Class.create(Node, { initialize: function(width, height) { Node.apply(this, arguments); this.x = 0; this.y = 0; this.width = width; this.height = height; this.color = dxs.GetColor(0, 0, 255); this.life = 100; }, ondraw: function() { width = this.width * this.life / 100; dxs.DrawBox(this.x, this.y, this.x + width, this.y + this.height, this.color, true); } });
Nodeクラスから派生します。
ondraw関数定義して描画処理を書けば、勝手に描画してくれる描画アイテムになります。
ここではDrawBox()で細長い四角を描いています。
だいぶやっつけですがこれで一応ゲームの形になりました。
ここまで大体4時間くらいでコーディングしてます。
2Dのゲームだったらさくさく書けますね。
3D系の関数を実装すればさらに画期的な環境になりそうだけど気力が・・・
DXScriptでゲーム作成 その3
チュートリアル3回目は敵を表示させて弾を撃たせてみます。
完成品はこちら(sample3.zip)
あまり複雑な動きをすると面倒くさい処理が長くなるので、単純に左右に動きながら全方向に弾を打つようにします。
1.準備
せっかくなので緑をボコりましょう。
東方素材蒐の早苗魔法詠唱.pingをsanae_magic.pngとリネームし、フォルダに設置します。
あと霊夢と同様背景を透過色にしておきます。
2.敵クラスの定義
プレイヤーはSpriteクラスに変数や関数を直接追加して作成していましたが、敵のほうはきちんとクラスを定義して作成してみます。
dxsfはenchant.jsのクラス生成処理をそのまま持ってきてるので、同じクラス定義ができます。
// 敵 var Enemy = Class.create(Sprite, { // 1 initialize: function() { Sprite.call(this, 48, 64); // 2 var x1 = Enemy.X_POS1 - this.width / 2; var x2 = Enemy.X_POS2 - this.width / 2; this.image = game.assets['sample3\\sanae_magic.png']; this.x = x2; this.y = Enemy.Y_POS; this.scale = 1.5; // 1.5倍 // 3 // アニメーション var timeline = new FrameAnimation(this, ["x"]); // 4 var time = 0; timeline.addEvent((time+Enemy.STOP_TIME/3) * dxs.fps, function() { this.shot(); }); // ショット timeline.addEvent((time+Enemy.STOP_TIME*2/3) * dxs.fps, function() { this.shot(); }); // ショット timeline.addEvent((time+Enemy.STOP_TIME) * dxs.fps, function() { this.shot(); }); // ショット time += Enemy.STOP_TIME; timeline.addPoint(time * dxs.fps, {"x": x2}); // 静止 time += Enemy.MOVE_TIME; timeline.addPoint(time * dxs.fps, {"x": x1}, FrameAnimation.cos); // 左へ timeline.addEvent((time+Enemy.STOP_TIME/3) * dxs.fps, function() { this.shot(); }); // ショット timeline.addEvent((time+Enemy.STOP_TIME*2/3) * dxs.fps, function() { this.shot(); }); // ショット timeline.addEvent((time+Enemy.STOP_TIME) * dxs.fps, function() { this.shot(); }); // ショット time += Enemy.STOP_TIME; timeline.addPoint(time * dxs.fps, {"x": x1}); // 静止 time += Enemy.MOVE_TIME; timeline.addPoint(time * dxs.fps, {"x": x2}, FrameAnimation.cos); // 右へ timeline.addEvent(time * dxs.fps, function() { timeline.reset(); }); // 最初に戻る this.addEventListener(ONFRAME, function() { timeline.update(); }); }, // 弾発射 shot: function() { var x = this.x + this.width / 2; var y = this.y + this.height / 2; for(var i=0; i<16; i++) { // 16方向に弾を発射する var bullet = new EnemyBullet(x, y, i); // 5 this.eb_group.addChild(bullet); } } });
1-enchant.jsと同様Class.createでクラスを定義します。
敵はプレイヤーと同様Spriteクラスから派生させます。
2-Spriteクラスのコンストラクタを呼んでいます。
3-プレイヤーと同じ大きさだと敵という感じがしないので1.5倍に拡大します。
画像の中心から拡大するので、拡大するとSpriteクラスのx、y、width、heightと画像の描画位置、サイズに差異ができます。
当たり判定などで座標を扱うときは拡大した分を補正する必要があります。
4-敵を一定間隔で左右に移動するようアニメーションを定義します。
このために、dxsf.jsにFrameAnimationというクラスを追加しています。(アップローダーからdxsf.jsを落としてver1.2にしてください。)
このFrameAnimationクラスはMMD(MikuMikuDance)のフレーム(左のほうにたくさん点が表示されるアレ)をイメージしています。
使い方は、例えばあるxとyいう変数をもつインスタンスを操作したいなら、コンストラクタにそのインスタンスと操作対象の変数名の配列を渡します。
var point = {"x": 0, "y": 0} var timeline = new FrameAnimation(point, ["x", "y"]);
そして、30フレーム後にxが100、60フレーム後にyが100になるように移動させたいなら、
引数にフレーム数と値の連想配列を指定してaddPoint()を呼びます。
timeline.addPoint(30, {"x": 100}); timeline.addPoint(60, {"y": 100});
これにより、x、yはそれぞれの指定フレームに100になるように、0から徐々に変化していきます。
xのほうが移動に掛ける時間が少ないので、yの二倍の速さで変化します。
要は、何フレーム目にどの場所に移動して欲しいか、を書けばあとは自動でアニメーションするということです。
さらにaddPoint()の第三引数に移動量計算関数を指定することで、ゆっくり→はやく→ゆっくりなど、移動の仕方も制御することができます。 →参考
サンプルでは右に10秒静止したあと左に2秒かけて移動。
左で10秒静止したあと右に2秒かけて移動。
初期位置に戻ったらリセットして無限ループさせています。
移動には移動量計算関数を指定してゆっくり→はやく→ゆっくりの移動をさせます。
さらにaddEvent()で指定フレームに関数を実行することができるので、これを使い10秒の静止時間中に等間隔で3回ショットを行います。
5-自身の中心を起点に、16方向に移動する弾を生成します。
複数の描画部品を格納できるGroupクラスに生成した弾を格納しています。
後ほど弾の当たり判定を作成するときに、弾の一覧をこのGroupから取得することにしましょう。
3.弾クラス
弾を描画するクラスを定義します。
// 敵の弾 var EnemyBullet = Class.create(Node, { // 1 initialize: function(x, y, direction) { Node.apply(this, arguments); this.x = x; this.y = y; this.r = 5; var speed = 5; this.vspeed = speed * Math.cos(2 * Math.PI * direction / 16); this.hspeed = speed * Math.sin(2 * Math.PI * direction / 16); // 弾移動関数 this.addEventListener(ONFRAME, function() { this.x += this.vspeed; this.y += this.hspeed; if (this.x < -50 || this.x > 690 || this.y < -50 || this.y > 530) { // 画面外に出たら自身を削除 this.removeAllListener(); this.parent.removeChild(this); } }); }, ondraw: function() { // 2 dxs.DrawCircle(this.x, this.y, this.r, dxs.GetColor(255,255,255), true); }, });
1-弾をNodeクラスから派生して作成します。
Nodeはdxsfにおける描画部品のベースクラスです。
SceneやGroupなどに追加するものはこのクラスから派生する必要があります。
Nodeは座標を持たないので、円形の物体などはこれに中心点と半径を定義すれば表現できます。
2-ondraw()を派生して定義することで、描画を行います。
この関数に描画処理を書いておけば、あとはフレームワークが自動処理します。
ここではDrawCircle関数で白色の円を描画しています。
これで弾を撃ってくる敵を作成できました。
弾を撃てるのが敵だけでは癪なので、今度はプレイヤーも弾を撃てるようにしましょう。
DXScriptでゲーム作成 その2
前回はキャラクターの歩行アニメーションまで実装しましたので、次はキーボードで移動できるようにします。
キーボードの左右キーで横移動、スペースキーでジャンプさせます。
完成品はこちら(sample2.zip)
1.移動処理
移動処理を記述します。
移動はプレーヤーオブジェクトに"速度"を持たせ、毎フレームごとにその時点の速度に応じて座標を移動させる方式をとります。
こうすると移動させたいときはその方向への速度を持たせれば勝手に移動していくので、後々移動処理を作るのが楽になります。
ジャンプも直感的に書けたり。
// 移動に必要な情報 // 1 player.vSpeed = 0; // 縦方向速度 player.hSpeed = 0; // 横方向速度 var vSpeed = -6; // ジャンプ速度 var vAccel = 0.2; // 重力 var hSpeed = 4; // 歩き速度 // 画面サイズを取得 var screen = dxs.GetScreenState(); // 2 var screenWidth = screen[0]; var screenHeight = screen[1]; // イベントリスナ player.addEventListener(ONFRAME, function() { // 3 player.y += player.vSpeed; player.x += player.hSpeed; // 範囲チェック if (player.y < 0) player.y = 0; if (player.y > screenHeight - player.height) player.y = screenHeight - player.height; if (player.x < 0) player.x = 0; if (player.x > screenWidth - player.width) player.x = screenWidth - player.width; // 上方向の速度を減速 player.vSpeed += vAccel; });
1-playerに縦方向と横方向の速度を変数に持たせます。
さらに縦横の移動速度と重力を定義。
ジャンプはy座標が減る方向への移動なので、数値がマイナスです。
逆に重力はプラスの数値。
この重力が小数点になっていることが微妙なポイントです。
DXライブラリはC言語なので、描画関数の座標は通常整数型になります。
小数点を使って座標を表したいときは、実数型を引数に取る〜F系(DrawGraphFとか)の描画関数に切り替える必要があります。
DXScriptはjavascriptなので、型を気にせず整数型でも実数型でも突っ込むことができます。
(実数型は小数点以下切捨てしてDXライブラリに渡されます)
整数型で作っていたけど途中で実数型に変えたい、というときも何も変更無しでできたりするわけです。
2-画面サイズを取得しています。
初のDXライブラリ関数呼び出しです。
DXライブラリの関数は頭に「dxs.」をつけると大体使えます。 ★重要
ここもポイントがありまして、GetScreenState()は元々
int GetScreenState( int *SizeX , int *SizeY , int *ColorBitDepth );
で定義されている関数です。
引数に変数のポインタを渡して画面サイズを取得します。
javascriptはポインタなんぞなく、引数も値渡しなのでこの形の関数は成り立ちません。
なのでDXScriptは引数にポインタを渡して値を取得する関数は、代わりに値を入れた配列を返します。 ★重要
このサンプルコードでは戻り値のscreen[0]にSizeXの値、screen[1]にSizeYの値、あと今回は使ってませんがscreen[2]にColorBitDepthの値が入っています。
3-イベントリスナを定義して、毎フレームごとに速度を適用する処理を記述します。
ONFRAMEイベントは前回歩行アニメーションで使用していますが、dxsfでは同じイベントに対して複数のイベントリスナを持つことが可能です。
現在の速度に応じて座標を移動し、画面からはみ出さないように範囲チェックをしています。
重力を適用して、縦方向の速度を減らします。
2.キー判定
キーを押されたら左右移動とジャンプを行う処理を記述します。
キーの状態を取得する関数はGetHitKeyStateAll()ですが、今回はせっかくなのでジョイパッドも使えるようにしてしまいましょう。
DXライブラリのGetJoypadInputState()関数はジョイパッドの状態だけでなく、キーが押されている場合にパッドのボタンが押されているように扱ってくれます。
// パッドイベントを処理 player.addEventListener(JOYPADCHANGED, function(id, padnum, button, old) { // 1 var space = (button & dxs.PAD_INPUT_10); // 2 var oldspace = (old & dxs.PAD_INPUT_10); if (space && (space != oldspace)) { // スペースキーが押された場合 player.vSpeed = vSpeed; } var right = (button & dxs.PAD_INPUT_RIGHT); var oldright = (old & dxs.PAD_INPUT_RIGHT); var left = (button & dxs.PAD_INPUT_LEFT); var oldleft = (old & dxs.PAD_INPUT_LEFT); if (right && (right != oldright)) { // 右キーが押された場合 player.hSpeed = hSpeed; player.setDirection(1); } else if (left && (left != oldleft)) { // 左キーが押された場合 player.hSpeed = hSpeed * -1; player.setDirection(3); } else if (!right && !left) { // どちらも押されていない場合 player.hSpeed = 0; } });
1-dxsfはGetJoypadInputState()をラップしたJOYPADCHANGEDイベントを用意しています。
ジョイパッドの状態が変化した時イベント発生し、引数に現在と直前のパッドの状態を返します。
2-定数値とビット演算してボタンの状態を取り出しています。
この辺の詳しいことはDXライブラリのサイト参照で。
関数と同じく、定数も頭にdxs.をつけることで使用できます。
10番のボタン(スペースキー)が押された場合は上方向の速度を与えてジャンプさせます。
左右ボタン(左右キー)が押された場合は横方向の速度を与え、さらに方向変更関数を呼んでアニメーションの向きを変化させます。
これでキャラクターが画面上を自由自在に移動することが出来るようになりました。
ここまででコードは改行コメント入れて94行!
まあぶっちゃけ参考にしたenchant.jsの設計が良いというだけなんですけどね。
ちょっと修正すればenchant.jsにも移植可能なコードになっているので、スマフォプログラミングの参考にもできるかと思います。
DXScriptでゲーム作成 その1
リリースしてからすっかり放置してしまったので、ここらでチュートリアル的なものを書いてみることにします。
今回はキャラクターを表示して、アニメーションさせるところまで行ってみます。
1.準備
DXScript.zipを解凍し、sample1フォルダを作って↓のような階層にします。
DXScriptフォルダ直下のmain.jsをsample1フォルダにコピーします。
\DXScript
\sample1 ⇐作成
main.js ⇐作成
キャラクターの画像も用意しましょう。
今回は同人サークル「はちみつくまさん」の東方素材蒐をお借りします。
霊夢の歩行絵(霊夢歩行絵.png)をreimu_walk.pngと名前変更し、sample1フォルダの下に置きます。
\DXScript
\sample1
main.js
reimu_walk.png ⇐new
さらに、GIMPなどで画像背景の緑色の部分を透過色にしておきましょう。
2.コンフィグファイルの変更
DXScriptフォルダ直下にあるconfig.jsをテキストエディタで開きます。
一番下にあるdxs.scriptsの部分を修正します。
/** 読み込む外部スクリプト/プラグイン */ dxs.scripts = [ 'dxs.js', 'dxsf\\class.js', 'dxsf\\dxsf.js', 'sample1\\main.js' // sample1フォルダのmain.jsを読み込むように修正 ];
3.画像の表示
ここからはプログラミングをしていきます。
sample1フォルダの下のmain.jsに書きこんでいきます。
dxsf(DXScriptの補助フレームワーク)はenchant.jsのような感じで使うことができます。
完成品はこちら(sample1.zip)
dxs.onLoad = function() { var game = new Game(); // 1 game.loadImages('sample1\\reimu_walk.png'); // 2 var scene = new Scene(); // 3 var player = new Sprite(48, 64); // 4 player.x = 0; player.y = 480 - player.height; player.image = game.assets['sample1\\reimu_walk.png']; // 5 scene.addChild(player); // 6 game.pushScene(scene);
1-Gameクラスのインスタンスを生成します。
基本となるクラスです。
enchant.jsとは異なり引数がありません。
あと、game.start()が不要です。
2-画像を読み込みます。
ここで、
game.loadImages('sample1\\reimu_walk.png', 'sample1\\marisa_walk.png');
のように複数のファイルを読み込ますこともできます。(今回は1ファイルですが)
3-Sceneクラスのインスタンスを生成します。
4-Spriteクラスのインスタンスを生成します。
Spriteクラスは画像を扱うクラスです。
引数は表示領域のサイズです。
のちのちプレーヤーが操作できるようにするので、playerという名前にしておきます。
描画するX座標、Y座標をセットします。
※デフォルトでは画面サイズは640*480です。なのでplayer.y = 480 - player.heightとしています
5-playerが描画する画像をセットしています。
2で読み込んだ画像はgame.assetsに蓄えられています。
6-playerをsceneへ、sceneをgameへセットします。
game->scene->各アイテムと階層構造になっています。
これで、画面左下に霊夢が表示されるはずです。
4.歩行アニメーション
歩行アニメーションをさせましょう。
とりあえず右方向にひたすら歩くようにします。
(移動はしません)
// 歩くのに必要な情報 var walkImageNum = [0, 1, 2, 1]; // 向き変更関数 0:↑ 1:→ 2:↓ 3:← player.setDirection = function(direction) { // 1 player.direction = direction; player.walkImageNumIndex = 0; player.animationStep = 0; player.animationSpeed = 5; player.frame = 3 * player.direction; // 2 } // 初期方向をセット player.setDirection(1); player.addEventListener(ONFRAME, function() { // 3 player.animationStep = (player.animationStep + 1) % player.animationSpeed; if (player.animationStep != 0) { return; } var offset = walkImageNum[player.walkImageNumIndex]; player.frame = 3 * player.direction + offset; // 画像indexを増やす player.walkImageNumIndex = (player.walkImageNumIndex + 1) % walkImageNum.length; }); }
1-キャラクターの向きを変更する処理を関数化しておきます。
生成したインスタンスにあとから変数を追加できるのがjavascriptの強み。
playerに方向を表す変数やアニメーション制御用の変数を追加&初期化しています。
2-frame変数は画像全体のうち、どの部分を表示するかを制御します。
reimu_walk.pngは3×4の絵が描かれた画像なので、例えばframe = 3だと左から1つ目、上から2つ目の絵が表示されます。
3-イベントリスナを定義して、アニメーションの処理を記述します。
ONFRAMEイベントは各フレームごとに発生するイベントです。
(デフォルトでは30FPS)
他にはマウスやキーボードのイベントなどを定義してあります。
さすがに1秒間に30回絵が書き換わると速くて見えないので、5フレームに一度処理を行うようにしています。
player.directionなどから計算してplayer.frameの値を変更することで、表示される絵を変更します。
DXScriptのページ
DXScriptはjavascriptでDXライブラリを操作し、ゲームプログラミングができるアプリです。
Windowsゲームを自分で作りたい!
そういう場合によく使われるのがDXライブラリと呼ばれるゲームライブラリです。
DXライブラリ
グラフィックからサウンド、ジョイパッド、果ては3Dモデル描画までサポートする便利なライブラリです。
最近MMDモデルの描画までサポートした隙のないDxライブラリですが、実際つかうとなると「C言語」という大きな壁が立ちふさがります。
C及びC++は最も習得の難しい言語と言っても過言でない難解言語。
これを覚えるだけで力尽きちゃう人はけっこういるのではないかと。
DXScriptは比較的習得が容易なjavascriptでゲームプログラミングが行えます。
これはgoogle chromeで使われてるjavascriptエンジンを使っています。
◇高速javascriptエンジン
◇コンパイル不要
◇ポインタとかメモリ管理とか面倒くさいものは不要
さらに
◇面倒なFPS制御を内蔵
◇ProcessMessage()の呼び出しも不要
◇enchant.js風の補助フレームワーク "dxsf"
サンプルコード
これだけで画面中央で画像が表示され、回転します。
dxs.onLoad = function() { var game = new Game(); game.loadImages('test.png'); // 画像のロード var scene = new Scene(); var image = new Sprite(128, 128); // スプライト生成 image.x = 250; image.y = 150; image.image = game.assets['test.png']; // イベントリスナ登録 image.addEventListener(ONFRAME, function() { // 1フレームごとに回転角度を増やす image.rotation = (image.rotation + 0.05) % (2 * Math.PI); }); scene.addChild(image); game.pushScene(scene); }
ご質問等はとりあえずコメントにください
いまのところの仕様
◇3D系の関数は未対応(プラグインで提供予定)
◇ソフトイメージ系の関数は未対応
◇文字入力系の関数は未対応
◇DXライブラリでリッチテキストは難しい・・・