Unity2Dスクリプト軽量化2:別スクリプト使用かつ分散化Update更新頻度を下げる

おはよう。ここでは何度か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);
Rigidbody2D.sharedMaterial

目的のスクリプトについてはリファレンスを見ながら当てはめてほしい。
上記画像で赤く囲んだ部分を見てほしい。

publicの後についている桃色?赤文字が型名だ。

 

一度しか呼ばれないStart関数(Awake関数も)で処理を済ませる

Start関数は一度しか使用しない。
更新頻度の高い座標以外、スタート関数でもろもろの処理を済ませよう。

GetComponent<型名>();は一度更新すれば使いまわせる。

//例 
this.anim = GetComponent<Animator>();
weapon = GetComponentInChildren<BoxCollider2D>();

ちなみに下のweaponでは「子」のオブジェクトを呼び出している。
ヒエラルキーで目的のオブジェクトに入っている別のブツだ。

 

何、GetComponentを使わない方がいい?

GetComponent軽量化2

scriputableobjectというデータ専用スクリプトを調べているとき、
「GetComponentは重たい」声を拾ってしまった。

私の場合はGetComponentでも十分であるが、重たい処理をなるべく減らしたい。
そこでやり方を変えた。上記画像を参照してほしい。

呼び込む関数がRigidbody2Dなら

[SerializeField] Rigidbody2D rb2d;
//インスペクターに直接代入する、privateを省略

//-----------------------------//
//継承先で使う場合は

[SerializeField] protected Rigidbody2D rb2d;
//protectedあるいはpublic(継承以外の参照スクリプトで使う場合)を入れる

スクリプトを更新したら

GetComponent軽量化1

上記画像のように、直接参照していく。
プレハブで編集する際、自身のゲームオブジェクトを参照すればいいので、
作業は他のアタッチに比べると楽だ。

上記のように仕込んでおけば、

void Start(){

rb2d = GetComponent<Rigidbody2D>();

}

を入れなくて済む。
スクリプトが短いならStart関数にGetComponentを入れてもいいが、
長くなるとインスペクターからの参入が楽だ。

参照:GetComponentを多用していました

 

アップデート更新を大量に呼ばない(更新時のみ更新)コツ

アップデート回数前

上:変更前。下:変更後

アップデート回数を減らす

Debug.Log系を使うとUpdateあるいはFixedUpdate内では、
0.5秒間間隔でデバックが表示される。

Updateのたびに数字を出しているため、スクリプトにいらん負荷がかかる

そこでスクリプトを使い、更新頻度を下げる。
さげるというか、物体が動いて更新した時のみ、
ある関数(デバック含む)を更新していく状態だ。

例えば私は敵と味方の距離計算を行った。

アップデートスクリプト

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

アップデートスクリプト2

上記スクリプトを書いた結果、更新頻度が減る。

今から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ゲームを創るなら、下記本が最も参考になると確信している。
むしろ上記本を教科書において、色々研究したほうがいい。

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

楽天:たのしい2Dゲームの作り方 Unityではじめるゲーム開発入門 [ STUDIO SHIN ]

 

真偽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の一部関数をそのまま持ってくる。
}

長すぎるスクリプトを別スクリプトに移動させると、
いちいちマウスホイールを使う必要がなくなる。

上記動画だけでわかりにくい場合は下記参照サイトも使ってほしい。

参照:partial classで整理しよう

 

継承を作って重複を避ける

start関数と継承

継承を用いて重複を防げば、より軽くなると分かった。
継承は基底クラスの親と派生クラスの子があり、
基底クラスは主な関数や計算、派生クラスは「それのみ使う」スクリプトだ。

スクリプト分割と合わせれば、かなりすっきりしたスクリプトを作成できる。

 

プレハブを「開いた」ときのGameObject挿入

プレハブから取り入れる

敵キャラといい仕掛けと言い、どんどんプレハブ化していく。
プレハブの問題はプレハブを調整する「開く」ボタンを押したとき、
ゲームオブジェクトを入れる場所が空欄である事実だ。

普通に入れようとしても、目的のオブジェクトがなくてげんなりする。

「アセット」を選ぶと目的のオブジェクトを簡単に見つけられる。
プレハブのうちにアセットから目的のオブジェクトを挿入し、
オブジェクトについているスクリプトから関数などを呼び出してみよう。

オブジェクトの入れ方はこちらで語っている。

また書くところがあるが、とりあえずこの辺で。

お願い

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

お知らせ

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

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

お願い1

Writer軽い自己紹介

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

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

 

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

 

■ 簡単な自分史 ■

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

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