50回目にして、とうとう最も実装のめんどくさい項目であろうゴール処理の実装を行います。
画像を作って、面白味もないめんどくせぇコードも書かないといけないという2重苦です。
実装結果は以下のようになります。
マリオ1のゴール処理をそのまんま実装します。
めんどいので、花火の実装は省きました、要望があれば追加します。
今回実装する内容
実装内容を確認したいと思います。
1.ゴール画像を作る!
2.ゴールに接触した際の当たり判定を作る
3.ゴールと接触した際に、フラグとマリオを地面まで落とす。
4.下まで降りたら、マリオを出口まで移動させる。
5.出口まで移動させたら、マリオの描画を止めて、時間に応じてスコアを加算する。
6.スコアがゼロになったら、城のフラグを表示させるアニメーションをさせる。
それでは、実装に入ります。
実装
まず、ゴール処理を行うためのクラス、goalObjectクラスを作成して、
その中で、描画と当たり判定処理を行わせるようにしたいと思います。
goalObject.js
まず、メンバー変数を定義します。
ゴールのフラグと、城のフラグは移動するので、変数を保持する必要があります。
function Goal(posX){
this.posX = posX;
this.flagY = 64;
// 高さに応じて決定する
this.score = 0;
// 城の旗
this.castleFlagY = 273;
}
続いて、マリオとの当たり判定を行う関数を実装します。
当たり判定はポールの幅の四角形でとっています。
/**
* マリオとゴールとの当たり判定
* @param {*} mario
*/
Goal.prototype.collisionWithMario = function(mario){
if(mario.goalAnimationState == INACTIVE){
// y軸 マリオの下とポールの上
if(64 <= mario.posY + mario.height){
// y軸 マリオの上とポールの下
if(448 >= mario.posY){
// x軸:ポールの右とマリオの左(幅はポールに合わせる)
if(this.posX + 19.5 >= mario.moveNumX){
// ポールの左とマリオの右
if(this.posX + 14.5 <= mario.moveNumX + 32){
// 高さに応じて、スコアを決定する
this.defineScore(mario.posY);
// マリオにゴールアニメーション用のセットさせる
mario.setGoalAnimation(this.posX - 16);
}
}
}
}
}
}
ゴールに触れた際は、高さに応じて変わるスコアを決定して、マリオがポールから落ちるアニメーションを動作させるための関数を呼びます。
こちらは後で確認します。
次にマリオとポールの高さによって変わるスコアを決定する関数を書きます。
/**
* スコアを決定して加算する
* @param {*} posY
*/
Goal.prototype.defineScore = function(posY){
if(posY <= 64){
this.score = 8000;
}
else if(posY > 64 && posY <= 128){
this.score = 4000;
}
else if(posY > 128 && posY <= 192){
this.score = 2000;
}
else{
this.score = 1000;
}
gScore += this.score;
}
フラグアニメーション関数
フラグをアニメーションさせる関数を実装します。
原作をチェックすると、ポールのフラグはマリオと当たり判定があった時点で下げて、ゴールゾーンのフラグは時間によるスコア加算が終わった地点で上げるようになっています。
/** * フラグを下げるアニメーションをさせる * @param {*} mario */ Goal.prototype.fragAnimation = function(mario){ if(mario.goalAnimationState != INACTIVE){ // 地面まで達した場合 if(this.flagY + 26 < 384){ this.flagY += 3; } else{ this.flag = 384; } } } /** * 城のフラグをアニメーションさせる処理 * @param {*} mario */ Goal.prototype.castleFragAnimation = function(mario){ if(mario.goalAnimationState == GOAL_ANIMATION_END){ this.castleFlagY -= 2; if(this.castleFlagY <= 248){ this.castleFlagY = 248; } } }
次に、update関数を作って、これらの関数を呼ぶようにします。
/**
* 更新関数
* @param {*} mario
*/
Goal.prototype.update = function(mario){
this.collisionWithMario(mario);
this.fragAnimation(mario);
this.castleFragAnimation(mario);
}
最後に、描画関数を作ります。
新しくマップチップ画像を作りましたので、入れ替えてください。また、うまく画像を切り取って表示する必要があります。
/*
描画関数
ctx:context
texture:img class
scrollX:X軸のスクロール量
*/
Goal.prototype.draw = function(ctx,texture,scoreTex,scrollX){
// ポールの先端の描画
ctx.drawImage(texture,416,416,32,32,this.posX - scrollX + 9,50,32,32);
// ポール(真ん中に描画する)
for(var i = 0;i < 10;++i){
ctx.drawImage(texture,480,416,32,32,this.posX - scrollX + 15.5,352 - (i * MAP_SIZE),32,32);
}
// 旗
ctx.drawImage(texture,448,416,32,32,this.posX - scrollX - 8.5,this.flagY,32,32);
// スコアの描画
if(this.score > 0){
this.drawScore(ctx,scoreTex,this.posX - scrollX,this.flagY - 14,this.score);
}
}
スコア関数は今までと変わらないので、省略。
const.js
const.jsにゴールアニメーション用に使う定数を定義します。
// chapter50
let NONE_ANIMATION = -1;
let GOAL_ANIMATION_DOWN = 2;
let GOAL_ANIMATION_WALK = 3;
let GOAL_ANIMATION_TIME_CNT = 4;
let GOAL_ANIMATION_END = 5;
続いて、マリオクラスに必要な関数を実装していきます。
mario.js
ゴールアニメーション処理を行うための、変数を定義します。
// ゴールアニメーション状態を管理する
this.goalAnimationState = INACTIVE;
// 重力など行動を止めるフラグ
this.isMarioStop = false;
// ゴールの位置
this.goalPosX = 36 * 32;
続いて、ゴールアニメーションをスタートさせるための関数を定義します。
マリオのX座標をポールの位置に吸着させて、キー入力や重力、時間の進行を止めるように変数をセットします。
/**
* ゴールアニメーション状態のセット
* @param {*} posX
*/
Mario.prototype.setGoalAnimation = function(posX){
// 位置を修正
this.moveNumX = posX;
this.goalAnimationState = GOAL_ANIMATION_DOWN;
this.isMarioStop = true;
this.keyDisable = true;
gTimeStop = true;
}
続いて、ゴールアニメーションを行うための関数を定義します。
/**
* ゴールアニメーション用関数
*/
Mario.prototype.goalAnimationAction = function(mapChip){
switch(this.goalAnimationState){
// マリオを下までおろす処理
case GOAL_ANIMATION_DOWN:
this.posY += 4;
// 一番下まで降りたら歩かせる
if(this.posY + this.height >= 384){
this.goalAnimationState = GOAL_ANIMATION_WALK;
this.posY = 384 - this.height;
// 重力を有効にする
this.isMarioStop = false;
}
break;
// マリオがゴールに向かって歩く
case GOAL_ANIMATION_WALK:
this.updateMapPosition();
this.moveX(mapChip,NORMAL_SPPED);
if(this.moveNumX >= this.goalPosX){
this.goalAnimationState = GOAL_ANIMATION_TIME_CNT;
}
break;
// 時間をスコアに変えている段階
case GOAL_ANIMATION_TIME_CNT:
//
break;
// カウントが終了した状態
case GOAL_ANIMATION_END:
break;
}
}
goalAnimationState変数に応じて、アニメーションを変えています。
中身は実装とそのまんまですね。
ゴールに入った時に、マリオの描画しないようにするので、
描画するかしないか判定する関数を書きます。
/**
* マリオを描画するか判定
*
* クリアして、次ステージに入ったあとは描画しない。
*/
Mario.prototype.isDraw = function(){
if(this.goalAnimationState >= 4){
return false;
}
return true;
}
また、いままでのように共通の動作ストップフラグでなく、マリオのみが動かないようにするための判定関数を新しく定義します。
/**
* chapter50
* マリオの動けないか判定
*/
Mario.prototype.stop = function(){
return this.isMarioStop || gActionStop;
}
マリオがゴールに入った時に、時間に応じてスコアを加算させる処理を行う必要がありますが、現状、時間がゼロになるとゲームオーバ処理が呼ばれるので、
タイムアップ処理をゴールアニメーション時と場合分けする必要があります。
/**
* マリオ時間切れ時の処理
*/
Mario.prototype.timeOut = function(){
switch(this.goalAnimationState){
case NONE_ANIMATION:
if(!this.isDead()){
this.setDeadParam();
}
break;
case GOAL_ANIMATION_TIME_CNT:
// 終わり状態に遷移させる
this.goalAnimationState = GOAL_ANIMATION_END;
break;
}
}
ここで、ゴール後は、時間を減らす速度を早め、スコアを加算する必要があるので、timerクラスを変更します。
timer.js
マリオの状況に応じて、場合分けをします。
/**
* timer update
*
* isStop:時間を止めるフラグ
* state:マリオのゴールアニメーション状態
*
* 時間切れ:trueを返す
*/
Timer.prototype.update = function(state){
switch(state){
case NONE_ANIMATION:
this.cnt--;
break;
case GOAL_ANIMATION_TIME_CNT:
this.cnt -= 60;
gScore += 50;
break;
}
if(this.cnt <= 0){
this.cnt = 0;
return true;
}
return false;
}
新しいマップチップの適応
ゴールや旗などを新しくマップチップ画像に追加したので、
マップチップを変更してください。
画像だと白くて、見えませんが、一番右下の×のドット絵を移動させたので、
すいませんが、×の切り取り部分を修正してください。
一番右下を使わないようにしていたことを忘れていました。
main.js
main.jsで、ゴールクラスの更新関数や、描画関数を呼びます。
timerの更新関数に引数を入れることや、index.htmlでgoalObject.jsを読み込むことを忘れないでください。
まとめ
内容自体にむずかしいところはありませんが、実装することはいっぱいありましたね。
全体のコードはgithubを参照してください。
感想
実際のマリオがどのような処理になっているかとか、ゴールとポールの幅はどんくらいとか、細かいところを調べるのに時間がかかった。
やっぱりめんどくさかったけど、時間が空いていない分、全体を理解しているから、書くのは早かった。
次はステージ移動処理を書こうと思います。
もうちょっとで完成ではねぇでしょうか?