Godotの Audio Spectrum Visualizer Demoを改造して遊んでみた
Unityの学習が捗りません。
なぜ捗らないか、C#と相性が悪いから?と悩んだ時期もあってWPF(C# + XAMLで作るデスクトップアプリケーション)を試しに勉強したら2週間で業務アプリケーション作れるぐらい身ににつきました。C#書きやすい。
そこでUnity 3Dを中断してUnity 2Dにも手を出してみました。一応、それらしいサンプルに従ったスクロールアクションゲームが作れたのですが全然楽しくないんですよね。ヒエラルキーがどうとかインスペクターがどうとか、ああ、そうかコーディングで完結せずマウスでのオペレーションが強要されることがとにかく嫌なんだと、やってて理解できました。
Godotはコーディング主体で開発できて快適
Unity料金体系の変更でGodotに注目が集まったことがありますが、個人で作る分にはどっちも無料なので対してGodotに注目していませんでした。
しかし、Unityのコンパイルの面倒さ(Unityはとにかくコンパイルがシームレスではありません。10年前の化石仕様だから仕方ないけどいい加減どうにかならないの?)と、元々Unityのサポートレベルの低いC#バージョンの低さのストレスも相まって、Godotがいいよと何度もGPTも提案してくるので根負けしてGodotを試すことにしました。
サンプルコードで試したのは、音楽に合わせてグラフが動くAudio Spectrum Visualizer Demoというものです。
軽快でストレスフリーなGodotに感動する
Godotまず初めに驚いたのがインストーラー不要でEXEでエディタ配布されていることです。インストールだけで何分も待たされるので気合入れないといけないUnityとは桁違いに快適でここでまず気に入りました。
次に、サンプルコードのプロジェクトの起動が早い早い。Unityはあれやこれやと待たされてショボいサンプルを動かすにも精神的苦痛があったのですが一瞬で開いて即試せるので一気にGodot楽しい!となりました。
Audio Spectrum Visualizer Demoはコード量も非常にコンパクトです。Unityの似たサンプルはもっと複雑だった記憶があるのでここでもGodotに軍配が上がりました。
下記はオリジナルのコードに、時間経過で色相が変化する改造を施した私のアレンジ版ですが、たったこれだけのコード量でスペクトラムの実装ができています。
extends Node2D
const VU_COUNT = 16
const FREQ_MAX = 11050.0
const WIDTH = 800
const HEIGHT = 250
const HEIGHT_SCALE = 8.0
const MIN_DB = 60
const ANIMATION_SPEED = 0.1
var spectrum
var min_values = []
var max_values = []
var default_font : Font = ThemeDB.fallback_font;
# テキストの色を指定する
var font_color = Color(0, 0, 1) # 青色 (R:0, G:0, B:1)
const MAX_HEIGHT_CONSTANT = 154.370698885464
const TIME_DURATION = 60.0 # 1分(60秒)を100%とする
var time_passed = 0.0 # 経過時間を保持する変数
const TIME_MAX = 5.0 # 経過時間を保持する変数
var time_per = 0.0 # 経過時間を保持する変数
func _draw():
# 例: draw_string_outline を使用する際の呼び出し
# 実際に使えるかはバージョンとクラスの仕様を確認してください
draw_string_outline(
default_font, # 使用するフォント
Vector2(WIDTH/2, HEIGHT/0.5), # 描画する位置
str(time_passed) + " / " + str(time_per), # 描画するテキスト
0, # 整列方法
-1, # 幅 (デフォルトは無制限)
16, # フォントサイズ
0.0, # アウトラインのサイズ
Color(1, 1, 1) # テキストの色
)
var w = WIDTH / VU_COUNT
for i in range(VU_COUNT):
var min_height = min_values[i]
var max_height = max_values[i]
var height = lerp(min_height, max_height, ANIMATION_SPEED)
# `height` が 0 または `max_height` が 0 の場合の対策
if max_height == 0:
max_height = 1 # 0 での除算を避ける
# 高さを 0〜1 に正規化(標準化)する(`MAX_HEIGHT_CONSTANT` を基準に)
var normalized_height = height / MAX_HEIGHT_CONSTANT
# `normalized_height` に応じて色相(hue)を設定
# 黄色の色相は約 0.16(水色は 0.5)
# 線形補間で 0.16 から 0.5 の範囲に `normalized_height` をマッピング
#var hue = lerp(0.16, 0.5, normalized_height)
var hue = abs(sin(time_passed * PI / TIME_MAX)) #time_per
# 彩度と明度を設定(彩度は最大 1.0、明度も最大 1.0 に設定)
var saturation = ease(normalized_height, 1.5) #lerp(0.0, 1.0, normalized_height)
var value = 1.0
# `Color.from_hsv()` を使用して色を生成
var bar_color = Color.from_hsv(hue, saturation, value)
draw_rect(
Rect2(w * i, HEIGHT - height, w - 2, height),
bar_color #Color.from_hsv(float(VU_COUNT * 0.6 + i * 0.5) / VU_COUNT, 0.5, 0.6)
)
draw_line(
Vector2(w * i, HEIGHT - height),
Vector2(w * i + w - 2, HEIGHT - height),
bar_color, #Color.from_hsv(float(VU_COUNT * 0.6 + i * 0.5) / VU_COUNT, 0.5, 1.0),
2.0,
true
)
# Draw a reflection of the bars with lower opacity.
draw_rect(
Rect2(w * i, HEIGHT, w - 2, height),
Color.from_hsv(float(VU_COUNT * 0.6 + i * 0.5) / VU_COUNT, 0.5, 0.6) * Color(1, 1, 1, 0.125)
)
draw_line(
Vector2(w * i, HEIGHT + height),
Vector2(w * i + w - 2, HEIGHT + height),
Color.from_hsv(float(VU_COUNT * 0.6 + i * 0.5) / VU_COUNT, 0.5, 1.0) * Color(1, 1, 1, 0.125),
2.0,
true
)
# Rect2 の定義
var rect = Rect2(w * i, HEIGHT - height, w - 2, height)
# 頂上の座標を計算(矩形の上辺中央の位置)
var top_center = rect.position + Vector2(5, -20) #+ Vector2(rect.size.x / 2, 0)
var text_color = Color(1, 1, 1) # 青色 (R:0, G:0, B:1)
var outline_size = 0 # アウトラインの太さ
var outline_color = Color(1, 1, 1) # アウトラインの色(黒)
var height_text = str(int(max_height))
# 例: draw_string_outline を使用する際の呼び出し
# 実際に使えるかはバージョンとクラスの仕様を確認してください
draw_string_outline(
default_font, # 使用するフォント
top_center, # 描画する位置
height_text, # 描画するテキスト
0, # 整列方法
-1, # 幅 (デフォルトは無制限)
16, # フォントサイズ
outline_size, # アウトラインのサイズ
text_color # テキストの色
)
func _process(_delta):
# 経過時間を加算
time_passed += _delta
time_per = time_passed / TIME_MAX
if time_passed >= TIME_MAX:
time_passed = 0.0 # 経過時間をリセットする
var data = []
var prev_hz = 0
for i in range(1, VU_COUNT + 1):
var hz = i * FREQ_MAX / VU_COUNT
var magnitude = spectrum.get_magnitude_for_frequency_range(prev_hz, hz).length()
var energy = clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
var height = energy * HEIGHT * HEIGHT_SCALE
data.append(height)
prev_hz = hz
for i in range(VU_COUNT):
if data[i] > max_values[i]:
max_values[i] = data[i]
else:
max_values[i] = lerp(max_values[i], data[i], ANIMATION_SPEED)
if data[i] <= 0.0:
min_values[i] = lerp(min_values[i], 0.0, ANIMATION_SPEED)
# Sound plays back continuously, so the graph needs to be updated every frame.
queue_redraw()
func _ready():
spectrum = AudioServer.get_bus_effect_instance(0, 0)
min_values.resize(VU_COUNT)
max_values.resize(VU_COUNT)
min_values.fill(0.0)
max_values.fill(0.0)
Godotはエディタ上でのコーディングがシームレスで快適
私は普段、色々な言語でプログラムを書いていますが、VSCodeを使ってもUnityは快適とはいいがたいなと感じています。それはUnityそのものに問題があるからで、C#がいくら書きやすい言語でもカバーできていません。というか言語が問題なのではなく、いちいちコンパイルしないといけないけどそのコンパイラがショボいのがUnityの最大の欠点だと私は思いました。
Godotは初めからテキストベースで開発できる環境が整っておりとにかく軽量です。そして、GDScriptは書きやすいです。UnityのC#なんかより絶対に書きやすいと2日書いただけで思いました。コードが直感的なのです。
Unityは今後使うのをやめて(有料アセット買ってるので手放すことに悩みましたが作るのがしんどいならやっぱりいらない)Godotを勉強していこうと思いました。Godotはコード見ただけでだいたい何しているか分かるので悩むことがないのも素晴らしいです。
自分に合わないものは、合わない!とハッキリ認めるのは大事だなと思いました。