Dripです。
kanamaruさん、こんにちは。
もともとes_系のスプライト関連命令は剛体に対する当たり判定処理を想定した設計ではありません。
シューティングゲームの玉と敵の簡単な描画や判定を実装するためのものとお考えください。
es_系命令で衝突した物体を押し出すような処理を書く場合、kanamaruさんの仰る通り、色々と問題が出てきます。
実装されようとしている当たり判定がある程度雑で良い場合はOBAQの利用を検討される事をお勧めします。
OBAQでのブロックの押し出し処理は次のような要領で実装可能です。操作はカーソルキーです。
#include "obaq.as" //dishを使わない場合は別途obaq.asを読み込む。dishを使用する場合はobaq.asを読み込まなくて良い。
qreset //obaq初期化
qborder -80,-80,80,80 //ボーダー設定
plr=-1 //プレイヤーID初期化
repeat 100 //100個のブロックを生成(重なり無効なので実際はこれより少なくなります)
sz=rnd(10)+3:qaddpoly my,4,rnd(150)+5,rnd(110)+5,0,sz,sz //ブロックを生成
if stat>-1:{ //生成に成功した場合だけパラメータ設定
if rnd(3) | plr=-1:{ //動けるブロックの生成
if plr=-1:{ //プレイヤーブロックは赤にする
plr=my:qmat my,mat_wire,$FF0000
}else{ //押せるブロックは水色にする
qmat my,mat_wire,$00FFFF
}
qtype my,type_bindR //回転固定
qinertia my,0.8,0 //惰性を高く、重力をなしに
}else{ //動かないブロック
qmat my,mat_wire,$888888 //動かないブロックは灰色にする
qtype my,type_bind //固定ブロック
}
qdamper my,0,0 //ブロックに刷れても抵抗を受けず摩擦もない
}
loop
repeat
stick ky,15 //カーソルキーで移動
if ky&1:qspeed plr,-0.3,0,0,0 //左移動
if ky&2:qspeed plr,0,-0.3,0,0 //上移動
if ky&4:qspeed plr,0.3,0,0,0 //右移動
if ky&8:qspeed plr,0,0.3,0,0 //下移動
redraw 0 //画面の更新を開始描画開始
color:boxf //背景クリア
qexec //物理動作
qdraw 1 //物理オブジェクト描画
redraw 1 //画面更新
await 30 //アイドル
loop
もしめり込みを一切許さない我流の正確な押し出し処理を実装したい!…という場合は自分で作るしかありません。
そうした処理を組む場合は再起処理で当たり判定を作ると比較的シンプルな記述で実現できます。
以下のサンプルではsimuBlock命令が再起することでブロック全体の当たり判定結果をpushBlock命令に返す仕組みになっています。
また実数の誤差問題やes_系命令の仕様を全て排除するため、今回は整数でブロック位置を管理しスプライトは一切使用しておりません。
実際はブロックの重なり配置よる再起ループを回避する仕組みも取り入れた方がより安全に運用できます。
今回はコメント多めにシンプルでわかりやすさ重視で書いてみましたが、モジュールにも慣れていないと理解することはかなり難しいかもしれません。
仕組みさえ理解できれば以下のような要領で可能ですので、どうぞ頑張ってみてください。
#include "hsp3dish.as" //dishは使っても使わなくてもOK。操作はカーソルキーです。
#module
//px,pyの座標にpsの幅を持つブロックを生成し、ブロックIDをstatに返す。statがマイナスなら生成失敗を意味する。
//pmが0以外なら動く(押す)ことが可能なブロックを意味する。
#deffunc regBlock int px,int py,int ps,int pm
hit=0 //生成時他の物体に触れてないかを示すフラグ
repeat max //全てのブロックとの重なりチェックをする
//生成するブロックが他のブロックに重なっていればhitに1を代入する
if abs(px-bx(cnt))<ps+bs(cnt) & abs(py-by(cnt))<ps+bs(cnt):hit=1:break
loop
if hit:return -1 //もし他のブロックと重なる配置の場合生成を中止する
bx(max)=px:by(max)=py //ブロックの中心位置X,Y
bs(max)=ps //ブロックの幅
bm(max)=pm //ブロックの移動可能フラグ(0以外なら移動可能)
max++
return max-1
//idのブロックをvx,vyだけ移動させる(押す)
#deffunc pushBlock int id,int vx,int vy
repeat 2 //x,yそれぞれ処理する。(cnt=0:X移動 cnt=1:Y移動)
mx=bx(id):my=by(id) //mx,myに移動させるブロックの現在位置X,Yを代入
if cnt=0:{ //Xの移動で移動量をシミュレーションする
if vx=0:continue:else:simuBlock id,vx,0,1 //X移動があればそのブロックを横に押すテスト
mx=stat-mx:my=0 //押した結果、移動させるブロックがどの程度移動したかをmx,myに代入
}else{ //Yの移動で移動量をシミュレーションする
if vy=0:continue:else:simuBlock id,0,vy,1 //Y移動があればそのブロックを縦に押すテスト
mx=0:my=stat-my //押した結果、移動させるブロックがどの程度移動したかをmx,myに代入
}
if mx=0 & my=0:continue //今回押したブロックは移動していなければここで処理終了
simuBlock id,mx,my //今回のテストで移動が発生した量だけ実際に押す
//このように移動量を確認してから押すことで、1つのブロックが「押せないブロック」と「押せるブロック」を
//同時に押した場合等で自分自身は「押せないブロック」に邪魔され動けないのにもう片方の「押せるブロック」だけが
//1フレーム分動くような状態を防げる。逆にそのような状態のほうが好ましい場合は移動量を予め調べる必要はない。
loop
return
//次の命令はモジュール内での利用を想定して設計しているため、使用にはいくつかのルールがある。
//idのブロックに対してvx,vyだけ移動させた場合、動いたほうの軸の座標をstatに返す。
//simに1を指定した場合は移動のシミュレーションだけを行い、実際の位置は移動しない。
//またvx,vyは必ずどちらかは0である必要がある。statに返るのはX,Yのどちらか移動のあった方の座標である。
#deffunc simuBlock int id,int vx,int vy,int sim,local i,local cpos
if bm(id)=0:if vx:return bx(id):else:return by(id) //押せないブロックだった場合移動しない。現在座標を返す。
//X軸またはY軸を移動させる。その際cpos,cpos(1)に移動した座標と移動前の座標を記憶する。
if vx:cpos=bx(id)+vx,bx(id):bx(id)=cpos:else:cpos=by(id)+vy,by(id):by(id)=cpos
for i,0,max,1 //全てのブロックに対して当たり判定を実施する(repeatを使わないのはネスト問題を解消する為)
if id=i:_continue //自分自身とは当たり判定しない
hx=(bs(id)+bs(i))-abs(bx(id)-bx(i)) //X方向にどの程度めり込んでいるか?(正の値ならめり込みあり)
hy=(bs(id)+bs(i))-abs(by(id)-by(i)) //Y方向にどの程度めり込んでいるか?(正の値ならめり込みあり)
if hx>0 & hy>0:{ //X,Y共にめり込んでいる。つまりブロックは重なっている場合
if vx:{ //横に押した時
hy=0:if vx<0:hx=-hx //縦のめり込み量を削除し、hxを横移動量に変換する(左移動ならマイナスにするだけ)
}else{ //縦に押した時
hx=0:if vy<0:hy=-hy //横のめり込み量を削除し、hyを縦移動量に変換する(上移動ならマイナスにするだけ)
}
simuBlock i,hx,hy,sim //めり込んだブロックを要求方向にめり込んだ分だけ押す(再起処理)
if vx<0 | vy<0:{ //マイナス方向の移動だった場合
//移動した座標が要求された移動先よりも手前の場合、その座標をcposに記憶する
if stat+bs(i)+bs(id)>cpos:cpos=stat+bs(i)+bs(id)
}else{ //プラス方向の移動だった場合
//移動した座標が要求された移動先よりも手前の場合、その座標をcposに記憶する
if stat-bs(i)-bs(id)<cpos:cpos=stat-bs(i)-bs(id)
}
}
next
if vx:bx(id)=cpos(sim):else:by(id)=cpos(sim) //ブロック座標を確定する(sim=1の時は移動なし)
return cpos //最終的にidのブロックが存在している移動軸の座標を返す
//idのブロックを指定色で描画する。
//またidにマイナスを指定した場合は全てのブロックを描画する。
//その際の描画色は移動可能=水色・移動不可能=黒に固定される。
#deffunc drawBlock int id
if id<0:{ //IDにマイナスが指定されている場合は全てのブロックを描画する
repeat max //全てのブロックを描画する
gmode 0,bs(cnt)*2,bs(cnt)*2 //ブロックのサイズを指定
if bm(cnt):color ,255,255:else:color //移動可能ブロックは水色、移動不可能ブロックは黒
grect bx(cnt),by(cnt) //ブロックを塗る
loop
}else{ //IDにブロックIDが指定された場合は指定された1つのブロックを現在色で描画するだけ
gmode 0,bs(id)*2,bs(id)*2:grect bx(id),by(id) //ブロックのサイズを指定してブロックを描画
}
return
#global //モジュールここまで!以下はモジュール命令を使った押し出しサンプル
regBlock rnd(ginfo_winx),rnd(ginfo_winy),20,1:plr=stat //プレイヤーブロックを生成する。plrにプレイヤーIDを代入
repeat 50 //壁またはプロックを最大50個まで生成する(重なる配置の場合は無視するためこれよりは少なくなる)
regBlock rnd(ginfo_winx),rnd(ginfo_winy),rnd(22)+10,rnd(3) //ランダムな位置と大きさで壁またはブロックを生成
loop
repeat //メインループ
stick ky,15 //カーソルキーで移動
if ky&1:pushBlock plr,-4,0 //左移動
if ky&2:pushBlock plr,0,-4 //上移動
if ky&4:pushBlock plr,4,0 //右移動
if ky&8:pushBlock plr,0,4 //下移動
redraw 0 //描画開始
color 255,255,255:boxf:drawBlock -1 //背景をクリアしてブロックを全て描画
color 255:drawBlock plr //プレイヤーブロックを描画
redraw 1:await 30 //画面更新とアイドル
loop