HSPポータル
サイトマップ お問い合わせ


HSPTV!掲示板


未解決 解決 停止 削除要請

2020
0926
kanamaruhsp3dish 標準スプライト命令 衝突判定について8解決


kanamaru

リンク

2020/9/26(Sat) 22:35:53|NO.91458

スプライトって衝突をes_check命令で簡単に検出できると思います。
今作ろうとしているのは、スプライトを方向キーで操作して
別のスプライトを押す処理です。
まず思い浮かぶのは衝突を検知して衝突したスプライトの位置も
同じように動かすというものです。
が、ここで問題になるのはこれで処理できるのは隣り合ってるスプライトのみです。
当たり前の話なのですが、今作ろうとしてるゲームの仕様として、
スプライトを押したときに押したスプライトが
また別のスプライトに衝突した場合はそのスプライトも押すことになります。
そのスプライトは検知はできません。
これを処理するには押したスプライトのIDを調べて
そのIDのスプライトに衝突したスプライトを調べ
またそのスプライトのIDを保存してというのを繰り返す必要があります。
が、これもまた問題があります。
といっても今作っているものの仕様上の問題なのですが、
ウィンドウの端や壁スプライトに
押されたスプライトがぶつかったときが大変なのです。
(ゴールスプライトにぶつかる分にはいいのですが)
その時は移動を取り消さなければいけません。
取り消すために移動させたスプライトの一覧を保存する必要があります。
リストが使えればいいですがhspにはないので大変です。
いい方法を知ってる人はいませんか?



この記事に返信する


kanamaru

リンク

2020/9/26(Sat) 22:42:01|NO.91459

ちょっと長くなりそうなので分割します。
移動を取り消す必要があるというのは、今の移動の仕組みにあります。
最初移動して壁スプライトに衝突したら移動しないという風に処理してたら
衝突した途端に操作を受け付けなくなりました。
衝突してるからです。
衝突している方向はわからないので。
そこで、
1.移動する
2.衝突したら逆向きに移動して移動を取り消す
といった所為を考え作りました。
で、この処理は押されたスプライトが壁スプライトに衝突したときも行う必要があります。
なので移動を取り消す仏用が出てきます。
質問が増えますが、この処理もいい方法があったら教えてください。
また、何度も壁スプライトというのが出てきますが
画面上には、
1.自機
2.壁
3.押せるもの
4.ゴール
の四種類のスプライトがあり
typeによって管理しています。



Drip

リンク

2020/9/28(Mon) 20:46:51|NO.91475

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



さか

リンク

2020/9/28(Mon) 22:34:52|NO.91476

衝突してから戻すより、衝突チェックして衝突するならば衝突処理
(止まる、壁が押される)するようにした方がスマートだと思います。

例えば以下です。ちょっと適当ですが。。
mx=100: my=100 ;自座標
vx=10: vy=10 ;自移動ベクトル
es_pos 0,mx+vx,my+vy ;衝突予定座標セット
es_check chk,0    ;衝突判定
if chk >= 0: vx=0: vy=0 ;衝突する場合、停止
mx+=vx: my+=vy ;自座標へ移動ベクトル加算
es_pos 0,mx,my ;移動座標セット



kanamaru

リンク

2020/9/28(Mon) 23:10:34|NO.91477

Dripさん。ありがとうございます。
そうだ。obaqがあった。
もともとスプライトにこだわりがあったわけではなく、
スプライトを思い出してより簡単に実装できるのではないかと使う言語を
切り替えたという経緯があります。
obaqを使う方がより簡単に実装できそうですし、
一番目の問題である押す処理が既に参考プログラムで完成してますし、
壁スプライト改め壁オブジェクトもありますし、
提示してくれたプログラムをもとに
作りたいゲームを完成させることができそうな気がしてきました。
(二番目の問題もありますがqcollisionで作れそうな気がします。)
obaqではないプログラムの方ももしうまくいかなかったら
参考にさせていただきます。(長いプログラムを書いてもらったのにすいません)
実際には作ってみないとわかりませんが、
作りたいゲームが形になりそうな気がします。
二番目の問題が解決しなかったら質問するかもしれませんが
とりあえず頑張ってみます。
本当にありがとうございました。



kanamaru

リンク

2020/9/28(Mon) 23:11:19|NO.91478

解決チェック忘れてました。



さか

リンク

2020/9/29(Tue) 22:15:38|NO.91483

解決となっていますが無視されたので簡単なプログラムを作りました。
Dripさんの提案は良いと思いますがロジックの話だと思いますよ。

こういうことですが意図が違いましたでしょうか。
左のリンゴは押せます。右のどくろリンゴは押せずに止まります。
ローカルに画像ファイルと一緒に置いて実行してください。

#include "hsp3dish.as" screen 0,640,480 ; スクリーン初期化 es_ini ; スプライトを初期化 buffer 3 picload "hsptv_img.png" ; hspインストールフォルダ\hsptvの下の画像ファイル es_size 64, 64, 70 es_pat 0, 0, 192, 3 ; my1 es_pat 1, 64, 192, 3 ; my2 es_link 1,0 es_pat 2, 0, 320 es_pat 3, 64, 320 gsel 0 ; 初期設定 x = 320: y = 200 es_set 0, x, y, 0 ; スプライトを配置 x1 = 200: y1 = y es_set 1, x1, y1, 2 ; スプライトを配置 x2 = 450: y2 = y es_set 2, x2, y2, 3 ; スプライトを配置 *@ redraw 0 color 0, 0, 0: boxf stick key,15 :title ""+key vx = 0: vy = 0 if key&1: vx = -10 if key&4: vx = 10 if key&8: vy = 10 if key&2: vy = -10 ;衝突チェック es_pos 0, x+vx, y+vy es_check chk,0 ; リンゴの場合、押す if chk == 1: x1 += vx: y1 += vy ; どくろリンゴと衝突の場合、止まる if chk == 2: vx = 0: vy = 0 x += vx: y += vy es_pos 0, x, y es_pos 1, x1, y1 es_pos 2, x2, y2 es_draw ; スプライトを描画 redraw 1 wait 10 goto *@b



Drip

リンク

2020/9/29(Tue) 22:50:38|NO.91486

さかさん

無視されたというよりは、さかさんの仰る事の意図はkanamaruさんの提起された問題とはかけ離れているように見えます。
kanamaruさんの最初の投稿にも書かれているように、既にさかさんが仰るようなシンプルな考え方は理解できていて、問題はその先の
「ブロックが複数のブロックを押し更にその末端のブロックが動かせない壁にぶつかった場合の挙動」に難儀されているように読み取れます。
さかさんのご提示されているサンプルもkanamaruさんの問題としている挙動を説明できていません。
別のスレッドでしつこく反応を求める行為もマナー違反ですし、より簡潔な解決策があればスレッドに追記するのも良いかと思いますが、それがない場合はこれ以上の論議は控えるべきかと思います。



kanamaru

リンク

2020/9/29(Tue) 23:20:11|NO.91487

すいません。確かに無視になってしまってました。
隣り合ったスプライトは押せたり押せなかったりはそれでできると思うんですけど、
隣り合ったスプライトしか動かすことができません。
一方でDripさんのはobaqのプログラムにしろ再帰使って実装したのにしろ
見事に並んでいるスプライトを一気に押すのが実装できていたというので
Dripさんのプログラムで解決にしました。
とはいってもさかさんの場合、追加で補足した方に対する回答の気もします。
そうだとしてもbaqを使った場合、衝突に関しては気にしなくても良さそうなので、
追加で補足したのはそもそも問題が解決どころか消滅したようなものなんです。
そのことを説明するべきでした。
なのでこっちとしてはかなり複雑なんです。まあ無視は一番いけないとは思いますが。
ロジックの話というのもあってるといえばあってるのですが、
既にあるもので実装できるならそっちを僕は使います。
なのでさかさん、ありがとうございました。



ONION software Copyright 1997-2023(c) All rights reserved.