2023-10-01に更新

GDScript を覚えようぜ(^~^)?

読了目安:12分

親記事から来た

📖 Godot って何だぜ(^~^)?

はじめに

ramen-tabero-futsu2.png
「 👇 GDScript の説明は 下のリンク先にあるぜ」

📖 Godot Engine 4.2の日本語のドキュメント / スクリプト言語

ノード

202309__godot__30-0005--Node-o2o0.png

ramen-tabero-futsu2.png
「 👆 Godot にあるものは全て ノード(Node)で、
GDScript というのは そのノードを操作するものみたいだな」

Python に似ているが全然別物

ramen-tabero-futsu2.png
「 GDScript は Python に似ているが 全然別物ということだぜ。
新しく覚え直せだぜ」

ramen-tabero-futsu2.png
「 また、GDScript の実行速度は遅いらしいぜ。
GDScript は C++ 言語で書かれたプログラムを呼び出すから、内部的な処理は速いらしいぜ」

kifuwarabe-futsu.png
「 Python と同じ生き方してんな」

Godot の独特な用語: シーン

ohkina-hiyoko-futsu2.png
「 👇 Godot には ノードと シーンという用語が出てくるんだけど、
計算機科学の わたしたちから見ると 造語のクセがあるわよ」

📖 ノードとシーン

kifuwarabe-futsu.png
「 えーと、つまり」

202309__godot__30-0031--GraphTheory.png

.kifuwarabe-futsu.png
「 👆 グラフセオリー(Graph Theory;グラフ理論)の一題材の ツリー・ストラクチャー(Tree Structure;木構造)を
わたしたちは 知っているが」

202309__godot__30-0035--GodotScene.png

kifuwarabe-futsu.png
「 👆 Godot は ツリーのことを シーン(Scene)と言い換えているのかだぜ?」

ramen-tabero-futsu2.png
「 多分そう」

202309__godot__30-0039--SubTree.png

ramen-tabero-futsu2.png
「 👆 さらに 計算機科学の わたしたちは 木の中に含まれるサブツリー(Subtree;部分木)を知っているぜ」

ohkina-hiyoko-futsu2.png
「 部分木もまた 木よね」

202309__godot__30-0043--GodotSceneNest.png

ramen-tabero-futsu2.png
「 👆 Godot では、 シーンもまた ノードになる、という 言い方 をしている」

kifuwarabe-futsu.png
「 じゃあ シーンは ツリーなんだ」

ohkina-hiyoko-futsu2.png
「 シーン、つまり サブツリーのファイルの拡張子は  .tscn のようね」

ramen-tabero-futsu2.png
「 なんて発音するか分からん嫌な拡張子だ…… ティーシーン?」

ohkina-hiyoko-futsu2.png
「 Godot は、 .tscn を再生するプレイヤーなのよ」

GDScript Reference

kifuwarabe-futsu.png
「 👇 GDScript のリファレンスがあるそうだぜ。リンクをメモしておこう」

📖 GDScript reference

なんか 画像素材を1つ用意しろだぜ

ramen-tabero-futsu2.png
「 プログラムのレッスンを進めていくんで、なにか 小さな画像素材を 1つ用意してくれだぜ」

2016_8_6_0_20_30_88_c1.png

kifuwarabe-futsu.png
「 👆 わたしで いいかだぜ?」

ramen-tabero-futsu2.png
「 しかたないな…… じゃあ それで」

新規プロジェクト作成

202309__godot__30-0059--NewProject.png

ramen-tabero-futsu2.png
「 👆 Godot で新規プロジェクトを作成するぜ」

202309__godot__30-0059--o2o0.png

ramen-tabero-futsu2.png
「 👆 最初に選ぶのが その他のノード って どうかしてるよな?」

202309__godot__30-0111--Sprite2D-o2o0.png

ramen-tabero-futsu2.png
「 👆 その中から Sprite2D を選ぶなんて、直観的に無理だぜ」

202309__godot__30-0113--Editor-Sprite2DNode-o2o0.png

ramen-tabero-futsu2.png
「 👆 これが Sprite2D をルート(Root;根)に持つ シーン(※つまりサブツリー)を作成したところだぜ」

202309__godot__30-0120--Texture-o2o0.png

ramen-tabero-futsu2.png
「 👆 テクスチャー欄へ さっきの画像を読み込めだぜ」

新規 GDScript 作成: ハローワールド

202309__godot__30-0125--NewScript-o2o0.png

ramen-tabero-futsu2.png
「 👆 次に 新規スクリプト を作ったらいいのかな?」

202309__godot__30-0127--TemplateEmpty-o2o0.png

ramen-tabero-futsu2.png
「 👆 テンプレートを Object:Empty にしとけだそうだぜ」

202309__godot__30-0129--CodeEditor.png

ramen-tabero-futsu2.png
「 👆 なんか コード・エディターが出てくるな。ここに GDScript を書けばいいのだろう」

kifuwarabe-futsu.png
「 かっこいいコードを書いてくれよ」

extends Sprite2D

func _init():
    print("Hello, world!")

ramen-tabero-futsu2.png
「 👆 サンプル通り書いてみよう」

202309__godot__30-0133--Run-o2o0.png

ramen-tabero-futsu2.png
「 👆 なんか 分からんな…… 現在のものを選択 で」

202309__godot__30-0135--HelloWorld-o2o0.png

ramen-tabero-futsu2.png
「 👆 Hello, world! と出力ビューに表示されたな」

コンストラクター: _init()

kifuwarabe-futsu.png
「 func _init(): メソッドは コンストラクタなんだ」

くるくる回す

202309__godot__30-0145--Rotation.png

extends Sprite2D

var speed = 400
var angular_speed = PI

func _init():
    print("Hello, world!")

func _process(delta):
    rotation += angular_speed * delta

ramen-tabero-futsu2.png
「 👆 サンプル通り書いてみよう。 くるくる回ってるぜ」

時間軸の1つ分の処理: _process(delta)

kifuwarabe-futsu.png
「 func _process(delta): メソッドは 時間 delta 分の処理なんだ」

Tips: Ctrl + Click

202309__godot__30-0149--CtrlClick-o2o0.png

ramen-tabero-futsu2.png
「 👆 Ctrl キーを押しながら コードをクリックすると 説明が出てきたり、定義に飛んだりするようだぜ」

ohkina-hiyoko-futsu2.png
「 そんな裏技仕込むの 止めてほしいわね」

洗濯機の中の衣類のように周る

extends Sprite2D

var speed = 400
var angular_speed = PI

func _init():
    print("Hello, world!")

func _process(delta):
    # その場で ねずみ花火のように くるくる回る
    rotation += angular_speed * delta

    # 洗濯機の中の衣類のように 周る
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta    

📺 動画

ramen-tabero-futsu2.png
「 👆 分かったぜ」

入力

ohkina-hiyoko-futsu2.png
「 👇 次の課題は 入力よ」

📖 プレイヤーの入力を聞く

kifuwarabe-futsu.png
「 入力のインプットを受け取る方法は 2種類あって、
Input シングルトンを使う方法と、 _unhandled_input() コールバック関数を使う方法があるようだぜ」

extends Sprite2D

var speed = 400
var angular_speed = PI

func _init():
    print("Hello, world!")

func _process(delta):
    # その場で ねずみ花火のように くるくる回る
    rotation += angular_speed * delta

    # 洗濯機の中の衣類のように 周る
    var velocity = Vector2.UP.rotated(rotation) * speed
    var movement = velocity * delta

    # 何も押さなければその場で回転
    var direction = 0
    # 左キー押下で頭上の方へ進む
    if Input.is_action_pressed("ui_left"):
        direction = -1
    # 右キー押下で足下の方へ進む
    if Input.is_action_pressed("ui_right"):
        direction = 1
    movement *= direction

    # 移動ベクトルを足す
    position += movement

📺 動画

ramen-tabero-futsu2.png
「 👆 分かったぜ」

202309__godot__30-1304--inputMap-o2o0.png

kifuwarabe-futsu.png
「 プロジェクト設定インプットマップ タブを見れば Input.is_action_pressed() メソッドの引数に何書いたらいいか
自分で調べられるそうだぜ」

ramen-tabero-futsu2.png
「 助かるぜ」

extends Sprite2D

var speed = 400
var angular_speed = PI

func _init():
    print("Hello, world!")

func _process(delta):

    var velocity = Vector2.ZERO

    # 上キーを押していなければ進まない仕組み
    if Input.is_action_pressed("ui_up"):
        velocity = Vector2.UP.rotated(rotation) * speed

    # その場で ねずみ花火のように くるくる回る
    rotation += angular_speed * delta

    # 洗濯機の中の衣類のように 周る
    var movement = velocity * delta

    # 何も押さなければその場で回転
    var direction = 0
    # 左キー押下で頭上の方へ進む
    if Input.is_action_pressed("ui_left"):
        direction = -1
    # 右キー押下で足下の方へ進む
    if Input.is_action_pressed("ui_right"):
        direction = 1
    movement *= direction

    # 移動ベクトルを足す
    position += movement

ramen-tabero-futsu2.png
「 👆 上キーを押していなければ 進まないという仕組みも追加したぜ」

Godot の言うシグナル: イベントハンドラーみたいなもん

ohkina-hiyoko-futsu2.png
「 👇 次の課題は シグナル(Signal;信号)よ」

📖 シグナルの使用

ramen-tabero-futsu2.png
「 イベントハンドラーじゃないのかだぜ? Linux みたいだな」

202309__godot__30-1331--NewScene-o2o0.png

ramen-tabero-futsu2.png
「 👆 新しいシーンを作れとのことだぜ。 シーンって何なんだぜ?」

kifuwarabe-futsu.png
「 サブツリーのルートノード(Root Node;根)なんじゃないか?

202309__godot__30-1333--RootNode-o2o0.png

ohkina-hiyoko-futsu2.png
「 👆 シーンは サブツリーなのよ。 サブツリーのルートをさらに選ぶのよ」

ramen-tabero-futsu2.png
「 シーンなんて用語 造語されたら ぐちゃぐちゃだぜ」

202309__godot__30-1336--2DScene-o2o0.png

ramen-tabero-futsu2.png
「 👆 2D シーン を選べとのことだぜ」

202309__godot__30-1338--Node2DRoot-o2o0.png

ramen-tabero-futsu2.png
「 👆 なんで 2D シーン を選んで、出てくるのが Node2D なんだぜ? 技術的に ぐちゃぐちゃだな」

kifuwarabe-futsu.png
「 べつに 技術をウリにしてないんだろ」

202309__godot__30-1342--AddChildNode-o2o0.png

ramen-tabero-futsu2.png
「 👆 子ノードとして Button を追加しろとのことだぜ」

Node/CanvasItem/Control/BaseButton/Button

kifuwarabe-futsu.png
「 👆 なんか クラス階層図みたいなツリー構造だな。 BaseButton の下に Button が出てくるの カッコ悪いよな」

ohkina-hiyoko-futsu2.png
「 みたい、じゃなくて、クラス階層図なんじゃない?」

202309__godot__30-1347--ButtonSubTree.png

ramen-tabero-futsu2.png
「 👆 あれっ? きふわらべ どこに行ったんだぜ?」

kifuwarabe-futsu.png
「 お父んの方が 別のサブツリーに行ったんだぜ」

ramen-tabero-futsu2.png
「 だいたい わかった」

202309__godot__30-1351--ButtonLabel-o2o0.png

ramen-tabero-futsu2.png
「 👆 フォント・サイズはどこで変えれるんだぜ?」

ohkina-hiyoko-futsu2.png
「 Godot は ノードを主張してるくせに Button のサブ・ノードが見えないじゃない」

kifuwarabe-futsu.png
「 べつに 技術をウリにしてないんだろ」

ramen-tabero-futsu2.png
「 もう 先々 苦労しそうだな」

202309__godot__30-1357--F6Key.png

kifuwarabe-futsu.png
「 👆 F6 キーを打鍵すると サブツリーを 動作テストできるそうだぜ」

ohkina-hiyoko-futsu2.png
「 そんな裏技仕込むの 止めてほしいわね」

202309__godot__30-1401--NodeSignal-o2o0.png

kifuwarabe-futsu.png
「 👆 ノード タブをクリックすると シグナルの一覧が出てくるぜ」

ramen-tabero-futsu2.png
「 お前は インスペクター なんじゃないの?
なんで インスペクターの隣の ノード タブをクリックしたんだぜ?
ノード タブは、インスペクターじゃないってのかだぜ?」

ohkina-hiyoko-futsu2.png
「 ノード タブは インスペクター じゃないのよ」

ramen-tabero-futsu2.png
「 Godot の国語は ぐちゃぐちゃ だな。技術的にクソだ」

kifuwarabe-futsu.png
「 べつに 国語も 技術も ウリにしてないんだろ」

202309__godot__30-1406--pressed-o2o0.png

kifuwarabe-futsu.png
「 👆 BaseButton の下に pressed() メソッドがあるから ダブル・クリックしろだぜ」

ohkina-hiyoko-futsu2.png
「 pressed() は メソッドなの? シグナルなの?」

kifuwarabe-futsu.png
「 pressed() メソッドは pressed メッセージが送られてきたときに実行されるイベントハンドラーなんじゃないか?」

ohkina-hiyoko-futsu2.png
「 本当にそうだろうか?」

202309__godot__30-1413--cutAndPaste-o2o0.png

ramen-tabero-futsu2.png
「 👆 きふわらべが 別のサブツリーに居て アクセスできなかったので、
カット&ペーストで 連れてきたぜ」

kifuwarabe-futsu.png
「 じゃあ 元 居たツリーは 今 どうなってんだぜ?」

202309__godot__30-1415--emptyTree.png

ramen-tabero-futsu2.png
「 👆 エンプティセット(Empty Set;空集合,くうしゅうごう)だぜ」

kifuwarabe-futsu.png
「 保存はできるのか?」

ramen-tabero-futsu2.png
「 不思議な話だが、エンプティセットは 保存できないぜ」

ohkina-hiyoko-futsu2.png
「 ウィンドウを閉じれば 同値よ」

202309__godot__30-1429--signal-o2o0.png

ramen-tabero-futsu2.png
「 👆 センダー(Sender;送信者)である BaseButton の pressed() メソッドが呼び出されたとき、
さらに レシーバー(Receiver;受信者)である Sprite2D の _on_button_pressed メソッドが呼び出される」

ramen-tabero-futsu2.png
「 と考えたらいいんじゃないかだぜ?」

ohkina-hiyoko-futsu2.png
「 シグナルは どこにあんのよ?」

ramen-tabero-futsu2.png
「 Godot の開発者たちは 国語のセンスがないことが分かった。わたしが 言い換えてやるぜ」

202309__godot__30-1436--eventHandler-o2o0.png

kifuwarabe-futsu.png
「 👆 わたしのコードに イベントハンドラーが 勝手に追加されたぜ」

ohkina-hiyoko-futsu2.png
「 じゃあ そこに ボタンが押されたときに やりたい処理を書けばいいのよ」

202309__godot__30-1436--eventHandler-o3o0.png

ramen-tabero-futsu2.png
「 👆 イベントハンドラーのシグネチャーの左横に 緑色の矢印が表示されていて、クリックできるそうだぜ」

202309__godot__30-1441--connection.png

ramen-tabero-futsu2.png
「 👆 説明が出てきた。まあ そうなんだろうな」

func _on_button_pressed():
    # 働いてたら休む。
    # 休んでたら働く。
    set_process(not is_processing())

ramen-tabero-futsu2.png
「 👆 ボタンを押下したときのコードを サンプルに従って書いたぜ。
メソッド名が悪いよな。意味が分からん。まあ Godot は、国語がウリではないから 仕方ない」

202309__godot__30-1448--run.png

ramen-tabero-futsu2.png
「 👆 あれっ? ボタンが無いぜ?」

kifuwarabe-futsu.png
「 エンプティセットを保存してないから、古いサブツリーを実行しているのでは?」

ramen-tabero-futsu2.png
「 実行ボタンをクリックしたときに実行される サブツリー は、どこで変更できる?」

202309__godot__30-1453--applicationRun.png

kifuwarabe-futsu.png
「 👆 プロジェクト設定の中を探していけば あるぜ」

202309__godot__30-1455--run.png

ramen-tabero-futsu2.png
「 👆 よしでけた」

Ctrl + F1

kifuwarabe-futsu.png
「 エディターに戻って Ctrl + F1 キーを押してみろだぜ」

202309__godot__30-1517--CtrlF7.png

ramen-tabero-futsu2.png
「 👆 お前が出てきたけど……」

ohkina-hiyoko-futsu2.png
「 ルートに戻ったんじゃないの?」

ramen-tabero-futsu2.png
「 メイン・シーンの方に戻ってほしいぜ」

Timer

202309__godot__30-1522--Timer-o2o0.png

ramen-tabero-futsu2.png
「 👆 サンプルに従って タイマーを追加するぜ」

202309__godot__30-1524--AutoStart-o2o0.png

ramen-tabero-futsu2.png
「 👆 Autostart をオンに設定」

202309__godot__30-1530--ScriptButton.png

kifuwarabe-futsu.png
「 👆 上図のボタンをクリックすると スクリプトのページが開くそうだぜ」

extends Sprite2D

var speed = 400
var angular_speed = PI

func _init():
    print("Hello, world!")

func _process(delta):

    var velocity = Vector2.ZERO

    # 上キーを押していなければ進まない仕組み
    if Input.is_action_pressed("ui_up"):
        velocity = Vector2.UP.rotated(rotation) * speed

    # その場で ねずみ花火のように くるくる回る
    rotation += angular_speed * delta

    # 洗濯機の中の衣類のように 周る
    var movement = velocity * delta

    # 何も押さなければその場で回転
    var direction = 0
    # 左キー押下で頭上の方へ進む
    if Input.is_action_pressed("ui_left"):
        direction = -1
    # 右キー押下で足下の方へ進む
    if Input.is_action_pressed("ui_right"):
        direction = 1
    movement *= direction

    # 移動ベクトルを足す
    position += movement


func _on_button_pressed():
    # 働いてたら休む。
    # 休んでたら働く。
    set_process(not is_processing())

# サブツリーが全てインスタンス化されたときに呼び出される
func _ready():
    # タイマーノード取得
    var timer = get_node("Timer")
    # timer ソースの timeout シグナルに _on_timer_timerout メソッドを接続
    timer.timeout.connect(_on_timer_timeout)

func _on_timer_timeout():
    # 可視性を反転
    visible = not visible

ramen-tabero-futsu2.png
「 👆 全部のコード載せたろ」

📺 動画

カスタムシグナル

ohkina-hiyoko-futsu2.png
「 👇 シグナルを自作する方法の解説も載ってるわよ」

📖 カスタムシグナル

このあと

ramen-tabero-futsu2.png
「 このあとも チュートリアルは続くが、洋ゲーなんで 興味無いんで スキップする!」

kifuwarabe-futsu.png
「 よっしゃ!」

ohkina-hiyoko-futsu2.png
「 じゃあ 本題へ戻りましょう

親記事へ戻る

📖 Godot って何だぜ(^~^)?

以下、独習

📖 GDScript reference

列挙型

ramen-tabero-futsu2.png
「 Godot に 列挙型は有るのかだぜ?」

kifuwarabe-futsu.png
「 👇 書き方がいくつか有るようだぜ。これを読んで分かるか?」

📖 Enum

enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}

func _ready():
    # Access values with Name.KEY, prints '5'
    print(State.STATE_JUMP)
    # Use constant dictionary functions
    # prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
    print(State.keys())

ramen-tabero-futsu2.png
「 あとは 調べてみるぜ」

別ファイルで定義した列挙型をインポートするには?

ramen-tabero-futsu2.png
「 別ファイルで定義した列挙型をインポートするには どうやったらいいんだぜ?」

kifuwarabe-futsu.png
「 👇 書き方がいくつか有るようだぜ。これを読んで分かるか?」

📖 How to export an enum and import to another script

# Author: samsfacee

# state_machine.gd
extends Node2D
class_name StateMachine
enum State { STATE_STANDING, STATE_JUMPING, STATE_DUCKING, STATE_DIVING}

# player.gd
var state = StateMachine.State.STATE_STANDING

ramen-tabero-futsu2.png
「 クラスに名前を付けて グローバルに公開するのか。それ以外の方法は?」

📖 How to declare a global named enum?

kifuwarabe-futsu.png
「 👇 preload を使う方法がありそうだぜ」

# Author:  Zylann

const MyNamedEnum = preload("path/to/MyNamedEnum.gd")

func _ready():
    print(MyNamedEnum.TYPE1)

ramen-tabero-futsu2.png
「 あとは 調べてみるぜ」

別ファイルで定義されたクラスを生成するには?

ramen-tabero-futsu2.png
「 別ファイルで定義されたクラスを生成するには どうやったらいいんだぜ?」

kifuwarabe-futsu.png
「 👇 この記事が参考になるかだぜ?」

📖 class_nameを使用した新しいオブジェクトのインスタンス化は、preload()関数やload()関数とは異なりますか?

Question: Robotex
Answer: klaas

あるファイルでクラスを定義:

extends Object
class_name MyClass

生成方法1:

# 名前付きスクリプトは起動時にグローバルに登録されます。
var obj = MyClass.new()

生成方法2:

# プリロードされたスクリプトは、コンパイル時にクラスの var または const にロードされます。
# これらはグローバルにアクセス可能ではありません。
var MyClass = preload("MyClass.gd")
var obj = MyClass.new()

生成方法3:

# ロードされたスクリプトは、実行時にクラス変数にロードされます。
# これらはグローバルにアクセス可能ではありません。
var MyClass = load("MyClass.gd")
var obj = MyClass.new()

ramen-tabero-futsu2.png
「 あとは 調べてみるぜ」

.

ツイッターでシェア
みんなに共有、忘れないようにメモ

むずでょ

光速のアカウント凍結されちゃったんで……。ゲームプログラムを独習中なんだぜ☆電王戦IIに出た棋士もコンピューターもみんな好きだぜ☆▲(パソコン将棋)WCSC29一次予選36位、SDT5予選42位▲(パソコン囲碁)AI竜星戦予選16位

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント