「初期化は大事だよ」という話

ゲーム開発をしています。(unityでも同じだけどこの記事ではgodotの話をしている。)

タイトル画面を作り、実際にゲーム画面を作る。そして、ゲームオーバー画面を作った。

ゲームオーバーするとタイトルに戻るという一連の流れだ。

このとき初期化が必要となる。シングルトンやstaticの値を初期化しなければ次のゲームで必ずエラーがでることになる。

作っているときは、あまり初期化のことを気にしないかもしれない。特にゲーム画面を作っているときは最低限の初期化はするがもう一度読み込まれるなんてことはあまり想定していない。

ゲーム画面はかなり多くのノードを使うことになるだろうけど、その全てを初期化しなければいけない。基本的にノードを移行するときに初期化されるものは多いが、上で書いたシングルトンとstaticは初期化されない。これでエラーが出るととても焦る。

そもそもゲームオーバーを作る前にゲーム画面を作り込んでいくとコード量が増えていく。

シングルトンもstaticも数が増えていくととても大変だ。

そして、実際ゲームが完成し、ゲームオーバーやエンディングを作ってタイトル画面に戻ってもう一度プレイしようとするとエラーで止まるというわけだ。だから最初から初期化のことを念頭においておかないといけない。というか、プレイ(ゲーム)画面もそこそこにゲームオーバーを作り一旦タイトルに戻る処理を作った方がいいかもしれない。全体の流れがあり、一応動いているということを確認してからゲーム画面を作り込むとエラーもすぐに見つけられる。

ということで、シングルトンやstaticはある意味では手動で初期化が必要となってくるので注意したい。

(Godot)tweenをするときはコピーを作れ

タイトルそのまま。

現在表示しているsprite2Dにtweenをしたくなるときがあるが、このsprite2Dを直接扱うとないかエラーが出るときがある。

だから一旦コピーして、コピーしたものをtweenする。このコピーは移動したり拡大したりするだけでそのまま消滅させることになる。

✳︎ちなみに使う関数は「Duplicate」というものがある。

本体を動かすしかないパターンはもちろん本体を動かすのだが、コピーで良いときはコピーを動かしたほうがいいという話。

こういう細かいことはAIに聞いてもなかなか教えてくれず自力で思いつくしかない。ある意味では盲点である。

ということで、tween周りをいじるとやたらとエラーが出るのだが、コピーを作ってコピーに動いてもらいそして消えてもらうということをやると大体うまくいく。

というメモ。

(Godot4) Tweenが動かないのは、Pausedのせいかもしれないという話

GetTree().Paused = true;

このコードは、プログラムを停止することができるコードだ。

私はバトルが終了時にこれを使っている。

そして、問題がおきた。このコードがあると当たり前だけど他のプログラムは止まるので、何をしても動かない。

ちょっとTweenを使ってバトル終了後にお金でも獲得したことをアピールしてみようと思って動かそうとしたが動かなかった。

「なぜ?なぜ?」

もちろん、「GetTree().Paused = true;」という」コードを書いているから動かないのだ。

しかし、これを書いたことを忘れていると大変なことになる。

「動かないとき=何かエラーがある」と思ってしまうからだ。

いろいろ考えた。というかエディターにエラーの文字はない。だからこそ余計に考えてしまう。

まず、_Readyで動かしてみるとtween自体は動いていた。だからコードはあっている。

なぜ、動かないだ?・・・。 ということで閃いた。先日書いたこいつが悪さをしているのだ。

ということで解決した。

        Tween tween = GetTree().CreateTween();
        tween.SetPauseMode(Tween.TweenPauseMode.Process);

解決するにはこのように書けばいい。たった一行である。

ちなみに私はGodotでの開発はC#しか使っていない。

ChatGPTに聞いても私がPausedをしているという想定はしていないので、全然違う答えを書いてくる。

結局自力で思い出すしかなかった。あぶなかった。一時間近い時間が溶けたと思うが、次からもうはまらないので、よしとしよう。

ということで、エラーなくプログラムが動かないときは、「GetTree().Paused = true;」をしたかどうかを思い出すと幸せになれるかもしれない。

(Godot)Node型にキャストしても良いことなどない

またハマった。

初心者みたいなことだ。もちろん私はまだ二ヶ月ほどしかやっていないから初心者なんだけど。

今回のバグは型。 LabelをNode型に変換して操作するという謎コードがあった。

これのせいで思ったようにいかない。
色も位置も変更できず、非表示(Visible)にもできない。

モンスターを扱っていたから困る。エフェクトが効かない。

ということで、変更したらうまくいった。
(ちょっとした訳ありなので)UIに画像を入れてモンスターにしているので、Control型だ。

普通はNode2Dにしておいた方が無難だと思う。

とりあえず、Node型は使わない方がいいと思う。

(Godot)CallDeferredが便利な件

CallDeferred();

これを知っているのと知らないとでは全然違う。

これは何か? 

すべてのシーンの_Ready()が実行されてから実行するメソッドだ。

これによって、UI等のロードが全て終わったタイミングで実行することができる。
それで、何が嬉しいのかといえば、Getnodeができるようになるという利点がある。

    
public override void _Ready(){
    CallDeferred(nameof(AfterReady));
}

private void AfterReady()
{
        
}

私の場合は、「シグナル」を使う時にハマって、これを知った。

(ChatGPTが教えてくれた。)

子ノード同士でシグナルを送るとき、相手のノードがまだ読み込まれていないと取得することができず、どうしたもんかと思っていた。全てaddchildが終わってから実行すれば取得できるので1フレーム待つ必要があった。そういうやり方もあるのだが、CallDeferredというものが用意されていた。

これを使うことで無事解決。 まあ詳しくはChatGPTに聞いてみてくれ。

(Godot)_Ready()の順番問題?

_Readyの中で、画像やシーンをロードさせることはよくあることだ。

しかし、今回はこれではまった。

いつものようにロードをしても読み込めていないというエラーが出る。

なぜそうなったのか?

説明がややこしいのだが、メインシーンで使うために小分けしているシーンが複数あり、その小分けしているシーンの間でのロードが必要だったからだ。

ChatGPTに、なんとなくやり方をきくと答えを教えてくれた。

メインシーンでロードをし、それを引数を使って渡せばいい」とのこと。

と、一行書いただけで理解できる人はどのぐらいいるか分からないが一応そういうことだ。

小分けしているシーンで、うまく読み込めないときは、親シーンで読み込んで、それを引数として渡す。

(同じことを2回書いた)

ということで、全て_Readyの中でロードして使えばいいというわけでなく、他からもってくるというテクニックがあるんだということを知った。

(Godot c#)AddChildするときは、そのシーンでやれ

AddChildでハマった。

なんとも初歩的なことだ。

タイトルにある通り、AddChildをするときは、つけたいシーンにあるスクリプトでやったほうが混乱が少ない。

全く関係ないスクリプトで hoge.AddChild(xxxx);なんてやりだしたらエラーが出る確率があがる。

素直に、this.AddChild(xxx)と書けるようにやるべきだ。

なんかエラーが出てしまうと、あれこれ考えて深みに入るが、ちょっとでも複雑そうなことはやめたほうがいいだろう。

という教訓。(ただの寝不足だったのかもしれない)

Godotは基本的にnewではなくGetNodeを使おう

つまらないミスをして1時間ほどハマった。

やっていることはtweenの処理。

tweenを扱っているクラスを別クラスでインスタンス化しようとしたときにnewを使ってインスタンス化したらハマった。

解決策は、シーンにnode2dを追加し、tweenを書いたファイルをアタッチし、そのファイルをGetNodeで読み込めばうまくいけた。

エラーは出ず、なんでtweenが動かないんだろうと考えていた。

AIに聞いてみると、tweenを使う時は必ずAddChildをしなければならないと言ってくるが、別にしなくてもテストでは動いていたので不思議だった。

結局、コードが長くなってきたりして、新しいクラスに分散させるときは、GetNodeで読み込んだ方がミスが少ない。

かなり初歩的なところでハマってなんだか疲れたが一応前進したのでオッケー。

続・Godot4.3(C#) シグナルの書き方メモ

以下のようなクラスを作った。
ゲームではありがちなコンボを管理するクラスだ。

このクラスのコンボ数が変化すると通知をするということになる。

using Godot;

public partial class ComboManager : Node
{
    [Signal]
    public delegate void ComboCountChangedEventHandler(int comboCount);

    private int comboCount = 0;

	public override void _Ready()
	{
        GD.Print("ComboManager 読み込み確認");
    }

    public void IncrementCombo()
    {
        comboCount++;
        EmitSignal(nameof(ComboCountChanged), comboCount);
    }

    public void ResetCombo()
    {
        comboCount = 0;
        EmitSignal(nameof(ComboCountChanged), comboCount);
    }

    public int GetComboCount()
    {
        return comboCount;
    }
}

---

受信側で接続するのだが、「ComboCountChangedEventHandler」という文字列を使うと失敗する。

「ComboCountChanged」というところだけ記述し、「EventHandler」という文字を削除する必要があった。

        // シグナルに接続
comboManager.Connect(nameof(ComboManager.ComboCountChanged), new Callable(this, nameof(_OnComboCountChanged)));

こんな感じ。

「EventHandler」と言う文字をなぜ消さなければいけないのかは知らんけど、これで動いたのでオッケー。

過去記事でも全く同じ話をしたかもしれんが知らん。復習して勉強中。

使っているのChatGPTとGeminiに聞いてみたが、全く別の話をしているので詰んでいた。

困ったものだ。

(Godot ) GetNodeは絶対パスの方が良さそう

同じ階層にあるシーンをGetNodeするとき相対パスではなく絶対パスで取得した方が個人的にはわかりやすい。

というのも昨日この件で詰んでいたからだ。同一階層のとき「../」をつけないでシーン名だけ書いていたらエラーになっていた。

ChatGPTにエラー内容を聞いたら取得できていないと言われて焦った。

そして「まさかな」とも思った。webでは同一階層のときそのままファイルパスを書けばよかったのだが、Godotでは違うみたいだ。

ということで、つまらないところで数時間詰んだ。とりあえず絶対パスで書いて、最終的にテストが終わった後で相対パスにしたいならしたらいいんじゃないか。

(あと、GDを使ってデバックもちゃんとしないとねえ)