スポンサーサイト

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

アクションゲームの斜め坂の実装方法

現在、斜め坂の当たり判定周りを実装しています。
まず、根幹の処理をいくつか変えたので、それの説明。

-----------------------------
【1】めり込みの際の、押し込み廃止
【2】弾性力追加

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


【1】めり込みの際の、押し込み廃止
主人公が右へ移動して、移動可能オブジェクトAと衝突したとします。

この時、今までは
こんな面倒な手順を取っていたのですが、
===================================
手順1:めり込んでいた分だけAを右に動かす
手順2:更に右に物体があった場合はそれも動かす
手順3:更に更に右側に移動不可の壁などがあった場合、
     手順1~手順2の移動をキャンセル

===================================
【参考画像】
Sample_20140905_1.jpg


上記手順を止めて、
めり込む場合は必ず、
めり込まない位置に戻す
という風にしました。

【参考画像】
Sample_20140905_2.jpg

これだと、押せるオブジェクトがあっても押せないじゃないか、という事になってしまいますが、
次の弾性力と合わせて説明します。


【2】弾性力追加
全ての物体に"跳ね返る度合い"の概念を追加しました。
オブジェクトが衝突した場合、この概念を用いて移動方向を決めます。

※下記ページを参考にしました。
【参考URL】EMANの物理学・力学・2物体の衝突

オブジェクトA、Bが衝突した場合、下記の様に計算します。
int t_SpeedA = t_ObjectA->m_DetailSpeedX;
int t_SpeedB = t_ObjectB->m_DetailSpeedX;
double t_ElastA = ( double )t_ObjectA->m_ElasticForce / 1000;
double t_ElastB = ( double )t_ObjectB->m_ElasticForce / 1000;
double t_RateA = t_ObjectA->m_Mass / t_ObjectB->m_Mass;
double t_RateB = t_ObjectB->m_Mass / t_ObjectA->m_Mass;

t_ObjectA->m_DetailSpeedX = t_SpeedA + ( int )( ( double )( -t_SpeedA + t_SpeedB )*( 1 + t_ElastA )/( t_RateA + 1 ) );
t_ObjectB->m_DetailSpeedX = t_SpeedB + ( int )( ( double )( -t_SpeedB + t_SpeedA )*( 1 + t_ElastB )/( t_RateB + 1 ) );



分かりにくいので、具体例。

■例1
主人公Aが移動速度2で、壁Bに衝突。
主人公Aの弾性係数は0とする。(跳ね返る弾力性が無い)


※壁Bは不動なので、質量を9999999などとして考えます。
(主人公の質量を10等にした場合、壁は質量が大きすぎて微動だにしない、という動作になる)

t_ObjectA->m_DetailSpeedX = 2 + ( -2 + 0 )*( 1 + 0 ) / ( 0 + 1 );
t_ObjectA->m_DetailSpeedX = 2 - 2;
t_ObjectA->m_DetailSpeedX = 0;
⇒主人公の移動速度は0となる

t_ObjectB->m_DetailSpeedX = 0 + ( -0 + 2 )*( 1 + 0 ) / ( 9999999 + 1 );
t_ObjectB->m_DetailSpeedX = 0 + 0
t_ObjectB->m_DetailSpeedX = 0
⇒壁の移動速度は0のまま変わらない


■例2
主人公Aが移動速度2で、壁Bに衝突。
主人公Aの弾性係数は1とする。(最大限跳ね返る)


t_ObjectA->m_DetailSpeedX = 2 + ( -2 + 0 )*( 1 + 1 ) / ( 0 + 1 );
t_ObjectA->m_DetailSpeedX = 2 - 4;
t_ObjectA->m_DetailSpeedX = -2;
⇒主人公の移動速度は-2となる
(壁にぶつかった速度と同じ速さで、反対方向に跳ね返る)

t_ObjectB->m_DetailSpeedX = 0 + ( -0 + 2 )*( 1 + 0 ) / ( 9999999 + 1 );
t_ObjectB->m_DetailSpeedX = 0 + 0
t_ObjectB->m_DetailSpeedX = 0
⇒壁の移動速度は0のまま変わらない


こんな感じですね!
で、めり込み解消の方にしか座標を修正しないのなら、オブジェクトを押せないのでは?
という点についてですが、


■1フレーム目
手順1:主人公が移動速度1で、箱Aに衝突する
手順2:弾性力の計算が行われ、箱Aの移動速度が1になる

■2フレーム目
手順3:箱Aが移動速度1で、箱Bに衝突する
手順4:弾性力の計算が行われ、箱Bの移動速度が1になる

■3フレーム目
手順5:箱Bが移動速度1で、壁に衝突する
手順6:弾性力の計算が行われ、箱Bの移動速度が0に

⇒主人公が箱を押している様な動作になる!



こんな感じで、「複数のオブジェクトを押している」動作になります。
1フレームで全部計算するのではなく、複数のフレームに分けて処理が行われます。

つまり、最終的な結果が出るまでに表示遅延が発生するのですが、
1秒間に64回画面を更新する場合に、4回で結果が出た場合、
1秒の16分の1、つまり0.0625秒程度の誤差になります。

肉眼で「あっ衝突後の位置が1回で更新されずに、1、2フレーム遅延して更新が完了している!」とか
分かる訳ないので、これで問題ない、という寸法です。



■斜め坂の実装方法
では、次に斜め坂の実装について。

斜め坂は、一次関数の直線と考えて、
オブジェクトの底との交点を求めて算出します。

※下記の2つルールを守って直線情報を定義します
ルール1:必ず右方向の直線になるように座標を指定
(始点のX座標を、終点のX座標より小さくする。
例えば、右上がりの直線情報は、
始点(200, 0 ) - 終点(0, 200)ではなく、始点(0,200 ) - 終点(200, 0)とする。
※左向きと右向きの直線が混在すると面倒だから、というだけの理由。)


ルール2:垂直な直線は存在しないものとする
(直線の傾きを出す所で、0除算が発生する為。
 垂直な直線を作りたい場合は、直線ではなく横幅1ドットの矩形として対応する)



例えば、オブジェクトAが右上がりの斜め坂に衝突する場合は、
⇒A ◢
----------------------------------------------
【1】まず、斜め坂を矩形(四角形)として衝突判定を行う
【2】衝突している場合、下記を求める
  ①斜め坂の始点(X座標)
  ②斜め坂の始点(Y座標)
  ③斜め坂の終点(X座標)
  ④斜め坂の終点(Y座標)
  ⑤斜め坂の傾き
  ⑥オブジェクトAの底辺
  ⑦オブジェクトAの右辺
  ⑧オブジェクトAの高さ(縦幅)

【3】上記から、オブジェクトAの右辺があるX座標での、斜め坂のY座標を求める
   傾き: (double)(④-②) / (③-①)
   斜め坂のY座標:②+(⑤×(⑦-①))

【4】「手順3で出したY座標 < ⑥オブジェクトAの底辺」であれば、
   "斜め坂にめり込んでいる"と判定
【5】めり込んでいる場合、「手順3のY座標 - ⑧オブジェクトAの高さ」を、
   オブジェクトAの新しいY座標とする

----------------------------------------------
こんな感じになります。

具体的に図を用いて計算してみます。


【2】各必要な値を求める
右方向へ進む緑色の長方形と、青色の坂道が衝突するとします。
この場合、必要な値は下記の様になります。

b_1010_1.png


【3】オブジェクトAの右辺がある位置での、
坂道のY座標を求める


右辺のある位置での坂道のY座標は、
始点のY座標 + 傾き×始点から右辺までの距離で出せます。
今回は下記の様になるので、
-------------------------------------
始点のY座標 … 2
傾き … -1
始点からオブジェクトAの右辺までの距離 … 3-2=1
-------------------------------------
2 + (-1×1) = 1となります。
※上記の図を見ても、オブジェクトAの右辺がある位置では、(X座標が3の所では)
 坂道のY座標は1である事が分かると思います。


【4】めり込んでいるかを判定
オブジェクトAの右辺の位置では、坂道のY座標は1でした。
つまり、オブジェクトAの底辺が1より大きければめり込んでいると判定します。

※図を見ても分かる様に、今回の例では2>1 (底辺>坂道のY座標)なので、
 めり込んでいると判定。


<追記予定!>
スポンサーサイト

物理演算システムの改善と、現在の状況報告とか

現在の状況報告をします。
ゲーム制作に関しては、アクションゲーム制作を優先的に実施しています。


■現在の予定
・[進行中]アクションゲームの物理演算処理修正
・[中断]パズルゲーム改良版作成
・[中断]ローグライクゲーム制作
・[中断]アプリ制作
・[中断]シミュレーションゲーム制作
・[時々進行]3Dモデルアニメーション付けソフト?作成

やりたい事に対して、手が追いついてない感じです。
スケジュール管理をどうにかしないと…。助けて(´・ω・`)

とりあえず、
自然消滅したり、やる気がなくなる事は無いので、
その一点だけはご安心を。



次に物理演算システムについて。
根幹から問題があった事が判明したので、
"間違っていた根幹をベースに作成していたソース"を削除/修正する事になりました。

何が間違っていたかというと、衝突判定ですね。

例えば、坂道の末端辺りに主人公が落下するとします。

【参考画像】
20140828_1.jpg

すると、坂道が主人公の頭上にワープします。
【参考画像】
20140828_2.jpg

一体、何が起こったか。


判定処理は下記の様になっています。
【3】以降は、問題が無くなるまでループして行われます。(再起関数)

【1】主人公の落下処理
【2】主人公と、床の衝突判定を実施
【3】めり込みを解消する方向(上)に、主人公を移動
【4】主人公に重なっているオブジェクトを、めり込みを解消する方向(上)に移動


例えば下記の状態でCと床の判定をした場合、
Cを上方向に修正、その修正したCとBが重なるのでBを上方向に修正、
更に修正したBとAが重なるので、Aを上方向に修正 ⇒ 床の上にCBAが乗っている様に見える
---------
A
B
C
床床床
---------
という感じになります。


上手く行けば問題は無いのですが、
衝突判定を総当たりにしてた為、
判定する順番(オブジェクトが登録された順番)によって、結果が変わるという
非常に面倒な不具合が発生しました。
※オブジェクトA,B,C,Dの順に登録されている場合、
AB、AC、BC、BD、CDの順番で判定が行われます。


簡単にいえば、主人公と坂道を判定して、
主人公を坂道の上に来るようにすればいい
所を、
先に主人公と床を判定してるもんだから、おかしな結果が出たのです。


あーなんてこった。
物体が移動する場合、
最も近い距離で衝突する物体から
衝突判定をすべき
だった。

ローグライクやシューティングゲームなんかでは、
とりあえず全部判定 ⇒ 衝突していれば移動不可、死ぬ
という風に、総当たりでやってたから、こんな単純な事に気付かなかった。

物理演算においては、"衝突判定の順番"も重要でした。
こんな単純なミスに数か月単位で苦悶してたとか愚の骨頂。

とりあえず、この問題については、
オブジェクトの右端、左端、上端、下端をそれぞれ昇順に並べて登録する様にしました。

----------------------------------
縦  横
64×64の正方形を、(0, 0)に配置
64×320の長方形を、(0, 100)に配置
480×64の長方形を、(-50, -50)に配置

RightList: 1363319
LeftList: -5000
TopList: -500100
BottomList: 63163429

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

で、例えば64×64の正方形が下方向に移動する場合、
自分の登録されている位置から末端にかけて検索し、
衝突していれば移動距離を修正します。
※検索は、上辺が下辺よりも大きくなった時点で終了。

■具体例
64×64の正方形の上辺情報は、TopListの添え字1の所に格納されており、
下方向へ10移動しようとしているとします。

この場合、添字[1]から添字[5]に向かって検索していきます。
そして、下辺+移動速度 > TopListの値かつ、衝突しているものが無いか探します。

[0]:-50
[1]:0
[2]:70
[3]:150
[4]:200
[5]:240

移動速度を足すと63 + 10 = 73、
つまり、添字[1]以降で73以下の値があった場合、衝突判定を行います。
また、上辺の位置が73以上の値([3]の150)と遭遇した際は、
(昇順に並べられている為)、それ以上検索しても73以下の値は無いので、検索を終了します。

【参考画像】
20140828_3.jpg

とりあえず、衝突判定に関しては(今の所)問題ないので、
キャラクターの仮画像を作って、動作確認しています。

歩き、小走り、走り、ジャンプ、しゃがみ、壁擦り、ジャンプ攻撃、攻撃とか、
色々モーション多すぎて大変。
問題なければしっかりしたキャラクター画像に差し替えます。

20140828_4.jpg


画像が優に100枚を超えそうな予感。
少しだけ、アニメ制作会社の大変さが分かった気がした。
(比較にならんぐらい忙しそうだ…)


余談ですが、最近このBGMを聞いています。
わグルま!という最近サービス終了してしまったゲームのBGMなのですが、
とても不思議な雰囲気で、落ち着くのです。

アクションゲームの物理演算の実装方法! -2014/07/18版-

物理演算の修正を行っていたのですが、
色々と不具合修正などで時間がかかってしまいましたorz

とりあえず、現時点での実装方法を記載します!

------------------------
【1】"力の方向"を設定
【2】衝突判定(Y軸方向)
【3】摩擦処理(X軸移動減衰)
【4】衝突判定(X軸方向)

------------------------
現状はこんな感じです。
とりあえず詳細に記載してみます。
※イメージを掴むための簡易的なソースコードを記載してます
 簡易的なので、実際のコードとは異なります


今まで、右キーを押す⇒右へ移動速度2で進むという感じだったのですが、
これを止めて、右へ2"進みたがっている"(力がかかっている)という風にしました。

■旧方式
if( GetInput( KEY_INPUT_RIGHT ) > 0 ){
t_SpeedX = 2;
}

■新方式
if( GetInput( KEY_INPUT_RIGHT ) > 0 ){
t_PowerX = 2;
}


そして、Y軸方向の衝突判定の際に、
状態に応じてPowerの値を減衰し、Speedに加算
します。

if( 普通の床と接触した ){
t_Power *= 1.0;
}else if( 氷の床と接触した ){
t_Power *= 0.2;
}else if( 何も接触しなかった(空中) ){
t_Power *= 0.4;
}
//加算する
t_Speed += t_Power;


例えば、右キーを押して一律で2ドット右へ進みたがるとします。
※数値は1000倍に細分化されているものとします。
 つまり「速度1000」で、1ドット進む。

この場合、下記の様な感じで、加速度合いが変わります。
■通常の床
2000⇒4000⇒6000⇒8000
■氷の床
400⇒800⇒1200⇒1600
■空中
800⇒1600⇒2400⇒3200

なので、「氷の床だと滑って、初速が出せない」といった動作にする事が出来ます。

で、上記のままだと、「ずっと右へ移動し続ける状態」になるので、
摩擦による減速処理を入れます。

■摩擦処理の例
Bの上をAが滑っている場合、下記の様な感じになります。
⇒[A]
[  B  ]
t_SpeedA += ( t_SpeedB - t_SpeedA )*0.01;
※0.01というのは摩擦の度合いです。
 オブジェクト毎に値を持たせておけば、オブジェクト毎に摩擦による減速を変えられます。
(Bの摩擦を0にした場合、t_SpeedA += 0;となり、減速せずに進み続けます)



あと新しく取り入れたのが、「影響度」という概念。

今まで、下記の様に列挙体の識別番号を作っておき、
その識別番号で、めり込んだ際の処理を変えていたのですが、
これを0~1000までの影響度の変数を持たせるように変更しました。

■今までの衝突判定
enum ObjectID {
OBJ_PLAYER,
OBJ_WALL,
OBJ_ENEMY
};


int Check( Object& t_ObjectA, Object& t_ObectB )
{
if( t_ObjectB->m_ID == OBJ_WALL ){
//壁は動かないので、オブジェクトAが壁の左側に来るように修正
t_ObjectA->m_PosX = t_ObjectB->m_PosX - t_ObjectA->Width;
return( -1 );
}else if( t_ObjectB->m_ID == OBJ_BOX ){
//箱(オブジェクトB)は動くので、めり込みが解消する様に箱の座標を修正
t_ObjectB->m_PosX = t_ObjectA->m_PosX + t_ObjectA->m_Width;

//オブジェクトAに押し出されたオブジェクトBが、
//他のオブジェクトと重ならないか確認

if( CheckAll( t_ObjectB ) == -1 ){
  //重なっていれば、オブジェクトBの位置が修正されているので、
     //その修正された位置を元に、めり込んだオブジェクトAの座標も修正

t_ObjectA->m_PosX = t_ObjectB->m_PosX - t_ObjectA->m_Width;
return( -1 );
}
return( 0 );
}
}



■現在の衝突判定
※下記の様にAが右方向へ進み、Bへぶつかる場合
  三[A][B]

//めり込んだ横幅を計算
int t_Dif = t_ObjectA_Right - t_ObjectB_Left + 1;
//影響度により、どのくらいめり込むかの修正
int t_ModDif = ( int )( t_Dif * ( ( double )t_ObjectB->m_Rate / 1000 ) );

//座標修正
SetObjectPosX( t_ObjectB, t_ObjectB_Left + t_ModDif );
SetObjectPosX( t_ObjectA, t_ObjectB_Left - t_ObjectA->m_Width );

//オブジェクトBが移動しなかった場合は、
//他オブジェクトとの衝突判定は不要

if( t_ModDif == 0 ){
return( 0 );
}

//移動したオブジェクトBが、他のオブジェクトと重なるか確認

if( CheckAll( t_ObjectB ) == -1 ){
//重なっていれば、関数内部でオブジェクトBの座標が修正されているので、
//それを元にオブジェクトAの座標も修正

SetObjectPosX( t_ObjectA, t_ObjectB->m_Left - t_ObjectA->m_Width );
}


こんな感じですね!
旧仕様では、オブジェクトの種類を増やす度に、
それ専用のコードを追記して行く必要がありましたが、

現在は、0~1000の影響度を設定すれば、
後は勝手にめり込み解消処理を行ってくれます。

もう少し物理演算の部分を修正したいのですが、どうしよう。
せめて"坂の転がり"あたりは実装したいと思っているのです。
(坂道にボールが落ちた場合、斜面に沿って転がっていく処理)

現在、斜面が45゜だろうが、めり込まない様に押し出して終わりですからね。
※こんな感じ。↓
    /
 ■/
  /

左斜め下に、加速して転がって行く事は無い。

時間かかる様であれば、現行の
衝突/めり込み解消/摩擦による速度減衰/重力落下のみでゲームを作って行こうかと思います。

また、よりよい実装方法を思いついたら、別途解説記事の様なものを作ってみます!
(物理演算の実装は難しいですね…)

アクションゲーム制作状況報告!

ちょっと仕事やら何やらで更新が疎かになっておりました。
すみません(_ _;)

ですが、色々あって
プライベートの時間が取れる様になったので、
更新頻度を上げていきたいと思います…!

ひとまず、アクションゲームについてですが、
必要な背景画像(仮)などを色々と作成中!

【参考画像】
A_Sample2.jpg

【参考画像】
A_Sample1.jpg

【参考画像】
A_Sample3.jpg


相変わらず、絵の描き方に関しては
独自路線を突っ走っております。

※調べてみた所、PCでの絵の描き方は下記らしいのですが、
ラフ画 ⇒ 線画 ⇒ 塗り ⇒ 完成!

自分は
塗り ⇒ 塗り ⇒ 塗り ⇒ 完成! です。


何となく、描画過程も貼ってみる。


■手順1■  大まかな背景を描く。
A_Sample4.jpg

■手順2■  キャラクターなどの構図を描く。
A_Sample5.jpg

■手順3■  少しずつ簡単に塗っていく。
A_Sample6.jpg

■手順4■  顔の狂いなどを直しながら、塗っていく。
A_Sample7.jpg


■手順5■  塗り損ねの部分や、大雑把に塗っていた部分を、丁寧に塗っていく。
A_Sample8.jpg
完成!
(修正を入れようと思えば、まだ入れられる箇所が沢山あります)


ひたすら棒線を重ねまくって描いてく方法だよ!
みんなもやってみよう!



…まぁ、それは置いておいて、
よりクオリティが上げられるように画像やら何やら用意中です。
実際のアクションゲーム中の画像なども、アップして行きたいと思います!
ブログ更新頻度上げていきます…!

差分ファイル作成、パッチ適用プログラムをUPしました!

パッチ適用プログラム(試作版)をUPしました!

パッチ適用なんて、3か月前まで全く原理が不明だったんですが、
何とか自力で実装出来て良かった。
(難しい分野だと思います。)

頑張れば(ある程度の事は)何とかなるよ!!


※試作版なので、大まかに下記4つの改善点があります。
----------------------------------------
【1】差分ファイルの圧縮を行っていないので、差分ファイルが大きめのサイズになる。
【2】効率化を行っていないので、多少時間がかかる。
【3】複数の差分ファイルの合併が行えない。
(Ver1.0⇒Ver3.0、Ver2.0⇒Ver3.0の、2つの差分を合併、
 Ver1.0もVer2.0も両方、Ver3.0に更新する、というパッチは作れない)
【4】ファイルによって、差分を生成出来ないかもしれない。

----------------------------------------
(差分ファイル圧縮、生成時間短縮を極めようとしたら、またえらい時間がかかりそう)


とりあえず、正常に動作すれば、商用利用問わず
当ブログの報告などは一切不要ですので、
お気軽にお使いください。


【参考URL】差分生成セット -半永久的倉庫
PASS:pass1234

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



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