スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

アクションゲームのジャンプ 実装方法

以前、現在製作中のアクションゲームにおいて、
------------------
・重力で落下する
・ジャンプする

------------------
という物理的な動きを実装したので、 その事を書いてみます。
何かの役に立つかな、と思う。)


マリオとかは実際どう計算しているのか分からないのですが、
とりあえず 自分は下記の基本的な公式を使いました。

-------------------------------------------------
v = v0 + at
y = v0 * t + 0.5 * a * t^2

(v0 = 初速、  a = 加速度、  t = 時間、  t^2 = tの二乗)
-------------------------------------------------

で、まず重力加速度が約9.8なので a = -9.8とし、 単位をドット、という事にしました。
(つまり、 約-10ドット = 加速度と定義)


それで 位置を算出してみたんですけども、
マップチップが16×16ドットしか無かったので、物凄くカクカクにしか動かず、
使い物になりませんでした。
(落下するのが速すぎる。)


で、しばらく悩んでいたところ
---------------------------------------------
【1】時間を指定する
【2】時間内にどれくらい移動するかを指定する
【3】時間と、移動量をもとに計算

---------------------------------------------
↑こうしたらいいんじゃない?と思いました。

時間と移動距離を指定し、任意のジャンプをさせる方式であれば、
「アイテムを取った時に、ジャンプが高くなる」という処理も簡単に出来そうですし。


で、実際にやった方法。
-----------------------------------------------------------------
200ミリ秒に 32ドット移動(ジャンプ)させたい場合 を考えます。
-----------------------------------------------------------------

まず、上記の1番目の式を使って 初速を求めます。

0 = v0 + (-9.8 * 200)
0 = v0 - 1960
1960 = v0


はい、初速が1960という事が分かりました。
※速度が0になっているのは、ジャンプした時の最高点(落下し始める寸前)は
速度が0になるからです。



次に2番目の公式を使って割合を求めます。

割合というのは、
例えば 1秒間で10m落下するとします。 高さが10mの場合は、1秒で丁度地面に衝突します。
しかし、ゲームの世界では1mしかないとします。
この場合「相対的に同じ落下運動をさせるにはどうすればいいか?」(1秒で地面衝突)というと、
実際の計算世界とゲームの世界の差の割合を求めて、割合をかければOKなのです。

今回の例では
-------------------------------
実際の計算する世界 = 10m
ゲーム内の世界 = 1m
-------------------------------
なので、1 ÷ 10 = 0.1、つまり0.1をかければ良い事になります。
10m × 0.1 = 1m すなわち、1秒間でちょうど地面に衝突してくれます。

※要するに、距離が10分の1なんだから、落ちるスピードも10分の1にする、という事。


では、割合を求める一部始終をば。

32 = 1960 * 200 + 0.5 * (-9.8) * 200 * 200
32 = 392000 + 20000 * (-9.8)
32 = 392000 - 196000
32 = 196000  //何をかけたら32になるか? を考える
割合 = 32 / 196000
割合 = 0.0001632653061 //大体の値です



これで下準備 終了です。

-<求めた値など まとめ>---------------------------------
初速: 1960
加速度: -9.8
時間: 200(ミリ秒)
高さ: 32ドット
割合: 0.0001632653061

--------------------------------------------------------

これらの値を使って、
0ミリ秒の時   0ドット上昇
200ミリ秒の時  32ドット上昇

となるかやってみましょう!

y = (1960 * 0 + 0.5 * (-9.8) * 0 * 0) * 0.0001632653
y = 0 * 0.0001632653061
y = 0    //OK!

y = (1960 * 200 + 0.5 * (-9.8) * 200 * 200) * 0.0001632653
y = 196000 * 0.0001632653061
y = 31.9999999956    // !


・・・。

うん、誤差があるんだすまない(´・ω・`)

でもfloat型に変換(キャスト)すれば32になります!
(座標の型はfloatなのである。)



で、無事 計算できたからOKなのかというと、実は駄目です。
何故か?

-----------------------------------------
・最高点に達した状態で止まるから。
・基準 + 変化量 という考え方だから。

-----------------------------------------
式を見れば分かるように、最高点に達するまでの軌跡を計算しているに過ぎません。
落下してもとの場所に戻るまでの部分は含まれてない のです。

【参考画像】
Jump2.jpg


「じゃあ2倍した時間まで処理を続ければ いいじゃない」と思うかもしれません。

確かに、2倍した時間、つまり400ミリ秒を代入して計算すれば

y = (1960 * 400 + 0.5 * (-9.8) * 400 * 400) * 0.0001632653
y = (784000 - 784000) * 0.0001632653
y = 0 * 0.0001632653
y = 0


上記の様に 0ドット上昇となり、もとの場所に戻る事が分かります。
でも駄目なのです。
何故か?

まず当たり判定の問題。
------------------------------------------------
上昇中は上にブロックなどが無いか?
下降中は下にブロックなどが無いか?

------------------------------------------------
と処理を分岐しなければならなくなります。面倒ですし、コードが見にくくなります。

もう1つはそもそもの座標の算出方法。
地面をy = 0とすると、
---------------------------------------
現在のy座標 = 0 + 0ドット上昇
現在のy座標 = 0 + 14ドット上昇
現在のy座標 = 0 + 26ドット上昇
現在のy座標 = 0 + 32ドット上昇

---------------------------------------

このように基準の座標 + 差分という算出方法をしているのです。 

例えば、下からの風によって主人公の体が2ドットずつ浮き上がるとします。
--------------------------------------
現在のy座標 += 2ドット
現在のy座標 = 0 + 0ドット上昇

現在のy座標 += 2ドット
現在のy座標 = 0 + 14ドット上昇

現在のy座標 += 2ドット
現在のy座標 = 0 + 26ドット上昇

--------------------------------------
こういう風にした場合どうなるか? というと普通にジャンプしたときと変わりません。
基準の座標 + 差分 を 代入して 上書きしているからです。

更に、地面からジャンプ ⇒地面に着地なら問題ないですが、地面からジャンプ ⇒ 空中の場合は、
空中で止まってしまいます。

突然、舞空術を習得してしまうのです。

【参考画像】
Jump3.jpg


と言うわけで、問題がありすぎという事が分かります。
そこで 自分が出した解決策とは・・・!


<↓以下、昨日の続きです>

解決策です。
まず、 座標を代入するやり方を止めました。

良く考えたら、普通は、現在位置に移動スピードを加算・減算しますね。
その様に変更しました。
----------------------------------
現在の座標 = 基準の座標 + 差分  ×
現在の座標 += 移動スピード     ○

----------------------------------

つまり、前述した物理式の1番目(v = v0 + at)を用いるようにしました。


それともう1つ。
-------------------------------
・重力の計算をする関数
・ジャンプの計算をする関数

-------------------------------
処理を このように2つの関数に分けました。

で、重力は宇宙にでも飛び出したりしない限り常時働きますよね。
なので、下記の様な関数を作り、毎回呼び出すようにしました。

void Gravity(void){
if(地面に接触していなければ){
  落下量 = 滞空している時間 × 加速度(-9.8)
if(落下量によって、主人公が地面にめり込まなければ){
主人公の座標 += 落下量
}else{
地面にめり込むので、地面にめり込まない位置へ修正
}
}
}



で、この関数を作った時に素晴らしい事に気付きました。
ジャンプの処理が簡易化される んです。

ジャンプは物理の公式1番目を用いるのですが、
加速度×時間の部分はGravity関数がやってくれるのでいらないんです。
Needless.jpg

つまりジャンプする処理は特定の時間中、ひたすら初速を足せばいいだけになるんです。

void Jump(void){
if(指定した時間×2 以下であれば){
if(移動場所に障害物がなければ){
主人公の座標 += 初速
}else{
ぶつかったので、障害物にめり込まない場所へ修正。
そして落下速度を0にし、Jump関数も終了する。
}
}
}

※指定した時間 = 最高点に達するまでの時間。
再び地面まで降りるところまで処理させるので、2倍した時間を終了条件にしてます。



つまり、物理の公式を2つの関数に分割すればよかったのです。
公式をそのまま まるごと使おうとしてたから訳の分からん事になってました。   (頭がカタい)
柔軟に考えることが大事ですね(`・ω・´)
スポンサーサイト

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

コメント

No title

難しく考えすぎではないですか?

No title

やっぱり考え方、計算がゴチャゴチャしてますかね・・。
んー自分もなんか複雑にやりすぎているような気がしますv-292

でも、一応 明日 簡易化した処理Verを書く予定ですv-290
(多分、分かりやすくなっていると思います)
コメントの投稿
管理者にだけ表示を許可する



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。