見出し画像

[ドット演算子]オブジェクト指向なんかキライだ!!!~暗黙の了解に16年前から躓いてたことに終止符を[プログラミング/Godot]

ボクは世界に対して一言いいたい。
『オブジェクト指向なんかキライだ!!!』
ふう…すこしスッキリしました。

自分は高校生の頃にC言語でCUIゲームを作って遊んだのがプログラミングの始まりでしたが、その後『Hot Soup Processor』を知ってWindowsのアプリを少し作っていました。
ゲーム好きなボクは次第にゲーム制作をしてみようと思い、参考書とともにオブジェクト指向型に挑戦することになります。
当時の書籍で多かったのが、JavaとC++のものでした。
しかし、オブジェクト指向のもつ『暗黙の了解』という当然知ってますよね?的なルールを知らず挫折を繰り返すことになります。
ずっと独学で他人のコードとにらめっこして、こうじゃないか? とコーディングしてきたボクは長い間この苦しみに囚われていました。

Godot Engineを始めて困ったこと


専門用語がわからないので困ったときに調べられない

当然ですが、プログラミングにも専門用語がありますよね。
独学で学んできたボクは、その専門用語がほとんどわかりません。
自分のベースにあるのは『猫でもわかるプログラミング』のC言語編の知識のみでした。
書籍も買っていました。本として調べられると更にやりやすいですしね。

ここで、問題が発生します。
変数とか関数とかそういうものはわかるのですが、オブジェクト指向という概念自体がわからないため、Googleで検索したい時や、誰かに「コレがわからないんです!助けて!」ができないのです。
コレ = ナニモノ?
プログラミングは日本語化した逆引きがすごくほしいです。

ノード(オブジェクト)間との『プロパティ値』の変更

まず最初にぶち当たったのはオブジェクト(GDScriptではノード)間を越えたプロパティ値そのオブジェクトにアタッチされたスクリプトの変数の操作でした。

別のノードのプロパティ値を変更するには、まずノードのIDを取得する必要があります。
現在のノードの子ノードとしてLabel型のノードがあり、その中のtextプロパティを変更したい場合は以下のように書きます。

extends Node2D

# プログラム実行時に子ノードとしてついている"TestLabel"ノードのIDを取得
onready var label_node = $TestLabel

# 1/60フレームごとに処理
func _process(_delta):
	# 子ノードのTestLabelノードのtextプロパティに"あいうえお"を上書き(代入)する
	label_node.text = "あいうえお"

ここでまず最初につまづきました。
どこかわかりますでしょうか?

label_node.text = "あいうえお"

こちらの部分です。
厳密にいうと label_node.text のところです。
そうなんです。"."このドットの意味がわかりませんでした。
これがよくわからなかったことが後々困ることになっていきます。
結果的にプロパティの値が変わるというのはなんとなく理解できてたんですが、このドット演算子といわれるウンコヤローが何を行っているかが不明でした。
ちなみに16年前にJavaを触ったときからずっとよくわかりませんでした。

こちらの処理なんですが、まずプログラムを実行する準備ができると、

onready var label_node = $TestLabel

onreadyがついたが実行されます。
厳密には _ready() のあとに実行されるみたいなのですが、処理順番がよくわからないため今回ははぶきます。
これは子ノードとして用意したLabel型名前がTestLabelのノードです。
このノードの割当番号を取得します。

Godotではノードのプロパティ値を変更する場合、ノードの割当番号を取得しないといけません。
プログラムを実行するとノードが作成されるのですが、このときに法則に従ってに番号が割り振られるようです。

[コンソール]
出力:
Label:[Label:1298]

コンソールにprint関数を使って出力してみたところ、今回は割当番号が1298になっていたようです。
ここではTestLabelという名前のノードにしてあるのですが、Godot EngineはLabel型の番号が1298としか認識してないようです。
このGodotくんが認識できるように変換してから次の".text"を処理します。

Label:[Label:1298].text = "あいうえお"

この時点ではこのようになっています。
Label:1298に対して、ドットがついている次の名前のプロパティ値(今回はtext)に"あいうえお"を上書きします。

この説明で違和感を覚えた方はプログラミングをしっかり基礎から勉強してきたのだと思います。

この言い方にしたのはボクがGodot Engineを始めてからそういう認識でコードを書いていたからです。
これが後々地獄を見ることになります。

【罠】組み込み関数などにドット演算子を使った場合

問題はここからです。
まずはドット演算子のわかりやすい各サイトの説明文からお読みください。

頼むから日本語でしゃべってくれ!!!!!!?クソがッ!!!(ガチギレ)
このようにプログラミングの世界では専門用語が使われすぎていて、あまりにも意識が高すぎるので日本語ではないです。
プログラミングの説明は日本語に直すのが面倒だったためか、元の英語のまま使われるようになってしまい、このような意味不明の文章になってしまいます。
そして、それが標準化してしまって、どの参考書も意識高い系文章になりボクのような1から10を聞いて1から躓くような人間が量産されるのだと思います。

それを踏まえて以下のコードを御覧ください。
Godotではよくあるプログラムの実行中に新たにノードを作成してどこかのノードにくっつける動作を書いています。

extends Node2D

# 名前が『test』のノードを子ノードとして追加
func _ready():
	# シーンからインスタンス(実体)を作成し、それを変数『test_add_node』に入れる
	var test_add_node = load("res://scene/Test.tscn").instance()
	# このノードに対して子ノード『Test』をくっつける
	add_child(test_add_node)

さて、ここで問題になってくるのは .instance() です。
変数に代入する部分はいらないので一旦排除します。

load("res://scene/Test.tscn").instance()

まずload()関数でシーンファイルからPackedScene型のデータを取得します。
実際に処理された後は[PackedScene:1266]みたいな文字列になります。

[PackedScene:1266].instance()

この状態になった時に、先ほどの話にでてきたプロパティ値を表しているという理論でいくと頭がおかしくなります。
なぜなら、そんなプロパティ値は存在しないからです。だって関数なんだもの。
まずは結果を見てみましょう。

Test:[Control:1271]

こうなりました。
ここでボクはわからなくなります。
[PackedScene:1266]は.instance()に対して何がどうなったのか、皆目検討もつきません。

ボクの思っていたドット演算子のあとの関数は、
.instance()で処理して戻ってきた結果が、[PackedScene:1266]について計算されると思っていたからです。

// 間違いのイメージ //
[PackedScene:1266].(処理結果)

処理結果の中身は数字なのか文字列なのかわからないけど、何かが返ってきてそこでプロパティ値を参照するのだと思っていました。
でも、どこを見てもそんなプロパティ値はありません。
ただ、これはもうほとんどテンプレの書き方だったので、こういうものだと思って書くことにしました。

【落とし穴】やりたいことがやれない、これはどうしたらいいのか?

	# このノードに対して子ノード『Test』をくっつける
	add_child(test_add_node)

こちらのコードは先程取得したインスタンスを子ノードとしてぶら下げなければコード上で扱えません。
インスタンスというのも専門用語なため難しいのですが…
今回は、インスタンスというのは、子ノードが作成されたけども、まだどのノードにもくっついていないため待機している状態だと思ってもらえれば大丈夫です。

この add_child() は子ノードをぶら下げるときに使う組み込み関数です。
()の中の待機状態のインスタンスをぶら下げることができます。

Mainのスクリプトでadd_child()したため、『Test』ノードはMainノードの子として付いた。

さて、ここで問題が起きます。

『Abc』ノードの子ノードとして『Test』ノードをくっつけたい

画像でもあるように『Abc』ノードにぶら下げたい場合はどうしたらいいのでしょうか?
以下は実際に試したものの一部です。
エラーで動かないものや意味のないコードです。

var abc = $Abc
add_child(abc + test_add_node)
var abc = $Abc
add_child(str(abc + test_add_node)
add_child($Abc/test_add_node)

実際にGodotでスクリプトを書いていたり、他の言語をやっていたりする方はどうしたら解決できるかはなんとなく想像つくのではないでしょうか?

しかしボクはこれが全く解決できなくて苦しみました。
結果的に配信中だったのでリスナーさん2人がかりで1時間ほど質疑応答および解説してもらいなんとかなりました。
答えは以下のコードです。

var abc = $Abc
abc.add_child(test_add_node)
なんで?どおして?え?なんでえ?は?うんちぶりぶり

『Abc』ノードに対してプロパティ値になるのなんで?うそだドンドコドーン。
若干知能が低くなりましたが、本当にこんな感じでした。

【解決】ボクの本当に知りたかったこと

あるリスナーさんがこう説明してくださいました。

その関数の add_child() は本来コードを書くときには self.add_child() になります。
selfは自分自身だから省略されて add_child() と書いても大丈夫になっています。
そして、関数のドット演算子の前に付いていた場合、その関数の引数として使うことができます。
GDScriptの場合は、関数内でselfを使う時にドット演算子の前のものが使用できます。

幽chの賢者リスナー

関数内の引数に使える…だと?その発想はなかった…
ただただ、これだけのことだったんです。
でも、ボクはたくさんの書籍を買って色々読んでもダメでした。
標準化された言葉で説明するのではなく、具体的な解決方法から示す方法や噛み砕いて日本語にしたらもっとプログラミングの導入が楽になりそうだなあと感じました。

以下は実際に組んだコードです。

extends Node2D

# 名前が『test』のノードを子ノード『Abc』の下に追加
func _ready():
	# 子ノードの『Abc』の番号を取得
	var abc = $Abc
	# シーンからインスタンス(実体)を作成し、それを変数『test_add_node』に入れる
	var test_add_node = load("res://scene/Test.tscn").instance()
	# 子ノードの『abc』に対して子ノード『Test』をくっつける
	abc.add_child(test_add_node)

最後に

ここまで読んでくださった方はありがとうございました。
ボクは0から1に到達するのがすごく苦手な人間です。
よく1から10まで説明してもなんたらみたいな話は聞きますが、自分の場合、0から1の説明がほしいなって常日頃から思っています。

ボクと同じようにゲームが好きだから、じゃあ自分もゲームが作りたい!
そういう流れでプログラミングを始める方は多いと思います。
でも、自分を含め周りのゲームプログラマーを目指していた友人たちが挫折しやめていくのを幾度となく見てきました。
作るのがつまらないわけではなく、わからない闇だからです。

世間にはRPGツクールやWOLF RPGエディターなどすぐれたゲーム制作ツールやエンジンが出回っています。
それなのになぜゲームが作れないか?
その一つにあまりにも常識すぎてみんなわかってるだろうがあるからじゃないかなと思っています。

もちろんやる気がなくてやらない人はそもそも離れていくのはわかりますが、こうして進もうとしているけども進めない。
そんな自分みたいな存在が減ってほしいとは思っています。

ものすごくわかりやすいアホでもわかるドット演算子の解説がありましたら、どうぞ教えて下さいませ。

いつも配信でアドバイスくれる方々ありがとうございます。
お疲れ様でした。

こちらはGodot Engineで挫折しながらも挫折してリスナーにすくい上げられている初心者ゲーム制作者のアーカイブです。
Youtubeでライブを行っていますのでよろしければ一緒に悩みましょう。

この記事が気に入ったらサポートをしてみませんか?