【DXライブラリ】ジャンプを実装する

DXライブラリとC言語でゲーム制作。今回はアクションゲームならほぼ必須なジャンプの実装をします。

ジャンプの基本

アクションゲームなら2D・3D問わずほぼあるジャンプ。高校の物理で習う「落下」や「放物運動」を思い出す人がいるかもしれないが、力学の公式を厳密に再現しなくてもジャンプの実装は可能。

  1. ジャンプボタンが押されたらY軸方向の速度(踏切の瞬間の速度)をセットしてジャンプ状態にする
  2. ジャンプ状態なら毎フレームY軸方向の速度を減少させる
  3. 2で求めたY軸方向の速度を移動ベクトルに加えてプレイヤーの座標を更新
  4. 床に着地(衝突)した場合Y軸方向の速度を0にしてジャンプ状態を解除

Y軸方向に初速を与えてあとは速度を一定値ずつ減少させるというシンプルな構造。√や2乗の計算がいらないので処理も早い。

余談だがファミコン時代のゲーム(スーパーマリオブラザーズなど)はハードのスペックの関係で重い計算処理はできなかったので上の方法を使っているとどこかのプログラミング記事で見かけた記憶がある。

サンプルコード

それでは実装。ジャンプできるだけでなく、ジャンプボタンの押し方で大ジャンプ・小ジャンプができるようにする。挙動としてはジャンプボタン押しっぱなしで大ジャンプ、一度でもジャンプボタンが離されたら小ジャンプに移行する流れ。

今回はまだ床との当たり判定を実装していないのでY座標が負になったら床に着地(衝突)したと判定し、ジャンプ状態を解除してY座標を強制的に0にしている。

あとジャンプで上昇中および下降中(落下)でのそれぞれのアニメーションの再生も合わせて実装。

まずジャンプをするボタンの設定を作る。キーコンフィグの記事ですでにジャンプをするボタンを設定しているのでそれをそのまま使うのだが、ジャンプボタンとメニュー画面に戻るボタンが一緒になっているので変更する。

Game.cpp

//Game.cppの更新関数の以下の部分を変更
void Game_Update() {
    //略

    if (Input_GetKeyboardDown(KEY_INPUT_ESCAPE)) { //Mキーが押されていたら
        SceneMgr_ChangeScene(eScene_Menu);//シーンをメニューに変更
    }
}

ジャンプ状態に関する列挙体を新しく作る。敵やNPCなどを実装するときにも使いまわすので新しく Define.h を追加してそこに書きこむ。

Define.h

#ifndef DEF_DEFINE_H

#define DEF_DEFINE_H

//オブジェクトのジャンプ関連の列挙体
typedef enum {
	OBJ_NO_JUMP, //ジャンプしていない
	OBJ_JUMP, //ジャンプ
	OBJ_LOWJUMP, //小ジャンプ
	OBJ_FALL //落下中
}Object_Jump;

extern Object_Jump object_jump;

#endif

Player.cppにジャンプに必要な定義・変数を追加する。

Player.cpp

//以下をインクルードに追加
#include "Define.h"

//プレイヤーの構造体
typedef struct {
	//以下の2つを追加
        float JumpPower; //Y軸方向の速度
        int JumpStatus; //ジャンプ状態に関する変数
}player_t;

//以下の定義を追加
const static float PLAYER_JUMP_POWER = 0.18f; //ジャンプ力
const static float PLAYER_GRAVITY = 0.008f; //プレイヤーの重力
const static float PLAYER_MAX_FALL_SPEED = -1.5f; //プレイヤーの落下速度の下限

//プレイヤーの行動状態の列挙体
typedef enum {
    //以下の2つを追加
    PLAYER_JUMP, //ジャンプ(上昇中)
    PLAYER_FALL, //落下
}Player_Action;

Player_Initialize() でジャンプ関連の変数を初期化する。

//初期化
void Player_Initialize() {
	//略

        //以下の処理を追加
        //ジャンプ力の初期化
        player.JumpPower = 0.0f;

        //ジャンプ関連の変数の初期化
        player.JumpStatus = OBJ_NO_JUMP;
}

Player_MoveUpdate() にジャンプ処理を追加する。

//プレイヤーの移動処理
void Player_MoveUpdate() {
    //略

    //プレイヤーがジャンプ・落下中でないかつジャンプボタンが押されていればジャンプ処理
    if (player.Action != PLAYER_JUMP && player.Action != PLAYER_FALL && Input_GetGamepadDown(config.jump)) {
        //ジャンプフラグを立てる
        player.JumpStatus = OBJ_JUMP;

        //Y軸方向の速度をセット
        player.JumpPower = PLAYER_JUMP_POWER;
    }

    //略

    //Y座標の計算
    if (player.JumpStatus >= OBJ_JUMP) {
        // Y軸方向の速度を重力分減算する
        player.JumpPower -= PLAYER_GRAVITY;

        // ジャンプボタンが一度でも離されたら小ジャンプに移行
        if (Input_GetGamepadUp(config.jump) && player.JumpPower > 0 && player.JumpStatus == OBJ_JUMP) {
            player.JumpPower /= 2.0f;
            player.JumpStatus = OBJ_LOWJUMP;
        }

        // 落下速度が下限を超えていたら修正する
        if (player.JumpPower < PLAYER_MAX_FALL_SPEED) player.JumpPower = PLAYER_MAX_FALL_SPEED;

        // 移動ベクトルのY成分をY軸方向の速度にする
        MoveVec.y = player.JumpPower;

        //移動ベクトルのY成分が負なら落下状態にする
        if (MoveVec.y < 0) {
            player.JumpStatus = OBJ_FALL;
        }
    }

    //プレイヤーがジャンプ状態の場合のプレイヤーの行動状態決定
    switch (player.JumpStatus) {
    case OBJ_NO_JUMP: //ジャンプも落下もしていない
        break;
    case OBJ_JUMP:
    case OBJ_LOWJUMP: //ジャンプ中
        player.Action = PLAYER_JUMP;
        break;
    case OBJ_FALL: //落下
        player.Action = PLAYER_FALL;
        break;
    default:
        player.Action = PLAYER_IDLE;
        break;
    }

    //略

    //Y座標が0より小さい場合(マップとの当たり判定未実装のため仮処理)。Y座標を求めた後に書く
    if (NowPos.y < 0) {
        //Y座標を0にする
        NowPos.y = 0.0f;

        //プレイヤーの行動を待機にする
        player.Action = PLAYER_IDLE;

        //ジャンプフラグをオフにする
        player.JumpFlag = false;
    }

    //略
}

Player_PlayAnimation() でジャンプ関連のアニメーション処理を追加する。

//プレイヤーのアニメーション処理
void Player_PlayAnimationUpdate() {
    //略

    //1F前のプレイヤーの行動と現在のプレイヤーの行動が違っていれば再生するアニメーションを変更
    if (player.PrevAction != player.Action) {
        //今までアタッチしていたアニメーションのデタッチ
        MV1DetachAnim(player.ModelHandle, player.AttachIndex);

        //再生するアニメーションの番号を取得
        switch (player.Action) {
        case PLAYER_IDLE:
            player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Idle");
            break;
        case PLAYER_WALK:
            player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Walk");
            break;
        case PLAYER_JUMP:
            player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Jump");
            break;
        case PLAYER_FALL:
            player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Fall");
            break;
        default:
            player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|T-Pose");
            break;
        }

        //略
    }

    //略
}

カメラの注視点について

ジャンプの実装ができたわけだが、今回の実装だとカメラの注視点もプレイヤーのジャンプに追従するようになっている。

ゲームによってはカメラがジャンプしたプレイヤーに追従すると着地付近などが視認しにくくなるため、ジャンプ中はカメラはY軸方向の移動をしないor制限をかけているゲームも多い。この辺りはゲームバランスとの相談になる。もちろん画面外に飛び出すような場合はちゃんとカメラを追従させること。

ジャンプ時に限らず横移動時に注視点をプレイヤーから外して視認性・操作性を上げるといった工夫をしているゲームもあり、何気に奥が深い点だったりする。観察してみると新しい発見があるかも。下は星のカービィやスマブラの生みの親で有名な桜井さんによる解説動画。

コメント

タイトルとURLをコピーしました