Unity

(作成:2017/11)

サンプルプログラムで幾つか遊んでみたので、理解した事を書いてみる。

開発の流れ

手法など

設計ではなく開発時の手順や考え方。「Unityの教科書」で学んだ事から掻い摘んで。

  • ゲームの部品を洗い出す
    • 各種シーン(OP、StageXX、ED……)、キャラクタ(自機、敵、弾、アイテム、ボス……)、挿絵、テキスト、Audio……
  • ゲームの部品に対し、スクリプトの割り当てを考える
    • 画面上で動作するもの: XXXController.cs
    • 条件(時間経過など)により生成されるもの: XXXGenerator.cs
    • UI更新やゲームの進行判断をするもの: XXXDirector.cs

例えば東方Projectみたいなシューティングの場合、道中はざっくり以下のようなコードが必要になるのだと思う。

  • 自機オブジェクト
    • PlayerController.cs: 移動、ショットなどの操作、敵や敵弾、アイテムとの衝突判定など
  • 敵オブジェクト
    • EnemyGenerator.cs: 時間経過の条件で画面上に出現するなど
  • アイテムオブジェクト
    • ItemGenerator.cs: 特定の敵を倒した時に画面上に出現するなど
  • 得点、Player数、Bomb数など
    • GameDirector.cs: 衝突判定やボスを倒した時の条件を受けて値を加減するなど


スクリプト

Unity 2017でスクリプトを作成すると、以下のようなテンプレが出来る。例えばあるオブジェクトに Controller という名前でスクリプトコンポーネントを追加した場合。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Controller : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

UnityEngine はUnityの標準クラスライブラリ。UI使う時は UnityEngine.UI とかも宣言して使う。
public class Controller : MonoBehaviour ブロック内がスクリプトの実体。MonoBehaviour を継承することで、Unity上で動作するスクリプトが書ける。なお、Unityルール上1つの.csファイルには1つのファイル名と同名のクラスしか定義できないらしい。コーディング上必要なクラスは別に.csファイル起こして書けって事かな。
void Start () にはオブジェクト初期化に必要なコードを、 void Update () には毎フレーム毎必要なコードを記述する。

逆引きリファレンス(エディタ操作)

リファレンスってレヴェルじゃないけれど、サンプルプログラムで得た操作方法を色々と。

オブジェクトを作る(床、壁、キャラクタなど)

Cube (箱型)、 Sphere (球形)、 Plane (壁や床用の単純平面) など 3Dオブジェクト、 Sprite (パラパラ漫画みたいな動画処理) など 2Dオブジェクトや EffectLightAudioVideoUICamera など一通りのオブジェクト作成ができる。

  • Hierarchy - Create

ついでにオブジェクトの使い分けに関して調べた事を簡単に。

  • Plane Quad Terrain の違い

どれも平面オブジェクトが生成されるけど、その違いは何っていう話。

オブジェクト 座標系 頂点数 ポリゴン数(三角形) 用途
Plane xz平面 121 200 単純な床や壁などに使う。
Quad xy平面 4 2 画像/動画ディスプレイや単純なUI表示に使う。
Terrain xz平面 289 512 広大なマップ作成などに使う。頂点数/ポリゴン数は可変。

オブジェクトに色を付ける

Material アセットを作り、色情報を仕込んで対象オブジェクトへD&Dすれば良い。

  • Assets - Create - Material
    • Albedo などMaterial情報を設定
  • Hierarchy - 対象オブジェクトへD&D

すると以下のように Inspector が Material へ追加される。ここでは Albedo で色変更をしているだけ。

オブジェクトのグループ化

空のオブジェクトを使って、その配下にD&Dして配置すれば良い。

  • Hierarchy - Create - Create Empty

例えば Ground1 - Ground4Wall1 - Wall4 のオブジェクトを Stage という名前でグループ化する場合。

  • Stage: ゲームのステージを表す Empty
    • Ground1 - Ground4: 床を表す Plane
    • Wall1 - Wall4: 壁を表す Cube

静的オブジェクト化

ゲーム中で動かないものは積極的に静的オブジェクト化しておくことで、パフォーマンスやヴィジュアル向上が見込める。
ここでは Stage オブジェクトを静的にする例。

  • Stage: ゲームのステージを表す Empty
    • Static: チェック

オブジェクトに物理演算を適用

Player オブジェクトに物理演算を使う例。

  • Player: プレイヤを表す Sphere
    • Add Component - Physics - Rigidbody

オブジェクトの動作をスクリプトで制御する

Player オブジェクトに PlayerController.cs スクリプトを割り当てる例。

  • Player: プレイヤを表す Sphere
    • Add Component - New Script
    • Name: PlayerController
    • Language: CSharp

Assets 内に PlayerController.cs が生成されるので、こちらにコーディングをする。

同じオブジェクトを量産する

単純にオブジェクトをコピーするのでなく、Prefabにアセット化することでプロパティを共通化できる。
ここでは Item オブジェクトを量産する例。

  • Item: アイテムを表す Capsule
    Project へD&D

  • Project - Item
    ItemPrefab にリネーム(区別のため)

  • Project - ItemPrefab
    Hierarchy へD&D

以降 Item のスクリプト追加などまとめて処理したい場合は Project - ItemPrefab を選択して表示される Inspector から行うか、 Inspector - Prefab - Apply を押して全適用する。

シーン再読込時に画面が暗くなるのを防ぐ

  • Window - Lighting - Settings
    • Auto Generate: アンチェック
    • Generate Lighting: クリック

Unityのバグ、というか処理速度の問題? 今一理由はハッキリしないけれど。
何でもUnityなどのゲームエンジンは光の計算をゲームを実行前に済ませておいて、実行中はその計算結果を使うことで描画を高速化している。で、Auto Generate だと光の変化を自動検出・再計算するしてくれるのですが、Sceneロード時は処理が間に合わないのか上手くいかない模様。

参考:
- Unity5,6のライト・照明について質問させてください。 - https://... - Yahoo!知恵袋
- 個人的なUnityはまりポイント - はきだめ

逆引きリファレンス(コーディング)

オブジェクト同士の衝突判定

アイテムを取得するなど。 PlayerItemPrefab に接触したら ItemPrefab を消す例。

  • Player: プレイヤを表す Sphere
    • Tag: Player
  • ItemPrefab: アイテムを表す Capsule(Prefab)
    • Sphere Collider - Is Trigger: チェック
    • ItemPrefab(Script)
//// ItemPrefab.cs

public class ItemPrefab : MonoBehaviour {
    // 省略 //

    // 接触判定
    void OnTriggerEnter(Collider hit)
    {
        // Playerタグを持つオブジェクトと接触した場合はアイテム取得とみなして消す
        if (hit.CompareTag("Player"))
        {
            Destroy(gameObject);
        }
    }
}

オブジェクトを前後左右に動かす

十字キーを使ってx-z軸方向に動かす例。

  • Player: プレイヤを表す Sphere
    • PlayerController(Script)
    • Speed: 10
//// PlayerController.cs

public class PlayerController : MonoBehaviour {

    public float speed = 10.0f;

    // 省略 //

    void Update () {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        Rigidbody rb = GetComponent<Rigidbody>();

        rb.AddForce(x * speed, 0, z * speed);
    }
}

Horizontal Vertical はジョイスティック or A W S D or 矢印キーにマッピングされる。
これを Mouse X Mouse Y にすればマウスの移動量にマッピングされる。

public float speed は移動量の乗算分で、 public 宣言しておくことでUnityの Inspector 上で数値を調整できるようになる。

カメラをオブジェクトに追従させる(1)

カメラを目的のオブジェクトの子要素にする方法。
- Player: プレイヤを表す Sphere
- Main Camera: ゲーム画面を表示する Camera ( Hierarchy からD&D)

但し Sphere のようにオブジェクトが転がるような場合、カメラも一緒になって回転する。

カメラをオブジェクトに追従させる(2)

プレイヤ位置を追従するコンポーネントをカメラに追加する方法。ここではシーン上のカメラ位置をオフセットにしつつ Player オブジェクトに追従する例。

  • Main Camera: ゲーム画面を表示する Camera
    • Transform
    • Position X: 0 Position Y: 9 Position Z: -5
    • Rotation X: 60 Rotation X: 0 Rotation X: 0
    • FollowPlayer(Script)
    • Target: Player(Transform)Hierarchy からD&D)
  • Player: プレイヤを表す Sphere
    • Transform
    • Position X: 0 Position Y: 1.5 Position Z: 0
    • Rotation X: 60 Rotation X: 0 Rotation X: 0
//// FollorPlayer.cs

public class ForrowPlayer : MonoBehaviour {

    public Transform target;        // ターゲットへの参照
    Vector3 offset;                 // オフセット

    // Use this for initialization
    void Start () {
        // シーン上の初期位置の関係をオフセットに使う
        offset = GetComponent<Transform>().position - target.position;
    }

    // Update is called once per frame
    void Update () {
        // 自身の位置をターゲット位置+オフセットにする
        GetComponent<Transform>().position = target.position + offset;
    }
}

UI - テキストを表示する

ゲーム画面上のUI(得点や時間などのステータス、クリックボタン他)を作成する方法。
ここでは画面左下に得点を表示する例。

  • Canvas: ゲーム画面上の UI
    • Canvas Scaler(Script) - UI Scale Mode: Scale With Screen Size
  • ScoreLabel: 得点を表示する Text
    • Rect Transform
    • Pos X:-230 Pos Y:-200 Pos Z:0
    • Width: 300 Height: 100
    • Text(Script)
    • Text: 0
    • Font Size: 60

UI - テキストを更新する

ここでは最初にシーン上の ItemPrefab 総数を表示し、 Player が取得する毎に減らしていく場合の例。

  • ItemPrefab: アイテムを表す Capsule(Prefab)
    • Tag - Item (追加)
  • GameController: ゲーム全体をコントロールする Empty
    • GameController(Script)
    • ScoreLabel: ScoreLabel(Text)Hierarchy からD&D)
//// GameController.cs

using UnityEngine.UI;

public class GameController : MonoBehaviour {

    public Text scoreLabel;     // UIコンポーネント

    // 省略 //

    void Update () {
        // Itemタグを持つオブジェクトの総数を取得し、UIに表示する
        int count = GameObject.FindGameObjectsWithTag("Item").Length;
        scoreLabel.text = count.ToString();
    }
}

UI - アクティブ化

Itemを全部取得したらゲームクリアを表示するなど。画面繊維ではなくUIの表示/非表示を切り替えて表現したい場合。

  • Canvas - WinnerLabel: "You Win" と表示する Text
    • Inspector
    • WinnerLabel: アンチェック
    • Rect Transform
      • Pos X:-230 Pos Y:-200 Pos Z:0
      • Width: 300 Height: 100
    • Text(Script)
      • Text: 0
      • Font Size: 60
  • GameController: ゲーム全体をコントロールする Empty
    • GameController(Script)
    • WinnerLabelObject: WinnerLabelHierarchy からD&D)
//// GameController.cs

using UnityEngine.UI;

public class GameController : MonoBehaviour {

    public GameObject winnerLabelObject;    // UIオブジェクト

    // 省略 //

    void Update () {
        // Itemが無くなったらクリアUIをアクティブ化
        if (count == 0)
        {
            winnerLabelObject.SetActive(true);
        }
    }
}

初期画面の再読込

障害物に当たったら最初の状態に戻すなど。 PlayerDangerWallPrefab に衝突すると負けとみなし初期画面に戻す例。

  • Player: プレイヤを表す Sphere
    • Tag: Player
  • DangerWallPrefab: 障害物を表す Cube(Prefab)
    • Static: チェック
    • DangerWallPrefab(Script)
//// DangerWallPrefab.cs

using UnityEngine.SceneManagement;

public class DangerWallPrefab : MonoBehaviour {

    // 省略 //

    // 衝突判定
    private void OnCollisionEnter(Collision collision)
    {
        // Playerが接触した場合、現在のシーンを再読込する
        if (collision.gameObject.CompareTag("Player"))
        {
            int sceneIndex = SceneManager.GetActiveScene().buildIndex;
            SceneManager.LoadScene(sceneIndex);
        }
    }
}