本チュートリアルの動画をYouTubeから試聴しながらの学習をお奨めしております。
「2Dアクションゲーム」のチュートリアルへようこそ!
目次
- 概要
- Chapter01 - 足場を増やす
- Chapter02 - ゴブリンに当たり判定をつける
- Chapter03 - ゴブリンが移動するようにする
- Chapter04 - ゴブリンを倒す
- Chapter05 - アニメーションを再生してから消滅させる
- Chapter06 - ダメージを実装する
- Chapter07 - 敵を増やす
- Chapter08 - ゲームを公開する
概要
このチュートリアルは、Spriteエディター を使って Animated Sprite アセットを作成し、実際にシーンを編集したりスクリプトを記述したりして簡単な 2D アクションゲームを構築するチュートリアルです。
作成するゲームでは Sprite エディター の扱い方から、2D アセット向けの物理エンジンの利用方法、当たり判定、Sprite Animation Clip の再生/停止等について扱います。シンプルなチュートリアルなので独自のオリジナリティな要素を追加することも可能です。
是非、チュートリアルを進める上で自分のオリジナリティな要素を追加して見てください。
→ https://support.playcanvas.jp/hc/ja/articles/4404199480089
まずはプロジェクトを作成しましょう。
本ワークショップではあらかじめスタートのプロジェクトを用意していますので、それをフォーク(複製)して始めます。
https://playcanvas.com/project/568982/overview/2dhandsonstartにアクセスしてプロジェクトをフォークしてください。
フォークすると PROJECT NAME が聞かれるので、好きな名前を入力して FORK してください。
Chapter01 - 足場を増やす
ゴブリンのところまでたどり着けるように足場を増やしましょう。
足場は Sprite(スプライト) アセットによって構築されているため、Sprite の機能を利用することで拡大縮小せずにタイリングして簡単に引き延ばすことができます。
HIERARCHY (ヒエラルキー) の 「Grass_Long」 を選択し、右側の INSPECTOR (インスペクター)内にある SPRITE の Size を調整します。
Size の Width , Height の値を調整することで縦横にタイリングされます。今回は width へ 「15」 と入力してください。
この状態で再生すると、Sandyちゃんとゴブリンがすり抜けてしまいます。
次のパートからゴブリンに当たるような処理を追加していきましょう。
Chapter02 - ゴブリンに当たり判定をつける
現状だとゴブリンに当たり判定が設定されていないため、Sandyちゃんがすり抜けてしまいます。
当たり判定を設定するためには、Entity(エンティティ) に Collision コンポーネントと Rigid body コンポーネントを追加する必要があります。
エディターを開き、ヒエラルキーから「enemy」を選択し、ADD COMPONENT(アドコンポーネント) から Collision と Rigid body を追加します。
Collision を追加すると、エディター から entity の当たり判定が確認できるようになります。
当たり判定の大きさはインスペクターの COLLISION から設定します。
デフォルトの Box ではゴブリンに対して大きくなっているので以下のように修正します。
Type : Capsule
Radius : 0.3
Height : 0.8
実行して正しくゴブリンのサイズでヒットすることが確認出来たらOKです。
Chapter03 - ゴブリンが移動するようにする
ゴブリンが左右にランダムに移動するような処理を加えます。
移動処理は Script コンポーネントから行います。
ASSET の右の ADD ASSET から Script を新規作成してください。
Script の名前は [enemy] と入力してください。enemy.js が作成されます。
enemy.js が作成できたら、ヒエラルキー から enemy を選択し、 ADD COMPONENT で Script を追加してください。
Script が追加できたら、 ADD SCRIPT から先ほど作った enemy.js を選択します。こうすることで entity に script をアタッチすることができます。
enemy.js がアタッチできたら、 enemy の横の edit ボタンを押して Code Editor(コードエディター) を起動します。
コードエディター が起動したら、 enemy.js を以下に書き換えてください。
var Enemy = pc.createScript('enemy');
/*attributesに右、左それぞれの移動量を定義*/
Enemy.attributes.add("RIGHT_MOVE",{type:"number",default:0.01});
Enemy.attributes.add("LEFT_MOVE",{type:"number",default:-0.01});
// initialize code called once per entity
Enemy.prototype.initialize = function() {
/*カウンタ初期化*/
this.count = 0;
/*左右移動を変更するインターバル*/
this.change_interval = parseInt(50 + Math.random() * 20,10);
/*どちらに進むか*/
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
};
// update code called every frame
Enemy.prototype.update = function(dt) {
/*毎フレーム加算する*/
this.count ++;
/*インターバルごとに向きをランダムで変更する*/
if(this.count % this.change_interval === 0){
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
}
/*指定した方向へ移動*/
this.entity.translate(this.movefor,0,0);
/*移動方向に合わせてspriteの向きを変更*/
this.setDirection(this.movefor);
};
/*ベクトルに応じてspriteの向きを変更するメソッド 引数:x軸の移動量*/
Enemy.prototype.setDirection = function(accu) {
this.entity.sprite.flipX = (accu < 0);
};
実行して、ゴブリンが左右に移動することを確認してください。
このままだと、ゴブリンの sprite は移動していますが、当たり判定は追従しておらず、その場に残り続ける形になっています。
これは Rigidbody の type が Static な状態の為に起こる現象です。
Translate で移動するオブジェクトが Rigid Body を持つ場合、 Rigid Body の Type は Kinematic に変更する必要があるので、インスペクターから変更してください。
また、Script コンポーネント内で attribute に定義した値は、定義したスクリプトの parse ボタンを押すことでコードエディター上に表示することができます。
以後、ゴブリンの左右の移動量はインスペクターから変更可能です。
試しに RIGHT_MOVE を 「0.03」 に変更してみましょう。
右への移動速度は速くなったでしょうか?
このように attribute を使うと Script の変数に対してコードエディター からアクセスすることができます。
Chapter04 - ゴブリンを倒す
Sprite Editor(スプライトエディター) を利用して、ゴブリンが倒れるときのアニメーションを作成しましょう。
GIF アニメーションと同じ様に、連続した複数枚の画像データが必要です。
Sandy_atlas フォルダ内の 「Sandy_all.png」 をダブルクリックすると、 スプライトエディター が立ち上がります。
FRAMES IN TEXTURE ATLAS (フレームテクスチャ一覧)内の 「goblin_death」 を全て選択し、NEW SPRITE FROM SELECTION をクリックするとアニメーションが生成されます。
New Sprite という名称のアニメーションが生成されるので、 スプライトエディター を閉じた後、 ASSETS 欄で「goblin_death」に変更してください。
生成した倒されたアニメーションを enemy に組み込んでいきましょう。
enemy を選択し、インスペクター内の ADD CLIP をクリックしてください。
新しく 「CLIP1」 という欄が生成されるので、空いているスロットルに 「goblin_death」 をドラック&ドロップしてください。
アニメーションがループ再生されてしまうので、 Loop のチェックを外してください。
FPS の数値を 「5」 に変更し、Name を 「death」 に変更してください。
Chapter05 -
アニメーションを再生してから消滅させる
PlayCanvas の物理エンジンを利用して、"当たった瞬間"を取得します。 enemy.js を以下のように上書きしてください。
var Enemy = pc.createScript('enemy');
/*attributesに右、左それぞれの移動量を定義*/
Enemy.attributes.add("RIGHT_MOVE",{type:"number",default:0.01});
Enemy.attributes.add("LEFT_MOVE",{type:"number",default:-0.01});
// initialize code called once per entity
Enemy.prototype.initialize = function() {
/*カウンタ初期化*/
this.count = 0;
/*左右移動を変更するインターバル*/
this.change_interval = parseInt(50 + Math.random() * 20,10);
/*どちらに進むか*/
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
/*衝突した際のイベントハンドラ*/
this.entity.collision.on("collisionstart",this._colstart,this);
};
// update code called every frame
Enemy.prototype.update = function(dt) {
/*毎フレーム加算する*/
this.count ++;
/*インターバルごとに向きをランダムで変更する*/
if(this.count % this.change_interval === 0){
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
}
/*指定した方向へ移動*/
this.entity.translate(this.movefor,0,0);
/*移動方向に合わせてspriteの向きを変更*/
this.setDirection(this.movefor);
};
/*ベクトルに応じてspriteの向きを変更するメソッド 引数:x軸の移動量*/
Enemy.prototype.setDirection = function(accu) {
this.entity.sprite.flipX = (accu < 0);
};
/*衝突した瞬間に呼ばれるコールバック*/
Enemy.prototype._colstart = function(ev){
if(ev.other.tags.has("player")){//衝突したentityが"player"tagを所持していたら
console.log("hit");//コンソールにhitと表示
}
};
[コードの解説]16行目で、collision コンポーネント内の on()メソッド から、衝突時のイベントハンドラを設定します。
第一引数はイベント名、第二引数はコールバック関数、第三引数はコールバック関数でのスコープを渡します。
"collisionstart" イベントは衝突した瞬間呼び出されます。
呼び出されたコールバック関数は42行目以降に記述してあり、引数は衝突してきたエンティティを返します。
エンティティが Sandy ちゃんの場合、"player" という tag が付与してあるため、43行目の if文 で分類可能です。
衝突したらコンソールに「hit」と出力します。
保存して実行し、launch の画面でブラウザのコンソールを開いてください。
参考:ブラウザコンソールを開く
コンソールを起動したら、ゴブリンに衝突するたびに、コンソールに 「hit」と表示されることを確認してください。
ここまで出来たら、 enemy.js の44行目を以下のように書き換えてみましょう。
this.entity.destroy();
保存して実行してみましょう。
衝突後、ゴブリンは正しく消滅したでしょうか?
続いて、先ほど作成したゴブリンのアニメーションを再生してから destroy するような処理に書き換えましょう。
enemy.js を以下のように書き換えます。
var Enemy = pc.createScript('enemy');
/*attributesに右、左それぞれの移動量を定義*/
Enemy.attributes.add("RIGHT_MOVE",{type:"number",default:0.01});
Enemy.attributes.add("LEFT_MOVE",{type:"number",default:-0.01});
// initialize code called once per entity
Enemy.prototype.initialize = function() {
/*カウンタ初期化*/
this.count = 0;
/*左右移動を変更するインターバル*/
this.change_interval = parseInt(50 + Math.random() * 20,10);
/*どちらに進むか*/
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
/*衝突した際のイベントハンドラ*/
this.entity.collision.on("collisionstart",this._colstart,this);
/*倒されたかどうか*/
this.isDie = false;
};
// update code called every frame
Enemy.prototype.update = function(dt) {
if(!this.isDie){//生きている状態
/*毎フレーム加算する*/
this.count ++;
/*インターバルごとに向きをランダムで変更する*/
if(this.count % this.change_interval === 0){
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
}
/*指定した方向へ移動*/
this.entity.translate(this.movefor,0,0);
/*移動方向に合わせてspriteの向きを変更*/
this.setDirection(this.movefor);
}else{//倒されている状態
if (!this.entity.sprite._currentClip._playing){//現在再生しているclipの再生が終了したら
/*自分自身を削除する*/
this.entity.destroy();
}
}
};
/*ベクトルに応じてspriteの向きを変更するメソッド 引数:x軸の移動量*/
Enemy.prototype.setDirection = function(accu) {
this.entity.sprite.flipX = (accu < 0);
};
/*衝突した瞬間に呼ばれるコールバック*/
Enemy.prototype._colstart = function(ev){
if(ev.other.tags.has("player")){//衝突したentityが"player"tagを所持していたら
this.die();
}
};
/*自分自身が倒れるときの処理 */
Enemy.prototype.die = function(){
/*isDieフラグを立たせる*/
this.isDie = true;
/*RigidbodyとCollisionを切る*/
this.entity.rigidbody.enabled = false;
this.entity.collision.enabled = false;
/*deathのアニメーションクリップを再生*/
this.entity.sprite.play("death");
};
[コードの解説]
Script の initialize 内で、 isDie という倒されたかどうかを表す変数を追加しました。
衝突した際に isDie を true にし、 rigidbody , collision の機能を off にします。
また、衝突した瞬間に death アニメーションの再生も行います。
update 内では isDie の値を監視しており、倒されたときは移動処理等は停止します。
sprite コンポーネント内の currentClip より、現在再生している animation clip の情報を取得し、アニメーションの再生が終了したところで自分自身を削除します。
保存して実行してみましょう。
正しくアニメーションが動いた後に消滅したことが確認出来たらOKです。
Chapter06 - ダメージを実装する
このままでは Sandy ちゃんは無敵なのでダメージを実装します。
ここでは、ゴブリンに横からぶつかったら Sandy ちゃんのダメージ、上からぶつかったら敵にダメージという処理を実装します。
enemy.js の54行目に以下のコードを追記してください。
var distance = ev.other.getLocalPosition().clone().sub(this.entity.getLocalPosition().clone());
console.log(distance);
ぶつかった際に、ぶつかってきたエンティティと自分自身の座標を算出し距離を求めるような処理を追記してみました。
保存してコンソールを確認してみましょう。
横からぶつかった時の値は約 0.114
上から(縦)ぶつかった時の値は約 0.604
横からぶつかった時と、上からぶつかった時で、 distance の値が異なっていることが確認できます。
この値を利用してぶつかった位置を取得してみましょう。
enemy.jsを以下のように書き換えます。
var Enemy = pc.createScript('enemy');
/*attributesに右、左それぞれの移動量を定義*/
Enemy.attributes.add("RIGHT_MOVE",{type:"number",default:0.01});
Enemy.attributes.add("LEFT_MOVE",{type:"number",default:-0.01});
// initialize code called once per entity
Enemy.prototype.initialize = function() {
/*カウンタ初期化*/
this.count = 0;
/*左右移動を変更するインターバル*/
this.change_interval = parseInt(50 + Math.random() * 20,10);
/*どちらに進むか*/
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
/*衝突した際のイベントハンドラ*/
this.entity.collision.on("collisionstart",this._colstart,this);
/*倒されたかどうか*/
this.isDie = false;
};
// update code called every frame
Enemy.prototype.update = function(dt) {
if(!this.isDie){//生きている状態
/*毎フレーム加算する*/
this.count ++;
/*インターバルごとに向きをランダムで変更する*/
if(this.count % this.change_interval === 0){
this.movefor = (Math.random() > 0.4)? this.RIGHT_MOVE : this.LEFT_MOVE;
}
/*指定した方向へ移動*/
this.entity.translate(this.movefor,0,0);
/*移動方向に合わせてspriteの向きを変更*/
this.setDirection(this.movefor);
}else{//倒された状態
if (!this.entity.sprite._currentClip._playing){//現在再生しているclipが終了したら
/*自分自身を削除する*/
this.entity.destroy();
}
}
};
/*ベクトルに応じてspriteの向きを変更するメソッド 引数:x軸の移動量*/
Enemy.prototype.setDirection = function(accu) {
this.entity.sprite.flipX = (accu < 0);
};
/*衝突した瞬間に呼ばれるコールバック*/
Enemy.prototype._colstart = function(ev){
if(ev.other.tags.has("player")){//衝突したentityが"player"tagを所持していたら
/*playerと自分自身の距離*/
var distance = ev.other.getLocalPosition().clone().sub(this.entity.getLocalPosition().clone());
if(distance.y > 0.7){//上からぶつかった
/*自分自身が倒れる*/
this.die();
}else{//横からぶつかった
/*playerのスクリプトからdamage()メソッドを呼び出す*/
ev.other.script.player.damage();
}
}
};
/*自分自身が倒れるときの処理 */
Enemy.prototype.die = function(){
/*isDieフラグを立たせる*/
this.isDie = true;
/*RigidbodyとCollisionを切る*/
this.entity.rigidbody.enabled = false;
this.entity.collision.enabled = false;
/*deathのアニメーションクリップを再生*/
this.entity.sprite.play("death");
};
[コードの解説]
55行目以降を追記しました。distance の y が 0.7 よりも大きかったら上からぶつかったと定義し、this.die()メソッド を呼び出します。それ以下だと Sandy ちゃんへのダメージとし、ぶつかってきたエンティティ(Sandyちゃん)の script コンポーネントにアタッチされた player.js 内にあらかじめ定義してあるdamage()メソッド を実行します。
実行して正しく動作することが確認できれば OK です。
Chapter07 - 敵を増やす
ここまで出来たら敵の挙動は完成したので、エディターから増やしてみましょう。
ヒエラルキーから Entity を選択し、上部の Duplicate entity から enemy を複製します。
デフォルトでは重なった位置に生成されるので、横にずらして6体程度複製しましょう。
それぞれのインスペクターから Enemy のスピードを微調整してもよいかもしれません。
実行して複数のゴブリンが正しく動いていることが確認出来たら OK です。
Chapter08 - ゲームを公開する
完成したゲームを公開してみましょう。
PlayCanvas では数クリックで PlayCanvas の公開環境にゲームを公開することができます。
エディター上部の Manage Scene をクリックし、出てきたウィンドウの左側から PUBLISH を選択、最後にPUBLISH TO PLAYCANVAS を選択します。
PUBLISH 画面になったら、一番下までスクロールして 「PUBLISH NOW」 をクリックします。
これだけで、PlayCanvas の公開環境に体験可能な状態で公開されました。
BUILDS 画面に記載された URL からゲームを遊ぶことができます。
発行された URL は PlayCanvas へのログインはせず遊ぶことができます!
X(旧Twitter) や Facebook など、たくさんシェアしてたくさん遊んでもらいましょう!
コメント
0件のコメント
サインインしてコメントを残してください。