おはよう。Unityをいじって半年が過ぎた。
半年を過ぎてもわからない部分も多く、日々発見だ。
わかる分野にて記述できるところのみ書く。
どんなゲームや記事、作曲他を作るにせよ、私は以下の手順でやっている。
- とりあえず動けばいい、重くてもいい
- 少しずつ軽く、作業を分け、まとめ、整えていく
少しでも軽量化しながら、分散化を狙いとしてプログラム組み立てをまとめていく。
前提:C#プログラミングの基本を知るべし

大前提としてC#プログラミングを知っていなければ話にならぬ。
C#+Unityに関する教材は書店で売っているから、手に取るべし。
C#専門の教科書もあるけど、Unity使用のみが目的なら、
C#専用よりもUnity専用の本を購入したほうがいい。
私はUnityの教科書を手に入れた。
自分が作るゲームで欲するなら二冊以上持つべきだろうが、
私は一冊あれば十分だと思っている。
どのUnity本もC#の基本について記載されているから、
書店でいろいろ立ち読みし「これだ」思った本を選ぼう。
本を購入する理由はノートにプログラムを書いて、体で覚えるためだ。
YOUTUBEにある動画を見てもいいけど、
一冊手に取って、ノートにプログラミング言語を書いた方が体で覚える。
体で覚えるとは暗記でなく「いざというとき使える」状態だ。
体に暗記させるという意味で使える。
体への暗記は動画より本を通して書き写す方が身につく。
主にノートで書くべき項目はC#スクリプトの基本部分だ。
int,float,string,bool,Vector,List,if……専門用語を体で覚えるべし。
まず基本がついていないと、プログラミングの話に入れない。
スクリプトはひとまとめと分散のどちらがいい?

私がプログラミングを通して悩んだ部分が、
できる限り一つにまとめるか、役割事に分けるかだ。
スクリプトを一つにまとめたほうが、Unityへの負担も少なくなり、
プレイ中もサクサク遊べるだろうと考える。
一方で分散して役割を細かく分けていけば、
一つにまとめるよりも重くなるだろうが、役割を分けているからわかりやすい。
以前使っていたウディタは良く分散化できていた。
ウディタのように分散化できていればなあ……
Unityを使いながら学び、やっと分散化のコツが見えた。
publicとinspector(インスペクター)のバランスを考える。
C#でpublicを書いたら、インスペクター上で変数に記載できたり、
他のゲームオブジェクトを代入できたりする。
例えばゲームマネージャーなるゲームオブジェクトを作ったとき、
gamemanagerという名前のスクリプトを入れたとしよう。
そこにプレイヤーの動き、HP表示、音楽、その他諸々入れると、
ずらっと表示の違う項目が並びすぎて、混乱をきたす。
音楽は音楽、プレイヤーの動きは動きと分けたほうがいい。
一方で状況によってどうしても分けられないときがある。
例:スタート時にならすSE、スタートコルーチンを使って初期表示を出すとき、
どうしても外からファイルを持ってくると、エラーが生じる(オブジェクト空エラー)。
分けられないときだけ、一つにまとめてしまう。
後は役割事に分けてしまうのがいい。
あるスクリプトを使って別のスクリプトでSEを再生
分散化として知らねばならぬ方法として、
あるスクリプトAを作り、Bというスクリプトにて使う場合だ。
ウディタだと「イベントの挿入」であり、
コモンセルフの1から9まで外部から引っ張ってこれる状態だ。
Unityの場合、あるスクリプトAにprivateでなくpublicと書いていれば、
いくらでも外部ファイルへ引っ張ってこれる。
※privateでも[SerializeField] private 〇〇;と、
[SerializeField] をつければ外部ファイルを引っ張ってこれる。
外部を利用してon/off(true/false)設定、se持ち込み、敵Hp代入など、
いくらでも外から持ち込められるわけだ。
例としてプレイヤーが攻撃やジャンプ、敵にあたったときなど、
動作のたびに音(SE)を鳴らす方法について記していこう。
音を鳴らす方法については、私以外のサイトでも説明している。
「統括ファイル」から「プレイヤーファイルに指示を送る」という、
外部委託といえばいいのかな。
準備として二つのスクリプトファイルと必要なオブジェクトを用意する。
下記画像はGameDirectorオブジェクト項目だ。
赤いアイコンの右部分にGameDirectorと書いてある。確認してほしい。

二つのスクリプトGameDirectorスクリプトとSoundManagerスクリプト。
※GameDirectorはオブジェクトとスクリプト、どちらも同じ名前にしている。
混乱するから注意して文章を読んでほしい。
そしてPlayerオブジェクトとPlMoveスクリプト

プレイヤーを動かす場合、スクリプトが重要となる。
始めに音声ファイルスクリプトから見て行こう。
必要な部分だけファイルから抜き出していった。
public class SoundManager : MonoBehaviour
{
private AudioSource audioS ;
[SerializeField] public AudioClip attackSE;
[SerializeField] public AudioClip attacksubSE;
[SerializeField] public AudioClip damageSE;
[SerializeField] public AudioClip jumpSE;
[SerializeField] public AudioClip deadSE;
[SerializeField] public AudioClip getSE;
void Start()
{
this.audioS = GetComponent<AudioSource>();
}
public void PlaySE(AudioClip clip){
if (clip != null){
audioS.PlayOneShot(clip, 0.5f);
} else {
Debug.Log("SEが入ってないよ");
}
} //void PlaySE
}
[SerializeField] public AudioClip attackSE;[SerializeField]が入っているので、privateでも十分だが、
どちらの作業にしろ、外から音声ファイルを入れねばならぬ。

二重丸◎部分をクリックし、音声ファイルを選べる。
別のファイルを入れたり、作り直したりするとき、
ここが空欄になるので、いちいち入れなければならないところが面倒だ。
音声ファイルを入れた場合、スクリプトをよく見ていくと、
private AudioSource audioS ;
void Start() { this.audioS = GetComponent<AudioSource>(); }
最初はprivateかpublicかを選ばなければならない。
次に使用コンポーネント(部品)ことAudioSourceを代入する。
現在GameDirector上で使える部品として、
インスペクター上にあるものすべて=
Transform,GameDirector(スクリプト),SoundManeger(スクリプト),AudioSoureceのみだ。
ここにRigidBody(剛体),Colliderなどを入れたら、
これらもスクリプト内で記述できる。なければエラーが出るだけ。
そしてaudioSは変数名であり、自由に決められる部分。
ウディタでいうコモンセルフ11、12みたいなものだ。
そしてスタート時にGetComponent<AudioSource>();と書くと、
audioSはAudioSource部品を使えますよと宣言し、以降の展開ができる。
public void PlaySE(AudioClip clip)
{ if (clip != null){ audioS.PlayOneShot(clip, 0.5f); }
else { Debug.Log("SEが入ってないよ"); } } //void PlaySE
次にpublic void PlaySE(AudioClip clip)と書いている。
publicと書いた部分が重要。publicと書かねば外部へ持ち込みできない。
privateだと絶対持ち込みできず、エラーが起きる。
voidは空という意味で、あまり気にしなくていい。
C#スクリプトを勉強すると、色々応用できるだろう。
PlaySEはただの箱・変数名であり、
PlaySE(AudioClip clip)と記載したところも重要。
()が空白だと、計算とかいろいろできるんだ(説明しにくいな)、
(AudioClip clip →部品名 変数名)と入れると、
外部から持ってくるとき、そのまま使えるんだ。
実例を見たほうが早い。まずPlayerオブジェクトに移り、
Plmoveスクリプトにて「外部から持ち込むよ」宣言しよう。
Plmoveスクリプトの中身一部だ。

ゲームキャラ移動スクリプトも合わせて置く
public class PlMove : MonoBehaviour{
[SerializeField] public GameObject director;
GameDirector damageHP ;
SoundManager soundM;
private bool attacking ;
void Start()
{
damageHP = director.GetComponent<GameDirector>();
soundM = director.GetComponent<SoundManager>();
} //Start
if( Input.GetButtonDown("Fire1") ) {
attacking = true;
}
if( attacking ) {
soundM.PlaySE(soundM.attackSE);
attacking = false;
}
}
[SerializeField] public GameObject director;[SerializeField] を入れていれば、privateでいい。
役割はGameDirectorオブジェクトを代入する。

赤く囲んだところにGameDirectorオブジェクトを入れる。
SoundManager soundM;
void Start() { soundM = director.GetComponent<SoundManager>(); } //Start
スクリプトの型と変数名を入れる。
スタート時にsoundM = director.GetComponent<SoundManager>();と代入する。
GameDirectorオブジェクトに入っている
SoundManagerスクリプトを、PlMoveスクリプトで使えるようになった。
if( Input.GetButtonDown("Fire1") ) { attacking = true; }
if( attacking ) { soundM.PlaySE(soundM.attackSE); attacking = false; }
GetButtonDown(“Fire1”)とはボタンだ。
私はウインドウズを使っているから、左Ctrlキーを押すと発動する。

プロジェクト設定から入力マネージャーを開けば設定できる。
(bool) attacking = true;はbool値であり、trueかfalseしかない。
数字のintにして、0か1でも変えがきく。
今、trueだから次が発動できる。
soundM.PlaySE(soundM.attackSE);
外部ファイルの最も重要な部分だ。
まずsoundM.と記載している。
soundM = director.GetComponent<SoundManager>();で、
directorはGameDirectorオブジェクトだ。
先ほどのSoundManagerスクリプトにPlaySEが入っている。
PlaySEをもう一度出すと、 PlaySE(AudioClip clip)だ。
()の中身はAudioClip部品で変数名はclipだよと書いている。
PlaySE(soundM.〇〇);
soundM.attackSE
〇〇の中身はSoundManagerで決めたatttackSEやgetSEなどを代入できる。
soundM.PlaySE(soundM.attackSE);を正式に書く場合、
director.GetComponent<SoundManager>();.PlaySE(director.GetComponent<SoundManager>();.attackSE);
Update部分でGetComponentを使うと重たくなるし、何より長い。
更新するたびにいちいち呼ぶのだから。
スタート時にGetComponentを使って、少しでも容量を軽くできる。
(専門用語でキャッシュという)
上記のやり方を体で理解できれば、
一つのファイルにあれこれ詰めこまず分散できる。
最初は何をやっているかわからないと思うが、
一つずつ役割を理解し、ノートに書いて、体で覚えてほしい。
もちろん私のやり方は一つであり、他にもやり方がある。
参照として置いておくので、ぜひ比べて使いやすいやり方をやってほしい。
インスタンスで別のスクリプトを呼び出す

下記参照サイトにて「ほかのスクリプトを呼び出す」項目があり、
instanceを使えばわざわざgameobjectをアタッチしなくても、
スクリプトのまま入れられると分かった。

上記スクリプトは2Dにおいて
主人公が攻撃ボタンを押し、攻撃部分と敵があたったら反応する形だ。
GameManager.csとEnemyManager.cs二つのスクリプトを用意する。
Enemyにて攻撃タグ(ここではWeapon)と敵が当たった場合(トリガー判定)、
GameManagerスクリプトを呼び出して、何かしらの反応させたいわけだ。
ここでの反応として、敵のHPを減らす。
ポイントとして他のスクリプトに参照させたいスクリプトについて、
始めの段階で次の定義を行う。
public static 参照させたいスクリプト名(ここではGameManager).instance;
※staticは継承を意味し、別のシーンに切り替わっても残る状態を示す。
例えば自分のHPを別のステージ(シーン)に切り替えたとき、
同じ値でいさせたい場合にstaticを使う。
staticを使わないと、シーンが切り替わるたびに前HPを引き継げず、
新しいシーンで設定した新しいHPになってしまう。
次にinstanceしたスクリプトについて、Awake(最盛時に即時、一度のみ起動)関数にて、
if(instance == null) instance = this;
if文は一行なら{}がいらない。
instanceが空(最初は空白で定義しているから当たり前)なら、
今からinstanceをこれ(ここではGameManager.cs)にします。
きちんと宣言しておかないと、
「オブジェクトが空ですよ(Null ReferenceException)」エラーが起き、
ゲームオブジェクトファイルを参照できなくなってしまう。
instance = this;と置いた後、EnemyManagerスクリプトを開き、
条件式によってGameManagerスクリプトを引っ張ってくる際、
参照させたいスクリプト名.instance.参照部分の関数や変数(int値やbool値など)など;
入れると、参照部分の関数あるいは変数をきちんと
publoc設定(privateでは働かない)している場合、きちんと働く。
インスタンスを使えば、
わざわざゲームオブジェクトファイルをアタッチせずとも働くと分かった。
場合によってはゲームオブジェクトファイルを参照したほうがやりやすい時もある。
試行錯誤しながら作っていこう。
※関連記事で変数などの参照方法も乗っている
効率化のための知恵
ゲーム作りにおいて、最初から効率化を目指してはいけない。
最初は非効率かつ重たくていいから、とにかく流れを作る。
ある程度終わりが見えてきたり、効率化の知恵がついたときに行う。
効率化を行うためには
それぞれの役割や意味を知る
例えばUpdateとFixedUpdateでは同じUpdateながらも役割が多少異なる。
Updateは不均一に、FixedUpdateは均一に更新してくれる。
そこでキーボタンはupdateに、物体の移動(キャラの動き)はFixedと分ける。
それぞれの役割を知ると、無駄なプログラムを減らせる。
キャッシュ化させておく
キャッシュ化とはvoid StartやAwakeにてあらかじめ、
代入など処理を行ってしまう方法だ。
だが注意も必要だ。すべてがキャッシュできるとは限らない。
例えばrigidbody.velocityといった速度の更新、
transformも動くキャラなら少々重くてもupdateに置かないとバグが起きる。
軽量化に関するサイトを載せて置くので、参照してほしい。
知識をつければ、より軽く早くゲームを遊べる。
ぜひ効率化分散化そして頭の整理も行って、サクサクなゲームを一緒に作ろう。
おすすめUnity本がありすぎて困る

最後におすすめUnity本や動画、サイトいっぱいある。
ロジックラボさんはもちろん、スタジオしまづさんは今でもよく参考にしている。
書籍だと上記ゲームの作り方が役に立つ。
アニメーションのやり方が他と違って参考になった。
他にも3Dなら3D専門の本など色々ある。
今回の記事で載せた動画はすべて参考になる。
ただ真似をするだけでなく、ノートを容姿いて一つずつプログラムを手書きし、
後で検索サイトを使って、言葉を一つずつ抑えるといい。
書籍においてあるなら必ず立ち読みし、
自分がどういうゲームを作りたいかを検討しよう。
ばいスクリプトの観方についてはこちらを読んでくれ。
かなり役に立つはずだ。
