おはよう。ブログのアクセス解析を見ていたら、
「Unity スクリプト 書き方」があがっていた。
自分もスクリプトの書き方に悩んだ人間なので、
自分がいかにして30日程度でサクサクできるようになったか、書いていく。
参考にしてほしい。
プログラミングは30日もあればサクサクできる

プログラミングは最初、何をすればいいか頭が真っ白になる。
30日もあれば大体サクサクできる。もちろん時間をかけるともっとサクサクできる。
スクリプトリファレンスやほかのサイト・動画を山荘しながら、
目的のプログラムを創り上げていくといいだろう……
問題はどこを探しても「自分の求めるプログラムがない」ときだ。
求めるプログラムがない場合、自作しなければならない。
自作のコツも含めて「初めてスクリプトに接する人」対象に書いていく。
一冊の本を購入し、プログラムを直接書き込まない!

まずはUnityに関する本を一冊購入する。
新規本でもいいし古本屋で手に入れてもいい。
たいていのUnity本は初めて手を出す人に合わせ、
必ず簡単なスクリプトの説明を載せている。
アマゾン:たのしい2Dゲームの作り方 Unityではじめるゲーム開発入門
スクリプトは「ただ記述」しても覚えられない

スクリプトの書き方について、ただ読むだけではいけない。
実際に書く作業は大切だけど、まず書くべき場所はコードでなく紙だ。
ペンとノートを用意し、紙にスクリプトの基本記述を書いていこう。
体にintやfloatなどスクリプトの基本作業を覚えこませるためだ。
実際にパソコンをにらめっこしている時間はもちろんだが、
むしろパソコンをオフにした時間こそ重要だ。
体に叩き込む作業として、毎日30分ほど時間をとって、
ひたすらUnityの教科書やたのしい2Dゲームの作り方に乗っている、
数々のスクリプトを「何も考えないで書き写して」いく。
その時点で意味は分からなくていい。頭の中が「??」でいい。
とにかく書き写して、体に英数字を叩き込んでいく。
その時点でも調べても「意味が分からない」プログラムがある場合、
そこで今すぐ解決しようと考えたら、一気にスランプの始まりだ。
なので、始めはわからなくていいから淡々と書いていく。
このやり方で少しずつ「頭でなく体で」プログラミングに慣れていく。
大切な復習作業

プログラミングの勉強はもう一つ、重要な作業がある。
復習だ。学んだ型番をしっかりと自分の言葉で言えるか?
即座に言えない場合、まだはっきりと自分の中に落とし込めていない。
時間をかけてスクリプト用語を教科書なしで、
自分の口からスラスラいえるようになれるよう、頑張ってほしい。
早く覚えておきたいキャッシュ化

スクリプトの勉強をしながら、同時に早く覚えておきたい処理がある。
キャッシュだ。キャッシュは上記二冊の教科書で記していない。
キャッシュと言っても現金でなく、関数を始める前に型番と変数を宣言する行為だ。
型番:int float string 自分で創った関数名やスクリプトの名前など
変数:型番に代入する「未知数x」のようなもの。
例: int korekara:
intが型番、korekaraが変数、変数は自分のわかりやすい名前で
キャッシュについてはこちらで詳しく書いている。
軽量化につながるので、早いうちに覚えておいた方がいい。
早く覚えたいインスペクターアタッチ

上記画像にある右側の状態。スクリプトにて
[SerializeField] Animator anim;
などを入れた状態で、インスペクターから値を入れると、
スクリプトの記述を一部省略できる。
スクリプトは短い内容ほど、未来の自分にとって扱いやすいので、
ぜひ[SerializeField]をおさえておこう。
インスペクターアタッチは自分の作成したスクリプトも入れられる。
別のスクリプト参照もできるわけだ。
後はもう一つ、スクリプトリファレンスを参照にしていくが、
はじめのうちは何を書いているか、さっぱりわからない。
なので人様のやり方を参照したうえで、
「これはなんだろう」思ったときに使うといい。
注意:Updateの扱い(一度のみ発動させたい)

UnityにおいてUpdate系は注意が必要だ。
Updateは何度もぐるぐる繰り返すからだ。
扉を開けたら音声を発動させるプログラムにて、
Update内でやると、繰り返しぐるぐる発動するため、音声がかき消されてしまう。
Update内で一度だけしか発動させたくない。
それこそStart関数のように一度のみ発動させたい場合、
下記プログラムのようにしなければならぬ。
private bool dooropen ;//初期値はfalse
void Update(){
if(dooropen){
dooropen = false; //即座にfalseへ変えて、繰り返し処理を防ぐ。
//ここに何かしらの処理
}
}//Update
何かしらの処理でdooropenがtrueになり(下記のtrigger/collision参照)、
trueの時のみUpdateが働く場合、何度も繰り返し発動する。
一度のみしたい場合、boolを上手く使って発動を抑えなければならない。
注意2:Triggerとcollision「スクリプト」での大きな違い

ゲーム作成にて当たり判定を生み出すTriggerとCollisionは抑えておこう。
トリガーは上記画像で赤線引いた箇所にあるチェックボックスに、
チェックマークをつければいい。
違いとしてtriggerはすり抜けられるが、collisionはぶつかる(すり抜け不可)。
void OnCollisionEnter2D(Collision2D col){
ItemManager iteM = col.gameObject.GetComponent<ItemManager>();
dooropen = true;
}
//接触で起動
void OnTriggerEnter2D(Collider2D tri){
ItemManager iteM = tri.gameObject.GetComponent<ItemManager>();
}
//すり抜けで起動、しかしCollisionEnterも同時に起動
スクリプト記述にて注意すべき箇所がある。
collisionは「物理接触」のみ反応するが、
triggerは「すり抜け」だけでなく「物理接触」も起動する。
triggerだと2度起動するわけだ。
入室/退室などドア系プログラムを働かせるとき、
Triggerでなくcollisionにしておかないと二度起動するため、
本来働かせたいプログラムが働かない場合がある。
例えばドアを開けるときに音声プログラムを入れたとする。
音声プログラムを入れた後、部屋(シーン移動)に入る仕組みだ。
トリガーだと二度働くので音声が途中で切られ、いきなりドアを開ける。
コリジョンだと一度しか働かないので、音声が終わるまで働く。
絶対すべきDebug.Log();

Unityはエラーとの闘いだ。
基本エラーについてはネットで調べると、大まかな予想がつく。
私を悩ませるエラー代表はNullReferenceExceptionだ。
特に参照している、値を入れているはずなのに、nullエラーが出ると絶望だ。
また一度解決したのに、しばらくたって色々スクリプトをいじった際、
なぜかエラーが出ると絶望に陥る。
なぜnullエラーが起きるのか、スクリプトを分析しなければわからない。
そこでnullエラーの糸口になる指標がDebug.Log();だ。
Debug.Log(“date”);と言った文字列を入れるか、
Debug.Log(vec);など変数を入れるかで、
エラーの出たところに一つずつ入れて検証していく。
nullエラーの代表原因として
- 時間差(変数のおく順番が原因)
- スクリプト付きゲームオブジェクトを非表示(機能なし)にしたまま再生
- 大文字小文字の間違い(大文字と小文字は違う)、スペルミスなど
- あなたの思い込み(自分はすでに値を入れていると勘違い)
とても厄介な原因が「あなたの思い込み」による間違いだ。
思い込みを解消するためにDebug.Log(“eror”);を使って検証していくわけだ。
目的のプログラムを作るために必要な思考
#unity スタッフロールが流れない形のエンディングを作成。スタッフロール(下から上に流れるスタッフ名など)を流すほうはネット検索だとたくさん出てくるが、パット表示されて、別の言葉が表示されるのは、検索しても出てこなかったので、作成に2日費やした。 pic.twitter.com/SoElu5rRoE
— せんけん (@megabi0) July 15, 2023
さてプログラミングの勉強も30日を超えると、少しずつ分かってくる。
わかってきてからが本番だ。
いかにして自分のしたいことをプログラムに変換させるか。
プログラミングを行う際、次の前提を頭に叩き込まねばならぬ。
私たち人間は機械よりもよほど高精度な条件分岐を心がけている。
機械は「言われたこと」しかやらないばかりか、
「書いてないがやってくれるだろう」は通じない。
私たちの脳はプログラムに比べて、かなり高精度な条件分岐を立てている。
例えばジャンプ一つをとっても、私たちは飛ぶ動作をすれば上に上がる。
しかしプログラミングは
- 飛ぶ対象が床と接しているか
- 床と接していないとき、飛べないようにしているか
- 飛ぶ最大値を越えたら、きちんと重力に沿って落ちれるか
最低三つの条件を考えながらプログラミングしなければならぬ。
上記動画で飛ぶときのやり方を載せている。
私阿智人間は高度な条件分岐を楽々こなしている。
言い換えるなら条件分岐すら何も考えないままやっている。
ゲームプログラミングの難しさは人間の当たり前にある。
私たちにとっての当たり前をいかに言語化・筋道立てて説明していくか。
当たり前を一つずつ言葉で説明できるほど、高度なプログラムも仕掛けられる。
当たり前の難しさを知ったところで今から一つ、
一緒にプログラムをみていきたい。
シナリオロールなしで文字表示
#プログラミング エンディングスタッフ名表示、配列forを使って文章を最後まで表示。高校数学「数列」の概念で、Start関数でないと絶対に失敗する(update関数だと、一気に最後まで表示)。
配列の概念をより突き詰めると、効率化の極致にたどり着くのだろうか。 pic.twitter.com/SyNpFQGA3K— せんけん (@megabi0) July 15, 2023
先ほど私のツイートを載せた。スタッフロール作成だ。
文字が出て、新しい文字が出る。私は2日費やした。
スタッフロール作成プログラムはこちら。
名前は「EndingMnager.cs」だ。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class EndingManager : MonoBehaviour
{
[SerializeField][TextArea(5, 7)] string[] textArea;
[SerializeField] string textAreaEnd;
[SerializeField] TMP_Text endText;
private bool rollOn,rollOn1,roll3;
private float times ;
private int i;
private IEnumerator hyouji ;
private WaitForSeconds wfs5 = new WaitForSeconds(5f),
wfs10 = new WaitForSeconds(10f), wfs1 = new WaitForSeconds(1f);
private Coroutine hyoujis;
private const string Credits = "Credit";
private void Start() {
hyouji = Hyouji();
endText.enabled = false;
Invoke(Credits , 3);
}
void Credit(){
endText.enabled = true;
hyoujis = StartCoroutine(hyouji);
}
IEnumerator Hyouji(){
for (i = 0; i < textArea.Length; i++)
{
endText.SetText(textArea[i]);
yield return wfs5 ;
}
endText.enabled = false;
yield return wfs1 ;
endText.enabled = true;
endText.SetText(textAreaEnd);
//ここで終わり
}
}
さて、あなたは私が「何をやっているか」説明できるだろうか。
コピペなら簡単にできる。
※コピペする場合、UIからtextmeshproを入れる必要がある。
参照までにtextmeshproに関する動画を載せる
上記プログラムを簡単に説明すると、
[SerializeField][TextArea(5, 7)] string[] textArea;
[SerializeField] string textAreaEnd;
[SerializeField] TMP_Text endText;
private bool rollOn,rollOn1,roll3;
private float times ;
private int i;
private IEnumerator hyouji ;
private WaitForSeconds wfs5 = new WaitForSeconds(5f),
wfs10 = new WaitForSeconds(10f), wfs1 = new WaitForSeconds(1f);
private Coroutine hyoujis;
private const string Credits = "Credit";
まずは型番と変数、場合によって代入し、キャッシュ化させている。
private void Start() {
hyouji = Hyouji();
endText.enabled = false;
Invoke(Credits , 3);
}
Start関数は初回のみ一度起動だ。
ここでコルーチン系のキャッシュ、そして文字の非表示を行っている。
その後3秒後に関数Creditを発動させる。
void Credit(){
endText.enabled = true;
hyoujis = StartCoroutine(hyouji);
}
3秒後に飛んだ関数で文字を表示に変える。
その後コルーチン関数を使って、秒数つき処理を行う。
IEnumerator Hyouji(){
for (i = 0; i < textArea.Length; i++) {
endText.SetText(textArea[i]);
yield return wfs5 ; }
endText.enabled = false;
yield return wfs1 ;
endText.enabled = true;
endText.SetText(textAreaEnd);
//ここで終わり
}
配列処理を使って、要素の分だけ繰り返し廻す。
回すとき、5秒だけ処理を止める(5秒だけ待つ)。
ループを終えたら次の処理を発動。
一度文字を非表示に、次に別の文字を出す。
とまあ、一部はしょって私が作ったプログラムを言語化した。
あなたは理解できているだろうか。
できているなら、あなたもC#と接して半年くらいたっていると考えている。
私の話が完全に分かるためには、
Invoke関数、IEnumerator、for配列などの意味や役割を、
しっかりと抑えていなければならぬ。
Unityスクリプトには「本には載っていないプログラム」が、
ネットには普通に乗っているからこそ、
「次々と新しい用語を覚えるの、大変だ」気持ちになってしまう。
本当に必要なプログラムはグーグル検索した際、
人様の情報経由で自然と接するから、あれもこれも先取りしなくていい。
言葉で説明するだけでも、かなり紙面をさいた。
一つずつ自分の言葉で何をしているか、きちんと言葉で表していこう。
自分の作業を一つずつ言語化できれば、
自作で「こういうの創りたい」ができるようになる。
どうしても思い通りに動かないプログラム対策

プログラミングをやってて最もストレスをためるときは、
微調整段階で「思った通りにプログラムが働かない」場合だ。
働かない場合、いったんパソコンから離れて、紙とペンを取り出し、
- 自分が目指したい展開
- 今まで自分がしてきたプログラム
二つを紙に書いていくしかない。
書いているうちに「これは」思ったら即座に書き込む。
書き込んだ後、すぐでもいいし時間を置いてでもいいので、
再びプログラミングに戻って試行錯誤してみよう。
ひらめきがおりてくるまでパソコンから離れ、今までしてきた流れを整理しなさいだ。
一緒に頑張っていこう。
