GameMakerでのStateパターンの実装は、チュートリアル動画などでは下記のように、functionを変数に持つ形が紹介されています。
これを、オブジェクトベースにできないかと考えたので紹介したいと思います。また、こういうやり方が「ありかなしか」が判断できないので、プログラミングに詳しい方の意見をいただけたら助かります。
■チュートリアルで紹介されてるよくある形
(stateの関数の内容は省略)
create event:
state = scr_player_walk; // ← 関数(function)
step event:
state();
■オブジェクト(struct)ベースで実装する形
このケースではidleとwalkのステートを60step毎に交互に切り替えます。
プロジェクトは、1つのスクリプトと1つのオブジェクト(とスプライト)で構成されます。
■スクリプト: "player_states"
function idle(_obj) constructor
{
obj = _obj;
sprite = spr_player_idle;
imgspd = 0.01;
cnt = 0;
init = function()
{
cnt = 0;
obj.sprite_index = sprite;
obj.image_speed = imgspd;
}
loop = function()
{
if (cnt++ == 60) end_state();
}
end_state = function()
{
with(obj) scr_state_change(new walk(id));
}
}
function walk(_obj) constructor
{
obj = _obj;
sprite = spr_player_walk;
imgspd = 0.2;
cnt = 0;
init = function()
{
cnt = 0;
obj.sprite_index = sprite;
obj.image_speed = imgspd;
}
loop = function()
{
with (obj) x += 2;
if (cnt++ == 60) end_state();
}
end_state = function()
{
with(obj) scr_state_change(new idle(id));
}
}
■ オブジェクト:o_player
create event
state = undefined;
state_change = function (_state)
{
if (!is_undefined(state)) delete state;
state = _state;
state.init();
}
state_change(new idle(id));
step event
state.loop();
どうでしょう。この方法ありでしょうか?
もしこれが「あり」なら、この方法にはメリットがあると思います。
- state毎にインスタンス変数を持てる
- state毎にメソッドを持てる
- state開始時に1回だけ実行する処理もスッキリ実装できる
疑問なのは、stateオブジェクトのメソッドで呼び出した側を変更していいのか?という辺り。「こういう実装は問題を生みがちなのでやらない方がいい」などということはありますか?
以上です。ご意見をお待ちしています。
idle や walk といったステートは状態の実装のみを責務として、ステートの遷移は実装を切り分けるのはどうでしょうか?
ステート遷移が複雑になるほどステートの内部も複雑化してしまう気がしまして・・・
また、切り分けることでステートを自由に組み合わせることが出来るようになるため使いまわしも用意になるかなと
例えばマグロ🐟は walk <-> attack の遷移しか出来ないことをステートを使いまわした上で表現出来るようになります
遷移に係わる部分は各stateの、
stateを終了する条件の部分
と、遷移先のstateを決めて遷移する部分
これを切り分けてstateの外に実装するのがよいということでしょうか。
自分もこんな感じでポーズメニューをつくりました。
自分の知識が浅いからなのか思ったよりも複雑なった印象を受けました。
GameMakerは他の言語と違ってポリモーフィズムをする際の制約がないんですよね。
インターフェースがないのでstateごとに同名の関数の存在が保証されないです。
新しいstateを追加したときに関数の作り忘れが起きてしまったら一気にバグります。
まぁコピペすればいいだけなんですが。
ただし、新しい関数を追加したり名前や引数を変更してしまったら他のstateも全部変更が必要です。
stateが少なければこれもまぁ問題ないかもしれません。
他の言語ではVisualStudioを使っていると名前の一括変換などの機能があるので楽なんですよね。
そして、特定のステートのみの共通な処理や他のstateのデータを利用するとなったとき若干やっかいです。
無理に共通化したりすると他のstateにも影響をうけます。
ここらへんから自分は複雑になってきた印象があります。
ただ自分はまだ設計が浅いのでもしかしたらもっとちゃんと設計すればそれらもスッキリ解決できるんじゃないかと勉強中です。
stateをオブジェクトにすることでかえって複雑になる可能性があるということですかね。
遷移する度にインスタンスの破棄とインスタンス化をするのが負荷になる可能性があるという指摘も頂きました。
いろいろ考えるべきことがありますね~
別口でご回答させていただきましたが、為念こちらにも。
gameMakerは学んだことがなく知識や仕様理解は暗いですが、気になった点としてインスタンスの生成を上げさせていただきました。
もう1点、idleとwalk内で同様の処理が記述されていることも、冗長になりがちな点と、コピペミス等で不具合が発生する原因になりそうであったため、少し気になりました。
上記2点をとりあえず解決するという目的で、自分ならこうするかな? という観点でコードを記載させていただきます。
先に述べました通り、gameMakerには疎いため、文法等に不備等がありましたらご容赦ください。(withってなんやレベルでした)
あくまで考え方のひとつとして見ていただければと思います。
以下、メモ書きです。
それっぽくかんがえたこと
newしないで切り替えできるように
(ここだけ見ると構成的にo_playerに持たせてもよさそうな?)
ついでにidleとwalkで共通していた処理も1か所書けばいいようになった気がする
何かしら実行するときはこれだけ見てればいい
gameMakerで出来るかどうかは知見のなさ故に不明瞭ですが、たぶんできるやろの精神
本当はアドレス指定よりKey値で指定できる形が望ましいと思いますが、とりあえずの形で
切り替えるときはloopで60cntした時だったところを、limit変数にしてみた
limitもidleとかに持たせたのでidleは60f, walkは40fとか個別指定もできるかも?
全く別のobjからもidleやwalkが使える
移動量とかも全部インスタンス生成時に設定できるからmotionで指定するとかできそうな気がする
汎用性は低そうなのでそこまでする必要があるかどうかは不明
ソースよりコメントに時間がかかってしまった長文になってしまい申し訳ございませぬ・・・友人さん ありがとうございます。
characterに各motionインスタンスを全部持たせて、init()とloop()もそっちに移動、
motionはloopから呼ばれるactionのロジックだけにするという構造なんですね。
いろいろ勉強させていただきます。
サンプルコードから詳しい説明まで、ありがとうございます!!
何に関心(AI,メニュー遷移,etc...)を持って実装をするかで大きく構造が変わるなと思い
オブジェクトの Sprite を管理するステートマシンを実装してみました
asa さんが最初に提示した実装例から以下のようなことを考えました
そこでアニメーションの管理のみを行うステートマシンを実装しました。
以下が仕様です。
true
が返った場合、遷移条件構造体の遷移先ステート名を元にステートを現在のものから置き換え、ループを抜けるこれにより挙動とアニメーション管理を分離出来ます。
*上記仕様をもとに実装しステートマシンを使用している部分
ステート毎に終了条件(遷移条件)が違い遷移先も条件で複数変わるので、仕方なくステート内にそれらを書いていましたが、ステートをインスタンス化するときにこういう方法でfunctionを渡すことができるんですね。
あと、進行方向とアニメの向きが必ずしも一致しないというのはその通りですね。後退しながら攻撃したり、体を対象の方に向けながら歩き回ったり。
ステートを、アニメの開始から終わりまでという単位で考えていたのでステートとアニメを一体化して捉えていましたが分けたらスッキリするなら分けたいですね。
考え方とサンプルコードで勉強になります。参考にさせて頂きます。
ありがとうございます!