おはよう、Unityをいじって1年以上が過ぎた。
慣れたからこそ、改めて軽量化に関し、
現時点でわかっている部分を伝えて、共に省略を目指したい。
キャッシュ:関数(メソッド)を書く前に型番と変数(+値)を決めよ

UnityはStart関数とUpdate関数から成り立つ。
Start関数の前に型名、変数名をおく場所がある。
型名:int ,float ,string ,GameObject,クラス名など
変数:適当な未知数xなどを示す。上記画像例だとsomreやdoorID、rhStageなど。※クラス名とは独自で作ったスクリプトの名前などを示す。
例えば私がSoundManagerスクリプトを作成したら、SoundManagerがクラス名となる。
private SoundManager(型番;クラス名) soundM(変数、英単語なら何でもOK);
始めのうちに型名と変数名、場合によって初期値を与えてしまう。
初期値を書いていなければ、C#独自の初期値(0やfalse,nullなど)になる。
はじめに初期値を設定すると、以降の関数(メソッド)で変数を使いまわせる。
関数内でも型番と変数名を指定できるが、
指定した関数のみ効果を持ち、他の関数で使用できなくなる。

型番+変数+初期値設定をキャッシュという。キャッシュを早めにやっておく。
特に文字列ことstringは絶対にやっておくべきだ。
Invokeの中身もInvoke(stringで記す関数,時間);なのでキャッシュできる。
Invoke("stage" , 1);
//---------------//
praivate string stages = "stage";
Invoke(stages , 1);
string型は文字列(“”がついた状態)のままでなく、
きちんと変数を置いて文字列をあらかじめ代入すると、
プログラミング的に効率化を図ってくれる。
影響あるようでない?bit数

上記画像元は下記リンクに記している。
パソコンでのbit数はメモリ量に影響を与える。
普通に創るならすべてint(32bit)で構わない。
私は気になるのでbyte(8bit)を良く使っている。
なお1バイト(byte) = 8bitだ。
byteでは使えない、intでしか使えない関数・変数もあるので注意が必要だ。
面倒ならすべてintで十分だ。
固定値であり、0以上255未満、intを入れなくても通じる場所はすべてbyteを入れている。
なおstring系の文字列は全角文字1文字をShift-JISでは2byte、
UTF-8では3byte使用するそうだ。英数字はどちらも1byte使う。
UTF-8を主に使うと仮定すると、全角1文字で3byteより、使う時は注意が必要だ。
なるべく全角は使わないうえ、string系もあまり入れない方がいいのかもしれぬ。
bit最小値はbyte、bool、半角たった1文字だ。
文字系は基本2byteを使うので、
文字列を使用せずにboolかbyteに置き換えるのがいいかもしれぬ。
2つの真偽地ならboolを、複数ならbyteで分けると。
最もタグはstringを使わなければ無理だが。
画像元と参照:UnityC#で扱うクラス
参照:組み込み型C#
値が固定されているならconstを使おう

「不変・固定化」させる変数と初期値について、
constで変数を固定化させた方がもっと早い。
private const string Enemy = "Enemy";
参照サイトに「gameObject.tagへのアクセスが重たい」書いている。
始めにキャッシュして値を固定化すれば、早く対応できる。
実際にスクリプトを組んで計測した結果、
CompareTag(“Player”)直接値を代入するより、
タグを事前にキャッシュしたCompareTag(Player)方が早い。
※キャッシュ:private string Player = “Player”;を最初に置く
参照:gameObject.tagをイコールで比較した場合とCompareTagを使った場合の処理時間の比較【Unity】【最適化】
※CompareTagはboolでGameObject.tagはstringとわかった。
文字列のため、数字に比べて処理が複雑になるのだろう。
コルーチン系のキャッシュは注意が必要
private IEnumerator fadeinTime;
void Start(){
fadeinTime = FadeinTime();
StartCoroutine(fadeinTime);}
最近コルーチンもキャッシュできると分かった。
型名で「IEnumerator」を指定し、Start関数で値を代入。
後は使うところで入力していけばいいわけだ。
ただしコルーチン系でも「一度しか使わない」ものはStart関数でいいが、
何度も使う(例:敵からダメージを食らう)場合、Start関数においてはいけない。
スタート関数はあくまでも一度のみ発動であり、以降は発動しなくなるからだ。
何度も発動させるなら、コルーチンキャッシュはしないほうがいい。
単純にStartCoroutine(FadeinTime());としたほうがいい。
同じ型番をひとまとめにしておく
private int hp;
private int mps = 2;
private int wwe ;
//-------------------//
private int hp , wwe , mps = 2;
上記はどちらも同じだ。コードの記述量が異なる。
どちらがコード処理にとって楽かと言えば後者だ。
前半は3行に対し、後半は1行で済ませている。
型番とアクセス修飾子(private,public,protectedなど)が同じなら、一つに束ねてしまおう。
束ね方は半角コンマ「,」をつけて、変数をつなげ、最後に「;」をつけるだけ。
ただ「あれ、この変数は何に使うんだっけ」わからなくなる時が来るから、
変数を入力した後、「//一行」か「/* 複数列コメント */」を用いて、
「この変数はここで使う」記しておこう。
途中で型番を設定しない
for(int i = 0; i > 10; ++i)
{Debug.Log(i);}
//----------------------------
private int i;
for( i = 0; i > 10; ++i)
{Debug.Log(i);}
例えばforなどループ処理やOnCollision関係にて、
その場で型番と定数を定義する方法(上記コード上側)がある。
iも事前にキャッシュしたほうが、型番を定めているため、
for内でいちいち型番設定を行う必要がない。
値が同じものはすべて等号(=)でつなぐ
float gravity = -9 ;//整数の場合、fをつけなくていい
movepos.y = gravity;
maxL.y = gravity;
//-----------------------//
movepos.y = maxL.y = gravity;
値が同じものはひとまとめにする。二行を減らし、一行にまとめられるので便利だ。
ベクトル”new Vector”をあまり使わない
float speed,gravitys;
void Update(){
this.transform.position = new Vector2( speed , gravitys);}
//-------------------------//
private Vector2 movepos;
void Update(){ movepos.x = speed; movepos.y = gravitys;
this.transform.position = movepos; }
先ほど型番とアクセス修飾子が同じものはひとまとめにし、
コード記述量を減らそうと書いたが、こちらは違う。
新しいベクトル(構造体)を設定する時、new Vector2(x , y)とnewを設定している。
newは設計図から実体を与えるために必要であり、
Startなど一度しか使わないところならnewを置いてもいい。
Updateなど何度も使うところでnewを繰り返すと、常に実体化させるため面倒くさくなる。
またベクトルの記述量も長くなる。
色々調べたところnew Vectorだと値においてプログラムは一気に読むのでなく、
まずx、次にy、次にzと常に確認を取りながら読み取るそうだ。
newを使わないで設定する方法として、
始めに型番+変数でベクトルを設定し、xとy(3次元はここにzがつく)を設定し、
設定したベクトル(ここではmovepos)を入れればいい。
計算は簡単なモノから複雑なものへ
float speed;
int hp;
vector3 velocity = speeed * transform.forward * Time.deltaTime * hp;
//→修正
vector3 velocity = hp * speeed * Time.deltaTime * transform.forward;
参照サイトによれば、計算の仕方一つにも処理を軽くする工夫がある。
intやfloatの計算は比較的簡単でも、ベクトルや回転(クォータニオン)は複雑だ。
上記式は小数点×ベクトル×小数点×整数となっている。
参照サイトによると、小数点×ベクトル計算後、再び小数計算を行うと、
二度同じ計算をしなければならないそうだ。
そこで整数×小数×ベクトル(クォータニオン)と軽い順番からやると、
スクリプトにとっても処理が楽だという。
計算をしているならぜひ見直してほしい。
いっそ計算とベクトルの代入を分けたほうが、プログラムとして楽かもしれない。
計算の応用:整数で単純化へ
private Vector2 playerpos , newposition , newplayerpos , distancepos;
private int distanceint;
protected void Destances(){
playerpos = player.transform.position;
newposition = this.transform.position;
newplayerpos = playerpos - newposition;
distancepos.x = (int)playerpos.x;
distancepos.y = (int)playerpos.y;
distanceint = (int)Mathf.Floor(distanpos.magnitude);
}
上記コードは主人公と敵の距離計算だ。
様々なサイトでは距離を測るやり方として
Vector3.sqrMagnitude:二乗の三平方定理を推奨している。
※Vector2.sqrMagnitude:でも可。sqrMagnitude=x^2 + y^2
ただ距離が大きいほど差も非常に大きく(万単位)になり、かえって厄介だ。
そこで主人公と敵の距離をベクトル引き算として、引き算した値を整数化し、
ルートを計算して四捨五入し、即座に整数化させている。
(int)は小数floatを強引に整数へ変化させる道具だ。
小数を省いて少しでも楽できるようにしている。
記事を書いているときに、二乗でもいい方法が思いついた。
現在、自分はこちらを使っている。
private Vector2 playerpos , newposition , newplayerpos ;
private int distanceint ,disX , disY;
private int const maxDis = 150 , maxY = 60;
//
//------------//
protected void Destances(){
playerpos = player.transform.position; //主人公座標
newposition = this.transform.position; //敵の座標
newplayerpos = playerpos - newposition;
disX = (int)newplayerpos.x; //整数化して計算簡略化
disY = (int)newplayerpos.y; //整数化=小数点切捨
distanceint = (disX*disX) + (disY*disY); //三平方の定理から斜辺の値2乗を出す
//sqrMagnitudeはfloat型で小数点を出す計算のため不採用。
distanceint = Mathf.Clamp(distanceint, 0, maxDis);//最小値と最大値設置
//distanceintの値がmaxDis = 150を超えた場合、distanceint = 150(最大値)
RangeEnemy();
} //Destances
void RangeEnemy(){
//カメラの高さと広さによって場合分け
if(disY >= maxY || distanceint >= maxDis){//最大値を超えた
//敵を止める処理(Rigidbody2D.constraints)など
}else if(disY < maxY || distanceint < maxDis){
//敵を動かす処理など
}
} //RangeEnemy
このようになるべく小数値の計算よりも整数の計算を行ったほうが単純だ。
静的staticを使って固定化した値を次々代入
axisH = Input.GetAxisRaw(Horizontal) ;
if ( axisH < 0 ) {
this.transform.localScale = new Vector(-1 ,1);
} else if ( axisH > 0 ) {
this.transform.localScale = Vector2.one; // (1 , 1)
}
//---------------------------//
(GameManagerスクリプトにて)
public static Vector2 minusOne = new Vector(-1 ,1 );
//---//
axisH = Input.GetAxisRaw(Horizontal) ;
if ( axisH < 0 ) {
this.transform.localScale = GameManager.minusOne;
} else if ( axisH > 0 ) {
this.transform.localScale = Vector2.one;
}
new Vector(-1 ,1);とGameManager.minusOneの違いだ。
一か所しか使わないならnewで十分だろう。
複数かついくつかのスクリプトで使うなら、
一つの大本スクリプト(ここではGameManager)に値を設定し、
いろんなスクリプトで使いまわしたほうが楽だ。
staticは複数のスクリプトにまたがって使える道具で、関数も扱える。
staticにはpublic(他スクリプトでも使用可)のほか、
private(そこでしか使えないが、関数をまたがって使える)もある。
なおベクトルにはVector2.one;のように最初から使えるものがある。
参照(Unity公式)のStatic変数を参考にしてみよう。
なければ私のように一つ作ってしまおう。
軽い[SerializeField]–GetComponentよりも
private string front ="front";
[SerializeField] Animator anim;
//インスペクター上から直接アタッチ
//---------------------//
private Animator anim;
void Start(){anim = GetComponent<Animator>(); }
//----------------------//
void Update(){anim.Play(front);}
GetComponentはスクリプトの行を長くさせるためだ。
[SerializeField]から直接アタッチすれば、
Start関数にGetComponentを代入せず、直接値を代入できる。

インスペクターの設定が若干面倒くさいが、
プレハブ化すれば、スクリプトにアタッチしたゲームオブジェクト以外の参照がなくなるため楽だ。
隠れてGetComponentを使っているtransform、Cameraには

調べたところ、TransformはこっそりGetComponentが入っている。
CameraはFindGameObjectWithTagを使っているそうだ。
ついでにthis.gameObjectも何かが入っていると聞いた。
とはいえ最新のUnityはほぼキャッシュ化されているそうだ。
一応キャッシュ化しておこう。そのまま使うより早いし、行も短くできるので。
Cameraのキャッシュはこちら。
private new Camera camera;
private CameraCtrl cameraC;//Cameraオブジェクトに張り付けたスクリプト
void Start () {camera = Camera.main;
cameraC = camera.GetComponent<CameraCtrl>();}
//new をつけたほうが、警告が出にくくなる。
一方でtransformやgameOBjectは
private Tranform trans;
private GameObject myob;
private string Muteki = "Muteki";
void Start(){trans = this.transform;
myob = this.gameObject;}
void Update(){trans.position = movepos;
myob.layer = 10;
myob.tag = Muteki;}
すでにある値をいちいちキャッシュ化しておくのは面倒くさい。
面倒だけど通常より処理が速い。
インスペクターはGameObjectよりスクリプトなどクラスを
[SerializeField] SoundManager ses;
[SerializeField] GameObject sound;
「sound」という名前のGameObjectがある。
アタッチする際、目的がスクリプトのみなら直接スクリプト名(上記コード上)を入れたほうが早い。
もちろん直接オブジェクトを扱うならGameObjectがいい。
例えばSetActive(true/false)を使ってオブジェクトを非表示/表示なら、
GameObjectをインスペクターに入れるといい。
どちらのやり方であろうと別スクリプトから値を代入できるのは、
publicとついたアクセス修飾子のみだ。
publicは基本、インスペクター上からも設定できる。
インスペクター上で設定する気のないpublicは[System.NonSerialized]をつけよう。
[System.NonSerialized] public int hp = 2;
また関数を別スクリプトで使うやり方もある。
インスペクターからファイルを代入した後、
public void Xyz(){//処理}
をつければ、普通に使えるよ(通常の関数はprivate)。
真偽boolと遅延関数Invokeを使ってUpdate量を減らす
void Update(){
if(mutekiget){//無敵へ
timeMuteki += Time.deltaTime;
if(timeMuteki < 12) {
this.tag = MutekiPlayer;
StartCoroutine("Muteki");}
else {
timeMuteki = 0;
Invoke("MutekiOff",0.5f);}
} }
void MutekiOff(){mutekiget = false;}
Update系は毎秒起きに更新するため、
中の値が同じだと、常に同じ値を新規に入れて更新する。
同じ値なのに新規に入れて更新する……無駄でしかない。
一度入れたらもう入れなくていいのに。
そこでboolとInvokeを使って、
値を入れたらUpdate関数内でも値を更新させない処理を行う。
Invoke(“関数名” , 秒数);で使える。
やり方として、まずboolでtrueかfalse(初期値)のどちらかのみ、
特定の関数が発動するかを考える。
再生時に即発動させたいならfalseを、何かを取得した時ならtrueに設定だ。
ここではmutekiget=trueの時のみ、中の式が発動する。
falseの時は一切発動しないわけだ。
※一応テストとしてDebug.Log(“適当な言葉”);を入れて、
中の関数が働いているかテストしよう。
上記スクリプトでは12秒を越えた後、遅延発動ことInvokeが働く。
ここでは0.5秒後に関数MutekiOffへ向かう。
MutekiOffではmutekiget = false;にするため、
Update内にあるmutekiget 以降の働きが停まる。
ちなみにmutekiget は無敵のアイテムをとるとtrueになるよう設定している。
(上記スクリプトには書いていない)
一度発動した後、繰り返しを防ぐ(Update内)
bool changeon;//初期値はfalse
void LateUpdate(){
if(!changeon) { //初期入力,早い
clip = clipold;//clipにclipold代入
bgms = bgmData.bgm[clip];
loopStart = bgms.LoopStart;
Invoke("LoopInput" , 0.55f);}
void LoopInput(){ changeon = true;}
boolとInvoke関数の合わせ技でUpdateに何度も同じ値入力を防ぐ方法をもう一つ。
bool changeon = false;が初期値であるため、
Unity再生ボタンを押したら即座に発動する。
0.55秒経ったらLoopInput関数に飛び、changeon = trueに変換。
changeonをfalseにする要素はシーン変更(場所移動など)以外なくなる。
値を入力したら、お役御免の働きを持っている。
Invoke関数の秒数は1(=1f)秒もあれば長いほうだ。
Debug.Logを使いながら、どれくらいで発動するかを見極めよう。
おまけ:”void Update()”量を減らし、まとめる
さらにUpdateはvoid update()量を一つに束ねる方法がある。
Updateもメモリを使うので、一つに束ねるとメモリが浮く。
上記リンクから飛んで参考にしてほしい。
リストの()に指定の数字を入れる
List<int> listB = new List<int>();
//(キャパシティ)こと()内に数字を入れるとメモリに優しい
List<int> listB = new List<int>(4);
//次のように省略できる
List<int> listB = new(4);
//使用後はリストを空にしてメモリ確保
void Ondestroy(){
for(int i = 0; i < listB.Length; i++){
listB[i] =null; }//中身を空白にしてメモリ解放
listB.Clear();
}
//listがGameObjectならnull代入前にDestroy()も入れておくと確実に中身を消せる。
scriptableobject内でリストを使う際、
()の中にあらかじめリストに必要な数字を入れたほうが軽い。
数字を入れないと、新しく数を追加するたびにいろいろ更新しなければならないが、
初めから数字を指定しておくと、いろいろのいくつかを省略できるからだ。
ついでにscriptableobjectはデータ保存調であり、
主人公や敵のHPほか、データを収納できる。
スクリプト内の呼び出しより早く処理できるので、
まだ入れていない人は上記動画を参考に入れよう。
消去:DestoryよりSetActiveよりenabled
[SerializeField] Animator anim; //敵アニメ
[SerializeField] SpriteRenderer spR; //敵画像
[SerializeField] BoxCollider2D boxC; //当たり判定や床との接触判定
[SerializeField] Rigidbody2D rb2d;
private GameObject gameO ;
void Start(){gameO = this.gameObject}
void Owatta(){
Destroy(gameO);//ヒエラルキーから姿を消す
gameO.SetActive(false);//ヒエラルキーには残るが、プレイ時から消える。
anim.enabled = rb2d.enabled = boxC.enabled = false;
//ヒエラルキーにも残るしプレイ時もついたままだが、処理は最も軽い
}
enabledはgameObjectにアタッチされた部品を引報じ/表示できる。
SetActiveはgameObject本体を非表示、表示できる。
Destroyはヒエラルキーから存在を消去できる。
Destoryを行うと、ごみとしてたまるそうだ。
ゴミ箱の要領が大きいとUnityにも影響が出るため、基本使わない。
※Destroyはメモリ確保のために必要。後述。
面倒さで言えばenabledだ。
アタッチ部品が多いならSetActive(false)にした方が便利だ。
メモリの軽さならenabled=falseのほうが早い。
SetActiveを上手く利用した機能として、プールがある。
球などを使いまわす機能だ。
上記動画を参照に、ぜひ作ってDestroyを使わないよう設定してほしい。
シーン変化前にDestroyとnullでメモリ確保を
private GameObject nowObject ;
//キャッシュ化
void Start(){nowObject = this.gameObject;}
//すったもんだでプレイ中に処理した後
//シーン変更で呼び出される
void OnDestroy(){
Destroy(nowObject); nowObject = null;
}
シーン変更をはじめ、二度と使わないゲームオブジェクトは、
OnDestroy関数を読んだうえ、
Destroy(中身はGameOBject);とGameOBjectをnull設定にすると、
メモリ解放にとてもいいと、調査して分かった。
GameObjectはもちろんSpriteなども、
使うシーン以外では使わない、シーン切り替え時は
必ず二つの作業をしておくと、別のシーンにメモリを引き継がないため、
サクサクとゲームプレイできる。
List系はClear();を使えばいい。
調べたところC#(unity)はシーンが変わると自動的に消してくれる。
あまり気にしなくていいのだが、一部は残ったままだという。
残ったものをnullとDestroyで確実に消し、メモリを楽にさせてあげよう。
結論:些細なものも次々キャッシュしたほうが早い

結局、型番と変数で些細なものでもキャッシュしたほうが早い。
特にstring型の文字は関数を動かす前に型名と変数名を置くと(キャッシュ)、
値が決まっているから早く処理できる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[SerializeField] Rigidbody2D rb2d;
//GetComponentを省くため、[SerializeField]でインスペクターから直接代入。
private const string Horizontal = "Horizontal" , Jump = "Jump" ,
Fire1 = "Fire1" , Enemy = "Enemy" , Player ="Player";
//""ではさむ文字列はすべてキャッシュ化
private string thistag;
private GameObject gameO;
private Transform mytrans;
private Vector2 movepos , mypos;
private float speed , axisH;
private WaitForSeconds wfs002 = new WaitForSeconds(0.02f);
//コルーチンで使うWaitForSecondsもここでキャッシュ
void Start(){
gameO = this.gameobject;//変数に入れたほうが早くなる
mytrans= this.transform;//これもキャッシュしたほうが早い
thistag = gameO.tag;//このスクリプトがついているときのタグ。
}
void Update(){
if( Input.GetButtonDown(Jump)){//処理}
//JumpやHorizontalなどもstringとして処理できる
}
void FixedUpdate(){
mypos = mytrans.position;
movepos.x = speed * axisH; movepos.y = gravity;
//x,y、Vector3ならzも使って分ける。
rb2d.AddForce( movepos );//new Vectorを使わない
}
void OnCollisionEnter2D(Collision2D dobject){
if (dobject.gameObject.CompareTag(Enemy)
&& gameO.CompareTag(Player))
{StartCoroutine("Damage");}
//"Damage"はstringでなくIEnumerator。Invokeも同じ。注意が必要。
}
//シーン変更時に掃除してメモリ確保
void OnDestroy(){
Destroy(gameO);gameO = null;
}
参考になれば幸いだ。
追記結論:if判断はなるべくboolで
#unity #プログラミング FPS値大変化!
if判断にてstringからboolに切り替えただけ。プレイはどちらも同じ。メモリはあまり変わらず。bool値のtrue/falseはキャッシュ(nowPlay = true , stoPlay = false )して使いまわしたほうが早いし、文字数も減らせるかもな。 pic.twitter.com/C7tZmM37TF— せんけん (@megabi0) August 9, 2023
自分で書いた通り、string系によるif判断を限りなく減らしてbool、
複数ならbyteやintに分けて判断した。どうしても無理なものだけstringにした。
intをできる限りbyte,sbyteにすると、結構な割合でスクリプトの整理につながった。
bit数を考慮したうえでプログラミングを行うと、
結構な割合でスクリプトを軽くできる。
