【プチコン4講座】ジャンプアクションの作り方:ハンズオン編
こんにちは。なおキーヌです。
ブログ毎日更新は271日目になります。
いきなりマリオ風のジャンプを作るのは解説も含めると結構複雑になります。
もし早くマリオのジャンプの仕組みが知りたい場合は
マリオ ジャンプ 仕組み
等で検索してもらった方が早いかもしれません。
と、今自分でググってみたのですが私も過去にマリオ風ジャンプの記事を書いていたようです(笑)
javascript版ですが、基本的な考え方は同じなのでプチコン4でも流用は可能です。
まずは簡単なジャンプから作っていきましょう。
とにかくボタンを押してジャンプさせてみよう
ジャンプの考え方については前回学んでもらったと思うので、早速ジャンプを作っていきましょう。
恐らく数学が得意な人はすぐに自然なジャンプを作ることが出来てしまうかと思いますが、
一旦何も考えずにどうやったらジャンプしたことになるかを考えて作ってみましょう。
ジャンプの最低条件、それは地面を蹴って両足が宙に浮くことです。
簡単ですね。
実際に作ってみましょう。
とりあえず最低限のコードを記述しておきます。
少し長いですが書き写してください。
' 画面クリア
ACLS
' 地面と判定するための座標定数
CONST #GND = 240-32
' ジャンプの最大座標
CONST #JUMP_MAX = 240-80
' ループ用変数
VAR G_I = 0
' ジャンプフラグ
VAR JUMPING = #FALSE
' 上昇中or下降中フラグ
VAR UPDOWN = #FALSE
' プレイヤーの定義
SPSET 0, 876
SPVAR 0, "X", 200-8
SPVAR 0, "Y", #GND
' 地面の描写
FOR G_I = 0 TO 24
TPUT 3, G_I, #GND/16+1, &HECC2
NEXT
' ループ開始
LOOP
D_CONTROLLER
VSYNC
ENDLOOP
' コントローラー関数
'───────────────────────────────
DEF D_CONTROLLER
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
VAR B = BUTTON(0)
' 左ボタン処理
IF (B AND 1 << #B_LLEFT) != 0 THEN
SPVAR 0, "X", SPVAR(0,"X")-1
ENDIF
' 右ボタン処理
IF (B AND 1 << #B_LRIGHT) != 0 THEN
SPVAR 0, "X", SPVAR(0,"X")+1
ENDIF
' ジャンプ中か調べる
IF JUMPING != #TRUE THEN
' ジャンプ中じゃなければAボタン処理が効く
IF (B AND 1 << #B_RRIGHT) != 0 THEN
' ジャンプフラグをONにする
JUMPING = #TRUE
' 上昇中フラグに切り替える
UPDOWN = #TRUE
ENDIF
ELSE
' 上昇中か下降中かで処理を変更
IF UPDOWN == #TRUE THEN
' ※上昇中
' ジャンプ位置最大まできているか調べる
IF SPVAR(0,"Y") <= #JUMP_MAX THEN
' 下降中フラグに切り替える
UPDOWN = #FALSE
ELSE
最大値にたどり着いていないならY座標を減らす
SPVAR 0, "Y", SPVAR(0,"Y")-1
ENDIF
ELSE
' ※下降中
' 着地しているか調べる
IF SPVAR(0,"Y") >= #GND
' ジャンプフラグを解除
JUMPING = #FALSE
' めり込んだときのためのプレイヤー座標を補正
SPVAR 0,"Y", #GND
ELSE
最大値にたどり着いていないならY座標を減らす
SPVAR 0, "Y", SPVAR(0,"Y")+1
ENDIF
ENDIF
ENDIF
' プレイヤーを移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
END
基本的なジャンプ処理を一気に書きました。
Aボタン処理のところをに詰め込んだので、コメントと合わせてじっくりと見てください。
ジャンプ処理のコアとなる部分は
- ジャンプ中かどうか
- 上昇中か下降中かどうか
- 最大の高さにたどり着いたかどうか
- 着地しているかどうか
の4つを調べる点にあります。
ジャンプ中フラグがONのときはボタンの処理を無効化しておかないと、
押している間どんどん上に上がって行ってしまいます。
後は上昇中か下降中かを調べないとどちらに座標を動かすかわかりませんよね。
このフラグを切り替えるためにはジャンプして一番上までいったかどうか。
落下中に着地したかどうかを調べないといけません。
ただボタンを押してプレイヤーが上昇して下降するだけでこれだけの処理がつまっています。
実際に動かしてみましょう。
……どうでしょうか?
ジャンプすることはできましたが、満足行かないジャンプになっていると思います。
背景に星をちりばめれば宇宙ステージみたいな感じでふわっとしてるという理由付けができれば
意外とこれでもありなんじゃないかなって思えてくるかと思います。
しかし今目指しているジャンプは宇宙空間ではなく地上でのジャンプです。
現状、ジャンプの進行具合をグラフを表すと以下のようになります。
体感的にもマリオのジャンプと比べると全然違いますよね。
マリオのジャンプはグラフにすると放物線を描いています。
放物線グラフと言えば二次関数です。
私は中学の時、二次関数の勉強とても苦手だったのですが
マリオのジャンプで例えてくれたらたぶんすんなりと頭に入っていたと思います。
やはり教える側はもうちょっと考えるべきだと思いますね。
当時小難しい話で理解出来てた同級生たちは凄いと思います。
ジャンプ処理の簡単な解説
コードではわかり辛かった人用に、ジャンプの仕組みの簡単な解説です。
ゲームで言えば現在いるY座標が224の時、数値をマイナスしていくと上に上がります。
つまりジャンプボタンを押したらY座標が減っていくということです。
しかしジャンプとは最終的には着地しますよね?
Y座標が減ってそのままだとジャンプというよりも浮遊です。
ドラゴンボールでいうところの武空術ですね。
今回はやりたいのは浮き続けるのではなくジャンプです。
ということでプログラミングで考えると以下のようになります。
ジャンプは起点を0としたらY座標をマイナスしていって、一定値までたどり着いたら最終的には0に戻す
これ、テストに出ます!
ジャンプアクションの基本となりますのでしっかり覚えておいてください!
ジャンプは物理法則に沿うのが基本
本来ジャンプというのは物理法則にしたがうものです。
地面を蹴った瞬間はY軸の減算が大きく、地面から離れる程減算する数が小さくなります。
そして地面に近づくほどY軸の加算が大きくなります。
これを実現できれば放物線を描くジャンプが完成します。
頭の良い人は計算式を使ってすぐに実現できるのですが、
プログラミングは答えが一つではないので決まっている動きをするのであれば、
事前に配列に数値を組み込んでそれを呼び出してしまえばいいわけです。
数学が出来ないからゲームプログラミングはできないは言い訳です。
計算式がわからないのであれば、ゴリ押しでやってしまうのも手です。
方法なんて後から知れば問題ありません。
プログラミングは動けば勝ちです。
仕事で共同開発する際は別ですが(笑)
不自然なジャンプを少し改良してみよう
今回は二次関数の計算式を使わずに宇宙空間ジャンプを重力のある地上ジャンプに変更してみましょう。
先ほども言ったように、配列に値を記述してゴリ押しする方法を試してみます。
物理法則を無視しつつ、尚且つ自然な感じの数値を作ってみます。
プレイヤーのY座標は「224」
ジャンプの最大地点Y座標は「180」
差は「44」です。
ということは簡単に考えると2で割ると22個2がでてきます。
その2を使って最大値である44を越えないように放物線を描く数値にしてみましょう。
着地地点からジャンプ最大値になるまでに44になればOKです。
後は同じように数値を逆転させればいいですね。
DIM JUMP_PROCESS[] = [-14,-10,-8,-6,-4,-2,2,4,6,8,10,14]
ジャンプした瞬間の値を少し強めて、あとは少しずつ等間隔で減らしていっています。
計算式を使っていないのでデジタル的な数値になってしまっていますが、
これでさっきの宇宙空間ジャンプから地上ジャンプ風には切り替わっていると思います。
試しにこれをつかって実装してみましょう。
実はこれを使うとジャンプ最大値と上昇中か下降中かが決まるので、
それぞれのフラグは不必要になります。
配列の中に入っている数値を足していくだけですからね。
代わりに配列のどこにアクセスしてるかの変数は必要になりますので注意してください。
配列の終わりも配列の長さで判断できます。
' ジャンプ配列アクセス用変数
VAR JPPC = 0
' ジャンプ配列
DIM JUMP_PROCESS[] = [-14,-10,-8,-6,-4,-2,2,4,6,8,10,14]
' コントローラー関数
'───────────────────────────────
DEF D_CONTROLLER
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
VAR B = BUTTON(0)
' ※他のボタン処理省略
' ジャンプ中か調べる
IF JUMPING != #TRUE THEN
' ジャンプ中じゃなければAボタン処理が効く
IF (B AND 1 << #B_RRIGHT) != 0 THEN
' ジャンプフラグをONにする
JUMPING = #TRUE
' 上昇中フラグに切り替える
UPDOWN = #TRUE
ENDIF
ELSE
' 現在座標にジャンプ配列の該当する値を取り出して足す
SPVAR 0, "Y", SPVAR(0,"Y") + JUMP_PROCESS[JPPC]
' 次のジャンプ配列にアクセスするためにインクリメント
INC JPPC
' もしアクセス変数と配列の長さが同じになったら
IF JPPC == LEN'(JUMP_PROCESS)-1 THEN
' プレイヤーのY座標補正
SPVAR 0, "Y", #GND
' ジャンプフラグを解除
JUMPING = #FALSE
' 次のジャンプのためにアクセス変数を初期化しておく
JPPC = 0
ENDIF
ENDIF
' プレイヤーを移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
END
これでプレイしてみると、まるでノミみたいなジャンプになります(笑)
というのも放物線のグラフとなるジャンプ配列が少なすぎるためです。
一旦わかりやすいようにFPSを低下させて動画にしてみました。
#petitcom #プチコン4 #NintendoSwitch
犬が跳び跳ねるだけ
fps低下中 pic.twitter.com/wIMqVoNwrj— なおキーヌ@ゲームクリエイターLv5 (@naokeyzmt) September 28, 2019
本来は小数点とかも使って細かくY座標を動かしてやる必要があります。
もしくは小数点を使わないならジャンプの高さをあげて配列の数を増やしてやるしかありません。
ということは結局二次関数の計算式を使わないとまともにやってられん!
ってなるのです。
ココで人間は初めて二次関数が必要だなと求めるようになります。
そして二次関数を勉強するとやりたいことが目の前にあるわけですから、
普通に学校の勉強をするよりかは理解度がかなり高まるという事です。
どこかの学校では既に取り入れているかもしれませんが、
数学の勉強とプログラミングの勉強は平行してやると、
賢い学生が育つんじゃないかなと素人ながらにも考えるようになりました。
話を戻しますと、今の状態では重力を感じるジャンプになりましたが完全にノミジャンプなので
「VSYNC 10」とかに設定して遅くしてやると放物線ジャンプ風になっているのがよくわかります。
二次関数を勉強してみるのもあり
ジャンプ処理に二次関数が必要なのが分かっていただけたと思います。
y=ax^2(ワイ イコール エーエックスの二乗)
これを使えばジャンプの基本的な放物線を作ることが出来ます。
中学数学レベルなので、大抵の人は完全に理解していると思います。
私みたいに勉強してこなかった人間には結構辛いかもしれません。
今ではyoutubeで調べれば無料で詳しい解説をしている動画もたくさんあっていい時代ですね。
マリオのジャンプを作るうえでは必須の計算式です。
面白いと感じるジャンプアクションゲームを作るなら覚えておいて損はないと思います。
微分積分や三角関数に比べれば小難しくはないのでこれを機に覚えてしまいましょう!
最後に今回のコードのまとめを書いておきます。
次回は二次関数を使ってマリオのジャンプに近づけるように頑張ってみましょう。
それでは。
' 画面クリア
ACLS
' 地面と判定するための座標定数
CONST #GND = 240-32
' ジャンプの最大座標
CONST #JUMP_MAX = 240-80
' ループ用変数
VAR G_I = 0
' ジャンプフラグ
VAR JUMPING = #FALSE
' ジャンプ配列アクセス用変数
VAR JPPC = 0
' ジャンプ配列
DIM JUMP_PROCESS[] = [-14,-10,-8,-6,-4,-2,2,4,6,8,10,14]
' プレイヤーの定義
SPSET 0, 876
SPVAR 0, "X", 200-8
SPVAR 0, "Y", #GND
' 地面の描写
FOR G_I = 0 TO 24
TPUT 3, G_I, #GND/16+1, &HECC2
NEXT
' ループ開始
LOOP
D_CONTROLLER
VSYNC
ENDLOOP
' コントローラー関数
'───────────────────────────────
DEF D_CONTROLLER
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
VAR B = BUTTON(0)
' 左ボタン処理
IF (B AND 1 << #B_LLEFT) != 0 THEN
SPVAR 0, "X", SPVAR(0,"X")-1
ENDIF
' 右ボタン処理
IF (B AND 1 << #B_LRIGHT) != 0 THEN
SPVAR 0, "X", SPVAR(0,"X")+1
ENDIF
' ジャンプ中か調べる
IF JUMPING != #TRUE THEN
' ジャンプ中じゃなければAボタン処理が効く
IF (B AND 1 << #B_RRIGHT) != 0 THEN
' ジャンプフラグをONにする
JUMPING = #TRUE
' 上昇中フラグに切り替える
UPDOWN = #TRUE
ENDIF
ELSE
' 現在座標にジャンプ配列の該当する値を取り出して足す
SPVAR 0, "Y", SPVAR(0,"Y") + JUMP_PROCESS[JPPC]
' 次のジャンプ配列にアクセスするためにインクリメント
INC JPPC
' もしアクセス変数と配列の長さが同じになったら
IF JPPC == LEN'(JUMP_PROCESS)-1 THEN
' プレイヤーのY座標補正
SPVAR 0, "Y", #GND
' ジャンプフラグを解除
JUMPING = #FALSE
' 次のジャンプのためにアクセス変数を初期化しておく
JPPC = 0
ENDIF
ENDIF
' プレイヤーを移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
END