|
 |
|
2013/3/11(Mon) 20:01:21|NO.52885
hspdxではes_checkやes_type等スプライト同士の重なりをチェックする為の命令がありますが、
これらの命令でのチェックでは単純な四角形の重なりしか検知出来ず、
他の多角形や、使用するキャラクタ画像の透明色部分を考慮した当たり判定は検知出来ません。
そこで、そのように使用する画像に合わせた(透明部分を考慮した)スプライトのヒットチェックを行う方法を模索しており、今回質問させて頂きました。
また今回は、ここで使用する画像が特定の画像("ほにゃらら.png"等)に確定していなくても対応できるような汎用的なものを求めています。
というのも、現在作成しているのはアクションゲームで、そのマップ作成者が使用したい画像を自由に追加出来るような仕様になっているからです。
自分で考えた案としては、
案①スプライトが使用するキャラクタ画像の透明色以外の部分に1x1dotの透明スプライトを設置してその透明スプライトで実際の当たり判定を行う。
メリット⇒理論上どんな形であっても正確にヒットチェックが行える。マップ作成者は特別な設定を行う必要が無く、手軽に扱える。
デメリット⇒膨大な数のスプライトが必要で、ヒットチェックの処理もかなり重そう。
案②マップ作成者が自分で当たり判定を設定する。マップ作成者に直線や四角形の組み合わせで当たり判定を設定させる。
メリット⇒①よりも少ないスプライトの数で済む。
デメリット⇒マップ作成者の手間が増える。円形や曲線の設定に著しく弱く、正確に設定しようとするとスプライトの数が増えてしまう。
どちらも一長一短で、共通したデメリットとしては「1つのマップのオブジェクトに対して複数のスプライトを使用しなくてはならない」といったことでしょうか。
この他の案や意見等ございましたら、どうぞ宜しくお願い致します。
#include "hspdx.as"
Check = 0
// Check = 0 なら 通常のes_hitのみ
// Check = 1 なら 案①
// Check = 2 なら 案②
es_ini 90010
onexit *終了時
es_screen 640,480,32,,1,1
buffer 1
color 128,128,128
font "MS ゴシック",30 : mes "■" : Circle_Size = ginfo(14),ginfo(15) //チェック対象のスプライト(アクションゲームでいう"主人公")の設定・配置
es_buffer 0,0,$FFFFFF
es_size Circle_Size(0),Circle_Size(1)
es_expat 0,0,0,0,0
es_set 0, mousex-Circle_Size(0)/2 , mousey-Circle_Size(1)/2 ,0,1
cls
font "MS ゴシック",300 : mes "回" : Star_Size = ginfo(14),ginfo(15) //アクションゲームでいう"壁"や"敵"の設定・配置
es_buffer 1,0,$FFFFFF
es_size Star_Size(0),Star_Size(1)
es_expat 1,1,0,0,0
Star_Pos = 320-Star_Size(0)/2 , 240-Star_Size(1)/2
es_set 1,Star_Pos(0),Star_Pos(1),1,1
dim range,2 //range(0)からrange(1)までのスプライトNOのヒットチェックをする
if Check = 0 : es_type 1,1 : range = 1,1 //通常のes_checkのみ
if Check = 1{ //案①
es_type 1,0
buffer 2 : es_buffer 2,0,$FFFFFF
gsel 1
es_size 1,1
es_expat 2,2,0,0,0
DotCount = 0
repeat Star_Size(0) : cn = cnt
repeat Star_Size(1)
pget cn,cnt
if (ginfo_r != 255) or (ginfo_g != 255) or (ginfo_b != 255){ //透明色以外の座標に透明なスプライトを設置
es_set 10+DotCount,Star_Pos(0)+cn,Star_Pos(1)+cnt,2,1
es_type 10+DotCount,1
DotCount++
}
loop
loop
range = 10,10+DotCount
}
if Check = 2{ //案②
es_type 1,0
buffer 2 : es_buffer 2,0,$FFFFFF
//座標(HitRangeX_1,HitRangeY_1)から座標(HitRangeX_2,HitRangeY_2)までに当たり判定を持つ透明なスプライトを設置
HitRangeX_1 = 201,201,413,201,263,263,352,263
HitRangeY_1 = 121,121,121,334,183,183,183,267
HitRangeX_2 = 225,437,437,437,376,287,376,376
HitRangeY_2 = 372,143,366,355,204,301,300,288
repeat Length(HitRangeX_1)
es_size HitRangeX_2(cnt)-HitRangeX_1(cnt),HitRangeY_2(cnt)-HitRangeY_1(cnt)
es_expat 2+cnt,2,0,0,0
es_set 10+cnt,HitRangeX_1(cnt),HitRangeY_1(cnt),2+cnt,1
es_type 10+cnt,1
loop
range = 10,10+Length(HitRangeX_1)
}
gsel 0
color 255
font "MS ゴシック",20,1
*メイン
es_pos 0,mousex-Circle_Size(0)/2 , mousey-Circle_Size(1)/2
es_cls 255,255,255
es_draw
es_check Hit,0,1,0,range(0),range(1) //ヒットチェック
if hit != -1 : pos mousex-Circle_Size(0)/2,mousey+Circle_Size(1)/2 : es_fmes "HIT"
es_sync 17,0 : await
es_getfps FPS : title "FPS:"+FPS+" (処理落ち無しの場合⇒57~58前後)"
goto *メイン
*終了時
es_bye
end

| |
|
2013/3/11(Mon) 22:02:41|NO.52887
hspdxは使ってませんが自分も同じようなことで悩んだことがあります。
案②の 「マップ作成者に直線や四角形の組み合わせで当たり判定を設定させる。」 を全自動化できればデメリットが消えて解決!
と思ったのですがどうでしょう。
これは私の話ですが、
画像の色で判断して当たり判定を変数に保存するものを作ろうとしたことがありました。
当たり判定の保存まではできたものの技術不足で変な当たり判定になりましたがw
大した回答じゃないですが参考になれば嬉しいです。
|
|
2013/3/11(Mon) 22:35:05|NO.52888
>_hamyuu様
貴重なアドバイスありがとうございます。
>案②の 「マップ作成者に直線や四角形の組み合わせで当たり判定を設定させる。」 を全自動化できればデメリットが消えて解決!
それは盲点でした。
もしそれが出来れば色んな問題が解決するかもしれませんね!
しかし直線と四角形だけならまだしも曲線や円形が出てくると自動化は私の技術では難しいかもしれません・・・
私の下手な知識でこの方法を突き詰めようとすると案①とそっくりなものになっちゃいそうですΣ(°д°lll)
この意見を参考に今後の課題として方法を検討させて頂きますが、引き続き回答は募集させて頂きます。
_hamyuu様ありがとうございました。
|
|
2013/3/11(Mon) 22:36:21|NO.52889
案②を発展させて
画像のエッジ検出→単純化して多角形化
とすれば自動でできそうですけどどうでしょう
|
|
2013/3/11(Mon) 22:53:12|NO.52890
>晩御飯様
>案②を発展させて
>画像のエッジ検出→単純化して多角形化
エッジ検出ですか!調べてみて写真編集アプリ等でよくみるアレがエッジ検出なのだと初めて知りました!
しかし問題が1つありまして・・・
hspdxのヒットチェックで多角形が簡単に扱えればいいのですが、
残念ながら1つのスプライトでは四角形しか表現出来ないことが問題をより複雑化してしまっている現状です。
だけども、辛うじて短辺を1dotにすることで直線を表現したり、四角形を回転させることは可能なので、
多角形の各辺上に回転させた直線を配置し、多角形のスプライトを擬似的に作って表現することは不可能では無いのかもしれません。
少し考えてみます。
参考になるアドバイスありがとうございました。
|
|
2013/3/12(Tue) 03:55:30|NO.52898
ああ長方形しか扱えないのですね・・・
では
透明部分と不透明部分で二値化→適当な粒度で平均化(モザイク)
→上下左右囲まれているスプライトを削除したり結合したり
とかはどうでしょう
ただこれ自分で実際に試したわけではないので効果のほどは保証しかねます
|
|
2013/3/12(Tue) 15:28:18|NO.52900
>>晩御飯様
返信ありがとうございます。
なるほどー!確かにそれなら割りと簡単に実装出来るかもしれません。
ひとまず、先に挙げた案①の方法に晩御飯様の方法を取り入れてみました。
そうしたら案①の時とは比べ物にならないくらいスプライトの数は減らせました。
が、それでも1つのイメージで数百個のスプライト数になってしまうのは、
イメージ数が増えてくるとヒットチェックが重くなってしまいそうで不安です。
現状では、単純な長方形のみを検出していますので、
"◇"や"あ"など、曲線や斜めの線が入ると極端にスプライト数が増加してしまいます。
私の技術では、まだまだこの方法を活かしきれていないので、
もっと処理を最適化して、同時に斜めの線を検出することが出来れば大幅にスプライト数を減らせるかもしれません。
#include "hspdx.as"
es_ini 90010,90010
onexit *終了時
es_screen 640,480,32,,1,1
//チェック対象のスプライト(アクションゲームでいう"主人公")の設定・配置
buffer 1
color 128,128,128
Circle_Size = 10,10
boxf 0,0,Circle_Size(0),Circle_Size(1)
es_buffer 0,0,$FFFFFF
es_size Circle_Size(0),Circle_Size(1)
es_expat 0,0,0,0,0
es_set 0, mousex-Circle_Size(0)/2 , mousey-Circle_Size(1)/2 ,0,1
//アクションゲームでいう"壁"や"敵"の設定・配置
color 255,255,255 : boxf
color : font "MS ゴシック",150 : mes "回◇■あ" : Star_Size = ginfo(14),ginfo(15)
es_buffer 1,0,$FFFFFF
es_size Star_Size(0),Star_Size(1)
es_expat 1,1,0,0,0
Star_Pos = 320-Star_Size(0)/2 , 240-Star_Size(1)/2
dim range,2 //range(0)からrange(1)までのスプライトNOのヒットチェックをする
es_type 1,0
buffer 2,Star_Size(0),Star_Size(1) : color 255 : boxf : es_buffer 2,0,$FFFFFF
gsel 1
DotCount = 0
dim SPset,Star_Size(0),Star_Size(1)
//当たり判定用スプライト設置パート
repeat Star_Size(0) : cn = cnt
repeat Star_Size(1)
pget cn,cnt
if (ginfo_r != 255) or (ginfo_g != 255) or (ginfo_b != 255) and (SPset(cn,cnt) = 0){ //透明色以外の座標に当たり判定用スプライトを設置
SPset(cn,cnt) = 1 //重複チェック防止用
cn2 = cnt
SP_Size = -1,-1
//連続した長方形のサイズを調べる
repeat Star_Size(0)-cn : h(0) = cnt
repeat Star_Size(1)-cn2 : h(1) = cnt
pget cn+h(0),cn2+h(1)
if (ginfo_r == 255) and (ginfo_g == 255) and (ginfo_b == 255){
if SP_Size(1) != -1 and SP_Size(1) > h(1) : SP_Size(0) = h(0)
if SP_Size(1) == -1 : SP_Size(1) = h(1)
break
}
loop
if SP_Size(0) != -1 : break
loop
//重複チェック防止用
repeat SP_Size(0) : h(0) = cnt
repeat SP_Size(1) : h(1) = cnt
SPset(cn+h(0),cn2+h(1)) = 1
loop
loop
//当たり判定用スプライトの設置
es_size SP_Size(0),SP_Size(1)
es_expat 2+DotCount,2,0,0,0
es_set 10+DotCount,Star_Pos(0)+cn,Star_Pos(1)+cnt,2+DotCount,1
es_type 10+DotCount,1
es_cls 255,255,255 : es_draw :es_sync : await 10 //当たり判定の可視化
DotCount++
}
loop
loop
range = 10,10+DotCount
//実表示部分をスプライトとして設置
es_set 1,Star_Pos(0),Star_Pos(1),1,1
dialog "スプライト数:"+DotCount+""
gsel 0
color 255
font "MS ゴシック",20,1
*メイン
es_pos 0,mousex-Circle_Size(0)/2 , mousey-Circle_Size(1)/2 //チェック対象のスプライトの移動
es_cls 255,255,255 : es_draw
//ヒットチェック
es_check Hit,0,1,0,range(0),range(1)
if hit != -1 : pos mousex-Circle_Size(0)/2,mousey+Circle_Size(1)/2 : es_fmes "HIT"
es_sync 17,0 : await //画面の更新
es_getfps FPS : title "FPS:"+FPS+" (処理落ち無しの場合⇒57~58前後)"
goto *メイン
*終了時
es_bye
end

| |
|
2013/3/13(Wed) 02:20:34|NO.52915
リージョンを使ってhspdxの判定と組み合わせる
スプライト自体は実体のあるものだけで良い
一時判定をスプライト同士でやってからリージョン判定をやつてる↓
#uselib "user32.dll"
#func InvalidateRect "InvalidateRect" int, int, int
#uselib "gdi32.dll"
#cfunc CreatePolygonRgn "CreatePolygonRgn" int, int, int
#cfunc CreateEllipticRgn "CreateEllipticRgn" int, int, int, int
#cfunc CreateRectRgn "CreateRectRgn" int, int, int, int
#cfunc GetStockObject "GetStockObject" int
#cfunc CombineRgn "CombineRgn" int, int, int, int
#cfunc GetRegionData "GetRegionData" int, int, int
#cfunc ExtCreateRegion "ExtCreateRegion" int, int, var
#func FillRgn "FillRgn" int, int, int
#func DeleteObject "DeleteObject" int
#func SetRectRgn "SetRectRgn" int, int, int, int, int
#func OffsetRgn "OffsetRgn" int, int, int
#define BLACK_BRUSH 4 // 黒
#include "hspdx.as"
es_ini 90010
onexit *終了時
es_screen 640,480,32,,1,1
//チェック対象のスプライト(アクションゲームでいう"主人公")の設定・配置
buffer 1
color 128,128,128
Circle_Size = 15,15
circle 0,0,Circle_Size(0),Circle_Size(1),1
es_buffer 0,0,$FFFFFF
es_size Circle_Size(0),Circle_Size(1)
es_expat 0,0,0,0,0
es_set 0, -Circle_Size(0)/2 , -Circle_Size(1)/2 ,0,1
;es_type 0,0
MyRgnX=0
MyRgnY=0
RgnNo=0 //キャラNo
RectSizeX=Circle_Size(0)
RectSizeY=Circle_Size(1)
//RgnNo,RectSizeX,RectSizeYをセットしてからgosub *SetRgn
gosub *SetRgn //リージョン設定
buffer 1
color : font "MS ゴシック",150 : mes "回◇■あ\n★☆★☆" : Star_Size = ginfo(14),ginfo(15)*2
es_buffer 1,0,$FFFFFF
es_size Star_Size(0),Star_Size(1)
es_expat 1,1,0,0,0
Star_Pos = 320-Star_Size(0)/2 , 240-Star_Size(1)/2
es_set 1,Star_Pos(0),Star_Pos(1),1,1
es_type 1,1
RgnNo=1 //キャラNo
RectSizeX=Star_Size(0)
RectSizeY=Star_Size(1)
//RgnNo,RectSizeX,RectSizeYをセットしてからgosub *SetRgn
gosub *SetRgn //リージョン設定
gsel 0
color 255
font "MS ゴシック",20,1
RegCount=0
repeat length(hRgn)
RegSize= GetRegionData(hRgn(cnt),0,0)
dim RegionData,RegSize/4 //デーア分確保
RegSize= GetRegionData(hRgn(cnt),RegSize,varptr(RegionData))//RegionDataの内容を保存しておけばExtCreateRegionで同じリージョンを作れる
if RegSize {RegCount+RegionData(2)}
loop
if RegCount {dialog "リージョンデータ内矩形総数:"+(RegCount)+""}else{dialog "リージョンデータを取得出来ません":end}
hTmp=CreateRectRgn(0,0,0,0) //作業用のリージョン
ddim sp_posx,10
ddim sp_posy,10
sp_posx(1)=1.0*Star_Pos(0)
sp_posy(1)=1.0*Star_Pos(1)
*メイン
es_cls 200,255,255
es_pos 0,mousex-Circle_Size(0)/2 , mousey-Circle_Size(1)/2 //スプライト移動
es_get posx,0,3
es_get posy,0,5
OffsetRgn hRgn(0),posx-RgnPosX,posy-RgnPosY //複数の矩形データを含むのでオフセットでリージョン移動(一つの矩形ならSetRectRgnで直接座標を指定出来る)
RgnPosX=posx//アニメーションで判定領域を変える場合は工夫が必要
RgnPosY=posy
if (loopcnt\400)<200 {sp_posx(1)+0.2}else{sp_posx(1)-0.2}
es_pos 1,sp_posx(1),sp_posy(1) //スプライト移動
loopcnt++
es_draw
//ヒットチェック
es_check Hit,0,1,0,1,1
if Hit>=0 {//スプライトの実体と矩形判定してからそのスプライトのキャラクターNOを取得してそれを元にリージョン判定
es_get CharaNo,Hit,12
OffsetRgn hRgn(CharaNo),int(sp_posx(CharaNo)),int(sp_posy(CharaNo)) //リージョンオフセット移動(判定が必要な時だけスプライトの位置に移動)
if CombineRgn(hTmp,hRgn(0),hRgn(CharaNo),1) >1 { //重なった領域を取得
pos RgnPosX-Circle_Size(0)/2,RgnPosY+Circle_Size(1)/2 :es_fmes "HIT"
RegSize= GetRegionData(hTmp,0,0) //重なった領域の配列バイト数
//配列RegionDataは上のほうでhRgn(1)のデータ分確保してるので、それ以上のデータはありえないから再確保を省いてる
RegSize= GetRegionData(hTmp,RegSize,varptr(RegionData)) //重なった領域矩形データを配列に取得(ちょい重いから毎フレーム数百回とかは使わないほうが良いかも)
if RegSize {
repeat RegionData(2),2 //RegionData(2)に矩形の数が入ってる、4~7は他の矩形データが収まる最小範囲、8から個別矩形データ
es_exboxf RegionData(cnt*4),RegionData(cnt*4+1),RegionData(cnt*4+2),RegionData(cnt*4+3),$ffff00
loop
}
}
OffsetRgn hRgn(CharaNo),-int(sp_posx(CharaNo)),-int(sp_posy(CharaNo)) //オフセット移動したのを戻す
}
es_sync 17,0 : await
es_getfps FPS : title "FPS:"+FPS+" (処理落ち無しの場合⇒57~58前後)"
goto *メイン
*SetRgn //バッファID1を判定画像として使用
hTmp=CreateRectRgn(0,0,0,0) //作業用のリージョン
hRgn(RgnNo)=CreateRectRgn(0,0,0,0) //このリージョンに追加していく hRgn(キャラクタNo)
repeat RectSizeY
gsel 1
cn =cnt //x座標
repeat RectSizeX
pget cnt,cn
if (ginfo_r != 255) or (ginfo_g != 255) or (ginfo_b != 255){ //透明色以外の座標に当たり判定用スプライトを設置
SetRectRgn hTmp,cnt,cn,cnt+1,cn+1
ts=CombineRgn(hRgn(RgnNo),hRgn(RgnNo),hTmp,2)
}
loop
gsel 0
FillRgn hdc, hRgn(RgnNo), GetStockObject(BLACK_BRUSH) //リージョン表示
redraw 1
await 0
loop
DeleteObject hTmp
gsel 1
return
*終了時
DeleteObject hTmp
repeat length(hRgn)
if hRgn(cnt){DeleteObject hRgn(cnt)}
loop
es_bye
end
OffsetRgnを使えば数百個の矩形が入ってるリージョンも一回で移動が出来る
現在位置からのオフセットだから少し面倒だけど・・・

| |
|
2013/3/13(Wed) 17:37:52|NO.52923
>暇人様
おお!こんな方法があったとは!!
わざわざスクリプトまで書いて頂いてありがとうございます。
非常にわかりやすかったです。
少し改変してes_checkとCombineRgnを組み合わせてから
アクションゲームのクライアントに突っ込んだら正常に動作してくれました。
今回は暇人様の方法を利用させて頂き、この質問は解決とさせて頂きます。
暇人様、本当に助かりました。重ねてお礼申し上げます。
他の回答者の方々のレスも大変に参考になりました。
_hamyuu様、晩御飯様、ありがとうございました。
|
|
2013/3/13(Wed) 22:59:23|NO.52926
NO.52915のは不透明1ドット毎にCombineRgnしてて処理食ってたので
一度リージョン用データ配列に全部入れてからExtCreateRegionでリージョンを作って
最後にCombineRgnするように変更
NO.52915の三倍ぐらい軽くなった
経過表示は1ドットずつ描画してるだけだから重いけど・・・
#define PROCESS_DISPLAY_ON 1 //経過表示有り無し 0=無し 1=有り
*SetRgn //バッファID1を判定画像として使用
hTmp=CreateRectRgn(0,0,RectSizeX,RectSizeY) //作業用のリージョン(最後にこの矩形と重なってる部分をリージョンにする)
hRgn(RgnNo)=0 //リージョンハンドルが入る配列 hRgn(キャラクタNo)
//リージョン用データを初期化()
dim RegionData,RectSizeX*RectSizeY*4
RegionData=32,1,0,0 ,0,0,RectSizeX,RectSizeY //初期値設定
rectcnt=2,2 //矩形の個数カウント(3個目からが個別の矩形になるので2)
repeat RectSizeY
gsel 1
cn =cnt //x座標
repeat RectSizeX
pget cnt,cn
if (ginfo_r != 255) or (ginfo_g != 255) or (ginfo_b != 255){ //透明色以外の座標に当たり判定用スプライトを設置
RegionData(rectcnt*4)=cnt,cn,cnt+1,cn+1
rectcnt++
}
loop
#if PROCESS_DISPLAY_ON //経過表示
gsel 0
repeat rectcnt-rectcnt(1),rectcnt(1)
boxf RegionData(cnt*4),RegionData(cnt*4+1),RegionData(cnt*4+2),RegionData(cnt*4+3)
loop
rectcnt(1)=rectcnt
await 0
#endif
loop
RegionData(2)=rectcnt-2,(rectcnt)*4*4 //矩形の数、リージョンデータの総バイト数
hRgn(RgnNo)=ExtCreateRegion(0,RegionData(3),RegionData) //リージョンデータをリージョン化(自動で最適化されたリージョンがhRgn(RgnNo)に返る)
ts=CombineRgn(hRgn(RgnNo),hRgn(RgnNo),hTmp,1) //hTmpとhRgn(RgnNo)の重なってる領域を取り出し
DeleteObject hTmp
gsel 1
return
後
> RegSize= GetRegionData(hTmp,RegSize,varptr(RegionData)) //重なった領域矩形データを配列に取得(ちょい重いから毎フレーム数百回とかは使わないほうが良いかも)
ここで使ってるRegionDataは今回に限って必要最大確保された状態だから大丈夫だけど
普通はそうとは限らないので、その都度必要なだけ確保するかメインループに入る前に
これ以上はありえないぐらい確保した方が良い
もし確保したバッファが足りないと終了時にアプリエラーが出たりする

| |
|
2013/3/14(Thu) 19:43:57|NO.52939
>暇人様
確認が遅れてお返事が遅くなってしまいました。
折角ご回答頂いたのに申し訳ございませんでした。
おおーっ!確かに早くなってますね!!
早速組み込んでみます。
>もし確保したバッファが足りないと終了時にアプリエラーが出たりする
念頭に置いておきます。ご丁寧にありがとうございます。
ついでにpgetもVRAMから取得するように変更したらさらに高速化されてかなり動作に満足しています。
本当に助かりました。ありがとうございました。
|
|