【Godot】Assset Libraryにアセットをリリースしてみた話
Godot Advent Calendar 2023の15日目の記事です。
リリースしたもの
FloatableBodyという水中を物体がそれっぽく浮き上がる簡易的な物理システムです。
MITライセンスで無料です!
Asset Libraryのリンク
日本語ドキュメント
デモ
Godot1週間ゲームジャム向けにミニゲームを作りました。 godotplayer.com
#Godot1week #GodotEngine pic.twitter.com/EpSLSsyIan
— うえした (@ueshita) 2023年11月5日
Asset Libraryでリリースする
この記事で解説するまでもなく、公式ドキュメントが一番詳しいです。 docs.godotengine.org
英語を書く必要はありますが、得意でなければDeepLでなんとかします。
一応審査はあるようです。提出して1~2日で公開されました。
皆さんも便利なものができたら、ぜひリリースしてみましょう。
【Godot】Godot 4.2のコンピュートシェーダで遊ぼう
Godot Advent Calendar 2023の6日目の記事です。
Godot Engine 4.2がリリースされましたね。めでたい!🎉
2023年は4.0から始まり、3回の大型アップデートがありました。
さて4.2アップデート内容に気になる項目はありましたか?
個人的には沢山ありますが、中でもコンピュートシェーダのところが目を引いたので解説していきたいと思います。
コンピュートシェーダとは名前の通り計算するシェーダです。GPUで処理するので単純で大量の計算をするような用途が得意です。 例えば万単位の大量のオブジェクトを動かしたり、物理シミュレーション、大きい画像の解析処理などに使われます。
コンピュートシェーダの強化
まず経緯を説明します。 Godot 4.0からコンピュートシェーダは使えるようになったのですが、これをリアルタイムレンダリングで活用するにはイマイチでした。 大量のオブジェクトを更新したり、水面の波動を計算してレンダリングに反映させることが難しかったのです。
Godot 4.2より前
Godot 4.2より前のコンピュートシェーダの使用手順は次の通り。
レンダリングではない用途や事前処理系ならこれでも問題ないのですが、コンピュートシェーダの結果を利用してリアルタイムレンダリングに活用しようとすると、RenderingServerへデータを転送する必要があります。これではCPUとGPU間でデータが毎フレーム行ったり来たりするという無駄が発生してしまい、せっかくのパフォーマンスが落ちてしまいますね。
Godot 4.2以降
Godot 4.2ではコンピュートシェーダについて、新しく次の3つのことができるようになりました。
他のエンジンだと普通にできていることだったりするので若干いまさら感ありますが、これでGodotでもまともにリアルタイムレンダリング用途でコンピュートシェーダを使っていけるようになったということです。
コンピュートシェーダの使い方
基本的にはこちらに書いてあります。(英語)
…が、ここに書かれている方法は従来の使い方で、コンピュートシェーダの出力をレンダリングで活用することはできません。
2023年12月現在、公式ドキュメントは追いついていなさそうです。そこでBastiaanOlij氏が作った公式のデモを見てみましょう。
実行してみます。画面をクリックすると波が発生して広がっていくデモでした。
デモの中身を見てみる
こんな感じ(エディタ上で波が起きている…)
デモのコードを見る前に、水面を波が伝わるやり方について簡単に解説します。下の図のように特定の地点の水面の高さをグリッド的に配置しておき、毎フレーム隣接している水面の高さに影響を与えていけばそれっぽい挙動になります。
さてコードはres://water_plane/water_plane.gd
なので見てみましょう。コメントは意訳しています。
コンピュートシェーダとバッファの初期化
まず_ready()を見てみます。
var texture : Texture2DRD var next_texture : int = 0 func _ready(): # レンダリングスレッドで`_initialize_compute_code`を呼び出す RenderingServer.call_on_render_thread(_initialize_compute_code.bind(texture_size)) # 子のMeshInstance3Dノードからマテリアルを取得 var material : ShaderMaterial = $MeshInstance3D.material_override if material: # マテリアルにテクスチャサイズを指定 material.set_shader_parameter("effect_texture_size", texture_size) # マテリアルからTexture2DRDを取得 texture = material.get_shader_parameter("effect_texture")
RenderingServer.call_on_render_thread
はGodot 4.2から使用できる新しいAPIです。
コンピュートシェーダの初期化処理はレンダリングスレッドで行わせるため、このAPIで_initialize_compute_code
を登録しています。
また、マテリアルからTexture2DRD
を取得しています。ここはあらかじめインスペクタからTexture2DRDが指定されていたものです。別にTexture2DRD.new()
で新しく作ってmaterial.set_shader_parameter
でもいいと思います。
次に_initialize_compute_code
を含むコンピュートシェーダの初期化周りのコードを見てみます。
var rd : RenderingDevice var shader : RID # コンピュートシェーダ var pipeline : RID # コンピュートパイプライン # 波の高さを表すバッファとして使うテクスチャ # - 1つは現在レンダリングするフレームで使われる # - 1つは前回レンダリングされたフレームで使われたもの # - 1つは前々回レンダリングのフレームで使われたもの var texture_rds : Array = [ RID(), RID(), RID() ] var texture_sets : Array = [ RID(), RID(), RID() ] func _create_uniform_set(texture_rd : RID) -> RID: var uniform := RDUniform.new() uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE uniform.binding = 0 uniform.add_id(texture_rd) # Even though we're using 3 sets, they are identical, so we're kinda cheating. return rd.uniform_set_create([uniform], shader, 0) func _initialize_compute_code(init_with_texture_size): # レンダリングフレーム中にレンダリングスレッドから呼ばれる # RenderingServerからRenderingDeviceを借りてくる rd = RenderingServer.get_rendering_device() # コンピュートシェーダを作成 var shader_file = load("res://water_plane/water_compute.glsl") var shader_spirv: RDShaderSPIRV = shader_file.get_spirv() shader = rd.shader_create_from_spirv(shader_spirv) pipeline = rd.compute_pipeline_create(shader) # 波の高さを表すバッファのテクスチャを作成 var tf : RDTextureFormat = RDTextureFormat.new() tf.format = RenderingDevice.DATA_FORMAT_R32_SFLOAT tf.texture_type = RenderingDevice.TEXTURE_TYPE_2D tf.width = init_with_texture_size.x tf.height = init_with_texture_size.y tf.depth = 1 tf.array_layers = 1 tf.mipmaps = 1 tf.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_COPY_TO_BIT for i in range(3): # RDテクスチャを作成してクリア texture_rds[i] = rd.texture_create(tf, RDTextureView.new(), []) rd.texture_clear(texture_rds[i], Color(0, 0, 0, 0), 0, 1, 0, 1) # テクスチャを参照するUniformSetを作成 texture_sets[i] = _create_uniform_set(texture_rds[i])
コンピュートシェーダ
本体となるコンピュートシェーダ(water_plane.glsl
)のコードがこちらです。
#[compute] #version 450 // 8x8単位で処理する layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; // 現在のバッファ、前回のバッファ、出力用のバッファ layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D current_image; layout(r32f, set = 1, binding = 0) uniform restrict readonly image2D previous_image; layout(r32f, set = 2, binding = 0) uniform restrict writeonly image2D output_image; // 制御用の情報 layout(push_constant, std430) uniform Params { vec4 add_wave_point; vec2 texture_size; float damp; float res2; } params; void main() { ivec2 tl = ivec2(0, 0); ivec2 size = ivec2(params.texture_size.x - 1, params.texture_size.y - 1); ivec2 uv = ivec2(gl_GlobalInvocationID.xy); // 領域外の読み書きをしないようにする処理 if ((uv.x > size.x) || (uv.y > size.y)) { return; } // バッファから読み込む float current_v = imageLoad(current_image, uv).r; float up_v = imageLoad(current_image, clamp(uv - ivec2(0, 1), tl, size)).r; float down_v = imageLoad(current_image, clamp(uv + ivec2(0, 1), tl, size)).r; float left_v = imageLoad(current_image, clamp(uv - ivec2(1, 0), tl, size)).r; float right_v = imageLoad(current_image, clamp(uv + ivec2(1, 0), tl, size)).r; float previous_v = imageLoad(previous_image, uv).r; // 前回の波の高さと周囲の波の高さから、新しい波の高さを計算する float new_v = 2.0 * current_v - previous_v + 0.25 * (up_v + down_v + left_v + right_v - 4.0 * current_v); new_v = new_v - (params.damp * new_v * 0.001); // 新しい衝撃を加える if (params.add_wave_point.z > 0.0 && uv.x == floor(params.add_wave_point.x) && uv.y == floor(params.add_wave_point.y)) { new_v = params.add_wave_point.z; } if (new_v < 0.0) { new_v = 0.0; } vec4 result = vec4(new_v, new_v, new_v, 1.0); // 結果をバッファに書き込む imageStore(output_image, uv, result); }
コンピュートシェーダの拡張子はglslです。なぜか現時点でGodotのエディタでは開くことができないです。(ナンデ?) コンパイルが成功したということは教えてくれますが、編集するにはVSCodeなど別のエディタを使いましょう。
コンピュート処理の実行とバッファの更新
さて実際にコンピュートシェーダを実行するコード周りを見ていきましょう。
func _process(delta): # 今回使用するテクスチャの番号を更新する next_texture = (next_texture + 1) % 3 # 描画に使用するテクスチャを更新する # `_initialize_compute_code` はまだ実行されていないかもしれないので、最初のフレームは空のRIDになることに注意 if texture: texture.texture_rd_rid = texture_rds[next_texture] # `render_process`をレンダリングスレッドで実行する # `_render_process`は並列に実行される可能性があるため、必要なパラメータを引数として渡しています RenderingServer.call_on_render_thread(_render_process.bind(next_texture, add_wave_point, texture_size, damp))
_render_process
はおそらく描画前にレンダリングスレッドで呼ばれるため、描画と同期してコンピュートシェーダは実行されます。
func _render_process(with_next_texture, wave_point, tex_size, damp): # シェーダ内で使用している構造体を作る var push_constant : PackedFloat32Array = PackedFloat32Array() push_constant.push_back(wave_point.x) push_constant.push_back(wave_point.y) push_constant.push_back(wave_point.z) push_constant.push_back(wave_point.w) push_constant.push_back(tex_size.x) push_constant.push_back(tex_size.y) push_constant.push_back(damp) push_constant.push_back(0.0) # Dispatchグループのサイズを計算 # シェーダ内でlocal_size_x,local_size_yの数値で割る var x_groups = (tex_size.x - 1) / 8 + 1 var y_groups = (tex_size.y - 1) / 8 + 1 var next_set = texture_sets[with_next_texture] var current_set = texture_sets[(with_next_texture - 1) % 3] var previous_set = texture_sets[(with_next_texture - 2) % 3] # シェーダ内部で使用するUniformSetを指定してコンピュートシェーダを実行 var compute_list := rd.compute_list_begin() rd.compute_list_bind_compute_pipeline(compute_list, pipeline) rd.compute_list_bind_uniform_set(compute_list, current_set, 0) rd.compute_list_bind_uniform_set(compute_list, previous_set, 1) rd.compute_list_bind_uniform_set(compute_list, next_set, 2) rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4) rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1) rd.compute_list_end() # 今回は同期のためのバリアは必要ない(Godotが描画で使う際に自動的に内部のバリアが使われるため) # ただし、コンピュートシェーダーの出力を別のコンピュートシェーダーの入力として使いたい場合は、バリアを追加する必要がある #rd.barrier(RenderingDevice.BARRIER_MASK_COMPUTE)
コンピュートシェーダとバッファの破棄
コンピュートシェーダのリソースの破棄は手動で行う必要があります。そしてまたレンダリングスレッドで破棄する必要があります。少し面倒ですね。
func _exit_tree(): # 描画用のテクスチャに無効なRIDを指定して参照されないようにする if texture: texture.texture_rd_rid = RID() # レンダリングスレッドで`_free_compute_resources`を呼び出す RenderingServer.call_on_render_thread(_free_compute_resources) func _free_compute_resources(): # バッファ用テクスチャを破棄 (UniformSetは自動的に解放されるらしい) for i in range(3): if texture_rds[i]: rd.free_rid(texture_rds[i]) # コンピュートシェーダを破棄 (Pipelineは自動的に解放されるらしい) if shader: rd.free_rid(shader)
コンピュート結果をレンダリングで使う
コンピュートシェーダで計算した波の高さを水面のレンダリングで活用します。
シェーダソースはwater_shader.gdshader
です。これは普通のGodotシェーダですね。
shader_type spatial; render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx; uniform vec3 albedo : source_color; uniform float metalic : hint_range(0.0, 1.0, 0.1) = 0.8; uniform float roughness : hint_range(0.0, 1.0, 0.1) = 0.2; uniform sampler2D effect_texture; // コンピュート結果のバッファをテクスチャとして使う uniform vec2 effect_texture_size; // コンピュート結果のバッファのテクスチャサイズ varying vec2 uv_tangent; varying vec2 uv_binormal; void vertex() { vec2 pixel_size = vec2(1.0, 1.0) / effect_texture_size; uv_tangent = UV + vec2(pixel_size.x, 0.0); uv_binormal = UV + vec2(0.0, pixel_size.y); } void fragment() { // その場所の波の高さと、隣接の波の高さを読み込む float f1 = texture(effect_texture, UV).r; float f2 = texture(effect_texture, uv_tangent).r; float f3 = texture(effect_texture, uv_binormal).r; // 波の高さを水面の傾きとして法線を計算(ライティングはGodotにおまかせ) vec3 tangent = normalize(vec3(1.0, 0.0, f2 - f1)); vec3 binormal = normalize(vec3(0.0, 1.0, f3 - f1)); NORMAL_MAP = normalize(cross(binormal, tangent)) * 0.5 + 0.5; ALBEDO = albedo.rgb; METALLIC = metalic; ROUGHNESS = roughness; SPECULAR = 0.5; }
まとめ
Godot 4.2以降のコンピュート処理の結果をレンダリングで使う方法のまとめです
- コンピュート関連の初期化、更新処理、終了処理は
RenderingServer.call_on_render_thread
でレンダリングスレッドから実行する - コンピュート処理を行うためのRenderingDeviceは
RenderingServer.get_rendering_device()
から取得する - コンピュート結果のテクスチャは
Texture2DRD
のtexture_rd_rid
にセットする
草を靡かせてみる
ここからは応用編です。
こちらの記事を参考に。Godotで似たようなことをしてみましょう。
完成したものがこちら
GitHubでGodotプロジェクト一式を公開しています。
完成物を少し解説
草の動き(コンピュート処理)
基本的に波を更新する部分は元と同じですが、そのままだと草がプルプルしすぎてキモイのでもう少し自然に動くように調整しています。
- 動きの加速度を調整して草がゆっくりと動くようにする
- 草の動きはそこまで広がらないので減衰しやすくする
- プレイヤーのコライダーで継続的に力が加わるようにする
草のレンダリング
草1本のベースメッシュを生成して、MultiMeshのインスタンスを量産するコードは次の通りです。
func _initialize_mesh() -> void: # 草1本を表現する三角錐の細長いメッシュを作る var st = SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) for i in range(3): var t1 := i * PI * 2 / 3 var t2 := (i + 1) * PI * 2 / 3 var v0 := Vector3(0.0, 1.0, 0.0) var v1 := Vector3(cos(t1), 0.0, sin(t1)) * 0.04 var v2 := Vector3(cos(t2), 0.0, sin(t2)) * 0.04 st.set_normal((v2 - v0).normalized().cross((v1 - v0).normalized())) st.add_vertex(v0) st.add_vertex(v1) st.add_vertex(v2) instance.multimesh.mesh = st.commit() # Area3D内を埋め尽くす草のインスタンスを作成する var box_shape: BoxShape3D = shape var area_size := box_shape.size var area_step := Vector3(area_size.x / count_x, 0.0, area_size.z / count_z) var area_offset := -area_size / 2 + Vector3(area_step.x * 0.5, 0.0, area_step.z * 0.5) instance.multimesh.instance_count = count_x * count_z for z in range(count_z): for x in range(count_z): var index := x + z * count_x # 草1本のトランスフォームを作成 var xform := Transform3D() # 草が生える位置の中心を計算 xform.origin = area_offset + Vector3(x * area_size.x / count_x, 0.0, z * area_size.z / count_z) # ランダムで草が生える位置を散らす xform.origin += area_step * Vector3(randf_range(-0.5, 0.5), 0.5, randf_range(-0.5, 0.5)) # ランダムで草の長さを散らす xform.basis = xform.basis.scaled(Vector3.ONE * randf_range(0.5, 0.8)) # ランダムで草が生える角度を散らす xform.basis *= Basis.from_euler(Vector3(randf_range(-PI/16, PI/16), randf_range(-PI, PI), randf_range(-PI/16, PI/16)), EULER_ORDER_YXZ) # インスタンスに作成したトランスフォームを指定 instance.multimesh.set_instance_transform(index, xform)
まとめ
コンピュートシェーダ楽しい ✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
草の実装について、YouTubeを探すと詳しい動画がいくつか見つかります。 www.youtube.com
【Godot】EffekseerプラグインをGodot4に対応しました【Effekseer】
はじめに
リリース自体は結構前に行っていたのですが、いろいろ忙しくて告知をしていないことに気づきまして…。 改めて告知したいと思います。
Effekseerって?
キラキラしたイケてるエフェクトを割と簡単に作れるツールです。
現在のメジャーバージョンは1.7です。
新機能のトリガーは再生中のエフェクトに変化を加えられるのでオススメです。
Godot4向けプラグイン(アドオン)
ダウンロードページ
次のURLからダウンロードできます。
https://github.com/effekseer/EffekseerForGodot4/releases/
プラグインの使い方
https://effekseer.github.io/Help_Godot/v4/ja/index.html を見てくださいね。
バグを見つけたら
GitHubでIssue報告をお願いします。🙇
Godot4で変わったところ
- 【3D】高度描画パネルが使えるようになりました。Godot3では難しかった機能です!
- 【3D】モデルの描画にインスタンシングを行うようになりました。高速化です!
- 【3D/2D】エディタビューポートでのプレビュー機能を追加しました。
プラグイン開発
EffekseerForGodot4のリポジトリはこちら https://github.com/effekseer/EffekseerForGodot4
Godot3ではGDNativeでしたが、Godot4はGDExtensionという方法に変わりました。 そのうち詳しく解説したいと思います。
レンダリングの仕組みなどは基本的にはGodot3版と同じです(細かいところは少し違います) ueshita.hatenablog.jp
【Godot】Godot4のGDScriptはどれくらい速くなったのか?
Godot Engine Advent Calendar 2022 7日目の記事になります。
結論
Godot4はGodot3と比較して、GDScriptはだいぶ速くなりました。
解説
参考URL
だいたいこちらに書かれています。
https://godotengine.org/article/gdscript-progress-report-typed-instructions
要約
上記の記事を要約しますと、GDScriptの実行エンジンに型付けされた命令を実装されましたということです。
次の処理内容が高速化されたとのことです。
高速化内容 | 高速化率 |
---|---|
計算命令 (+や&など) | 約25-50% |
配列の添え字(array[index])アクセス | 約5-7% |
組込型の関数(array.resize(n)等)呼び出し | 約70% |
ネイティブクラスの関数の呼び出し | 約120-150% |
組込み関数(absやrandf等)の呼び出し | 約25〜50% |
イテレーション(for-inループ) | 約10~50% |
詳細な解説
GDScriptエンジンに型付けされた命令が実装されると何が嬉しいかを解説します。
例えばこのようなGDScriptコードがあったとします。
func add(a: int, b: int) -> int: return a + b
変数(引数)a
と'b'はint型と明示されていますが、Godot3ではコンパイル時にチェックが走るだけで、実行時は動的型の変数として処理されていました。
Godot4ではこれらが実行時もint型の変数として確定した前提で処理されるようになります。
上記では+演算子の処理内容が次のように異なってきます。
Godot3 | Godot4 |
---|---|
|
|
実行時に型チェックが不要になるので、これは速くなりそうですね!
また関数の呼び出し側も明示的に型付けされていれば、引数の型チェックが不要になるためこれも速くなるとのことです。
実測
本当に速くなったのか、実際に計測してみました。 (参考記事も2年前ですし…)
計測環境
計測マシン
使用エンジン
計測結果
種類 | Godot3.5.1 | Godot4.0.beta7 | 高速化率 |
---|---|---|---|
int演算 | 52580 | 30462 | 72.608 |
float演算 | 48105 | 27786 | 73.127 |
配列アクセス | 51241 | 42845 | 19.596 |
組込み関数 | 130234 | 99035 | 31.503 |
組込み型の関数 | 78387 | 54279 | 44.415 |
ネイティブクラスの関数 | 169966 | 25432 | 568.316 |
スクリプトの関数 | 308276 | 129272 | 138.471 |
スクリプトクラスの関数 | 206974 | 95454 | 116.831 |
フィボナッチ数列の生成 | 1323376 | 633049 | 109.048 |
ネイティブクラスの関数呼び出しがえぐいくらい速くなっていることを除いて、前述の高速化内容と概ね一致してそうです。
計測コード
https://gist.github.com/ueshita/7d60a47574113c8e5c1b262f36726a9a
追記 (2023/1/13)
Godot4.0.beta11がリリースされました。 アップデート内容にGDScript関連の多くの改善と最適化が入ったようです。
Many improvements, fixes, and optimizations have been done to GDScript and its runtime environment (GH-62688, GH-64253, GH-69590, GH-69991, GH-70246, GH-70464, GH-70613, GH-70655, GH-70658, GH-70702, GH-70838, GH-70859, GH-71051, GH-71107, and more).
というわけで、同条件でベンチマークを取り直してみました。
種類 | Godot4.0.beta7 | Godot4.0.beta11 | 高速化率 |
---|---|---|---|
int演算 | 30462 | 23086 | 131.95 |
float演算 | 27786 | 17040 | 163.06 |
配列アクセス | 42845 | 39787 | 107.69 |
組込み関数 | 99035 | 81489 | 121.53 |
組込み型の関数 | 54279 | 53841 | 100.81 |
ネイティブクラスの関数 | 25432 | 23369 | 108.83 |
スクリプトの関数 | 129272 | 130316 | 99.20 |
スクリプトクラスの関数 | 206974 | 92584 | 103.10 |
フィボナッチ数列の生成 | 1323376 | 661419 | 95.71 |
【Godot】エンジンをデバッグビルドしてみよう
Godot Engine Advent Calendar 2022 3日目の記事になります。
はじめに
Godot Engineはオープンソースで開発されているため、GitHubに行けばソースコードにアクセスし放題です。 そこでソースコードを眺めているのも楽しいですが、ちょっといじってみたいと思うときもあると思います。
例えば次のような理由です。
- Godot Engineのお気持ちをもっと知りたい
- Godot Engineのバグを調査したい
- Godot Engineの開発に貢献したい
これらを行うには、エンジンをビルドしてデバッグ実行できるようにする必要がありますね。
本記事はGodot Engineのデバッグビルドをして、デバッグ実行するところまで紹介したいと思います。
基本的には公式ドキュメントに書かれています。より詳しく知りたい時はそちらを参照してもらえればと思います。
環境準備
今回はWindowsでVisual Studioを使うことを前提とします。
エンジンをビルドするには次のソフトのインストールが必要です。
- Git
- Visual Studio
- Python
- SCons
インストール済みの場合はスキップしてください。
ある程度の経験者向けのザックリ解説になっているため、不明点などありましたら逐次ググって対処するようお願いします。
Gitのインストール
GitはGitHubからソースコードを入手するために使用します。 今回はCUIで説明するため、Git for Windowsをインストールします。 GUIツールが良ければ、ForkやSourceTreeでも構いません。
Visual Studioのインストール
もし仕事でしたら会社にVisual Studio Professionalを用意してもらいましょう。
個人なら無料でVisual Studio Communityをインストールして使うことができます。
https://visualstudio.microsoft.com/ja/vs/
Pythonのインストール
Godot EngineのビルドするためにSConsと呼ばれるビルドツールが必要です。 SConsをインストールする前にPythonをインストールする必要があります。
インストーラでインストールすると、pythonへのPATHがセットされて、どこでもpython
コマンドが使えるようになると思います。
python
Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
Sconsのインストール
Pythonをインストールすると付いてくるpip
コマンドを使用してSCons
をインストールします。
コマンドプロンプトでインストールを行います。
pip install scons
上手くいくと次のメッセージが出力されると思います。
Collecting scons Using cached SCons-4.4.0-py3-none-any.whl (4.2 MB) Requirement already satisfied: setuptools in c:\devel\python\lib\site-packages (from scons) (65.5.0) Installing collected packages: scons Successfully installed scons-4.4.0
Godotのビルド
ビルド手順はGodot3系とGodot4系で微妙に違うため、それぞれ解説したいと思います。
Godot3編
ソースコードを入手する
現在Godot3系は3.xブランチで開発されています。
そのままgit clone
するとGodot4系のmasterブランチが取れてしまいます。(後でブランチを切り替えればいい話ではありますが…)
そこで今回は、3.xブランチを指定してcloneします。
git clone https://github.com/godotengine/godot.git -b 3.x
また、3.5ブランチ、3.4ブランチなども存在しているので、既存のエンジンバージョンに合わせてソースコードを取得することもできます。
エンジンをデバッグビルドする
git cloneして作成されたgodot
フォルダに移動してscons
コマンドでビルドします。
cd godot scons platform=windows vsproj=yes
vsproj=yesを追加するとVisual Studioプロジェクトファイルを作ってくれるため、ぜひ付けましょう。
上手くいけばgodot/bin
フォルダ内にgodot.windows.tools.64.exe
が出力されていると思います。
Godot4編
ソースコードを入手する
現在Godot4系はmasterブランチで開発されています。
そのままgit clone
すればOKです。
git clone https://github.com/godotengine/godot.git
エンジンをデバッグビルドする
Godot3版と違うところは、デバッグビルドすることを示すdev_build=yes
を付けることです。
cd godot scons platform=windows vsproj=yes dev_build=yes
vsproj=yesを追加するとVisual Studioプロジェクトファイルを作ってくれるため、ぜひ付けましょう。
上手くいけばgodot/bin
フォルダ内にgodot.windows.editor.dev.x86_64.exe
が出力されていると思います。
Godotのデバッグ実行
vsproj=yes
を付けてscons
すると、godot.sln
が作成されています。
こちらをVisual Studioで開いてみましょう。
この状態でデバッグ実行することはできますが、プロジェクトランチャーが起動するだけで、プロジェクトを開いているときのエディタやゲーム実行時のデバッグはできません。(プロジェクトを開いたりゲーム実行するのは別プロセスになるため)
それらを行うにはデバッグオプションのコマンド引数を設定する必要があります。
まずソリューションエクスプローラのgodotを右クリック → プロパティ で次のダイアログが開きます。 コマンド引数のところを変更します。
エディタをデバッグ実行する
プロジェクトを開いたエディタをデバッグするには、次のコマンド引数を設定します。
-e D:\MyProj\project.godot
エディタがデバッグ実行できています。
Visual Studioからブレークポイントを貼ってブレークできることを確認します。
ゲームをデバッグ実行する
プロジェクトのゲーム実行しながらエンジンをデバッグするには、次のコマンド引数を設定します。
--path D:\MyProj
※project.godot
は付けません。
メインシーンが実行されます。
任意のシーン実行したい時は、次のコマンド引数を指定します。
--path D:\MyProj scene.tscn
その他オプションを使うときは、こちらを参照してください。
まとめ
今回は次の内容を紹介しました。
Godot Engineはオープンソースで活発に開発が進められている、現在勢いのあるゲームエンジンです。
比較的簡単にソースコードを見ていじれる。手元で動かして試せるというエンジンです。
バグを直してGitHubでPull Requestすることもできれば、機能追加して提案することもできます。 もし開発者にMergeされればそれはエンジンの一部になり、あなたはコントリビュータの一員になります。
そんな一歩に繋がる?かもしれない記事でした。
【Godot】ノイズテクスチャから毒の沼を作る
やること
こちらの動画のテクニックをGodotで実装したいと思います。
Illegal Noise Texture Hacks #gamedev #shader #VFX pic.twitter.com/398aioENGr
— Sam @13 (@Sam_Makes_Games) 2020年9月26日
手順
ノイズテクスチャを用意
動画ではPhotoshopの雲模様フィルタでノイズテクスチャを作成するとありますが、Godotにはノイズテクスチャを生成する機能があります。超便利!
ファイルシステム → フォルダ → 新規リソース から
インスペクターからノイズテクスチャにOpenSimplexNoiseをセットします。 Seamlessにもチェックを入れておきます。
スプライトにノイズテクスチャを設定してみました。
シェーダを書く
shader_type canvas_item; render_mode unshaded; uniform sampler2D gradient; uniform vec2 size = vec2(512, 512); void fragment() { vec2 uv = UV * size / vec2(512, 512); float t0 = texture(TEXTURE, uv).r; float t1 = texture(TEXTURE, uv - vec2(TIME * 0.025)).r; float t2 = texture(TEXTURE, uv + vec2(TIME * 0.025)).r; float t12 = (t1 + t2); float offset = (t0 + t12) * 0.5; float t3 = texture(TEXTURE, vec2(offset)).r; float contrast = t3 * t12; vec3 color = texture(gradient, vec2(contrast, 0.0)).rgb; COLOR = vec4(color, 1.0); }
スプライトにマテリアルをセットする
SpriteのMaterialにShaderMaterialをセットし、作成したシェーダをセットします。 これだけでは真っ暗なのでShader Paramを開き、GradientにGradientTextureをセット、さらに内部のGradientにグラデーションパラメータをセットします。
結果
こんな感じになりました。毒々しい!
3D対応
3Dもいけます。
シェーダ
Spatialシェーダ版です。 ライティングをONにしています。
shader_type spatial; uniform sampler2D noise; uniform sampler2D gradient; uniform vec2 size = vec2(512, 512); void fragment() { vec2 uv = UV * size / vec2(512, 512); float t0 = texture(noise, uv).r; float t1 = texture(noise, uv - vec2(TIME * 0.025)).r; float t2 = texture(noise, uv + vec2(TIME * 0.025)).r; float t12 = (t1 + t2); float offset = (t0 + t12) * 0.5; float t3 = texture(noise, vec2(offset)).r; float contrast = t3 * t12; ALBEDO = texture(gradient, vec2(contrast, 0.0)).rgb; }
まとめ
シェーダ楽しい✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
【Godot】Editor拡張する (Inspector Plugin編)
みなさんEditor拡張沼に浸かっていますか?
ゲームエンジンと言ったらEditor拡張ですよね。
Godot Engineも例に漏れず、強力なEditor拡張が行えます。
↓こんなこともできました。
GodotのEditor拡張。InspectorPluginでEffekseerのプレビューをインスペクタで出来るようにしてみた。Effekseer開いて編集も楽ちん #GodotEngine pic.twitter.com/GEwfak52Qy
— うえした (@ueshita) 2021年10月1日
今回は↑のようなインスペクタ拡張を行う方法を紹介したいと思います。
公式の参考ページ
キャラを増やすボタンをインスペクタに置く
ボタンを押してキャラを増やすシンプルなEditor拡張を行います。
1. プラグインの体裁を整える
インスペクタを拡張するのでInspector Pluginを作成します。
Inspector PluginをGodotエディタで実行するためには、Godotプラグインの体裁を整える必要があります。
今回はMyPlugin
という名前で作りました。
まずプロジェクトルートから次のディレクトリを掘ります。
- res://addons/MyPlugin/
ここに次のファイルを追加します。
- plugin.cfg
[plugin] name="MyPlugin" description="A fantastic editor plugin." author="" version="1.0.0" script="plugin.gd"
- plugin.gd
tool extends EditorPlugin func _enter_tree(): pass func _exit_tree(): pass
そうすると、プロジェクト設定のプラグイン一覧に追加されるので 有効 にチェックを入れます。
2. Inspector Pluginを作成する
Inspector Pluginのスクリプトを書きます。
今回はインスペクタにカスタムコントロールとしてボタンを追加して、ボタンが押されたらキャラをランダムに配置するようにしました。
- MyInspectorPlugin.gd
tool extends EditorInspectorPlugin class_name MyInspectorPlugin var target: MyCharactor # 選択されたノード、ファイルがこのスクリプトの対象かどうか判定する func can_handle(object: Object) -> bool: # オブジェクトのクラスがMyCharactorだったら対象とする return object is MyCharactor # インスペクタに表示されるときに1回呼ばれる func parse_begin(object: Object) -> void: # インスペクターに表示しているオブジェクトを保存 target = object # ボタンを作る var button = Button.new() button.text = "押すと増える" # イベント登録 button.connect("pressed", self, "_button_pressed") # UIのカスタムコントロールとして追加 add_custom_control(button) # ボタンが押されると呼ばれる func _button_pressed(): # エディタのルートノード var root = target.get_tree().edited_scene_root # キャラをロードしてインスタンス化する var chara = load("res://models/Dman.fbx").instance() as Spatial # 位置をランダムに配置する chara.transform.origin = Vector3(rand_range(-3, 3), rand_range(-3, 3), rand_range(-3, 3)) # ルートの子として追加 root.add_child(chara) # キャラのオーナーを設定 (Editorでは必要) chara.set_owner(root) print("増えたぞ")
今回、Insupector Pluginの対象判定のためにクラス型を使っているため、対象になる空のクラスを作りました。
- MyCharactor.gd
extends Node class_name MyCharactor
3. 動作確認する
適当なノードに先ほどの空クラスのスクリプトをアタッチしています。
このノードをクリックするとインスペクタにボタンが追加されていると思います。
このボタンを押しまくると…
D言語くんが大量増殖しました!
なんか上手くいかないときは、プラグインの有効チェックを外して再度有効にするとリロードが行われます。
インスペクタで3Dレンダリングを行う
EditorInspectorPluginのadd_custom_control
にはControl
ノードを突っ込めるため、ぶっちゃけ何でもできます。
例えばノードやアセットの3Dプレビュー機能が欲しかったりしたら、ViewportContainer
を使って3Dレンダリングを行うこともできます。
1. Controlシーンを作る
今回はレスポンシブルにするため、ルートノードの型をVBoxContainer
にしました。
ViewportContainer
Stretch
にチェックを入れるMin Size
のYを設定しないと、Viewport
がゼロサイズになってしまうので適当な値を入れる
Viewport
Own World
にチェックを入れる
3Dシーン
Camera, DirectionalLight, 適当なキャラを配置します。
2. スクリプトを作る
CharaPreview.gd
動かないのは面白くないので、tool
属性を付けたスクリプトをキャラにアタッチしました。
tool extends Spatial func _process(delta: float): transform = transform.rotated(Vector3.FORWARD, deg2rad(360.0) * delta)
MyInspectorPlugin.gd
作成したシーンをロードしてadd_custom_control
に突っ込むスクリプトを書きます。
tool extends EditorInspectorPlugin class_name MyInspectorPlugin var target: MyCharactor var scene: Control # 選択されたノード、ファイルがこのスクリプトの対象かどうか判定する func can_handle(object: Object) -> bool: # オブジェクトのクラスがMyCharactorだったら対象とする return object is MyCharactor # インスペクタに表示されるときに1回呼ばれる func parse_begin(object: Object) -> void: # インスペクターに表示しているオブジェクトを保存 target = object # シーンを追加する scene = load("res://addons/MyPlugin/MyInspectorPlugin.tscn").instance() # ボタン押下のイベント登録 #scene.get_node("Button").connect("pressed", self, "_button_pressed") # UIのカスタムコントロールとして追加 add_custom_control(scene)
3. 動作確認する
インスペクタで荒ぶるD言語くんが見れました。
おわりに
Godot EngineではInspector Pluginでなんでも出来ることが分かりました。
この調子でゲーム制作がどんどん効率化できそうです。
しかし、効率よくゲームを作るためEditor拡張していたら夢中になってしまい、いつまで経ってもゲームが出来上がらないなんてことは避けましょうね。
おまけ
Effekseerプラグインに実例があるので、よかったらご参考ください。 https://github.com/effekseer/EffekseerForGodot3/tree/main/Dev/Godot/addons/effekseer