Unityティーチャートレーニングデイ -認定プログラマー編-

1.7K Views

October 09, 20

スライド概要

Unity認定プログラマーの試験範囲を念頭にプログラマーとして知っておきたいUnityの実装方法について学びます。また、Unity AnalyticsなどUnityが提供するサービス(機能)についても実装方法(Unity認定プログラマー試験レベル)について学びます。

■こんな人におすすめ
・(調べながらでも) Unityの一通りの機能を使える
・C#の基本的な文法は理解している

■トレーニング環境と準備について
・使用するUnityのバージョン: Unity2019.4.8f1
・使用するプロジェクト:BaseProject_2019.4.8.1f
以下のURLからダウンロード可能です。
https://drive.google.com/file/d/1L3WExri_sPexkMKz-OaJ0mQTLj4v88LH/view?usp=sharing

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Generative Art — Made with Unity ティーチャートレーニングデイ 「認定プログラマー編」 ユニティ・テクノロジーズ・ジャパン株式会社 荒川 巧也 1

2.

自己紹介 荒川 巧也 ユニティ・テクノロジーズ・ジャパン株式会社所属。 ユニティでは”トレーナー”として主に企業様向けに Unityに関する導入講義実施や効率的な実装方法 のアドバイスを行っています。 2

3.

Unity認定プログラマー試験 前提条件の内容 : • Unity を使用したビデオゲームまたは 3D インタラクティブコンテンツ のプ ログラミングの実務経験 2 年以上 • C# を含むコンピュータープログラミングの実務経験2 年以上 • 構想から完成までソフトウェア開発ライフサイクル全般に携わった経 験 • ゲーム開発、インタラクティブエンターテイメント、デザインビジュアラ イゼーションなど、Unity を使用したソフトウェア開発のための専門的な ア プリケーションの知識 • キャラクターや環境の設定など、Unity でのビジュアル /3D アセット および アニメーションパイプラインについての基本的な知識 3 (一部抜粋

4.

認定プログラマー試験の試験範囲 ・プログラミングの核となるインタラクション ・アートパイプラインでの作業 ・アプリケーションシステムの開発 ・シーンと環境デザインのためのプログラミング ・パフォーマンスとプラットフォームの最適化 ・ソフトウェア開発チームでの作業 4

5.

今回使用するUnityのバージョンなど Unityのバージョン: Unity2019.4.8f1を使用します。 使用するプロジェクト:BaseProject_2019.4.8.1f ※以下のURLからダウンロード可能です。 https://drive.google.com/file/d/1L3WExri_sPexkMKz-OaJ0mQTLj4v88LH/view?usp =sharing 5

6.

Profiler 6

7.

アセットストアからダウンロードする *新規プロジェクトを作成しましょう! 7

8.

新規プロジェクトを作成 8

9.

3D Beginner : Complete Project 9

10.

MainSceneを開く 10

11.

Main Sceneが開く 11

12.

Stats (Statics) FPS : 1秒間に何フレーム処理を行えるか。 CPU: メインスレッドとレンダリングスレッドに費やした時間 Batches: Unityが複数のオブジェクトをレンダリングの際にまとめようとします。 Saved by batching : まとまったバッチ数 SetPass Calls : CPUからGPUに送られる描画命令(レンダリングパス)の数。 12

13.

バッチ処理について オブジェクトをスクリーンに描画するためには、グラフィックスAPI (OpenGL や Direct3D 等) に対してドローコールが必要です。グラフィックス API はドローコールごとに非常に多くの処理をするため、ドローコールは高負荷で、 CPU 側のパフォーマンスオーバーヘッドの原因になりま す。これは大抵、ドローコール間に状況の変更(例えばマテリアルの切り替えなど) が発生することに起因し、グラフィックスドライバーで高負 荷な検証と 変換処理の原因となります。 Unity では、それに対処するために2 つのテクニックが使用されます。 動的バッチ処理 : かなり小さいメッシュ用に、その頂点を CPU 上に変換して多数の類似したものを群にし、ひとまとめにして 描画します。 静的バッチ処理 : 静的 (つまり、動かない ) ゲームオブジェクトを大きなメッシュと結合して、それを高速でレンダリングします。 https://docs.unity3d.com/ja/2019.4/Manual/DrawCallBatching.html 13

14.

静的バッチ処理について 静的バッチ処理 : 静的 (つまり、動かない ) ゲームオブジェクトを大きなメッシュと結 合して、それを高速でレンダリングしま す。 14

15.

Batching Staticにチェックを入れる 15

16.

Batching Staticにチェックを入れた 16

17.

Saved by batchingの値が上がっている 17

18.

Edit → Project Settings 18

19.

Profiler Profilerを使うことによって CPUやGPU、メモリの負荷など再生することによって確認すること ができます。「 Window → Analysis → Profiler」 19

20.

Profilerの役割 Profilerを使うことで現在のプロジェクトにおける負荷を視覚的に確認する。また Profilerを使うことで 具体的に負荷がかかる箇所を確認することができる。 プロジェクトの制作者は負荷がかかる原因を突き止めて負荷がなるべくかからないように検討するこ とができる。 20

21.

とりあえずゲームを再生してみます 21

22.

Profilerの構成について Profiler Modules 詳細 22

23.

時間のかかっている処理を確認する 23

24.

ボトルネックの探し方 処理負荷がかかっており 60FPS以下になっている。 24

25.

Deep Profile Deep Profileを使用すると、Unity標準の幾つかのAPIだけでなく、もっと深い階層のAPI 負荷も見れるようになります。 25

26.

Deep Profileだと各関数の負荷を確認することもできる 26

27.

モジュールを追加したり減らすことができる 27

28.

実機でテストすることができます 28

29.

おすすめの資料 【CEDEC2018】一歩先のUnityでのパフォーマンス /メモリ計測、デバッグ術 https://www.slideshare.net/UnityTechnologiesJapan/unity-111054310 29

30.

Tips: occlusion culling オクルージョンカリングは、あるオブジェクトが他のオブジェクトに隠されていて現在カメラに映 らないときに、オブジェクトのレンダリングを無効にする機能です。 https://docs.unity3d.com/ja/2018.4/Manual/OcclusionCulling.html 30

31.

Occluder StaticとOccludee Staticにチェックを入れる 31

32.

Window→Rendering→Occlusion Culling 32

33.

Occlusion Cullingを有効にする 33

34.

Occlusion Cullingのパラメータについて Smallest Occluder: Occlusion Cullingが有効になるオブジェクトの大きさ Smallest Hole : Occlusion Cullingが有効になるオブジェクトの最小の隙間 Backface Threshold: 100以下の値にすると積極的にデータを省こうとする。 34

35.

Occlusion Cullingの効きを視覚で確認 他のオブジェクトに隠されていて現在カメラに映らないときに、オブ ジェクトのレンダリングを無効にしています。 35

36.

Backface Thresholdの効果について 36

37.

Backface Thresholdの効果について 37

38.

Tips : Physicsに関するパフォーマンス最適化 Fixed Timestep 設定 (Time ウィンドウ内 ) を調整して、物理演算の更新に費やす時間を削減できます。 Timestep を高くすると、物理演算の 精度を犠牲にして CPU のオーバーヘッドを削減します。多くの場合、精度を下げることと引き換えに速度を上げるのは許容できるトレードオ フです。 Time ウィンドウで Maximum Allowed Timestep を 8–10FPS の範囲の設定し、最悪の場合の物理計算に要する時間を制限します。 メッシュコライダーはプリミティブコライダーよりもはるかに高いパフォーマンスオーバーヘッドを持っているため、使用を控えめ目にしてくださ い。プリミティブコライダー付きの子オブジェクトを使用して、メッシュの形状を近似することが可能な場合もよくあります。子コライダーは、親の リジッドボディによって単一の複合コライダーとして一括して制御されます。 ホイールコライダーは、厳密にはソリッドオブジェクトの意味でのコライダーではありませんが、それにもかかわらず、高い ヘッドを持っています。 CPU のオーバー https://docs.unity3d.com/ja/2018.4/Manual/iphone-Optimizing-Physics.html (一部抜粋) 38

39.

CapsuleColliderとMeshColliderの負荷の違いを確認します 新しいシーンを作成します 39

40.

John Lemonをシーン上に追加する 40

41.

John Lemonをシーン上に配置した 41

42.

John Lemonを複製してCapsuleColliderとMeshColliderを付ける 42

43.

Colliderを付けることができたらプレハブ化する 43

44.
[beta]
using UnityEngine;
public class ObjInstatiation : MonoBehaviour
{
public GameObject obj;

ObjInstatiation
MainCameraに追加

void Update()
{
if (Input.GetMouseButtonDown(0))
{
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
GameObject instantiatedObj = Instantiate(obj) as GameObject;
instantiatedObj.AddComponent<Rigidbody>();
instantiatedObj.transform.position = new Vector3(x, y, 0);
}
}
}
}
}

44

45.

CapsuleColliderを追加したJohnLemonを追加 45

46.

コンテンツを再生して左クリック CapsuleColliderの追加モデルが生成される 46

47.

同じようにMeshColliderを付けたJohnLemonを追加 47

48.

MeshColliderの追加モデルが生成される 48

49.

Tips: FrameDebugger フレームデバッガー を使用すると、特定のフレーム上で実行されているゲームの再生を凍結し、 そのフレームをレンダリングするために使用されている個々の ドローコール を表示することがで きます。同様に、ドローコールをリストとして、デバッガーはまた、それを1つずつ実行することが できます。だから、シーンが、どのようにそのグラフィックエレメントから構成されていくかをとても 詳細に見ることができます。フレームデバッガウィンドウ (Window > Analysis > Frame Debugger) は、drawcall 情報が表示され、構築中のフレームの playback を制御することがで きます。 https://docs.unity3d.com/ja/2019.4/Manual/FrameDebugger.html 49

50.

より詳しく確認できる 50

51.

ベースとなるミニゲーム (アソシエイト試験対策で制作したゲーム) 51

52.

Object Pool コンテンツ制作においてオブジェクトを生成(Instantiate)したり削除(Destroy)することは 一般的に行われています。ただし、オブジェクトの生成と削除は負荷がかかります。 Object Poolは一度生成したオブジェクトを非表示にして必要に応じて表示(有効化)して 使いまわす方法です。何度もオブジェクトを生成したり削除する必要がなくなるので負荷 を改善することができます。 52

53.

このベースのミニゲームにObjectPoolを適用する 53

54.

PlayerShootを開きます →PlayerShootを開きます。 54

55.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShoot : MonoBehaviour {
public GameObject bullet; //PrefabのFireを追加
public Transform muzzle; //空のオブジェクトの muzzleを設定

PlayerShoot.cs
ベースとなるプロジェクトを確認。

public float bulletSpeed = 1000f;
// Use this for initialization
void Start () {
}

今のままだと左クリックの分だけ bulletを生成する

// Update is called once per frame
void Update () {
// 左クリックが押された時に
if (Input.GetMouseButtonDown(0))
{
// Fireを生成する (シンプルに Instantiateをしている。 )
GameObject bullets = Instantiate(bullet) as GameObject;
// Fireの位置を調整する
bullets.transform.position = muzzle.position;
Vector3 bulletDirection;
bulletDirection = gameObject.transform.forward * bulletSpeed;
// Fireに力を加えて発射
bullets.GetComponent<Rigidbody>().AddForce(bulletDirection);
}
}
}

55

56.
[beta]
using

System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShoot : MonoBehaviour {
public GameObject bullet;
public Transform muzzle;
public float bulletSpeed = 1000f;
Animator animator;
List<GameObject> bulletStocks;

Object Pool 適用:
PlayerShoot

// Start関数内でbulletを10個生成する
void Start()
{
animator = GetComponent<Animator>();
bulletStocks = new List<GameObject>();
for (int i = 0; i < 10; i++)
{
GameObject obj = (GameObject)Instantiate(bullet);
obj.SetActive(false);
bulletStocks.Add(obj);
}
}
//左クリックで押された数のBulletを有効化する
void Fire()
{
for (int i = 0; i < bulletStocks.Count; i++)
{
if (!bulletStocks[i].activeInHierarchy)
{
bulletStocks[i].transform.position = muzzle.transform.position;
bulletStocks[i].transform.rotation = muzzle.transform.rotation;
bulletStocks[i].SetActive(true);
Rigidbody bullet = bulletStocks[i].GetComponent<Rigidbody>();
bullet.AddForce(bullet.transform.forward * bulletSpeed);
break;
}
}
}
// Update is called once per frame
void Update () {
// z キーが押された時
if (Input.GetMouseButtonDown(0))
{
animator.SetTrigger("Attack");
Fire();
}
}
}

56

57.

Bulletを開く ベースとなるプロジェクトを確認。 using System.Collections; using System.Collections.Generic; using UnityEngine; 3秒後に削除される public class Bullet : MonoBehaviour { // Use this for initialization void Start () { Destroy(gameObject, 3.0f); //3秒後に削除 } } 57

58.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour {

Object Pool 適用:
Bullet

private void OnEnable() //オブジェクトがアクティブになったときに呼び出される
{
transform.GetComponent<Rigidbody>().WakeUp();
Invoke("HideBullet", 3.0f); //3秒後にHideBullet関数を呼び非アクティブ化する
}
void HideBullet()
{
gameObject.SetActive(false);
}
private void OnDisable() //オブジェクトが非アクティブになったときに呼び出される
{
transform.GetComponent<Rigidbody>().Sleep();
CancelInvoke();
}
}

58

59.

EnemyControllerを開く void OnCollisionEnter(Collision col) { if(col.gameObject.tag == "Bullet") { Destroy(col.gameObject); TakeDamage(damageValue); } } Bulletがカラスに当たった時に削除する。 59

60.

EnemyControllerを書き直す void OnCollisionEnter(Collision col) { if(col.gameObject.tag == "Bullet") { //Destroy(col.gameObject); col.gameObject.SetActive(false); TakeDamage(damageValue); } } Destroyするかわりにオブジェクトを非アク ティブ化する。 60

61.

Fireが生成されるようになった 61

62.

ワークショップ1 カラスが生成される部分をObject Poolのパターンに書き直す 62

63.
[beta]
using UnityEngine;
using System.Collections;

ワークショップ1
こちらをまず変更してみましょう

public class EnemyManager : MonoBehaviour
{
public GameObject enemyPrefab; //Karasuのプレハブを追加する
public Transform[] spawnPoints; //Karasuの生成ポイントを追加する
public float interval = 3.0f; //Karasuの生成インターバル
float time = 0.0f;
void Update()
{
time += Time.deltaTime; //時間をはかる
if(time > interval) //インターバルの時間以上に過ぎれば
{
int spawnPointIndex = Random.Range(0, spawnPoints.Length); //生成ポイントをランダムで選ぶ
GameObject enemy = Instantiate(enemyPrefab,spawnPoints[spawnPointIndex].position,spawnPoints[spawnPointIndex].rotation); //Enemyを生成する
time = 0f; //時間を0秒に戻す
}
}
}

63

64.

おすすめのチュートリアル https://learn.unity.com/tutorial/object-pooling# 64

65.

コルーチン 我々になじみのあるStart関数やUpdate関数は基本的に順番に実行されて おり、処理を途中で中断したり待機させたりすることができません。 コルー チン(Coroutine)は処理を中断したり待機したり終了させることができます。 https://docs.unity3d.com/ja/2018.4/Manual/Coroutines.html 65

66.

using System.Collections; using System.Collections.Generic; using UnityEngine; コルーチンを使った例 public class CoroutineBase : MonoBehaviour { // Start is called before the first frame update void Start() { StartCoroutine("CoroutineTest"); } //コルーチン関数を定義 private IEnumerator CoroutineTest() //コルーチン関数の名前 { Debug.Log("スタート"); yield return new WaitForSeconds(3.0f); Debug.Log("スタートから3秒後経過した"); } } 66

67.

using System.Collections; using System.Collections.Generic; using UnityEngine; コルーチンを使った例 public class CoroutineBase : MonoBehaviour { // Start is called before the first frame update void Start() { StartCoroutine("CoroutineTest"); } //コルーチン関数を定義 private IEnumerator CoroutineTest() //コルーチン関数の名前 { //コルーチンの内容 Debug.Log("スタート"); yield return new WaitForSeconds(3.0f); Debug.Log("スタートから3秒後経過した"); yield return new WaitForSeconds(5.0f); Debug.Log("スタートから8秒後経過した"); } } 67

68.

using System.Collections; using System.Collections.Generic; using UnityEngine; public class CoroutineBase : MonoBehaviour { // Start is called before the first frame update void Start() { StartCoroutine("CoroutineTest"); } コルーチンを使った例 //コルーチン関数を定義 private IEnumerator CoroutineTest() //コルーチン関数の名前 { //コルーチンの内容 Debug.Log("スタート"); yield return new WaitForSeconds(3.0f); Debug.Log("スタートから3秒後経過した"); yield return new WaitForSeconds(5.0f); Debug.Log("スタートから8秒後経過した"); StartCoroutine("CoroutineCallTest"); } IEnumerator CoroutineCallTest() { yield return new WaitForSeconds(2.0f); Debug.Log("違うコールチンが呼ばれます"); } } 68

69.

Tips:Script Execution Order Setting 異なるクラス間で、どのスクリプトの Start()が先に呼ばれるかを明示的に指定する方法を説明します。 CallTest.cs *********************** using UnityEngine; public class CallTest : MonoBehaviour { // Start is called before the first frame update void Start() { Debug.Log("ユニティの本社はアメリカにあります "); } } *********************** SecondCall.cs *********************** using UnityEngine; public class SecondCall : MonoBehaviour { // Start is called before the first frame update void Start() { Debug.Log("今日の夕食はカレーです "); } } *********************** 69

70.

Tips:Script Execution Order Setting 70

71.

Tips:Script Execution Order Setting 「 Script Execution Order」を開きます。 ① Edit > Project Settings > Script Execution Orderを開きます。 71

72.

Tips:Script Execution Order Setting ② Script Execution Orderの「+」を押して登録したいスクリプトを選択します。 72

73.

Tips:Script Execution Order Setting ③ スクリプトの実行順を設定します。 ④ 実行順は、先に実行したいスクリプトが上で後に実行したいスクリプトは下に移動させます。 (正確には、数値が低い方が優先されます。標準のコンポーネントは実行順は 0です。) CallTest.csを優先した場合: 73

74.

Tips:Script Execution Order Setting 74

75.

Tips:Script Execution Order Setting SecondCall.csを優先した場合: ↓SecondCallを上にしている。 75

76.

Tips:Script Execution Order Setting 76

77.

*ここからプロジェクトに戻ります。 77

78.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI; //名前空間追加

EnemyController

public class EnemyController : MonoBehaviour
{
public int enemyHealth = 10;
public int damageValue = 5;
public Slider HpSlider;
NavMeshAgent nav; //NavimeshAgent型の変数
GameObject target; //ターゲット追加
// Use this for initialization
void Start()
{
nav = GetComponent<NavMeshAgent>(); //自分自身のNavMeshAgentを参照
target = GameObject.FindGameObjectWithTag("Player"); //targetにPlayerを参照
}
void Update()
{
nav.SetDestination(target.transform.position);
}
//*********************************************************************************

78

79.
[beta]
using UnityEngine;
using UnityEngine.AI;
using System.Collections;
public class EnemyController : MonoBehaviour
{

コルーチンを使った例:

public int enemyHealth = 10;
public int damageValue = 5;
public Slider HpSlider;
NavMeshAgent nav;
Transform target;
// Use this for initialization
void Start()
{
nav = GetComponent<NavMeshAgent>();
target = GameObject.FindGameObjectWithTag("Player").transform;
StartCoroutine(UpdateTargetPath());
}
IEnumerator UpdateTargetPath()
{
float refreshRate = 0.5f;
while (target != null)
{
Vector3 targetPosition = new Vector3(target.position.x, 0, target.position.z);
nav.SetDestination(targetPosition);
yield return new WaitForSeconds(refreshRate);
}
}

//*********************************************************************************

79

80.

シーン移行にコルーチンを使用してみる 80

81.

StartGameを開きます using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class StartGame : MonoBehaviour { public void LoadNewScene() { SceneManager.LoadScene(1); //シーンを呼び出す } } 81

82.

ロード確認用のスライダーを追加する 82

83.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class StartGame : MonoBehaviour
{
[SerializeField] Slider slider;
AsyncOperation asyncLoad;

// Start is called before the first frame update
void Start()
{
StartCoroutine(LoadScene("Main"));
}

StartGameの中身を書き換え
次のシーンを先に読み込みを行います。
そして、ボタンが押された時には次のシーンを再生するようにしま
す。

IEnumerator LoadScene(string name)
{
asyncLoad = SceneManager.LoadSceneAsync(name);
asyncLoad.allowSceneActivation = false;
slider.value = 0f;
while (true)
{
yield return null;
slider.value = asyncLoad.progress;
if (asyncLoad.progress >= 0.9f)
{
slider.value = 1f;
}
}
}
public void GoNextScene()
{
asyncLoad.allowSceneActivation = true;
}
}

83

84.

Sliderを追加します 84

85.

Scriptable Object 85

86.

Scriptable Object ScriptableObject は、クラスインスタンスとは無関係に、大量のデータを保存するために使 用できるデータコンテナです。ScriptableObject の主な使用例の 1 つは、値に対し複数の コピーを避けることによってプロジェクトのメモリ消費を削減することです。これは、プロジェ クトに、プレハブ (同じデータをアタッチした MonoBehaviour スクリプトに格納します) があ る場合に便利です。プレハブは、インスタンス化するたびにそれぞれがデータのコピーを作 成します。 https://docs.unity3d.com/ja/2018.4/Manual/class-ScriptableObject.html 86

87.

Scriptable Objectを使うと良いこと プレハブでは、同じパラメータであっても 10、100と同じオブジェクトが生成 (Instantiate)するたびにこれらの変数がメ モリに確保されてしまう。例えばパラメータ の使用メモリが10KBだとしてプレハブが10 体いれば使用メモリは100KBになる。 それに対してScriptableObjectを使えば同 じ100体いても使用メモリは10KBのままで ある。 87

88.

Scriptable Objectの生成サンプル using UnityEngine; [CreateAssetMenu(menuName = "ScriptableObject/Create ParameterTable", fileName = "ParameterTable")] public class ScriptableSample : ScriptableObject { public string name; public int age; public string occupation; } 88

89.

Scriptable Objectを生成する 89

90.

パラメータを追加する 90

91.

using System.Collections; using System.Collections.Generic; using UnityEngine; Scriptable Objectを読み込む ScriptableObjectTest public class ScriptableObjectTest : MonoBehaviour { public ScriptableSample sample; // Start is called before the first frame update void Start() { Debug.Log(sample.name); Debug.Log(sample.age); Debug.Log(sample.occupation); } } 91

92.

Scriptable Objectを適用させる 92

93.

Resourcesフォルダを作成しそこにScriptable Objectを追加 *動的にScriptable Objectを読み込む 93

94.
[beta]
動的にScriptableObjectを読み込み
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScriptableObjectTest : MonoBehaviour
{
// public ScriptableSample sample;
// Start is called before the first frame update
void Start()
{
ScriptableSample sample = Resources.Load<ScriptableSample>("ParameterTable");
Debug.Log(sample.name);
Debug.Log(sample.age);
Debug.Log(sample.occupation);
}
}

94

95.

ScriptableObjectが読み込まれた 95

96.

Scriptable Objectを使ってカラスのパラメータを複数作ってみる カラスを複製してマテリアルを適用する 複製: Ctrl + D 96

97.

複製したカラスの色違いを作る 97

98.

2種類のカラスプレハブができた 98

99.
[beta]
準備:EnemyControllerを以下のように変更する
public class EnemyController : MonoBehaviour {
int enemyHealth = 10;
int damageValue = 5;
int enemyScore = 10;
void TakeDamage(int damage)
{
enemyHealth -= damage;
HpSlider.value = (float)enemyHealth /10 ;

void TakeDamage(int damage)
{
enemyHealth -= damage;
HpSlider.value = (float)enemyHealth /10 ;

if (enemyHealth <= 0)
{
enemyHealth = 0;
ScoreManager.score += 10;

if (enemyHealth <= 0)
{
enemyHealth = 0;
ScoreManager.score += enemyScore;

Destroy(gameObject);

Destroy(gameObject);

}
}

}
}

99

100.
[beta]
CharacterManagerを作成する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "ScriptableObject/Create NewCharacter", menuName = "NewCharacter")]
public class CharacterManager : ScriptableObject
{
public int enemyHealth;
public int damageValue;
public int enemyScore;
}

100

101.

Scriptable Objectを追加する ↑2つ用意する。 101

102.

以下のようなパラメータにしてみた 102

103.

EnemyControllerにScriptableObjectを読み込むようにする using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityEngine.UI; public class EnemyController : MonoBehaviour { public CharacterManager characterParameter; int enemyHealth = 10; int damageValue = 5; int enemyScore = 10; int enemyMaxHealth; //EnemyのMaxの値代入するための変数を追加する public Slider HpSlider; NavMeshAgent nav; GameObject target; private void OnEnable() { enemyHealth = characterParameter.enemyHealth; damageValue = characterParameter.damageValue; enemyScore = characterParameter.enemyScore; enemyMaxHealth = enemyHealth; //enemyHealth = 10; HpSlider.value = 1; } //以下は変更なし 103

104.

EnemyControllerにScriptableObjectを読み込むようにする void TakeDamage(int damage) { enemyHealth -= damage; HpSlider.value = (float)enemyHealth /enemyMaxHealth ; if (enemyHealth <= 0) { enemyHealth = 0; ScoreManager.score += enemyScore; //Destroy(gameObject); gameObject.SetActive(false); } } 104

105.

EnemyControllerにScriptableObjectに追加する 105

106.
[beta]
using UnityEngine;
using System.Collections;
public class EnemyManager : MonoBehaviour
{
public GameObject[] enemyPrefab;
public Transform[] spawnPoints;

どちらかのカラスが生成されるにする
*実際にはObjectPoolが適用されたものに以下の変更部分を変更して書き直しましょう。

public float interval = 3.0f;
float time = 0.0f;
void Update()
{
time += Time.deltaTime;
if(time > interval)
{
int enemyIndex = Random.Range(0, enemyPrefab.Length);
int spawnPointIndex = Random.Range(0, spawnPoints.Length);
GameObject enemy = Instantiate(enemyPrefab[enemyIndex],spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation);
time = 0f;
}
}
}

106

107.
[beta]
using UnityEngine;
using System.Collections;
using System.Collections.Generic; //追記

書き換える部分
(人によって書き方が違うと思いますので参考まで)

public class EnemyManager : MonoBehaviour
{
public GameObject[] enemyPrefab;
public Transform[] spawnPoints;
public float interval = 3.0f;
float time = 0.0f;
List<GameObject> enemyStocks;
private void Start()
{
enemyStocks = new List<GameObject>();
for (int i = 0; i < 10; i++) //10体生成
{
int enemyIndex = Random.Range(0, enemyPrefab.Length);
GameObject obj = (GameObject)Instantiate(enemyPrefab[enemyIndex]); //enemyPrefabを生成
obj.SetActive(false);
enemyStocks.Add(obj);
}
}

107

108.

EnemyManagerにStrongKarasuとKarasuを追加 108

109.

ランダムで2種類のカラスが生成されるようになる 109

110.

パラメータの違うカラスが生成された 110

111.

ワークショップ2 ScriptableObject読み込んでテキストのタイトルロゴの違う作ってみましょう 111

112.

使用する素材はこちらにある 112

113.

*このようにSciptableObjectを用意して読み込みます 113

114.

Asset Bundle 114

115.

Asset Bundleについて AssetBundle (アセットバンドル) はランタイムに読み込むプラットフォーム特有のアセッ ト (モデル、テクスチャ、プレハブ、オーディオクリップ、シーン全体) を含むアーカイブファ イルです。アセットバンドルは互いの依存関係を示すことができます。例えば、アセットバ ンドル A のマテリアルはアセットバンドル B のテクスチャを参照できます。ネットワークを 使った効果的な配布のために、アセットバンドルはその使用の要件に応じてビルトイン のアルゴリズムの中の 1 つで圧縮されます (LZMA と LZ4)。 https://docs.unity3d.com/ja/2018.4/Manual/AssetBundlesIntro.html 115

116.

サンプルでこのアセットをAssetBundle化してみます 116

117.

Unityプロジェクトに取り込みます 117

118.

アセットバンドル化します 118

119.

アセットバンドル名を付ける ↑AssetBundleの名前を付けます。 大文字は使えません。 119

120.

次は全て1つのアセットバンドルにまとめてみます 120

121.

同じ”item”にまとめます 121

122.

EditorフォルダにBuildAssetBundleスクリプトを追加 122

123.
[beta]
BuildAssetBundle (アセットバンドル化します)
using System.IO;
using UnityEditor;
public class BuildAssetBundle
{
// プロジェクト/AssetBundleItem フォルダの下にアセットバンドルビルドする
[MenuItem("Assets/Build AssetBundle")]
public static void Build()
{
var assetBundleDirectory = "./AssetBundleItem";
if (!Directory.Exists(assetBundleDirectory)) {
Directory.CreateDirectory(assetBundleDirectory);
}
BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
// ビルド終了表示
EditorUtility.DisplayDialog("アセットバンドルビルド終了", "アセットバンドルビルドが終わりました。", "OK");
}
}
123

124.

Assets → AssetBundle 124

125.

アセットバンドルのファイルが生成される 125

126.
[beta]
LoadAssetBundleを追加する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoadAssetBundle : MonoBehaviour
{
IEnumerator Start()
{
//itemアセットバンドルをロードします
var ab = AssetBundle.LoadFromFile("AssetBundleItem/item");
if (ab == null) {
yield break;
}
// ロードしたファイルの中から 4 Side Diamond(プレハブ) をロードして生成する
var prefab = ab.LoadAsset<GameObject>("4 Side Diamond");
Instantiate(prefab);
}
}

126

127.

4 Side Diamondをロードすることができた 127

128.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

今度はアセットバンドルの中身を全てロードする

public class LoadAssetBundle: MonoBehaviour
{
IEnumerator Start()
{
var ab = AssetBundle.LoadFromFile("AssetBundleItem/item");
if (ab == null) {
yield break;
}
//アセットバンドル化されていたプレハブを全て読み込んで生成する
var prefabs = ab.LoadAllAssets<GameObject>();
foreach (var prefab in prefabs)
{
Instantiate(prefab);
}
}
}

128

129.

オブジェクトがロードされた 129

130.

シーンデータのAssetBundle化について アセットバンドルはオブジェクトだけでなくシーンごとアセットバンドル化できます。 *サンプルシーン Secondを作成します。 130

131.

Secondシーンを”newscene”アセットバンドル化する 131

132.
[beta]
シーンを読み込みます
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

LoadNewSceneFromAssetBundle

public class LoadNewSceneFromAssetBundle: MonoBehaviour
{
IEnumerator Start()
{
var ab = AssetBundle.LoadFromFile("AssetBundleItem/newscene");
if (ab == null) {
yield break;
}
// Secondシーンをロードする
yield return SceneManager.LoadSceneAsync("Second", LoadSceneMode.Additive);
}
}

132

133.

Secondシーンが読み込まれた 133

134.

StreamingAssets Unity プロジェクトにおける StreamingAssets と呼ばれるフォルダーに配置したファイルは ビルド先のプラットフォームの、特定のフォルダーにそのまま何も変換されない状態で保持 されます。フォルダー名は Application.streamingAssetsPath プロパティーを取得するこ とができます。プラットフォームごとに配置されるフォルダーの場所は異なるため、常に Application.streamingAssetsPath を使用することをお勧めします。 https://docs.unity3d.com/ja/2018.4/Manual/StreamingAssets.html 134

135.

AssetBundleファイルをStreamingAssetsから読み込む “StreamingAssets”フォルダを作成する 135

136.

テキストをAssetBundleにして読み込んでみる 136

137.

HelloWorldというテキストを追加する 137

138.

アセットバンドル”test”というラベル作成 138

139.

新しい“ExportAssetBundleToStreamingAssets”をEditorフォルダに追加する 139

140.

ExportAssetBundleToStreamingAssets using UnityEngine; using System.Collections; using UnityEditor; using System.IO; public class ExportAssetBundleToStreamingAssets { [MenuItem("Assets/Build AssetBundles To StreamingAssets")] public static void Build() { BuildPipeline.BuildAssetBundles("Assets/StreamingAssets",BuildAssetBundleOptions.None,BuildTarget.StandaloneWindows); AssetDatabase.Refresh(); // ビルド終了を表示 EditorUtility.DisplayDialog("アセットバンドルの状態","アセットバンドルビルドが終わりました","OK"); } } 140

141.

AssetBundleファイルにビルドする 141

142.
[beta]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

LoadAssetBundleFromAS
StreamingAssetsフォルダから読み込む

public class LoadAssetBundleFromAS : MonoBehaviour
{
IEnumerator Start()
{
AssetBundle ab = null;
// AssetBundleロード
yield return LoadFromStreamingAssets("test", x => ab = x); //アセットバンドル"test"をロードする
var texts = ab.LoadAllAssets<TextAsset>();
foreach (var text in texts)
Debug.Log(text);
// AssetBundleををアンロードする
ab.Unload(false);
}

IEnumerator LoadFromStreamingAssets(string assetBundleName, System.Action<AssetBundle> callback)
{
string path = Application.streamingAssetsPath + "/" + assetBundleName;
byte[] info = File.ReadAllBytes(path);
AssetBundleCreateRequest req = AssetBundle.LoadFromMemoryAsync(info);
yield return req;
callback(req.assetBundle);
}
}
142

143.

プロジェクトを再生する 143

144.

おすすめのチュートリアル素材 https://learn.unity.com/tutorial/assets-resources-and-assetbundles# 144

145.

Editor拡張 145

146.

Attribute Attributeとは、日本語で「属性」という意味になります。クラスや変数に属性を追加する ことで特別な挙動を追加することができます。 example : [SerializeField] private int count; https://docs.unity3d.com/ja/2019.4/Manual/Attributes.html 146

147.

Range 値の範囲を指定する using UnityEngine; using System.Collections; public class HelloExtension : MonoBehaviour { [Range(1,100)] public int level = 1; // Use this for initialization void Start () { } // Update is called once per frame void Update () { Debug.Log (level); } } 147

148.

using UnityEngine; using System.Collections; Multiline public class HelloExtension : MonoBehaviour { [Range(1,100)] public int level = 1; [Multiline(5)] public string message; // Use this for initialization void Start () { } // Update is called once per frame void Update () { Debug.Log (level); Debug.Log (message); } } 148

149.

[HideInInspector] using UnityEngine; using System.Collections; public class HelloExtension : MonoBehaviour { [Range(1,100)]public int level = 1; [Multiline(5)]public string message; [HideInInspector] public int x = 10; // Use this for initialization void Start () { Debug.Log (x); } // Update is called once per frame void Update () { Debug.Log (level); Debug.Log (message); } } 149

150.

[SerializeField] using UnityEngine; using System.Collections; public class HelloExtension : MonoBehaviour { [Range(1,100)]public int level = 1; [Multiline(5)]public string message; [HideInInspector] public int x = 10; [SerializeField] private int y = 5; // Use this for initialization void Start () { Debug.Log (x); Debug.Log (y); } } 150

151.

using UnityEngine; using System.Collections; public class HelloExtension : MonoBehaviour { [Range(1,100)]public int level = 1; [Multiline(5)]public string message; [HideInInspector] public int x = 10; [SerializeField] private int y = 5; [Space(10.0f)]public float figure = 30; // Use this for initialization void Start () { Debug.Log (x); Debug.Log (y); } // Update is called once per frame void Update () { Debug.Log (level); Debug.Log (message); } } Space 151

152.
[beta]
using UnityEngine;
using System.Collections;
public class HelloExtension : MonoBehaviour {
[Range(1,100)]public int level = 1;
[Multiline(5)]public string message;
[HideInInspector] public int x = 10;
[SerializeField] private int y = 5;
[Header("プレイヤーのスピード ")]
[Space(10.0f)]public float speed = 30;
// Use this for initialization
void Start () {
Debug.Log (x);
Debug.Log (y);
}
// Update is called once per frame
void Update () {
Debug.Log (level);
Debug.Log (message);
}
}

Header

152

153.

エディター拡張 UnityはUnityエディタを自分でカスタマイズすることが可能です。 153

154.

エディター拡張をする準備 “Editor”という特別な名前のフォルダーを作り、そ の中にエディター拡張用のスクリプトを書いていくよ うになります。 154

155.

メニューに表示 using UnityEngine; using UnityEditor; using System.Collections; public class CreateObject : EditorWindow { [MenuItem ("Tools/ CreateObject")] static void OpenWindow(){ } } 155

156.

Windowが表示されます using UnityEngine; using UnityEditor; using System.Collections; public class CreateObject : EditorWindow { public static CreateObject window; [MenuItem ("Tools/ CreateObject")] //メニュー項目を作る。 static void OpenWindow(){ window = (CreateObject)EditorWindow.GetWindow(typeof(CreateObject)); //ウィンドウを作る。 window.titleContent = new GUIContent("Create Object"); // ウィンドウ名を設定します。 } } 156

157.
[beta]
クリックするとログが出るようにする
using UnityEngine;
using UnityEditor;
using System.Collections;
public class CreateObject : EditorWindow {
public static CreateObject window;
[MenuItem ("Tools/ CreateObject")]
static void OpenWindow(){
window = (CreateObject)EditorWindow.GetWindow(typeof(CreateObject));
window.titleContent = new GUIContent("Create Object"); // ウィンドウ名を設定します。
}
void OnGUI(){
if(GUI.Button(new Rect(0, 0, position.width, position.height), "クリックしてね")){
Debug.Log("クリックされた");
}
}
}
157

158.
[beta]
using UnityEngine;
using UnityEditor;
using System.Collections;
public class CreateObject : EditorWindow
{
public static CreateObject window;
private GameObject cube;

Cubeを生成する

[MenuItem("Tools/ CreateObject")]
static void OpenWindow()
{
window = (CreateObject)EditorWindow.GetWindow(typeof(CreateObject));
window.titleContent = new GUIContent("Create Object");
}
void OnGUI()
{
if (GUI.Button(new Rect(0, 80, 100, 100), "クリックしてね"))
{
Debug.Log("クリックされた");
}
if (GUI.Button(new Rect(100, 80, 100, 100), "キューブ"))
{
cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = new Vector3(Random.Range(-5,5), Random.Range(-5, 5), Random.Range(-5, 5));
}
}

}
158

159.

Cubeが生成されます 159

160.

Unity Studentプランのご紹介 https://store.unity.com/ja/academic/unity-student Unity Student には、5 シートの Unity Teams Advanced が付属します。 160

161.

もっとUnityを学びたい方へ https://unity.com/ja/products/unity-learn 161