|
|
2022/2/10(Thu) 13:07:30|NO.95363
こんにちは。
またまた質問させていただきます。
無事、ミサイル誘導や爆発アニメーションの実装など実現出来ました。
ここで、同じモデルの敵を複数違う座標から出現、させたいのですが
この場合、皆様はどのようになされていますか?
自作エディタなどGUI?で敵を配置するなんてのも出来たりするのでしょうか?
|
|
2022/2/10(Thu) 17:57:00|NO.95364
配列変数を使い、ループ処理を使います。
敵がいくつになろうとプログラムが複雑になることはありません。
|
|
2022/2/10(Thu) 19:34:56|NO.95366
スマホ投稿、ネット接続PCなしの部外者です。
シューティングゲーム と言っても
縦/横/固定/3D(アフターバーナー等)
/FPS タイプなのか、分からないので
はっきり書いた方が回答しやすいです。
敵配置や動作パターンは
hsp3のサンプル、他人様のソース等を見るのも
良いかと。3D、FPS系は見たことないですが。
後、自作エディタですが、縦/横タイプなら
hsp3 ではないですが、シューティングゲーム自体
制作出来るソフトが存在します。
...なので、やる気次第。
(※↑こういった物を作れるレベルの方なら
シリーズ物、ステージが多い等
...でない限り、直接プログラムした方が
早く完成出来ると、思います。)
|
|
2022/2/10(Thu) 22:06:17|NO.95368
こんばんは
付け加えますと、ゲーム自体はコックピット視点からなる、奥や手前から敵が出現するタイプのシューティングゲームを作ろうと思っています。
銃を発射するプログラムの応用で、敵の出現パターンを作成するのもありでしょうか?
|
|
2022/2/10(Thu) 23:12:55|NO.95370
今回も私のやり方では、という話ですが。
powさんの言われるように基本的には
敵は前回のショットのような方式で出現させています。
まずは、敵の出現パターンを複数用意します。
例えば
パターン1:タイプAの敵を横に3機並べて出現
パターン2:タイプBの敵を縦に3機並べて出現
それぞれパターン別に配置プログラムを作っておき
ifやswitchで分岐させたりサブルーチンにしておきます。
パターン1の配置ならば
タイプAの敵の配置をX座標をずらしながら3機分行う、といった感じです。
後はこの出現パターンを
適当なタイミングで選んで呼び出せば良いわけです。
出現タイミングもパターンも乱数で決めてしまうというのも一つの手ですが、
私の場合は
各ステージごとの出現タイミングやパターンのデータをファイルにしておき
ステージ開始時に読み込むといった方法を採っています。
データの作成は完全に手作業です。
タイミングやパターンの数値を並べたcsvファイルを作成。
それを読み込ませゲームを動かしてみて調整を繰り返すといった具合です。
・・・もちろん面倒くさいです。
ちゃんと配置用のツールでも作った方が楽かもしれませんが
それはそれで面倒くさい・・・。
今回はさすがに簡単なサンプルを作って、というわけにはいかないので
説明だけになってしまいました。
もちろん他の方法もあるとは思いますので、
一つの考え方として参考にしていただけたらうれしいです。
|
|
2022/2/11(Fri) 07:08:25|NO.95373
私の場合、以下のように敵も味方も弾丸も配列で管理、処理するように考えます。
; 初期化部
obj_size = 10; 画面に出現できるオブジェクトの最大数
dim obj_name, obj_size; 表示名
dim obj_type, obj_size; 種別 (敵味方、弾丸などの識別)
ddim obj_pos_x, obj_size; 座標X
ddim obj_pos_y, obj_size; 座標Y
ldim obj_action, obj_size; オブジェクトの行動を定義したサブルーチン
dim obj_interval, obj_size; 行動の速さ (少ないほど速い)
; 生成部
#deffunc set_enemy int n; n = 生成する番地
obj_name.n = "敵"
obj_type.n = OBJTYPE_ENEMY; enumで定義しておくこと
obj_pos_y.n = double(rand(50))
obj_action.n = *obj_move_straight; 別途、サブルーチンで定義しておく
obj_interval.n = 10
return
; 実行部
dim time
repeat; メインループ
await 25
time ++
; 描画準備
boxf
redraw 0
/* この辺りに共通の画面の描画 */
; オブジェクトのアクション
repeat obj_size
if time \ obj_interval.cnt : continue; インターバルを迎えてないのでスキップ
gosub obj_action.cnt; オブジェクトに設定された行動を行なう
/* この辺りで、行動の結果を踏まえてオブジェクトを描画 */
loop
redraw 1; 描画結果を画面に反映
/* この辺りでプレイヤー側の操作受付 */
loop
実際には、敵や弾の生存判定、破壊した敵の再生性、生成するオブジェクトの設定や分岐、ダメージ判定なども必要かと思いますが、上記の処理であれば、ステージにより敵数を調節する、行動パターンを変える、共通の行動パターンを使いまわす、などがやりやすくなります。味方を配置したり、追尾弾を仕込んだりもできるかと…
あと敵の生成パターンに関しては、 rnd関数 で決め打ちするのが簡単です。もし、ステージ毎に出現パターンを固定したいなら、ステージ開始時に「randomize ステージ番号」のようにシード値を固定してやると楽かと…
| |
|
2022/2/13(Sun) 04:30:16|NO.95401
私なら配列ではなくモジュール型変数で実装します。
例えば弾幕シューティングであれば弾丸だけでも大量の配列を必要とするものの
配列変数だと上限を定めなくてはならないし、PCが重くなってしまったりする懸念もあるので
moddel等で適宜削除してリソースを開放できる方が資源節約的にも
ゲームバランスの調整もし易いのかなと思っているのですが、私自身中級者ぐらいなので自信はありません。
|
|
2022/2/13(Sun) 13:56:17|NO.95408
こちら参考にして頂くとよいかもしれません。
#define global bgbuf 2
#define global chrbuf 3
#define global bombuf 4
#enum SE_BOM = 0
; レーザーの移動
;
#module mylaser x,y
#modinit int px, int py
x=px : y=py
return
#modfunc laser_move
x+=12
if x>640 : return 1
pos x,y : gcopy chrbuf,0,64
return 0
#modfunc laser_getpos var resx, var resy
resx=x
resy=y
return
#global
; 敵1体の処理
;
#module enemy flag,x,y,px,py
#modinit
; 敵発生
;
flag=0
x=640
y=rnd(360)+40
px=rnd(3)-5
py=rnd(5)-2
return
#modfunc enemy_move
; 敵移動
;
x+=px:y+=py
pos x,y
if flag {
gcopy bombuf,0,(flag-1)*64
flag++
if flag>17 : return 1
return 0
}
gcopy chrbuf,0,128
if x<-64 | x>640 | y<-64 | y>480 : return 1
return 0
#modfunc enemy_hit int tx, int ty
; 敵ヒットチェック
if flag : return 0
if (abs(tx-x)<48)&(abs(ty-y)<48) {
mmplay SE_BOM
flag=1 : return 1
}
return 0
#global
#module
#deffunc enemy_init
; 敵全体の初期化
;
etime=0
dimtype mod_enemy,vartype("struct"),32
return
#deffunc enemy_move_all
; 敵全体の移動
;
etime++
if etime>12 : etime=0 : newmod mod_enemy,enemy
;
max=0
gmode 2,64,64
foreach mod_enemy
enemy_move mod_enemy.cnt
if stat : delmod mod_enemy.cnt
max++
loop
color 255,255,255:pos 0,0:mes "Enemy:"+max
return
#deffunc enemy_hit_all int p1, int p2
; 敵全体のヒットチェック
;
retval=0
chkx=p1:chky=p2
foreach mod_enemy
enemy_hit mod_enemy.cnt,chkx,chky
if stat : break
loop
return
#global
#module
; 自キャラの移動・操作
;
#deffunc my_init
mx=200:my=200:mflag=1
dimtype mod_laser,vartype("struct"),64
return
#deffunc my_move int key
; 自機の移動
if key&2 : my=my-6
if key&8 : my=my+6
if key&1 : mx=mx-6
if key&4 : mx=mx+6
mx=limit(mx,0,511)
my=limit(my,0,415)
if mflag>1 {
mflag++
if mflag>60 : end
if mflag&2 : return
} else {
enemy_hit_all mx,my
if stat : mflag=2
}
pos mx,my:gmode 2,128,64:gcopy chrbuf
return
#deffunc my_shot int key
; レーザーの発射
if key&16 {
newmod mod_laser, mylaser, mx+64, my+32
}
; レーザーの移動
res=0
gmode 5,64,64,256
foreach mod_laser
laser_getpos mod_laser.cnt,x,y
enemy_hit_all x,y
if stat : delmod mod_laser.cnt : continue
laser_move mod_laser.cnt
if stat : delmod mod_laser.cnt
loop
return
#global
*main
; メインプログラム
;
randomize
title "shooting sample"
mmload dir_tv+"se_bom.wav",SE_BOM
buffer chrbuf
picload dir_tv+"shootchr.bmp"
buffer bombuf
picload dir_tv+"bom.bmp"
gosub *makebg
screen 0,640,480
my_init
enemy_init
repeat
redraw 0
gosub *putbg
stick key,15,1
if key&128 : end
my_move key
my_shot key
enemy_move_all
await 20 ; 全体のスピード調節
redraw 1
loop
end
*putbg
; 背景表示
;
gmode 0,640,480
pos bgx,0:gcopy bgbuf
bgx-=8:if bgx<-640 : bgx+=640
pos bgx+640,0:gcopy bgbuf
return
*makebg
; 背景の作成
;
buffer bgbuf,1280,480,0
cls 4
gmode 5,64,64,200
repeat 32
x=rnd(1280)-32:y=rnd(480)-32
pos x,y:gcopy chrbuf,64,64
loop
bgx=0
return
| |
|
2022/2/16(Wed) 16:54:35|NO.95439
こんにちは。横から失礼いたします。
本稿はpowさんの敵の出現のさせ方からは少々外れますが、ネットヤンキーまみさんの仰るスプライト管理の手法について少し書かせていただきます。
モジュール変数のほうが配列管理より高速でリソース的にも有利とのことですが、これはネットヤンキーまみさんがご自身でご確認されたことなのでしょうか。
HSPのモジュール変数はコードの整理しやすさには一役買っていますが、速度面、リソース面では大きな犠牲を払っています。
以下のサンプルを実行し、同数のモジュール変数の確保と配列変数の確保にかかる時間とメモリ消費量を確認してみてください。
メモリ消費量はデバッグウィンドウからモジュール変数の表示を有効にすると確認ができます。
#uselib "kernel32.dll"
#cfunc GetTickCount "GetTickCount"
#define max 50000 //もし差がわかりづらい場合はこの数値を増やしてください。
#module mod x
#modinit
x=0
return
#deffunc regmod //新しいモジュール変数の領域を1つ確保する
newmod m,mod
return
#deffunc mclr //モジュール変数を初期化する
dimtype m,vartype("struct")
return
#global
objsize 200,32,40:pos 50,20
button ""+max+"個のモジュール変数を登録",*mo
button ""+max+"個の配列変数を登録",*ar
stop
*mo
mclr //モジュール変数開放
mes "モジュール変数登録開始..."
time=GetTickCount()
repeat max:regmod:loop
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
*ar
dim a,1 //一旦配列初期化
mes "配列変数登録開始..."
time=GetTickCount()
repeat max:a(cnt)=0:loop
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
モジュール変数の方が遥かに重く、メモリを大きく消費しているのがわかるかと思います。
またforeach命令の動作もrepeatより有利ということはなく、シューティングゲームのように動作速度とリソース管理が優先される場合、モジュール変数での管理は配列変数に比べて大きく不利になることにご注意ください。
更に、HSPのモジュールではモジュール変数は自分自身の配列IDを知り得ません。(ちょっとテクニックを使わないと知ることができません。)
例えばショットの動きを作ってる間、そのショットのIDを簡単に敵やプレイヤーに伝えたりできません。
そのような状況下で強引にモジュール管理してしまうと、かえって配列変数より複雑で重くなってしまうこともしばしばありますので、充分にモジュール変数の特性を理解された上でコードを設計するようにされてください。
余談ですが、HSP3Dishではモジュール変数が未サポートとマニュアルに明記されており、デバイスによっては正しく動作しない可能性があることにもご注意ください。
また配列変数だと上限を定めなくてはならないとのことですが、
HSPは配列を自動的に確保する仕様ですので必ずしも上限を定める必要はありません。
以下のサンプルでは一切の配列変数宣言や上限を定めたりをしていませんが、大量の弾幕を配列変数で生成管理しています。
#include "hsp3dish.as"
wx=ginfo_winx
wy=ginfo_winy
repeat
repeat 2 //毎フレーム2発生成
id=-1 //生成するID
repeat idMax //既存の空きIDを探す
if sID(cnt)=0:id=cnt:break //空きIDを発見
loop
if id=-1:id=idMax:idMax++ //空きIDが無ければ新規ID取得
sID(id)=1:sx(id)=0.5*wx:sy(id)=0.5*wy:sa(id)=0.1*time+m_pi*cnt //弾生成
loop
redraw 0 //描画開始
color 255:gmode 0,2,8
repeat idMax //全ての弾を描画
if sID(cnt):{ //弾があれば
sx(cnt)+=sin(sa(cnt)) //弾の移動
sy(cnt)+=cos(sa(cnt))
if sx(cnt)<0 | sy(cnt)<0 | sx(cnt)>wx | sy(cnt)>wy:{ //画面外なら
sID(cnt)=0 //弾消滅
}else{ //画面内なら
grect sx(cnt),sy(cnt),-sa(cnt) //弾描画
}
}
loop
time++
pos 10,10:color:mes "MAX:"+idMax
redraw 1
await 16
loop
このような場合はモジュール管理よりもシンプルなコードで弾幕を実現することができます。
ただし、スプライトの上限を定めない場合、配列管理でもモジュール管理でもどちらにも言えることですが、メモリリークやバグ等には充分に注意してプログラムしないと想定外の量のスプライトが生成され、ユーザーのPCに大きな負荷をかけかねません。
できるだけスプライトには上限を設け、バグチェックは入念に行うようにされてください。
と、ここまで書いてしまうと、モジュール変数はほとんど利点がないようにさえ見えてしまうかもしれませんが、外側のスクリプトから干渉を受けないモジュールの特徴を理解して書かれたコードは読みやすく、プログラムの上下関係が明瞭になり、配列管理では絶対に得られない強みがあります。
余談ながら、逆に、ネットヤンキーまみさんが別スレッドで話題に上げられていたように、モジュール内の変数やラベルにモジュールの外部から@で干渉してしまうようなコーディングは自らモジュールの強みを潰す行為になり得ます。
http://hsp.tv/play/pforum.php?mode=all&num=95378
@を使った記述は絶対にダメ…というわけではありませんが、綺麗なコードが書けるように理解と経験を深めていってください。
| |
|
2022/2/18(Fri) 04:17:12|NO.95451
皆様、様々な回答ありがとうございました。
とりあえず、基本的にはショットなどの応用など、配列変数などを使った方法などが一般的のようですね。
なかなか苦戦しておりますが、試行錯誤を続けてなんとか完成まで、頑張ります。
|
|
2022/2/18(Fri) 07:05:52|NO.95453
なんか思い込みの激しい方が約一名いるようですね(*´・ω・)
|
|
2022/2/18(Fri) 17:27:26|NO.95462
Drip様
ご指摘、ご教示ありがとうございます。
ご指摘の件ですが、モジュールの方速度面で高速とは言っておりません。
メモリが逼迫する事でPC全体の動作速度が遅くなる、という事を言っておりました。
もしモジュール変数の速度が有利だとすればそれはPC全体には及ばない
あくまでHSP単体での話になると思いますので、文脈からご理解いただけますと幸いです。
>メモリ消費量はデバッグウィンドウからモジュール変数の表示を有効にすると確認ができます。
>モジュール変数の方が遥かに重く、メモリを大きく消費しているのがわかるかと思います。
>またforeach命令の動作もrepeatより有利ということはなく、シューティングゲームのように動作速度とリソース管理が優先される場合、モジュール変数での管理は配列変数に比べて大きく不利になることにご注意ください。
まず前提として、使用サイズとバッファサイズが少ない方がリソースの消費量が少ないという事ではないのでしょうか。
Drip様の認識と私の認識が違う為でしょうか。
実際にシューティングゲーム等において実用的な場面を想定するとこの点がまず疑問です。
|
|
2022/2/18(Fri) 18:39:05|NO.95463
ネットヤンキーまみさん
>メモリが逼迫する事でPC全体の動作速度が遅くなる、という事を言っておりました。
>もしモジュール変数の速度が有利だとすればそれはPC全体には及ばない
>あくまでHSP単体での話になると思いますので、文脈からご理解いただけますと幸いです。
言っている事の趣旨がよく伝わってこないのですが、メモリが逼迫すればPCが重くなるのは当然かと思います。
モジュール変数の速度が有利というのもどこから出てきた話なのでしょうか。
何を仰りたいのでしょうか。
>まず前提として、使用サイズとバッファサイズが少ない方がリソースの消費量が少ないという事ではないのでしょうか。
「使用サイズ」をご確認いただければわかるとおり、モジュール変数で配列管理と同じ事をしようとした場合、どうしてもモジュールのほうがデータ量(メモリ使用量)が増大する事がわかるかと思います。
もちろんリソースや速度を犠牲にしてでもモジュール変数を使用するメリットがあるという場合はモジュールを使用してよろしいかと思いますが、ネットヤンキーまみさんが当初から仰っている資源節約や重さの改善の説明にはなっていないような気がしましたので注意喚起させていただきました。
御託ではなくモジュールによる高速化やリソースの削減が感じられる具体的なサンプルや例を提示いただければ見方も変わるかと思います。
>実際にシューティングゲーム等において実用的な場面を想定するとこの点がまず疑問です。
私は実用的な場面を想定した場合に問題となる点を実例やサンプルコードを含めて説明しているのですが、それでも納得がいかないというのであれば、それがわかる実例やスクリプトの提示をお願いいたします。
|
|
2022/2/18(Fri) 19:01:08|NO.95464
自分もネットヤンキーまみさんと同じく、複数のオブジェクトを動的に管理する場合はモジュール変数を使うと思います。
その方が管理しやすいですし、例えばシューティングゲームであれば、
1面から2面へ切り替わる時に、前の面で使用した変数を初期化する必要があるはずですから、
#modtermに変数開放処理等が記述できるモジュール変数を使った方が便利だと思いますね。
シューティングでも例えば7面もあるような長い作品だとそれだけ多くの変数を管理するわけで、
大きな作品を作ろうとする場合、変数管理が容易になるモジュール変数はとても便利なものです。
モジュール変数はモジュールを使わなかった場合と比較して複雑なスクリプトを簡単な記述で実装できるわけですから、
開発時間の短縮と、保守の安定性向上に役立っていると思います。
|
|
2022/2/18(Fri) 19:15:14|NO.95466
外部から乱入して申し訳ないです、
モジュール自体使ってない(※使えない)
万年初心者です。
例えば、マクロ、モジュール等を展開する
場合のコストって、どうなんでしょうか?
(※圧縮、解凍の処理が近いのかな?)
c言語とかなら、同じ処理をさせる場合
(※ソースの書き方にもよりますが)
hsp3 より数倍速い...ので、ある程度
このコストをある程度、吸収出来ると
思うのですが...
私の書いた事が、合っているなら
複雑なモジュールになれば、累積して
動作等が遅くなる事は、あり得るのでは
ないでしょうか?
もちろん、ソースが見やすくなる等の利点
もあるので、適度なモジュール化は
大規模なソースには必要と思います。
その辺りのバランスを考えて
ソフトを作れるように...なりたいです。
|
|
2022/2/18(Fri) 19:21:49|NO.95467
youdaiさん、こんにちは。
>#modtermに変数開放処理等が記述できるモジュール変数を使った方が便利だと思いますね。
そうですね。
#modtermによる後始末は例えばhgimg3等でオブジェクトIDの管理をモジュール変数で行っている場合等、オブジェクトの開放を自動的に行うことができますから、そうした目的を持って利用される場合は良いかと思います。
以下のように50フレームごとに新しいステージを作成する処理をモジュール変数で作った場合、一見効率的でスマートに処理されているように見えます。しかしプログラムを終了するとWindowsのエラーが…^^;
経験者は適切にモジュール変数を開放してこの問題を回避しますが、モジュール変数の特徴を理解した上で使用する事が大切ですね。
//あえてコメントを書いてません。
//知らない人は…プログラム終了時にエラーが発生する原因を探してみましょう^^;
#include "hgimg3.as"
hgini
#module mod id
#modterm
delobj id
return
#modinit
regobj id,box
setpos id,rnd(100)-50,rnd(60)-30,rnd(100)-80
return
#deffunc reg
if box=0:addbox box,10,10
newmod m,mod
return
#deffunc init
dimtype m,vartype("struct")
return
#global
repeat
if cnt\50=0:init:repeat 50:reg:loop
hgdraw
hgsync 16
loop
|
|
2022/2/18(Fri) 19:38:30|NO.95468
zrs90(5さい)さん、こんにちは。
HSPの #define によるマクロはスクリプトの置き換えです。
#define によってスクリプトを置き換えたほうがご自身で見やすくなるという場合は使用されてください。
これによってプログラムの動作が高速化するということは全くありません。
(テスト実行開始にかかる時間や、実行ファイル生成にかかる時間に影響することはありますが、よほどの量にならないと知覚できないかと思います。これはマクロにもモジュールにも言える事です。)
モジュールは…スクリプト上で見た場合、明確に領域が仕切られているように見えるのですが、内部的にはあんまりHSPの通常の空間と違いがないようです^^;
あえてモジュールで記載したから高速になるということはありませんので、ご自身で整理しやすい方を採用されるとよろしいかと思います。
(内部的な動作に関しては個人の解析の範囲内の話になりますので、様々な知見があるかと思います。ご自身で負荷テストを行い、良い管理方法を探すのが良いかと思います。)
|
|
2022/2/18(Fri) 19:39:09|NO.95469
>私は実用的な場面を想定した場合に問題となる点を実例やサンプルコードを含めて説明しているのですが、それでも納得がいかないというのであれば、それがわかる実例やスクリプトの提示をお願いいたします。
上記件、承知致しました。
//ネットヤンキーまみver//
#include "hsp3dish.as"
#module bullet sx,sy,sa
#modinit double p1,double p2,double p3
sx=p1 : sy=p2 : sa=p3
return
#modfunc bullet_mv int p1, int p2
//p1,p2=画面の左端、上端を指定 ※弾消去の為
sx+=sin(sa)
sy+=cos(sa)
if sx<0 | sy<0 | sx>p1 | sy>p2:{ //画面外なら
delmod thismod
}else{ //画面内なら
grect sx,sy,-sa //弾描画
}
return
#global
wx=ginfo_winx
wy=ginfo_winy
repeat
repeat 2 //毎フレーム2発生成
newmod sID,bullet,0.5*wx,0.5*wy,0.1*time+m_pi*cnt
loop
redraw 0 //描画開始
color 255:gmode 0,2,8
foreach sID //全ての弾を描画
bullet_mv sID(cnt),wx,wy
loop
time++
pos 10,10:color:mes "MAX:"+length(sID)
redraw 1
await 16
loop
頂いた2つ目のスクリプトをモジュール変数で全く同じ動作を再現させて頂きました。
なるべく分かりやすくなる様にと思い、アレンジさせて頂きました。ご理解お願いいたします。
>このような場合はモジュール管理よりもシンプルなコードで弾幕を実現することができます。
余談ですがモジュール変数で記述する場合の方が圧倒的に分かりやすくシンプルになる様に思います。
>「使用サイズ」をご確認いただければわかるとおり、モジュール変数で配列管理と同じ事をしようとした場合、どうしてもモジュールのほうがデータ量(メモリ使用量)が増大する事がわかるかと思います。
使用サイズが実際のメモリ消費量なんですね!
ご教示ありがとうございます。
上記まみverのスクリプトとDrip様のスクリプトで比較すると
まみverについては
(変数名/使用サイズ)※MAX=462時
Sid/7263
※他弾丸に使用している変数は無し
Drip様ver だと
※MAX=462時
sa /3627
sy /3627
sx /3627
sid/1813
idmax/4
id/4
この様な結果になりました。
>モジュール変数で配列管理と同じ事をしようとした場合、どうしてもモジュールのほうがデータ量(メモリ使用量)が増大する事がわかるかと思います。
使用サイズが実際のメモリ消費量だとすればDrip様の方が
70%程多く使っている事になるので、リソース的に配列の方が不利、という事ではないのでしょうか。
それとも、これらは同じ事をやっている様に見えて根本的に違うのでしょうか。
また、余談ですが初心者や経験が浅い人に配列で大量の変数を管理するのはなかなか難しいとも思います。
既にこのサンプルだけでも変数の数が圧倒的に違います。
一つの配列変数のミスが全体に響くので、メンテナンス性はモジュール変数の方が遥かに優れていると思います。
あと、納得いかない、喧嘩を売っている訳でもなく、純粋に疑問に思い質問させて頂いております。
>言っている事の趣旨がよく伝わってこないのですが、メモリが逼迫すればPCが重くなるのは当然かと思います。
モジュール変数の速度が有利というのもどこから出てきた話なのでしょうか。
何を仰りたいのでしょうか。
繰り返しになりますが、速度が有利、という事は言ってなく
リソース面で有利なのではないか、という事です。
| |
|
2022/2/18(Fri) 20:33:32|NO.95470
速度面の比較についても検証してみました。
単なる配列変数への代入とモジュール変数の生成を比較ではなく
より実践的な想定で
・配列変数で弾丸を定義する場合と弾丸をモジュール変数で生成する場合
の速度の違いを検証してみました。
配列においての弾丸の生成方法についてはDrip様のやり方をそのままトレースしています。
モジュール変数での生成は上記まみverのスクリプトと同じ。
#uselib "kernel32.dll"
#cfunc GetTickCount "GetTickCount"
#define max 50000
#module bullet sx,sy,sa
#modinit double p1, double p2 , double p3
sx=p1:sy=p2:sa=p3
return
#deffunc mclr //モジュール変数を初期化する
dimtype m,vartype("struct")
return
#global
objsize 200,32,40:pos 50,20
button ""+max+"個のモジュール変数を登録",*mo
button ""+max+"個の配列変数を登録",*ar
stop
*mo
mclr //モジュール変数開放
mes "モジュール変数で登録開始..."
time=GetTickCount()
repeat max:newmod sid,bullet,0.1,0.1,0.1 :loop
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
*ar
dim a,1 //一旦配列初期化
mes "配列変数登録開始..."
time=GetTickCount()
repeat max
id=-1 //生成するID
repeat idMAX //既存の空きIDを探す
if sID(cnt)=0:id=cnt:break //空きIDを発見
loop
if id=-1:id=idMax:idMax++ //空きIDが無ければ新規ID取得
sID(id)=1:sx(id)=0.5:sy(id)=0.5:sa(id)=0.1 //弾生成
loop
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
私の環境だと625ms対95860msと10倍以上の差をつけてモジュール変数の方が早い、という結果になりました。
MAX=500という実践的な範囲でも 0ms対16msでモジュール変数の方が早いという結果になりました。
なので今となっては速度的にもモジュール変数の方がいいと思っています。
なんでここまで上から目線でボロクソに言われないいけないんでしょうか。
| |
|
2022/2/18(Fri) 20:37:50|NO.95471
ネットヤンキーまみさん
>使用サイズが実際のメモリ消費量だとすればDrip様の方が
>70%程多く使っている事になるので、リソース的に配列の方が不利、という事ではないのでしょうか。
もしもそれを本気で仰っているのであればモジュールに対する理解が甘すぎるかと思いますので、まず基本的な事から学習を始めていただきたく思います。
もし煽り目的で仰っているのであれば、屁理屈を並べることはやめていただきたく思います。
ネットヤンキーまみさんが比較しているデータは変数のID管理部分のみで、肝心の座標や角度を管理している変数のメモリ使用量を比較できていません。
またこの部分はデバッグウィンドウに表示されないため比較が難しい部分です。
初心者に対してそれを説明するのが困難なことから、私のサンプルではID管理部分のみで既にモジュール変数のほうがメモリの消費が激しいことを説明しているのです。
この問題をわかりやすくするために、今程ネットヤンキーまみさんが提示されたサンプルのrepeat 2 となっている箇所を repeat 50にして実行してみてください。
こうすることでプログラムの負荷は25倍となり、リソースの消費量もわかりやすくなります。
タスクマネージャーからhsp3dish.exeのメモリ消費量を見てみてください。
私のコードではrepeat 2 を repeat 50 に変更したところでhsp3dish.exeは10MB強程しか消費していませんが、ネットヤンキーまみさんのコードでは20MB近いメモリを消費していることがわかるかと思います。
全く同じ処理をさせているのに倍近い無駄が発生していることが明らかです。
これでもモジュールを使用したほうがリソースの節約になる、というのであれば使えばよろしいかと思いますが、私であればそれを他人にお勧めすることはありません。
>リソース面で有利なのではないか、という事です。
不利です。
|
|
2022/2/18(Fri) 20:38:07|NO.95472
コード間違えました。
29行目の
dim a,1 //一旦配列初期化
は正しくは
dim sID,max //一旦配列初期化
dim sx,max //一旦配列初期化
dim sy,max //一旦配列初期化
dim sa,max //一旦配列初期化
でした。結果は変わりませんが、念のため。
|
|
2022/2/18(Fri) 21:17:36|NO.95473
ネットヤンキーまみさん
>・配列変数で弾丸を定義する場合と弾丸をモジュール変数で生成する場合
>の速度の違いを検証してみました。
確かに、自動的に新たな空きIDを探すポイントに関してはrepeatで配列を探すよりもnewmodを使用したほうが素早く空きIDを見つけることができます。
もしも毎フレーム何百発もの弾幕を生成するというかなりの特殊条件下では「空きIDの取得」というポイントでnewmodで空きを取得したほうがお手軽で早く見えます。
ただし、それもrepeatで毎回単純に配列を検索した場合でのみ言えることであり、配列を拡張してIDを取得して以降は次の配列処理までの間配列の検索を行わない処理を加えた場合、やはり配列での処理がモジュールでの処理速度を上回ります。以下のように、私の repeat max の中身を修正してみてください。
#uselib "kernel32.dll"
#cfunc GetTickCount "GetTickCount"
#define max 50000
#module bullet sx,sy,sa
#modinit double p1, double p2 , double p3
sx=p1:sy=p2:sa=p3
return
#deffunc mclr //モジュール変数を初期化する
dimtype m,vartype("struct")
return
#global
objsize 200,32,40:pos 50,20
button ""+max+"個のモジュール変数を登録",*mo
button ""+max+"個の配列変数を登録",*ar
stop
*mo
mclr //モジュール変数開放
mes "モジュール変数で登録開始..."
time=GetTickCount()
repeat max:newmod sid,bullet,0.1,0.1,0.1 :loop
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
*ar
dim a,1 //一旦配列初期化
mes "配列変数登録開始..."
time=GetTickCount()
repeat max
if new=0:{ //配列拡張が行われていない場合の処理
id=-1 //生成するID
repeat idMAX //既存の空きIDを探す
if sID(cnt)=0:id=cnt:break //空きIDを発見
loop
if id=-1:id=idMax:idMax++:new=1 //空きIDが無ければ新規ID取得
}else{ //配列拡張が行われている場合、検索を行わない
id=idMax:idMax++
}
sID(id)=1:sx(id)=0.5:sy(id)=0.5:sa(id)=0.1 //弾生成
loop
new=0 //変数newは連続したID取得のあとに0に戻すことを忘れないようにしましょう。
mes "登録完了しました。かかった時間:"+(GetTickCount()-time)+"ms"
stop
このように連続した空きID取得に特化して設計すれば、配列管理でのID取得速度はモジュールと比較して10分の1程度に抑えられます。
こうした管理の自由度の高さも配列管理のメリットになりますね。
ただ、それ以前の問題として、毎フレーム数百発を超える弾幕を常時生成するようなプログラムの場合、肝心な処理部分ではその数分の弾幕を処理することになりますので、実用範囲を超えてしまうかと思います。
1フレーム中に弾幕を200発生成した場合でも60FPSで1秒間の間に12000発の弾幕が画面を舞うことになりますから、動作安定のために部分的にマシン語を使用するなどの工夫なども必要になってくるかと思います。(マシン語を使用する場合は配列変数でないと管理が難しくなります)
本件はネットヤンキーまみさんの「リソースの節約」とは異なる視点になりますが、何らかの特殊な目的があってモジュールを使用されるのであればよろしいかと存じます。
| |
|
2022/2/18(Fri) 22:16:20|NO.95474
Drip様
弾幕シューのような常時大量の弾を動かすもので動的に確保解放やらリソースやら
ってそんなに重要なことなんでしょうか?
私にはどうも合点がいかないのです
|
|
2022/2/18(Fri) 22:52:27|NO.95475
?さん
>弾幕シューのような常時大量の弾を動かすもので動的に確保解放やらリソースやら
>ってそんなに重要なことなんでしょうか?
さすがに1フレーム中に数百発もの弾を常時生成するような状況を想定したリソース管理をすることは通常重要ではないと思います。
シューティングゲームにおいて大量の弾幕を見せたい開発者の気持ちはわかりますし、軽量化するための様々なテクニックはあります。
ただそれをモジュールで管理することが色々と有利…という指摘の問題について説明させていただいたところ、どうにも反論をいただくため逐一説明させていただいております。
初心者のうちは動作効率はさておき、書き易いように書いて問題があったら対応していく程度で良いかと思います。きっと勉強になると思いますよ。
|
|
2022/2/18(Fri) 23:17:34|NO.95476
Drip さん、私の拙い疑問?に
わざわざ、答えて頂き
ありがとうございました。
|
|
2022/2/18(Fri) 23:40:39|NO.95477
Drip様
ありがとうございます。
私などは配列でスイスイ記述するほうが完全に慣れてます。^^
より深い理解をするのはとても重要ですね。
|
|
2022/2/20(Sun) 13:05:29|NO.95489
敵弾の管理ですが、これはゲームデザインの仕方とも関係してくる話かもしれませんが、
自分の場合、敵弾はあらかじめ最大数を決めたうえで配列を用いて管理しています。
敵弾のみならず自機ショット、敵機、爆発など、必要な配列はゲーム起動時に全て最大数確保し、
動的に変更することはありません。
そこで確保されるメモリが無駄とかは考えないですね。
確かに弾数が増えると、弾を発生させるとき空きを探すのが非効率になるかもしれませんが、
新しい弾をセットしたときや弾を消滅させたときにその配列のインデックスを保存しておき、
次回シークするときはそこを起点にすれば多少は時間短縮になるのかもしれません。
(自分は面倒なのでそこまでしていませんが)
|
|
2022/2/20(Sun) 14:30:00|NO.95490
>>95470
のスクリプトですが、
CPU Intel inside CORE i5(何世代かは不明)
メモリ2GB
の9年前のノートパソコンだと
見事に落ちました
|
|
2022/2/20(Sun) 16:14:36|NO.95492
多分、Windows10? で、9年前のCore i5
(多分、第2〜4世代)、2GのノートPC
多分、内蔵グラフィック
CPUもノート用の省電力版なら遅いはず。
osが64ビット版なら、なおさら。
(多分32ビットとは思うが)
メモリが4G載ってたら、問題なかったかも。
CPU以外、ほぼWindows10の最低動作要件に近い。
ちゃんとos更新しているなら、かなりキツいのでは?
os設定、アプリ等の稼働状況によっては
ハードディスクへのスワップが出ても
おかしくない。
タスクマネージャーから、CPUとメモリの状態
見てみれば、何か分かるんじゃない?
|
|
2022/2/20(Sun) 18:27:40|NO.95499
95470のスクリプトは検証しても、余り意味がないと思いますよ(デザインが違うので)
ネットヤンキーまみさんは”モジュール型変数”は便利という事を伝えたかっただけですし、
Dripさんはその”モジュール型変数”についての間違いを正しく指摘されてると思います。
(ちょっと、まみさんの追加の文章が蛇足だったのかもですね。。。投稿者さんへの敷居も高いですし)
”モジュール型変数”便利って気持ちはわかります。オススメしたかったのでしょう。
ただ、ゲームメイン中の”動的メモリ確保”はオーバーヘッドがあるので速度でいうとボトルネックになるかもしれませんね。
また、リソースとしてはdelmodはallocateしたメモリを再利用しますから、(freeされる訳じゃない)
いわゆるのクラスやGC的なイメージされていたのならやはり間違っていて指摘は正しいと思います。
ZAPさんの意見には賛成です。
※”安定した管理”には最大値で決めないと速度やメモリの想定ができないので最低動作スペックが出せませんから。
私の頃はメモリ管理は自分でやれといわれていたので、デザインパターンとしてはプールを選びます。
”モジュール”で分けて管理しやすくし、配列なのでメモリ消費も少なく
弾生成も1回のオーダーで早く、”モジュール型変数”ではないのでDishにも使える。。。という一例です。
#module tama
#define MAX 512
#deffunc local init
ddim x, MAX
ddim y, MAX
ddim xV, MAX
ddim yV, MAX
dim i, MAX
dim pool, MAX ; stack
dim poolI, MAX : poolI = 0
repeat MAX : i.cnt = -1 : loop
repeat MAX : pool.cnt = cnt : loop ; pushする
return
#defcfunc local create
if poolI >= MAX : return -1
result = pool.poolI : pool.poolI = -1 : poolI++ ; popする
i.result = result
return result
#deffunc local remove int aI
poolI-- : pool.poolI = i.aI : i.aI = -1 ; pushする
return
#deffunc local setpos int aI, double aX, double aY
x.aI = aX : y.aI = aY
return
#deffunc local setspeed int aI, double aX, double aY
xV.aI = aX : yV.aI = aY
return
#deffunc local update
repeat MAX
if i.cnt == -1 : continue
_i = i.cnt
x._i += xV._i
y._i += yV._i
if (x._i<0)|(y._i<0)|(x._i>ginfo_sx)|(y._i>ginfo_sy){ ; 弾削除
remove cnt ; pushする
}
loop
return
#deffunc local draw
rgbcolor $00FF00
repeat MAX
if i.cnt == -1 : continue
_i = i.cnt
boxf x._i-2, y._i-2, x._i+2, y._i+2
loop
return
#deffunc local debug
font "",8
pos 0, 0 : mes "pool: "+poolI
repeat 32: mes strf("%3d:%3d", pool.cnt, i.cnt):loop
return
#global
init@tama
*MAIN
stick pad, 256
if pad & 256 {
_i = create@tama(); 弾生成
if _i>=0 {
setpos@tama _i, mousex, mousey
setspeed@tama _i, 0, -6.
}
}
if pad & 512 {
repeat 32
_i = create@tama(); 弾生成
if _i>=0 {
setpos@tama _i, mousex, mousey
_a = M_PI*4.*cnt/32
setspeed@tama _i, 2.*cos(_a), 2.*sin(_a)
}
loop
}
update@tama
redraw 1 : await 16 : redraw 0 : color : boxf
draw@tama
debug@tama
goto *MAIN
でも、これも敷居が高いので、まずは”配列変数とループ処理”ってところからじゃないですかね。
問題が出て、質問する頃には理解も増してると思いますので。。。
| |
|
2022/2/21(Mon) 19:23:09|NO.95525
話の内容が少々違う方向に行ってしまったようで
powさんがまだ見ているかわからないので今更ですが、
「顧客が本当に必要だった物」(かもしれない)を作ったので載せておきます。
配置パターンを用意して敵を1グループずつ出現させるサンプルです。
配置パターンを作らずに1機ずつ配置することも出来ますが
非常に面倒な作業になります。
パターンを作っておけば1つの配置データで複数の敵をポンっと
配置することができます。
また、「この敵はこういうパターンで出現する」とした方が
プレイヤーにとって印象に残りやすいと思います。
もちろん、ゲーム内容によってこの方法が最適とは限りませんので
そこは、いろいろ工夫してください。
以下のスクリプトを適当なファイル名で保存し
#const ENEMY_MAX 100 ; 敵最大
dim ENEMY , ENEMY_MAX ; 敵タイプ
dim ENEMY_X , ENEMY_MAX ; 敵座標
dim ENEMY_Y , ENEMY_MAX
notesel ENEMY_DATA ; 配置データ読み込み
noteload "DATA.txt"
dim SET_COUNT , notemax ; 配置カウント
dim SET_PATTERN , notemax ; 配置パターン
dim SET_DATA , notemax ; 配置データ
repeat notemax
noteget GET_DATA , cnt ; 各データ読み込み
split GET_DATA , "," , D1 , D2 , D3
SET_COUNT(cnt) = int(D1)
SET_PATTERN(cnt) = int(D2)
SET_DATA(cnt) = int(D3)
loop
SET_MAX = notemax ; 配置データ数
STAGE_COUNT = 0 ; ステージのカウント
randomize ; 乱数初期化
cls 4 ; モード選択メニュー
color 255 , 255 ,255
mes "[A]:DATA MODE"
mes "[B]:RANDOM MODE"
mes "SELECT MODE [PUSH A or B]"
*SELECT_MODE
getkey A_KEY , 'A'
getkey B_KEY , 'B'
if A_KEY { SET_MODE = 0 } ; データで敵出現
if B_KEY { SET_MODE = 1 } ; ランダムに敵出現
wait 1
if A_KEY + B_KEY = 0 { goto *SELECT_MODE }
*MAIN
redraw 0
color 0 , 0 , 0 ; 画面クリア
boxf
if SET_MODE = 0 { ; データで配置
repeat SET_MAX ; 敵の配置
if STAGE_COUNT = SET_COUNT(cnt) { ; ステージのカウントが配置カウントに到達
D = SET_DATA(cnt) ; X 座標のデータ
if SET_PATTERN(cnt) = 1 { gosub *SET_PATTERN1 } ; 各パターンごとの配置
if SET_PATTERN(cnt) = 2 { gosub *SET_PATTERN2 }
}
loop
}
if SET_MODE = 1 { ; ランダムに出現
if rnd(30) = 0 {
P = rnd(2) + 1 ; パターン
D = rnd(600) ; X 座標
if P = 1 { gosub *SET_PATTERN1 } ; 各パターンごとの配置
if P = 2 { gosub *SET_PATTERN2 }
}
}
repeat ENEMY_MAX ; 敵の行動
if ENEMY(cnt) > 0 {
if ENEMY(cnt) = 1 { gosub *MOVE_ENEMY1 }
if ENEMY(cnt) = 2 { gosub *MOVE_ENEMY2 }
}
loop
STAGE_COUNT ++ ; ステージのカウント+1
color 255 , 255 , 255
pos 0 , 0
mes "COUNT:" + STAGE_COUNT
redraw 1 ; 画面表示
await 30
goto *MAIN
*MOVE_ENEMY1
ENEMY_Y(cnt) += 5 ; 移動
if ENEMY_Y(cnt) > 500 { ENEMY(cnt) = 0 } ; 一定位置で終了
color 255 , 0 , 0
pos ENEMY_X(cnt) , ENEMY_Y(cnt) ; 表示
mes "▽"
return
*MOVE_ENEMY2
ENEMY_Y(cnt) += 3 ; 移動
if ENEMY_Y(cnt) > 500 { ENEMY(cnt) = 0 } ; 一定位置で終了
color 0 , 255 , 0
pos ENEMY_X(cnt) , ENEMY_Y(cnt) ; 表示
mes "◇"
return
*SET_PATTERN1
repeat 3
gosub *SEARCH_BLANK ; 未使用の敵を探す
if E < 0 { break } ; 見つからなかった場合
ENEMY(E) = 1 ; 敵タイプ
ENEMY_X(E) = D ; X 座標
ENEMY_Y(E) = -10 - 20 * cnt ; Y 座標
loop
return
*SET_PATTERN2
repeat 3
gosub *SEARCH_BLANK ; 未使用の敵を探す
if E < 0 { break } ; 見つからなかった場合
ENEMY(E) = 2 ; 敵タイプ
ENEMY_X(E) = D + 20 * cnt ; X 座標
ENEMY_Y(E) = -10 ; Y 座標
loop
return
*SEARCH_BLANK
E = -1 ; 見つからなかった場合
repeat ENEMY_MAX
if ENEMY(cnt) = 0 { E = cnt : break } ; 未使用ならば番号をセット
loop
return
こちらを"DATA.txt"としてスクリプトと同じ場所へ保存してください
50,1,300
100,1,500
150,1,400
150,1,450
200,2,200
250,1,350
300,2,500
320,2,500
350,1,300
| |
|
2022/3/20(Sun) 02:50:41|NO.95771
こんばんは、最近別の作業に専念していたので気づくのが遅れましたが、参考にさせていただきます。
Makoto様
毎度、毎度ありがとうございます。m(_ _)m
TXTからどのような形で、読み込むのが前から気になっていましたが、これでモヤモヤが解決しました。
改良して、上手く組み込んでみますね。
|
|