2024/12/20(金) [n年前の日記]
#1 [godot] ゴルフコースのモデルデータをマテリアル別に分離してみた
_昨日
に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。
今まで、ゴルフコースのモデルデータは、blender上で1つのオブジェクトとして扱っていたけれど。Godot Engine上で衝突判定をしやすくするために、マテリアル毎に別オブジェクトとして分離してみることにした。その結果、ボールがどの場所と衝突しているか、すんなり判別できるようになった。
結果画面は以下のような感じ。ボールが居る場所を判別できている。
今まで、ゴルフコースのモデルデータは、blender上で1つのオブジェクトとして扱っていたけれど。Godot Engine上で衝突判定をしやすくするために、マテリアル毎に別オブジェクトとして分離してみることにした。その結果、ボールがどの場所と衝突しているか、すんなり判別できるようになった。
結果画面は以下のような感じ。ボールが居る場所を判別できている。
◎ この方法は面倒臭い :
「分離してみた」と、さらっと書いてるけれど、実際にやってみたら、この作業が面倒臭い…。
以下のような流れで作業したのだけど…。
とにかく各作業を、マテリアルの数だけ繰り返さないといけない…。
1回だけしかこの作業をしないなら、まあ手作業でもどうにかと思えるけど。実際には、元になるコースモデルデータに何か修正を加えたら、この作業をまた最初からやり直さないといけない。こんな作業の流れでは、トライアンドエラーがやりづらい…。
もし、どうしてもこの方法でどうにかしたいなら、どこかしらを自動化しないといかん気がする。
一番最後の方法は、毎回ゲームを実行するたびに初期化処理で時間がかかってしまうから、それはちょっとどうなんだという気もする…。本来、最初から処理済み/分離済みのモデルデータを持っておけば、その初期化処理は不要になるはずで…。
以下のような流れで作業したのだけど…。
- blender上で、マテリアル毎に、別オブジェクトに「分離」する。編集モードに入って、マテリアルを選択。マテリアルリストのすぐ下にある「選択」をクリックして、そのマテリアルが割り当てられているポリゴン群を選択状態にする。Pキーを押すと出てくる「分離」メニューから「選択」を選んで、選択中のポリゴンを別オブジェクトして分離。…これをマテリアル数分、繰り返す。
- 分離して作ったオブジェクトを1つだけ選択して、Wavefront形式(.obj)で「選択中」のオブジェクトだけをエクスポート。…これをマテリアル数分、繰り返す。
- Godot Engine のプロジェクトフォルダに、複数の .obj をコピー。Godot Engine は自動でインポートして使えるようにしてくれる。
- StaticBody3D と MeshInstance3D を追加して、MeshInstance3D の Mesh に、.obj を1つだけ、ドラッグアンドドロップで指定。…これをマテリアル数分、繰り返す。
- .obj を指定した MeshInstance3D を選択して、上部メニューの「メッシュ」→「コリジョン作成」を選んで、コリジョン用ノード CollisionShape3D を生成。…これをマテリアル数分、繰り返す。
- 各 StaticBody3D に、グループ名を指定する。…これをマテリアル数分、繰り返す。
とにかく各作業を、マテリアルの数だけ繰り返さないといけない…。
1回だけしかこの作業をしないなら、まあ手作業でもどうにかと思えるけど。実際には、元になるコースモデルデータに何か修正を加えたら、この作業をまた最初からやり直さないといけない。こんな作業の流れでは、トライアンドエラーがやりづらい…。
もし、どうしてもこの方法でどうにかしたいなら、どこかしらを自動化しないといかん気がする。
- blender上で、1つのオブジェクトをマテリアル毎に別オブジェクトとして「分離」してくれるPythonスクリプトを書く。
- なおかつ、blender上で複数選択されたオブジェクト、もしくは特定の条件に合致した複数オブジェクトを、それぞれ別の .obj として一括エクスポートするPythonスクリプトを書く。
- あるいは、1つのWavefront形式(.obj)を読み込んで、マテリアル毎に別の .obj として変換/保存する Pythonスクリプトを書く。
- はたまた、Godot Engine上で、1つの表示用モデルデータ(Mesh)を元にして、マテリアル毎に、StaticBody3D、CollisionShape3D、CollisionShape3D用のMeshを生成する処理を書く。
一番最後の方法は、毎回ゲームを実行するたびに初期化処理で時間がかかってしまうから、それはちょっとどうなんだという気もする…。本来、最初から処理済み/分離済みのモデルデータを持っておけば、その初期化処理は不要になるはずで…。
◎ スクリプト内の記述 :
どの場所と衝突しているかについては、RigidBody3D にアタッチしたスクリプト内で、以下のように書いてみた。
_integrate_forces(state) という関数は、物理計算をする上で毎フレーム呼ばれる関数。普通は _process(delta) という関数の中に処理を書くけれど、物理計算が必要、かつ、_process(delta) よりもっと細かいフレーム数で確実に処理したい場合は、_integrate_forces(state) の中に処理を書けばいいらしい。
ただ、この _integrate_forces(state) は、スリープ中、もしくはフリーズ中の時には呼ばれないらしい…。上記の処理では、sleeping という変数を見て、スリープ中じゃなければ処理をすることにしているけれど、Godoy Engine側が正常に動作しているのであれば、ここで sleeping は見なくてもいいんじゃないかな…。
get_colliding_bodies() で、その時衝突している *Body3D のリストが得られる。
node.is_in_group(グループ名) で、そのノードが、指定したグループの中に含まれているかどうかを調べることができる。
linear_velocity は移動速度。angular_velocity は回転速度。今回、当たった場所に応じて、移動速度と回転速度を減衰させるようにしている。
ただ、ボールを撃ち出した瞬間にも速度が減衰されてボールが飛んでいかない場面が多々あったので、撃ち出した瞬間に is_shot という変数に一定の秒数を入れて、撃ち出してから0.n秒間は速度を一切減衰させないことにした。
var gndkind: String = "" var gndgrouplist = [ {"grpname": "ob", "vec_damp": 0.3, "ang_damp": 0.2}, {"grpname": "pond", "vec_damp": 0.1, "ang_damp": 0.1}, {"grpname": "road", "vec_damp": 0.9, "ang_damp": 0.6}, {"grpname": "bunker", "vec_damp": 0.3, "ang_damp": 0.2}, {"grpname": "rough", "vec_damp": 0.6, "ang_damp": 0.4}, {"grpname": "teeingarea", "vec_damp": 0.8, "ang_damp": 0.6}, {"grpname": "fairway", "vec_damp": 0.7, "ang_damp": 0.5}, {"grpname": "green", "vec_damp": 0.8, "ang_damp": 0.6}, {"grpname": "cup", "vec_damp": 0.9, "ang_damp": 0.9}, ] # ... func _integrate_forces(state): if not sleeping: gndkind = "" var vec_damp = 1.0 var ang_damp = 1.0 for node in get_colliding_bodies(): for grp in gndgrouplist: var grpname = grp["grpname"] if node.is_in_group(grpname): gndkind = grpname vec_damp = grp["vec_damp"] ang_damp = grp["ang_damp"] if is_shot <= 0.0: if vec_damp <= 1.0 or ang_damp <= 1.0: linear_velocity *= vec_damp angular_velocity *= ang_damp
_integrate_forces(state) という関数は、物理計算をする上で毎フレーム呼ばれる関数。普通は _process(delta) という関数の中に処理を書くけれど、物理計算が必要、かつ、_process(delta) よりもっと細かいフレーム数で確実に処理したい場合は、_integrate_forces(state) の中に処理を書けばいいらしい。
ただ、この _integrate_forces(state) は、スリープ中、もしくはフリーズ中の時には呼ばれないらしい…。上記の処理では、sleeping という変数を見て、スリープ中じゃなければ処理をすることにしているけれど、Godoy Engine側が正常に動作しているのであれば、ここで sleeping は見なくてもいいんじゃないかな…。
get_colliding_bodies() で、その時衝突している *Body3D のリストが得られる。
node.is_in_group(グループ名) で、そのノードが、指定したグループの中に含まれているかどうかを調べることができる。
linear_velocity は移動速度。angular_velocity は回転速度。今回、当たった場所に応じて、移動速度と回転速度を減衰させるようにしている。
ただ、ボールを撃ち出した瞬間にも速度が減衰されてボールが飛んでいかない場面が多々あったので、撃ち出した瞬間に is_shot という変数に一定の秒数を入れて、撃ち出してから0.n秒間は速度を一切減衰させないことにした。
◎ 場所の種類 :
必要になりそうな場所の種類としては、以下になるのだろうか…。
いや、以下も必要かもしれない。
おそらく、旗包み? なる状態もあるんだろうな…。おそらく、ボシュッと音がして、速度がゼロになるはず。
ピン(旗竿)にボールが当たって跳ね返る状態もあるだろう…。カツーンと跳ね返りそうなイメージだけど、実際はどうなんだろう。
木の幹に当たった時も、カツーンと跳ね返りそうな気がする。ただ、木の葉や木の枝に当たった時は、そこで速度が減速するのではないか。たぶん。
カップインかどうかを判別するために、カップの底も種類として必要かなと思ったけれど、ボールが停止状態になった時にカップの中にあるかどうかを判別すれば、カップインかどうかの判別はできるかな…。たぶん。
cup (カップ) green (グリーン) fairway (フェアウェイ) teeingarea (ティーイングエリア) rough (ラフ) bunker (バンカー) road (道路) pond (池、川) ob (OB、アウトオブバウンズ)
いや、以下も必要かもしれない。
flag (旗) flagstick (旗の竿) tree or trunk (木、木の幹) leaves or brunch (木の葉、木の枝)
おそらく、旗包み? なる状態もあるんだろうな…。おそらく、ボシュッと音がして、速度がゼロになるはず。
ピン(旗竿)にボールが当たって跳ね返る状態もあるだろう…。カツーンと跳ね返りそうなイメージだけど、実際はどうなんだろう。
木の幹に当たった時も、カツーンと跳ね返りそうな気がする。ただ、木の葉や木の枝に当たった時は、そこで速度が減速するのではないか。たぶん。
カップインかどうかを判別するために、カップの底も種類として必要かなと思ったけれど、ボールが停止状態になった時にカップの中にあるかどうかを判別すれば、カップインかどうかの判別はできるかな…。たぶん。
◎ 各アイテム大きさと重さ :
一応メモ。ゴルフボールの大きさと重さは以下。
カップの大きさ。
旗竿(ピン)の長さ。
blender上で、このあたりを意識しながらコースのモデリングをしないといけない。
ただ、あくまでゲームなので、ボールが見えやすいように大きくしちゃっても良さそうな気もする。2倍〜10倍のサイズでもいいんじゃないか…? もちろん、カップやピンの大きさも、ボールに合わせて大きくしないと…。
以下、参考ページ。
_ゴルフボールの基礎知識 | ボール | DUNLOP GOLFING WORLD
_ゴルフボールの大きさ知っていますか? | インドアゴルフ業界で店舗数No1,STEPGOLF
_なぜゴルフのカップはあの大きさなのか | インドアゴルフ業界で店舗数No1,STEPGOLF
_ゴルフのカップ(ホール)の直径はなぜ108ミリなのか | Honda GOLF | Honda公式サイト
_ゴルフのピン(旗竿)の長さは何メートル?ワンピンについても解説(季節・暮らしの話題 2022年09月10日) - 日本気象協会 tenki.jp
_<ゴルフ英語-その1->なぜ「旗竿」を「ピン」と呼ぶの? | Gridge[グリッジ]?ゴルフの楽しさをすべての人に!
_“旗竿"のことを「ピン」と呼ぶのはなぜ? 【明日使えるゴルフ用語】 - Myゴルフダイジェスト
_なぜ旗竿はピンと呼ぶの? - ゴルフスクールナビ
- 重さ : 45.93 g (0.04593 kg)
- 直径 : 42.67 mm (0.04267 m)
カップの大きさ。
- 直径 : 108 mm (0.108 m)
- 深さ : 101.6 mm (0.1016 m)
- 地表からの深さ : 25.4 mm (0.0254 m) より下であること。
旗竿(ピン)の長さ。
- 長さ : 2.13 〜2.43 m
blender上で、このあたりを意識しながらコースのモデリングをしないといけない。
ただ、あくまでゲームなので、ボールが見えやすいように大きくしちゃっても良さそうな気もする。2倍〜10倍のサイズでもいいんじゃないか…? もちろん、カップやピンの大きさも、ボールに合わせて大きくしないと…。
以下、参考ページ。
_ゴルフボールの基礎知識 | ボール | DUNLOP GOLFING WORLD
_ゴルフボールの大きさ知っていますか? | インドアゴルフ業界で店舗数No1,STEPGOLF
_なぜゴルフのカップはあの大きさなのか | インドアゴルフ業界で店舗数No1,STEPGOLF
_ゴルフのカップ(ホール)の直径はなぜ108ミリなのか | Honda GOLF | Honda公式サイト
_ゴルフのピン(旗竿)の長さは何メートル?ワンピンについても解説(季節・暮らしの話題 2022年09月10日) - 日本気象協会 tenki.jp
_<ゴルフ英語-その1->なぜ「旗竿」を「ピン」と呼ぶの? | Gridge[グリッジ]?ゴルフの楽しさをすべての人に!
_“旗竿"のことを「ピン」と呼ぶのはなぜ? 【明日使えるゴルフ用語】 - Myゴルフダイジェスト
_なぜ旗竿はピンと呼ぶの? - ゴルフスクールナビ
[ ツッコむ ]
以上です。