ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

Unity経験者のGodotの遊び方:カスタムテーマの設定方法と、スクリプトからLabelの設定を変える方法について


このページではGodot4.2.1を使用して実験しています😃

以前、Labelのフォントの色を設定するのにTheme Overridesを使用すると書きました。
Theme Overridesは、そもそも設定されたTheme(テーマ)の項目を上書きする項目になりますが、同じ色のLabelを複数作成する場合など、それぞれのLabelTheme Overridesを設定するのは大変です。

そういう場合はカスタムテーマを作成し、Theme(テーマ)に設定する事で、同じ設定を反映する事ができます。

さて、肝心のThemeの設定の仕方ですが、


パッと見ると、Themeには設定できる項目がない様に見えますが、
Themeはここではなく、Theme専用のツールを使って編集することができます。

テーマエディタ

Godotファイルシステムの何もないところで右クリックし、「新規リソース」を選択します。


「Resouceを新規作成」のウィンドウからThemeを選択します。



「リソースを別名で保存」のウィンドウが表示されるので、ファイル名を指定して保存します。
下図の様な画面が表示されます。これがテーマエディタの画面になります。



まず、試しにLabelの設定を行ってみたいので、矢印の位置の+をクリックして、表示されるメニューからLabelを選択し、「タイプを追加」をクリックします。
下図の様にLabelで設定変更可能な項目を設定できる様になります。



図中の説明の様に、Themeに追加した項目の+をクリックすると、その設定項目がThemeに追加され、 インスペクターで設定変更できる様になり、プレビューでも状態を確認できる様になります。



テーマエディタ保存をクリックしてから閉じるテーマエディタを閉じます。
シーンツリーLabelのインスペクターから、Themeをクリックし、読み込みから先ほど作成したThemeファイルを読み込みます。
これで、Themeカスタムテーマを設定する事ができました。


スクリプトでThemeを変更する。

ゲーム中に文字の設定を変えないのであればここまでで問題ないのですが、ゲーム中に設定を変更する場合は下記の方法で可能です。

add_theme_color_override("font_color", Color(x,y,z,w))

Labelにアタッチされているスクリプトに、このメソッドを追加します。
このメソッドはフォントの色を変更する場合です。

他の要素を変更する場合はadd_theme_・・・の後に続くcolorを下図の赤字の項目に変更し、override(・・・の後の文字を青字に変更します。



大文字、小文字やスペースの扱いが微妙に異なる場合がありますので、詳細は公式ドキュメントを確認してください。





Unity経験者のGodotの遊び方:Body系ノードの移動方法と衝突検知方法について


Unityで作成したColorChangePanicをGodotへ移植する作業がだいぶ進んで、簡単なテストができるレベルまでになったのですが
youtu.be
敵増殖時の生成が、Unityは元の敵に吸い付くように生成されているのに対して、Godotではちょっと反発して生成されているのが気になりました。

Object(GodotではNode)の移動手段も、衝突判定方法も、UnityGodotでは異なっているのですが
まだ習い始めということもあって、今はよくわからないままに、弾も敵もRigidBody2DというNodeルートノードにして作成していました。

どういう理屈で生成の挙動がUnityGodotで変わってしまっているのか判りませんが、いい機会なのでルートノードになりやすい、いくつかのNodeについて、それぞれの移動手段と、衝突判定方法について纏めておこうと思います。


Unityの場合はGameObjectを移動させる方法として、

transform.positionを直接変更する。
transform.translateで移動量を指定して、移動させる。
Rigidbodyコンポーネントをアタッチして、力を加えて移動させる。

などがあり、①と②の方法であれば、どのGameObjectでも使うことができます。
衝突が発生した場合は、イベント関数のOnCollisionEnterなどが呼び出されます。
そのため、Unityでは比較的容易にObjectを移動させて、衝突判定をとることができます。

ところがGodotの場合は、①のpositionを直接変更する移動方法であれば、どのNodeでもできる様ですが、
②と③の様な手段となると、そうもいかない様で、移動させるメソッド・衝突を検知する方法ともNodeによって異なるみたいです。

zenn.dev
こちらのサイト様でかなり詳しく説明されていましたので、実際にNodeスクリプトを作成して実験してみようと思います。

RigidBody2Dノード

まずはUnityで馴染みが深いRigidBodyです。

このノードUnityの時と同じ様に、add_forceで力を加えたり、velocityを直接操作したりするという移動方法のほか、move_and_collide()メソッドでも移動させる事ができます。

衝突判定については、RigidBodyシグナルBody_enteredが使えるので、シグナルから衝突判定することがきます。
スクリプトで衝突判定をとる事もできますが、この場合、移動方法によって判定方法が変わる様です。

まずmove_and_collide()メソッドを使う場合です。

extends RigidBody2D

var speed :int = 160
var coll_item
var Obj_A
var Obj_B
var message_label

func _ready():
    Obj_A=get_node("../ObjA").get_class()
    $"../ObjA/CollisionShape2D/ColorRect/Label".text="Obj-A \n"+str(Obj_A)
    Obj_B=get_node("../ObjB").get_class()
    $"../ObjB/CollisionShape2D/ColorRect/Label".text="Obj-B \n"+str(Obj_B)
    message_label=$"../Label"

func _physics_process(delta):
    coll_item=move_and_collide(Vector2(1,0)*speed*delta)#右にspeed*delta移動する。

    if coll_item!=null:
        message_label.text="Obj-Aがぶつかったのは、"
        message_label.text += str(coll_item.get_collider().get_class()) #衝突した相手の型を表示
        message_label.text += "の" + str(coll_item.get_collider().name) #衝突した相手の名前を表示

move_and_・・・というのは、Body系ノードを移動させるメソッドで、
move_and_collide() は移動中に他のcolliderにぶつかった場合、動きを止めて「衝突情報」を返してくれます。
そして、この移動方法は、衝突した相手に物理的な運動効果を与えない移動になる様です。

この「衝突情報」から.get_colliderで相手のcolliderを取得し、更にget_collider.get_classノードの型を、get_collider.nameで名前を確認する事ができます。
youtu.be

次に、velocityを直接操作する場合です。
add_forceの場合は、加えられた力を物理計算した結果として、velocityで移動していると思いますので、そちらは省略します。)

func _physics_process(delta):
    linear_velocity=Vector2(1,0)*speed
    
    coll_item=get_colliding_bodies()

    if coll_item!=null:
        for body in coll_item:
            message_label.text="Obj-Aがぶつかったのは、"
            message_label.text += str(body.get_class()) #衝突した相手の型を表示
            message_label.text += "の" + str(body.name) #衝突した相手の名前を表示

velocityの移動の場合は、move_and_collide()の様に、移動中colliderにぶつかると止まるというわけではないため、同じフレーム内で複数の衝突が発生する可能性があります。
get_colliding_bodies()を使用すると、そのフレームで衝突があった場合、相手のcollider全てが配列変数に取得されます。

ちょっと分かりにくかったのですが、move_and_collide()での取得は「衝突情報」でしたが、get_colliding_bodies()では相手の「collider」が取得されるため、型や名前を取得する場合には、.get_colliderが不要でしたので、注意が必要です。

そしてvelocityでの移動は、衝突した相手に物理的な運動効果を与える移動になる様です。
youtu.be
相手がRigidBodyの場合、この様に相手を移動させてしまいます。
ただ、相手がCharacterBodyStaticBodyの場合は、物理挙動をしているノードでは無いからなのかvelocityでの移動でも、相手を移動させてしまうことはない様です。


CharacterBody2Dノード

次は、CharacterBodyです。
CharacterBodyは、RigidBodyで使用できたmove_and_collide()メソッドの他、move_and_slide()メソッドも使用できるようになります。

move_and_collide()は移動中にcolliderにぶつかると、そこで動きを止めますが
move_and_slide()は移動中にcolliderにぶつかっても、動きを止めるのではなく、あらかじめ指定しておいた角度以下なら障害物に沿ってスライドするなど、まさにゲームキャラクターの移動に使うことを前提にしたような便利なメソッドです。
そして、CharacterBody2Dノードは、move_and_collide()move_and_slide()の2つのメソッドを移動に使用することができます。

衝突の検知については、移動手段のバリエーションに反してか、シグナルBody_enteredが用意されていないため、シグナルで衝突を検知することができない様です。


移動の実験ですがmove_and_collideの使い方はRigidBodyと同じ様でしたので、move_and_slideを実験してみます。

extends CharacterBody2D # ← ここが変わっている事に注意。

var speed :int = 160
var coll_item
var Obj_A
var Obj_B
var message_label

func _ready():
    Obj_A=get_node("../ObjA").get_class()
    $"../ObjA/CollisionShape2D/ColorRect/Label".text="Obj-A \n"+str(Obj_A)
    Obj_B=get_node("../ObjB").get_class()
    $"../ObjB/CollisionShape2D/ColorRect/Label".text="Obj-B \n"+str(Obj_B)
    message_label=$"../Label"

func _physics_process(delta):
    if coll_item==null:
        #ぶつかるまでは右に毎フレームspeedピクセルの移動を定義
        velocity=Vector2(1,0)*speed
    else:
        #ぶつかったら左に毎フレームspeedピクセルの移動を定義
        velocity=Vector2(-1,0)*speed  
    
    move_and_slide()#velocityの定義にしたがって移動。
    
    if get_slide_collision_count()!=0: #このフレームでの衝突数を確認
        for i in range(get_slide_collision_count()): #衝突数でforを回す
            coll_item = get_slide_collision(i) #i番目の衝突情報を格納
            
            #衝突情報coll_itemからget_collider()で衝突したNodeの情報を取得して、
            #get_class()でNodeの型を取得
            message_label.text="Obj-Aがぶつかったのは、"
            message_label.text += str(coll_item.get_collider().get_class()) #衝突した相手の型を表示
            message_label.text += "の" + str(coll_item.get_collider().name) #衝突した相手の名前を表示

move_and_slide()colliderにぶつかっても移動を止めるわけではないため、velocityの移動と同じように、同じフレーム内に複数の衝突が発生する可能性があり、「衝突情報」は配列変数として格納されます。
しかし、velocityとは異なり、取得されるのは「collider」ではなく、「衝突情報」なのでcolliderを取得するためにget_colliderが必要になります。

そして、move_and_slide()の移動はvelocityに従った移動のように見えますが、衝突した相手に力を及ぼす事がない移動になる様です。

youtu.be

Area2Dノード

最後は、Area2Dです。
StaticBodyというものもあるのですが、これは地面のような動かないObjectを作ることを想定しているようで、その使用用途からあまり移動させる事はないのでは?と思いましたので省略します。
一応、StaticBodymove_and_collide()メソッドを使うことができます。
そして、シグナルBody_enteredが用意されておらず、この辺りはRigidBodyと同じイメージなのかもしれません。

最後のArea2Dは、今までのNodeと異なり、move_and_・・・メソッドやvelocityのような移動手段は用意されていないようで、移動させたい場合はpositionを直接操作する様です。

移動手段が限られていますが、衝突判定としてはシグナルbody_enteredだけではなく、area_enteredというArea同士の衝突を検知する、ちょっと変わったシグナルが用意されています。

公式ドキュメントを読むと、Areaは例えば、取得できるコインのようなObjectを作成したりするのに向いている様です。
UnityでいうところのOnTriggerEnterの様なものなのかもしれません。
あとは、今回のテーマとは変わりますが、エリア内に特殊は効果を設定したい場合などにも利用できるみたいです。

youtu.be


参考サイト
zenn.dev
zenn.dev





Unity経験者のGodotの遊び方:Signalについて


Signals(シグナル)

今日は、Godotの4大要素の一つのSignals(シグナル)の引数について実験してみたので、その結果を踏まえてSignals(シグナル)について簡単にまとめておこうと思います。

Signalは、何か特定のイベントが発生した時に、指定しておいた関数を実行する仕組みです。
関数に引数を渡すこともできます。

このあたりはボタンの作成をUnityGodotで比較してみると、割と簡単に理解できると思います。

ボタンで確認する

Unityの場合
まずはボタンによって呼び出される関数Aを、public関数として作成します。
この時点では、関数Aは完成させる必要はありませんが、最低限、public関数として宣言する必要があります。

次にボタンのインスペクターで、①の欄に、先ほど作成した関数Aを含むスクリプトがアタッチされたGameObjectを入れます。
そうすると、②の欄で関数Aが選択できます。

関数Aの宣言で、下のスクリプトの様にボタンから受け取る引数を予め指定しておくと、②の欄で関数を指定した時点で、そのすぐ下に、引数を入力するウィンドウも表示されます。

public class ButtonFunction : MonoBehaviour
{
    public void ButtonClick(int value)//ボタンが押された時の関数をpublic関数・int型の引数を受け取ると宣言。
    {
        Debug.Log(value);
    }
}


これで、このボタンをクリックすると、関数Aが実行される様になります。


Godotの場合
Unityでは、ボタンによって呼び出される関数Aを先に作成しましたが、Godotではこの時点では、あってもなくても、どちらでも構いません。
まずは、ボタンのインスペクターから図の位置のノードをクリックします。


デフォルトで設定されているイベントのSignalの一覧が表示されます。
今回はボタンを押した時のSignalなので、pressed() を選択してから、右下の接続をクリックします。


「メソッドにシグナルを接続」というウィンドウが表示されますので、左下の高度な設定をクリックします。
すると、図の様に「呼び出し引数を追加」という画面が表示された状態になります。


④をクリックして、関数に渡したい引数のタイプを(⑤)選択し、⑥の追加をクリックします。


図の様に、引数を入力できる様になるので、受け渡したい値を設定します。


このSignalを受け取るスクリプトがアタッチされたノードを(⑦)クリックします。


実行したい関数が既にある場合は、選択をクリックすると、⑦で指定したスクリプトの中の関数が表示されるので、その中から、実行したい関数を選択してから、接続をクリックします。

実行したい関数がまだ存在しない場合は、関数を選択せずに接続をクリックします。

これで指定したスクリプトSignalが接続され、ボタンをクリックするとこの関数が実行される様になります。

細かな操作は違いますが「ボタンを押す。」という機能だけで言えば、基本的にUnityGodotで大きな違いは無い様に感じます。
ただ、GodotSignalは、単にボタン操作だけではありません。


カスタムシグナル

スクリプトを使って、下記の様に独自のSignalを作ることもできます。

signal mysig(arg:int)#ここでは、int型の「呼び出し引数」の付いたカスタムシグナルを宣言しています。

このスクリプトがノードにアタッチされると、そのノードにカスタムシグナルが追加されます。

この様にして、簡単にカスタムシグナルを作成することができ、ボタンの時と同じ様に関数に接続することができます。

この接続もスクリプトを使う事で動的に行うこともできます。

mysig.connect(self._sig_test)#カスタムシグナルを接続する。

このメソッドを使うことで、カスタムシグナルをこのスクリプト(self)にある_sig_testに接続する事ができます。

ただしカスタムシグナルは、あらかじめシステムが用意してくれているSignalと異なり、何かのイベントで自動的に
発信するようなことはありません。

発信もスクリプトで行います。

mysig.emit(10)#「呼び出し引数」をつけて、カスタムシグナルを発信する。

これで、自分専用のSignalを作成して、発信させたいタイミングでemitすることで、好きな関数を呼び出すこと
ができます。


システムのシグナルを動的に作成する

さらにスクリプトを使って、システムが用意してくれているSignalを、動的に作成して使用する事もできます。

extends Node

var arg1=10#テスト用変数

func _ready():
    
    var timer_node = Timer.new() #Timerノードを動的に作成
    timer_node.wait_time = 0.5 #作成したTimerノードを0.5秒に設定
    
    #Timerノードのtimeoutシグナルを、_on_timer_timeout関数に接続
    timer_node.timeout.connect(_on_timer_timeout.bind(arg1))#bindで「呼び出し引数」を追加
    
    add_child(timer_node)#Timerノードをシーンツリーに追加
    timer_node.start()#Timerを開始する。

これで、システムのSignalを動的に作成して利用することもできます。


UnityでいうところのOn Collision Enterのような基本の組み込み関数も、GodotではSignalになっています。
先ほどの下図にある様に、マウスがボタンの上に来た時にSignalを発してくれるmouse_enteredや、逆にマウスが離れた時のmouse_exitedなど、UnityでいうEvent Systemのような機能まであります。


Signalを使って信号をやり取りする事で、シーン同士を連携させることができる様です。


参考サイト
zenn.dev




Unity経験者のGodotの遊び方:インスタンス化と実体化について


UnityGodotで共通する事として、GameObject(GodotではNode)はインスタンス化される必要があります。

ところが、このインスタンス化の概念が、UnityGodotでは少し異なる様です。
最初、すごく混乱させられたので、備忘録として簡単にまとめておきます。

Unityにおいて、ゲーム内で新しいGameObjectを生成する場合、

using UnityEngine;

public class InstansPrefab : MonoBehaviour
{
    public GameObject InstansPrefab; // Prefabを変数として指定

    private void Start()
    {
        // Prefabをインスタンス化
        var instansObject = Instantiate(InstansPrefab);
    }
}

これだけで、Prefabから新しいinstansObjectが生成され、ヒエラルキーウィンドウ上とゲーム画面上に表示されます。
もちろん、この段階で新しいGameObjectUpdate関数も動き始めます。

対して、Godotではと言いますと、

extends Node

var Instans_Prefab:PackedScene = preload("res://Scene/enemy.tscn")#Prefabを変数として指定
        
func _instans_prefab():
    #Prefabをインスタンス化?
    var Instans_Object=Instans_Prefab.instantiate()

一見、同じように見えます。
ですが、Godotでは、これだけではゲーム画面上には新しいInstans_Objectは表示されません。
そして、Unityでいうヒエラルキーウィンドウに当たるシーンドッグにも、この新しいInstans_Objectは表示されません。

試しに、色々と実験してみます。

   #先ほどのインスタンス化?の続き・・・

    #変数の内容を出力ウィンドウに表示してみる。
    print(Instans_Object)

    #オブジェクトにある初期設定用関数にアクセスできるか試してみる。
    Instans_Object._Set_Parameter(100.0,0) #初期設定用、今は仮に100.0と0を指定

    #オブジェクトの座標を出力ウィンドウに表示してみる。
    print(Instans_Object.position)

上記の処理はすべて成功します。
Instans_Objectは間違いなく生成され、どこかに存在している様です。

試しに、今度はInstans_Object_process関数printを追加してみます。

extends RigidBody2D
var Speed:float
var Color:int

func _Set_Parameter(speed:float,color:int) -> void:
    Speed=speed
    Color=color

func _process(delta):
    print("process test") #毎フレーム、"process test"を出力ウィンドウに表示させたい。

結果、このprintは処理されず、出力ウィンドウに表示してくれませんでした。

この2つの実験の結果から、Instans_Objectはどこかに存在して、その関数にアクセスする事もできますが、
_process関数が実行していない事が分かりました。

公式チュートリアルでは、練習として2Dと3Dの避けゲームを作成し、そこで、敵を大量に生成していたので、
もう一度、公式をじっくりと読んでみて、次の一文に気づきました。

メインシーン — Godot Engine (4.x)の日本語のドキュメント


Unityを触ってきた僕としては、シーン内で生成されたオブジェクトは、当然そのシーンに追加されるイメージでしたが
どうもGodotでは、そういう訳ではない様です。

Unityでいうシーンは、言うなれば、1つの世界の様なものだと僕はイメージしています。
特に意識する事なくヒエラルキーウィンドウ上でオブジェクトを作成し、
作成されたオブジェクトは、特に意識する事なくそのシーンに追加されます。

Godotにおいてのシーンとは、単純に(もしくは厳密に)ノードの集合体という意味が強いのかもしれません。
Godotの4大要素の一つにScene Tree(シーンツリー)がある事から、
Unityの様に「特に意識する事なく」ではなく、生成したシーンをScene Tree(シーンツリー)のどこに置くか
をきちんと指定しないと、実体として存在できない様です。

extends Node

var Instans_Prefab:PackedScene = preload("res://Scene/enemy.tscn")#Prefabを変数として指定
        
func _instans_prefab():
    
    var Instans_Object=Instans_Prefab.instantiate()
    add_child(Instans_Object)#生成したInstans_Objectを、このスクリプトがアタッチされたNodeの子に追加。

これで、ようやくシーンドッグとゲーム画面の両方にInstans_Objectが表示され、Instans_Object_process関数
も動き始めてくれました。

つまり、Godotにおいては、いわゆるインスタンス化と、シーンドッグ上に存在させる言わば「実体化」は異なる様です。
これを上手く使えば、色々と面白い仕組みを作る事ができるのかもしれませんね。




Unity経験者のGodotの遊び方:Nodeの種類ごとの色の設定方法について


Godotで色を設定する方法が、Nodeの種類によって違っているため、結構苦労したので、備忘録として簡単にまとめておきます。

Unityを経験している人は、GameObjectの色を変えたい場合は、Materialを作ると思いますが、
Godotの場合は、NodeによってはMaterialを必要としない場合があるみたいです。

この図は、「CSGMesh3D」という3D系のNodeのインスペクターになります。
CSGMesh3D」は簡単に立体を作ることができるNodeです。

このインスペクターにはMaterial欄の選択肢に、「新規StandardMaterial3D」があり、まずこれを選択します。

すると、比較的UnityのMaterialと似たイメージの画面が表示されます。
色の設定はAlbedoです。
この辺りもUnityと同じっぽいと思いました。

次の図は、2D系のNodeで「Sprite2D」のインスペクターになります。
Sprite2D」は画像を表示する、UnityでいうImageの様な機能を持つNodeです。

このインスペクターにはMaterial欄の選択肢に、先ほどの「新規StandardMaterial3D」がありません。
この場合は、Visibility欄にある「Modulate」か「Self Modulate」で色を設定できます。


Materialは必要ありません。
MaterialAlbedoだけがここにある様な感じで色を設定する事ができます。
設定するときの注意ですが、ModulateはそのNodeだけではなく、このNodeの子に設定されたNodeにまで影響を及ぼします。
このNodeだけの色を変えたい場合は、Self Modulateで色を設定する様にします。

最後は、ユーザーインターフェイス系のNodeで「Label」のインスペクターになります。
Label」は画面上に文字を表示するためのNodeです。

文字の色は、Theme OverridesColorsFont Colorで設定します。

文字に縁取り線をつけたい場合は、Font Outline Colorで設定します。
ただ、縁取り線を表示するのはこれだけでは不十分で、下にあるConstansOutline Sizeで縁取り線の太さ
も忘れずに設定する必要があります。




Unity経験者のGodotの遊び方:スライダーについて

オブジェクトプールについては、アセットライブラリに、ズバリ「Godot Object Pool」がありました。 godotengine.org

残念ながらGodot3を対象にしているようで、そのまま流用はできない様ですが、これをベースに移植していこうと思います。

オブジェクトプールの解決の道筋は立ったのですが、 Color Change PanicをGodot化していくために、もう一つ調べないといけないことがあるのに気づきました。

それはUnityでいうSliderです。

Color Change Panicでは色変わりやキャノン、弾をうつまでのリキャスト、など様々にSliderを使っています。 それに、これから他のゲームを作るためにもSliderは必要になってきますので、最初にしっかり勉強しておく必要があります。

公式ドキュメントを読んでいると、どうやらGodotではSliderはProgress Barという様です。 さらに、好きな見た目を使えるTexture Progress Barというものもある様ですが、まずは基本のProgress Barからしっかり調べてみます。

Progress Bar

公式ドキュメント docs.godotengine.org

よく使うプロパティの設定は、

Fill ModeValueの値に応じてスライダーがどのように溜まってくるかの設定ができます。

 Fill Begin to End:左から右にバーが溜まります。
 Fill End to Begin:右から左にバーが溜まります。
 Fill Top to Buttom:上から下にバーが溜まります。
 Fill Buttom to Top:下から上にバーが溜まります。

Show Percentage:スライダーのValue値の数値表示をon/offできます。
Min ValueValueの最小値。
Max ValueValueの最大値。
StepValue値の増減量。
Value:この値をスクリプトで操作することで、スライダーが変化します。

スライダーの見た目を変えるには、StylesBackgroundFillを操作します。
両方とも同じ使い方で、<空>の横をクリックしてメニューを表示させて設定します。

色だけを変更したい場合は「新規 StyleBoxFlat」を選択します。

BG Colorで好きな色に変更します。

Texture Progress Bar

公式ドキュメント docs.godotengine.org

基本的にはProgress Barと同じですが、画像をスライダーにすることを前提にしているためか、色々と設定が追加されています。

Fill Modeの種類も増えています。
増えたのは以下の様な項目で、円形スライダーを作るのに役に立ちそうです。
 Clockwise:時計方向にバーが溜まります。
 Counter Clockwise:反時計方向にバーが溜まります。
 Bilinear(Left and Right):真ん中から左右方向にバーが溜まります。
 Bilinear(Top and Bottom):真ん中から上下方向にバーが溜まります。
 Clockwise and Counter Clockwise:時計方向と反時計方向の両方にバーが溜まります。

TexturesUnderProgressの2つがあり、Underに設定したテクスチャーがスライダーの背景になり、
Progressのテクスチャーがスライダーのバー部分になります。

Tintでテクスチャーに色補正をかけることができます。

先ほどの図の例では、TexturesUnderProgressには同じ丸いイラストがセットされています。
それをTintで色補正をかけ、Underは灰色、Progressは水色にしています。

他には、Radial FillInitial Angleで、Fill ModeBilinear(Left and Right)Bilinear(Top and Bottom)
もしくは、Clockwise and Counter Clockwiseの時に、円形の開始角度を設定できます。



スクリプトで制御する

valueの値を変えてやれば良いので、例えば、この様な

extends CanvasLayer

func _process(delta):
    if Input.is_action_just_pressed("ui_accept"):
        $TextureProgressBar.value += 1

Enterを押下するたびにvalueを+1すれば、スライダーもvalue値に応じて変化してくれます。




Unity経験者のGodotの遊び方:乱数・awaitについて

昨日、GameManagerらしきものをGodotで作成することはできたので、Enemy.gd(これで、Enemyスクリプトを表すらしいです。)
を一気に進めていこうと思います。

Unityで作成した時のEnemyControllerには、下記の関数がありました。
・SetParameter関数
・SetEnemyColor関数
・Moving関数
・ScaleVertically関数
・OnCollisionEnter2D関数

EnemyGeneratorクラスからEnemyが生成されるタイミングで、SetParameter関数とSetEnemyColor関数が呼ばれ、
GameManagerの設定値からEnemyの初期設定を行います。

Moving関数はEnemyを徐々に下に移動させる機能と、色変わりもしくは成長する機能を持ちます。
ScaleVertically関数は、画面に動きが欲しかったので、EnemyをゆっくりY方向に伸縮させています。

今日のところは、これらをGDScript化していきます。

まずは、変数の宣言に、SetParameter関数とSetEnemyColor関数を作ります。

extends Area2D

@export var Color_Change_Span :float #敵の色が変わるスピード
@export var Growth_Span :float #敵が大きくなるスピード
@export var Drop_Down_Speed :float #敵が落ちてくるスピード
@export var Living_Time :float #敵が生きている時間

var Next_Color_Change_Time :float #次に色が変わる時間
var Next_Growth_Time :float #次に大きくなる時間

var Enemy_Color :int #敵の色
var Enemy_Size :int #敵の大きさ

@export var Puru_puru_Time :float #ぷるぷるタイム

var isIncreasing :bool =true #呼吸方向判定用
var Base_Scale :Vector3 #基本のスケール

@export var Scale_Change :float #スケールの増減値

@export var Scale_Interval :float #スケール変更の間隔
var Color_Change_Timer :float
var Puru_puru :bool
var Puru_puru_Counter :int
var Color_Changing :bool

var Base_Size :float =0.4 #Enemy_Sizeの基準値
var Base_Growth_Size :float =0.05 #Enemy_Size が1増えた時の成長量の基準値

func _ready():
	_Scale_Vertically()


func _Set_Parameter():
	Living_Time=0
	Color_Change_Span=GameManager.Set_Color_Change_Span-GameManager.Game_Level*0.15
	Drop_Down_Speed=GameManager.Set_Drop_Down_Speed+GameManager.Game_Level*0.02

	Next_Color_Change_Time=Color_Change_Span
	Next_Growth_Time=Growth_Span

	# _Moving()
	_Scale_Vertically()

func _Set_Enemy_Color(color:int):#intを引数で受け取る。
	Enemy_Color=color
	$Sprite2D.modulate=Color(GameManager.Colors[Enemy_Color].x,GameManager.Colors[Enemy_Color].y,GameManager.Colors[Enemy_Color].z,1)
	pass

SetParameter関数

func _Set_Parameter():
	Living_Time=0
	Color_Change_Span=GameManager.Set_Color_Change_Span-GameManager.Game_Level*0.15
	Drop_Down_Speed=GameManager.Set_Drop_Down_Speed+GameManager.Game_Level*0.02

	Next_Color_Change_Time=Color_Change_Span
	Next_Growth_Time=Growth_Span

	_Scale_Vertically()

ここでは何も難しいことはしていません。

GameManagerというグローバルクラスから、
・Game Level変数 ・・・ゲームレベルを格納
・Set_Color_Change _Span変数 ・・・基本の色変わりの間隔を格納
・Set _Drop _Down_Speed変数 ・・・基本の落下速度を格納
を取得してきて、計算した結果をプライベート変数のColor_Change_Span変数と、Drop_Down_Speed変数に代入しています。


SetEnemyColor関数

func _Set_Enemy_Color(color:int):#intを引数で受け取る。
	Enemy_Color=color
	$Sprite2D.modulate=Color(GameManager.Colors[Enemy_Color].x,GameManager.Colors[Enemy_Color].y,GameManager.Colors[Enemy_Color].z,1)
	pass

ここは昨日作成した通りです。
GameManagerのColors配列変数から色番号に沿って色情報を取得して、Sprite2Dの色を設定しています。

Moving関数 → _physics_process関数

func _physics_process(delta): # ← deltaを渡している。
	Living_Time+=delta
	print("Living_Time ",Living_Time)
	if GameManager.Game_Over:
		#将来、ゲームオーバーの処理を書く場所
		pass

	position.y+=GameManager.Set_Drop_Down_Speed #今だけの暫定。完成時にはDrop_Down_Speedに置き換える。

	if position.y<+5:
		# $QueueFree()
		# Enemyをオブジェクトプールに返す処理を書く
		pass
	

	if Living_Time>Next_Color_Change_Time:

		Color_Change_Timer+=delta

		if Color_Change_Timer>Puru_puru_Time:
			Puru_puru=!Puru_puru
			Color_Change_Timer=0
			Puru_puru_Counter+=1

			if Puru_puru:
				$Sprite2D.scale=Vector2(
					(Base_Size+Enemy_Size*Base_Growth_Size)-Scale_Change/2,
					(Base_Size+Enemy_Size*Base_Growth_Size)-Scale_Change/2)
				
			else:
				$Sprite2D.scale=Vector2(
					(Base_Size+Enemy_Size*Base_Growth_Size)+Scale_Change/2,
					(Base_Size+Enemy_Size*Base_Growth_Size)+Scale_Change/2)

		if Puru_puru_Counter>6:
			Puru_puru_Counter=0
			Puru_puru=false

			match randi_range(0,1):
				0:
					_Set_Enemy_Color(GameManager._Set_Enemy_Color())
					Next_Color_Change_Time+=Color_Change_Span
					pass
				1:
					Enemy_Size+=1
					$Sprite2D.scale=Vector2(
						(Base_Size+Enemy_Size*Base_Growth_Size),
						(Base_Size+Enemy_Size*Base_Growth_Size))

					Next_Color_Change_Time+=Color_Change_Span
					pass||<

今日のメインはここです🤣
Unityではasync/awaitで行っていましたが、よくよく見返せば、別に非同期である必要はなかったので、普通にUnityでいうFixedUpdateの
_physics_process(delta)関数で作る事にしました。
Unityと同じく、ほぼ毎フレームで呼び出される関数です。

func _physics_process(delta): # ← deltaを渡している。
	Living_Time+=delta

いきなり、Unityと異なる場所です。
GodotではTime.deltaTimeはdeltaと書きます。しかも、関数で引数で渡しておかないと関数内で使用できません。

	position.y+=GameManager.Set_Drop_Down_Speed #今だけの暫定。完成時にはDrop_Down_Speedに置き換える。

昨日も書きましたが、Godotではシーン内のノードのプロパティは比較的簡単にアクセスすることができます。
今回は、スクリプトがアタッチされているArea2D自身のpositionを変更したいのですが、

positionはUnityと同じようにTransformの下になるのですが、Godotの場合、positionと書くだけで大丈夫です。
大きな違いは、Unityでは2Dも3DもpostionやscaleはVector3ですが、Godotの場合は2DではpositionもscaleもVector2になります。
Unityの感覚でVector3で計算しようとするとエラーになってしまうので、最初のうちは注意が必要そうです。

			match randi_range(0,1):
				0:
					_Set_Enemy_Color(GameManager._Set_Enemy_Color())
					Next_Color_Change_Time+=Color_Change_Span
					pass
				1:
					Enemy_Size+=1
					$Sprite2D.scale=Vector2(
						(Base_Size+Enemy_Size*Base_Growth_Size),
						(Base_Size+Enemy_Size*Base_Growth_Size))

					Next_Color_Change_Time+=Color_Change_Span
					pass

randi_rangeというのは、乱数を発生させる関数で、引数で範囲を指定します。

randi_range(0,1):

最初の引数が最小値を示し、二番目の引数が最大値を示します。
ですので、この場合は0か1の乱数が作成されます。
少数の乱数を作成したい場合は、

randf_range(0.0,1.0):

この様に、randの次の文字をintのiから、floatのfにかえて引数も少数表記にします。
これで0〜1.0の少数の乱数が作成されます。
次にmatchですが、これはUnityでいうSwitch分岐になります。

つまり、乱数で0か1を作成し、その数字に合わせて、成長か色変わりかを判定する仕組みです。



ScaleVertically関数

func _Scale_Vertically():
	while true:
		if GameManager.Game_Over:
			break
		
		await get_tree().create_timer(Scale_Interval).timeout

		if isIncreasing:
			scale=Vector2(
				(Base_Size+Enemy_Size*Base_Growth_Size),
				(Base_Size+Enemy_Size*Base_Growth_Size)*0.96)
		else:
			scale=Vector2(
				(Base_Size+Enemy_Size*Base_Growth_Size),
				(Base_Size+Enemy_Size*Base_Growth_Size)*1.02)

		# 増減の方向を切り替え
		isIncreasing = not isIncreasing

今日の最後です。
Godotは3.5版から4版の変更でかなり破壊的な改革が入った様で、async・awaitも仕様が変わった様です。
まだしっかりと公式ドキュメントを読みきれていないのですが、どうも、関数内にawaitがあると、その関数は非同期として扱われるようです。
ですので、この関数の様にwhile trueで無限ループする処理を作っても、問題ありませんでした。

await get_tree().create_timer(Scale_Interval).timeout

Godot特有の書き方で、変数宣言したScale_Intervalの秒数のTimerを作成して、そのTimerがタイムアウトするまでawait。
つまり、Scale_Interval秒だけawaitしてくれます。

これで基本の敵の挙動はできたから、次はEnemyGeneratorかなあ🧐
www.youtube.com