Unityスクリプト軽量化分散化他スクリプトの関数などを呼び出すコツ

おはよう。Unityをいじって半年が過ぎた

半年を過ぎてもわからない部分も多く、日々発見だ。
わかる分野にて記述できるところのみ書く。

どんなゲームや記事、作曲他を作るにせよ、私は以下の手順でやっている。

  1. とりあえず動けばいい、重くてもいい
  2. 少しずつ軽く、作業を分け、まとめ、整えていく

少しでも軽量化しながら、分散化を狙いとしてプログラム組み立てをまとめていく。

 

前提:C#プログラミングの基本を知るべし

大前提としてC#プログラミングを知っていなければ話にならぬ
C#+Unityに関する教材は書店で売っているから、手に取るべし。

C#専門の教科書もあるけど、Unity使用のみが目的なら、
C#専用よりもUnity専用の本を購入したほうがいい。

私はUnityの教科書を手に入れた。
自分が作るゲームで欲するなら二冊以上持つべきだろうが、
私は一冊あれば十分だと思っている。

どのUnity本もC#の基本について記載されているから、
書店でいろいろ立ち読みし「これだ」思った本を選ぼう

本を購入する理由はノートにプログラムを書いて、体で覚えるためだ。

YOUTUBEにある動画を見てもいいけど、
一冊手に取って、ノートにプログラミング言語を書いた方が体で覚える

体で覚えるとは暗記でなく「いざというとき使える」状態だ。
体に暗記させるという意味で使える。

体への暗記は動画より本を通して書き写す方が身につく。

主にノートで書くべき項目はC#スクリプトの基本部分だ。
int,float,string,bool,Vector,List,if……専門用語を体で覚えるべし。

まず基本がついていないと、プログラミングの話に入れない。

本:Unityの教科書 Unity 2021完全対応版 2D&3Dスマートフォンゲーム入門講座

本:Unityの教科書 Unity 2021完全対応版 2D&3Dスマートフォンゲーム入門講座 [ 北村愛実 ]

 

スクリプトはひとまとめと分散のどちらがいい?

unity画面

私がプログラミングを通して悩んだ部分が、
できる限り一つにまとめるか、役割事に分けるかだ。

スクリプトを一つにまとめたほうが、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スクリプト

yunithiスクリプト

プレイヤーを動かす場合、スクリプトが重要となる。

始めに音声ファイルスクリプトから見て行こう。
必要な部分だけファイルから抜き出していった。

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スクリプトの中身一部だ。

Cスクリプト

ゲームキャラ移動スクリプトも合わせて置く

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

赤く囲んだところに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を使って、少しでも容量を軽くできる。
(専門用語でキャッシュという)

上記のやり方を体で理解できれば、
一つのファイルにあれこれ詰めこまず分散できる。

最初は何をやっているかわからないと思うが、
一つずつ役割を理解し、ノートに書いて、体で覚えてほしい。

もちろん私のやり方は一つであり、他にもやり方がある。
参照として置いておくので、ぜひ比べて使いやすいやり方をやってほしい。

参照:【Unity C#】他のスクリプトの関数を実行する

参照:Unityで他のスクリプトのメソッドを呼び出す3つの方法

 

インスタンスで別のスクリプトを呼び出す

スクリプトunityインスタンス参照

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

スクリプトunityインスタンス参照2

上記スクリプトは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では働かない)している場合、きちんと働く。

インスタンスを使えば、
わざわざゲームオブジェクトファイルをアタッチせずとも働くと分かった。

場合によってはゲームオブジェクトファイルを参照したほうがやりやすい時もある。
試行錯誤しながら作っていこう。

参照:ほかのスクリプトから関数を実行するやり方

※関連記事で変数などの参照方法も乗っている

参照:インスタンス(instance)とは何か、その使い方例

 

効率化のための知恵

ゲーム作りにおいて、最初から効率化を目指してはいけない。

最初は非効率かつ重たくていいから、とにかく流れを作る。
ある程度終わりが見えてきたり、効率化の知恵がついたときに行う。

効率化を行うためには

それぞれの役割や意味を知る

例えばUpdateとFixedUpdateでは同じUpdateながらも役割が多少異なる。

Updateは不均一に、FixedUpdateは均一に更新してくれる。
そこでキーボタンはupdateに、物体の移動(キャラの動き)はFixedと分ける。

それぞれの役割を知ると、無駄なプログラムを減らせる。

 

キャッシュ化させておく

キャッシュ化とはvoid StartやAwakeにてあらかじめ、
代入など処理を行ってしまう方法だ。

だが注意も必要だ。すべてがキャッシュできるとは限らない。
例えばrigidbody.velocityといった速度の更新、
transformも動くキャラなら少々重くてもupdateに置かないとバグが起きる。

軽量化に関するサイトを載せて置くので、参照してほしい。

参照:ゲームの最適化テクニック総まとめ

参照:描画負荷を減らすテクニック

参照:C#スクリプトの処理を軽量化するテクニック

知識をつければ、より軽く早くゲームを遊べる。

ぜひ効率化分散化そして頭の整理も行って、サクサクなゲームを一緒に作ろう。

 

おすすめUnity本がありすぎて困る

最後におすすめUnity本や動画、サイトいっぱいある。
ロジックラボさんはもちろん、スタジオしまづさんは今でもよく参考にしている。

書籍だと上記ゲームの作り方が役に立つ。
アニメーションのやり方が他と違って参考になった。

アマゾン:たのしい2Dゲームの作り方 Unityではじめるゲーム開発入門 単行本

他にも3Dなら3D専門の本など色々ある。

今回の記事で載せた動画はすべて参考になる。
ただ真似をするだけでなく、ノートを容姿いて一つずつプログラムを手書きし、
後で検索サイトを使って、言葉を一つずつ抑えるといい。

書籍においてあるなら必ず立ち読みし、
自分がどういうゲームを作りたいかを検討しよう。

ばいスクリプトの観方についてはこちらを読んでくれ。

かなり役に立つはずだ。

お願い

めがびちゃんからお知らせ♪

お知らせ

megabe-0へ訪問した"本当"の理由

まさか記事の書き形一つでこうなるとは…

お願い1

Writer軽い自己紹介

ティラノスクリプトや小説家になろう、ピクシブ他で物語を書きながら、 「私が気になった事件」の裏側を作家の視点で書いているおっさん。

プロフィール画像は自画像でなく、Megabe-0ブログのマスコット、めがびちゃん。

 

雷が苦手で、光を見ると頭が固まる(元から固い)。 月初めは墓参りと神社参拝を行い、賽銭箱へ1万円を入れた際、とても気持ちがすっきりした。

 

■ 簡単な自分史 ■

0歳:釧路のある病院で生まれる。暇さえあれば母乳を吸って、ご飯を4膳食べても体重が落ちるほど、母のダイエットにものすごく貢献したらしい

 

3歳:行方不明になり、全裸で海を泳ごうとしたところ、いとこのお姉さんに発見され、この世へ留まる

 

8歳:自分のお金でおもちゃのカードを初めて買い、経済を知る。なぜか父親に怒られ、家出するがすぐに見つかる。

 

12歳:学校で給食委員長になる。委員長として初めて全校生徒の前にて演説する際、原稿用紙を忘れてアドリブで笑いを誘いながらも何とかやり過ごし、多くの生徒に名前と顔を覚えてもらう。また、運動会の騎馬戦では変なアドリブを行い、多くの笑いを誘った。

 

18歳:初めて好きな人ができたけれど、告白が恥ずかしくてついにできず、別れたことを今でも根に持っている(妻となる人にははっきり言えてよかった)

 

21歳:大学在学中、アルバイトを始める。人手不足かつとても忙しい日々を過ごしながら「どうせなら自分から楽しいことをしていきたいなあ⇒起業って選択肢があるのか」働き方の選択肢を見つける

 

27歳:自分で作った会社がうまくいかず、一度たたんで都落ち。実家でとことん自分を責める日が続く。「何をやっても駄目だな、お前は」など。自分を責めても自殺ができず、体中から毒素があふれ出て苦しい日々を送る。寝るのも怖かった日々。

 

28歳:「このままじゃいけない」決心を決め、小学校からの勉強をやり直す。高校の勉強で躓きながらも、学び直すうちに「自分は何もわかっていなかったんだなあ」大切な教えに気づかされる。 加えて、小説やイラストなど「今までの自分が手を出さなかった分野」に手を伸ばしてみた。

 

29歳:「定義」と「自己肯定」こそが生き方を決めると気づかされ、不安な日々が起きても、心が強くなったと感じる。でも子供の誘惑にはめっぽう弱くなる。

 

35歳:人生初の交通事故(物損)に出会う。冬道の運転で車を上下に大回転(スピンではない)を体型氏、何とか命を取り留め、なぜ生きているのかわからない状態に陥る。

自分の生き方はすべて自分が握っている。わずかな瞬間にしか現れない「自分の真実」を表に引きずり出し、ピンチからチャンスを生み出す発想や視点をブログやメルマガ他で提供中。