おはよう。ここでは何度かUnityの最適化について触れている。
改めて最適化につながる情報があったので、イチから見直す感じでみっちり書きたい。
本題へ入る前に、Unityスクリプトについては多くの人が語っている。
多くの人に助けられた。この場を借りてお礼を申し上げます。
私もスクリプトに関する情報を載せ、
あなたのゲーム作成の役に立てばいいなと、心から考えています。
最適化の基本:関数の前に定義(キャッシュ)せよ
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour {
//ここに定義(型名 変数名)を入れる
void Start () { //使わないなら消しとけ
//初期化に使用
//消してもOK
}
void Update () { //使わないなら消しとけ
// Update はフレームごとに 1 回呼び出されます
}
} //NewBehaviourScript
最適化の基本は関数をやる前にすべて定義だ。
定義は「公/私 型名 変数名 場合によっては代入」がある。
[SerializeField] public(公/私) int(型名) baseMedia(変数名) = 3(代入);
//publicの場合、私は[SerializeField]をつけている。
//[SerializeField]はインスペクター上で操作できるよと言う意味。
//privateでも置けばインスペクター上から操作できる。
一方「このスクリプトでしか使わない」場合は
private int baseMedia2;
定義の部分にUpdate内などで使う関数をすべて入れる。
例外はoncollisionenterやontriggerenter系だ。
Update関数内で新たに型名と変数名を入れてもいいが、
Updateは1秒間に何度も呼ばれるため、何度も定義をしている状態だ。
スクリプトに無駄な処理を費やしてしまううえ、
例えばUpdate関数内で定義した変数は別のところでは使えない。
別のところで使うと「存在しない(does not exist)」エラーが生じる。
基本の更新がなく、定義が一度でいいなら初めに定義したほうがいい。
始めに定義しておくと、別の関数でも使用できるから。
public class Shimon : MonoBehaviour {
private int speed = 1;
void Update () {
int jump = 1;
}
void FixedUpdate () {
speed +=5; //最初で定義しているからエラーが出ない
jump +=5; //エラー(does not exist)が出る。
//jumpは Update ()内のみ使える
}
}//class Shimon : MonoBehaviour
ちなみに私がスクリプトでやっている主な定義例はこちらだ。
// int
private int speedMax = 6;
//float
private float axisH ;
private float attackTime = 0.2f;
//bool
private bool jumping = false ;
// string
private string nowAnime ;
//Vector
private Vector2 mypos ;
//Layer
private LayerMask GroundLayer ;
//Inspector
private Animator anim;
private Rigidbody2D rb2d;
private SpriteRenderer spR;
// Time
private WaitForSeconds wfs002 = new WaitForSeconds(0.02f);

目的のスクリプトについてはリファレンスを見ながら当てはめてほしい。
上記画像で赤く囲んだ部分を見てほしい。
publicの後についている桃色?赤文字が型名だ。
一度しか呼ばれないStart関数(Awake関数も)で処理を済ませる
Start関数は一度しか使用しない。
更新頻度の高い座標以外、スタート関数でもろもろの処理を済ませよう。
GetComponent<型名>();は一度更新すれば使いまわせる。
//例
this.anim = GetComponent<Animator>();
weapon = GetComponentInChildren<BoxCollider2D>();
ちなみに下のweaponでは「子」のオブジェクトを呼び出している。
ヒエラルキーで目的のオブジェクトに入っている別のブツだ。
何、GetComponentを使わない方がいい?

scriputableobjectというデータ専用スクリプトを調べているとき、
「GetComponentは重たい」声を拾ってしまった。
私の場合はGetComponentでも十分であるが、重たい処理をなるべく減らしたい。
そこでやり方を変えた。上記画像を参照してほしい。
呼び込む関数がRigidbody2Dなら
[SerializeField] Rigidbody2D rb2d;
//インスペクターに直接代入する、privateを省略
//-----------------------------//
//継承先で使う場合は
[SerializeField] protected Rigidbody2D rb2d;
//protectedあるいはpublic(継承以外の参照スクリプトで使う場合)を入れる
スクリプトを更新したら

上記画像のように、直接参照していく。
プレハブで編集する際、自身のゲームオブジェクトを参照すればいいので、
作業は他のアタッチに比べると楽だ。
上記のように仕込んでおけば、
void Start(){
rb2d = GetComponent<Rigidbody2D>();
}
を入れなくて済む。
スクリプトが短いならStart関数にGetComponentを入れてもいいが、
長くなるとインスペクターからの参入が楽だ。
アップデート更新を大量に呼ばない(更新時のみ更新)コツ

上:変更前。下:変更後

Debug.Log系を使うとUpdateあるいはFixedUpdate内では、
0.5秒間間隔でデバックが表示される。
Updateのたびに数字を出しているため、スクリプトにいらん負荷がかかる。
そこでスクリプトを使い、更新頻度を下げる。
さげるというか、物体が動いて更新した時のみ、
ある関数(デバック含む)を更新していく状態だ。
例えば私は敵と味方の距離計算を行った。

こちらは敵と味方の距離を測る関数だ。
下記関数ではdistanceint(変数)の数を知るために計算している。

上記スクリプトを書いた結果、更新頻度が減る。
今からUpdate頻度を下げるスクリプトを書いていく。
上記画像スクリプトとは別の型名や変数に変える
public class Kurisu : MonoBehaviour {
private int speed; //更新用(主に使用)
private int speedKeep; //保存用(Update更新頻度を下げるのに必要)
private float flotarian;
void Update(){
Verocyan(); //自分で創った関数の表記法
if(speed != speedKeep){
speedKeep = speed;
//speedが更新されたらspeedKeepの値もspeedに変わる
//speedとspeedKeepの値が同じなら更新されない
//ここに何かしらの変数や条件を追加する。
} //(speed != speedKeep)
}//Update
int Verocyan(){ //自分で創った独自関数
//うんたらかんたら計算してflotarianの数値を出す
return speed = (int)flotarian;
//少数floatを整数へ変更し、計算を終える
}
アップデートの頻度を下げるテクニックを使えば、
少しは軽くなるじゃないだろうか。

Update更新頻度を下げるコツは上記本に乗っている。
上記本ではアニメーション再生において、頻度を下げるコツが載っている。
2Dゲームを創るなら、下記本が最も参考になると確信している。
むしろ上記本を教科書において、色々研究したほうがいい。
真偽boolとメソッド関数の組み合わせでUpdateを軽量化
public bool keyx:
void Update(){
//うんたらかんたら色々記す
if(keyx) Arufa();
else Beta();
} //Update
void Arufa(){
//keyx=trueのみ働くプログラムを創る
}//Arufa
void Beta(){
//keyx=falseのみ働くプログラムを創る
}//Beta
サクっと上記のようなプログラムを作った。
Updateは頻繁に更新する。
そこで真偽値boolやint,stringなどに値や文字を入れて分岐を創る。
分岐を創ると「条件に合うも」関数が働く。
アクションゲームを創ると、複数の敵がいる。
敵アルファに作用させたくてもベータにはさせたくない。
作用させたいほうのみ更新させて、させない分野は働かせなくていい。
それぞれ関数を作って分岐すると、余計な働きを抑えられる。
後で軽く触れる継承で、よりUpdateの軽量化について考えさせられる。
長すぎるスクリプトを分割して見通しよく立てる
敵キャラクターのスクリプトにおいて、あまりにも行数が長くなった。
そこで上記動画を参照しながら、スクリプトの分割を行った。
上記動画は英語を使っているが、YOUTUBEには本厄があるうえ、
本質は動画にあるスクリプトの分割であり、言語がわからなくてもできる。
私の場合は元あるEnemyManagerから敵の攻撃だけを分けた
EnemyManager2ファイルを作った。
大きなポイントとして、publicとclassの間にparticalを入れる。
//Enemymanager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
public class EnemyManager : MonoBehaviour
{
//以下、定義や関数など長すぎるので省略
}
public class EnemyManager : MonoBehaviour ここに
public partial class EnemyManager : MonoBehaviour
particalを入れる。
public partial class EnemyManager : MonoBehaviour {
//以下、定義や関数など長すぎるので省略
}
次に別のファイル(EnemyManager2.cs)では
//Enemymanager2.cs 別スクリプト
using UnityEngine;
public partial class EnemyManager
//EnemyManager2でない! EnemyManagerを書くべし。
//: MonoBehaviour 入れても入れなくてもOK
{
//ここにEnemyManagerの一部関数をそのまま持ってくる。
}
長すぎるスクリプトを別スクリプトに移動させると、
いちいちマウスホイールを使う必要がなくなる。
上記動画だけでわかりにくい場合は下記参照サイトも使ってほしい。
継承を作って重複を避ける

継承を用いて重複を防げば、より軽くなると分かった。
継承は基底クラスの親と派生クラスの子があり、
基底クラスは主な関数や計算、派生クラスは「それのみ使う」スクリプトだ。
スクリプト分割と合わせれば、かなりすっきりしたスクリプトを作成できる。
プレハブを「開いた」ときのGameObject挿入

敵キャラといい仕掛けと言い、どんどんプレハブ化していく。
プレハブの問題はプレハブを調整する「開く」ボタンを押したとき、
ゲームオブジェクトを入れる場所が空欄である事実だ。
普通に入れようとしても、目的のオブジェクトがなくてげんなりする。
「アセット」を選ぶと目的のオブジェクトを簡単に見つけられる。
プレハブのうちにアセットから目的のオブジェクトを挿入し、
オブジェクトについているスクリプトから関数などを呼び出してみよう。
オブジェクトの入れ方はこちらで語っている。
また書くところがあるが、とりあえずこの辺で。
