teriyaki note

好きなものはラーメンと将棋

Unityで脱出ゲームを作る part.4 / モノを動かす編

< 前回 | 一覧 | 次回 >

前回まではアイテムに関する機能を実装してきたが、ゲーム画面の機能も実装してみよう。

ということで、ゲーム画面で何かをクリックしたときに物を動かす機能を実装してみる。

例えば、「鍵を使ってドアを開ける」のような感じ。
前回スクリプトがベースになってる)

成果物

視点を90度ずつ回転
f:id:teriyaki398:20181029230159g:plain


鍵を使うとドアが開けられる
f:id:teriyaki398:20181029230220g:plain

使用したアセット

今回もスクリプトと画像ファイルなどはGitHubで公開している。

github.com

前準備

部屋の作成

本番での部屋づくりの練習を兼ねて、360度見渡せる部屋を作ってみた。

f:id:teriyaki398:20181029231830p:plain:w550

ゲーム画面はこんな感じ

f:id:teriyaki398:20181029231911p:plain:w550

assetstore.unity.com

この3Dモデルを元に作ってみたが、クオリティが高いので、適当に配置するだけでそれっぽくできる。

画面の見栄えを良くするためには、以下のあたりが参考になる。

tsubakit1.hateblo.jp

UIの作成

今回の目標として、その場で90度ずつ視点を回転させてみたい。
実際はもっと色々と視点を動かす必要がありそうだが、とりあえずで。

まずは視点を操作するためのボタンを用意する。

スクリプト側から操作しやすいようにTagをCameraUIに変更し、アイコン画像を変更した。

f:id:teriyaki398:20181030111713p:plain:w550

ドアを動かすための準備

ドアを動かすためには、トリガーとなるオブジェクトを用意する必要がある。
そのオブジェクトをクリック -> スクリプトを呼び出す -> スクリプトがドアを動かす という要領だ。

空のオブジェクトを作成し、

  • 名前をDoorLockに変更
  • Layerを9: Clickableに変更(rayが当たればなんでも良い)

をした。取得できるアイテムと動かせるアイテムを区別するために、前回までのように取得できるアイテムのレイヤー名は10: Getableに変更しておいた。

f:id:teriyaki398:20181029233644p:plain:w550

f:id:teriyaki398:20181029233543p:plain:w550

全く見えない... (うっすらと緑の線が見える)

このように、ゲーム画面上に空のオブジェクトなどを配置すると非常に見づらいことがある。

そんな時はGizmoを設定しよう。
GizmoとはSceneビューでの視覚的な補助を行ってくれる拡張機能のこと。

f:id:teriyaki398:20181029235233p:plain:w550

まずは左上のアイコンをクリックして、何かしらのマークに変更してみよう。

さらに、専用のスクリプトを用意して、空のオブジェクトにドラッグ&ドロップでアタッチする。

貼り付けるスクリプトは以下でよい。

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

public class Gizmo : MonoBehaviour {

    
    // Update is called once per frame
    void OnDrawGizmos () {
        Gizmos.color = new Color(255,0,0,0.7f);
        Gizmos.DrawCube(transform.position, this.transform.localScale);
    }

}

f:id:teriyaki398:20181029235526p:plain:w550

このように空オブジェクトと同じサイズの赤い立方体とオブジェクト名が表示された。

これならどこまでがクリックの有効範囲か分かりやすい。

スクリプト

今回のデモに関係するスクリプトは以下の通り。

クラス 役割
TestManager.cs ゲーム全体の管理
ItemListController.cs アイテムリストの管理
CameraController.cs カメラ移動の管理
EventController.cs ゲーム内のイベント管理
DoorLock.cs イベントの内容

CameraController クラス

カメラを動かすためのクラス。今回はすごく単純な実装になっている。

Unityには物体を簡単に動かすためのアセットとしてiTweenがあるので、そちらを使った。

assetstore.unity.com

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;


public class CameraController {
    // Singleton
    private static CameraController cameraController = new CameraController();

    // GameObjects
    public GameObject mainCamera;

    public static CameraController getInstance(){
        return cameraController;
    }

    private CameraController(){
        mainCamera = GameObject.Find("Main Camera");
    }

    public void main(GameObject clickedUI){
        int currentAngle = (int)mainCamera.transform.localEulerAngles.y;
        
        switch(clickedUI.name){
            case "RightButton":
                iTween.RotateTo(mainCamera, iTween.Hash("time", 0.6, "y", ((currentAngle/90 + 1) % 4) * 90));
                break;
            
            case "LeftButton":
                iTween.RotateTo(mainCamera, iTween.Hash("time", 0.6, "y", ((currentAngle/90 - 1) % 4) * 90));
                break;
        }

    }


}

まずはカメラオブジェクトをコンストラクタで取得する。

具体的に処理を行っているのは以下の部分だけ。

public void main(GameObject clickedUI){
    int currentAngle = (int)mainCamera.transform.localEulerAngles.y;
    
    switch(clickedUI.name){
        case "RightButton":
            iTween.RotateTo(mainCamera, iTween.Hash("time", 0.6, "y", ((currentAngle/90 + 1) % 4) * 90));
            break;
        
        case "LeftButton":
            iTween.RotateTo(mainCamera, iTween.Hash("time", 0.6, "y", ((currentAngle/90 - 1) % 4) * 90));
            break;
    }

}
  • クリックされたUIの名前で分岐
  • 右ボタンなら現在の角度から90度足した角度にする
  • 左ボタンなら現在の角度から90度引いた角度にする

剰余をうまく使うことで 0 -> 90 -> 180 -> 270 -> 0 -> ... となるようにしている。

EventController クラス

ゲーム画面で何かがクリックされた時に、クリックされた物体を元にどのイベントを発生させるかを管理するクラス。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;


public class EventController{
    // Singleton
    private static EventController eventController = new EventController();

    // EventScripts
    public DoorLock doorLock;

    public static EventController getInstance(){
        return eventController;
    }

    private EventController(){
        doorLock = new DoorLock();
    }

    public void main(GameObject selectedGameObject){

        switch(selectedGameObject.name){
            case "DoorLock":
                doorLock.main();
                break;
        }

    }

}

こちらもCameraControllerとほぼ同じで大したことはしていない。

今回はDoorLockクラスというドアの開閉を担うクラスを実行させたいので、そのインスタンスをコンストラクタで生成する。

クリックされたオブジェクトがDoorLockのときにそのスクリプトを実行させている。

DoorLock

ドアの操作に関するクラス。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;


public class DoorLock{
    // GameObjects
    public GameObject door;

    // Controllers
    public ItemListController itemListController;

    public DoorLock(){
        door = GameObject.Find("Door_Wood");

        itemListController = ItemListController.getInstance();
    }

    public void main(){
        string selectedItemName = itemListController.getSelectedItemName();

        if(selectedItemName == "Key"){
            // ドアを開ける
            iTween.RotateTo(door, iTween.Hash("time", 3f, "z", 90f));

            // 鍵アイテムを消去
            itemListController.removeSelectedItem();
        } else {
            iTween.ShakeRotation(door, iTween.Hash("time", 0.3f, "z", 2f));
        }
    }
}

今回の設定では、鍵アイテムを選択しているとドアが開くとしたいので、itemListController.getSelectedItemName()から現在選択状態にあるアイテムの名前を取得している。

もし選択されたアイテムが鍵だったら

  • ドアを開ける(ドアを90度回転させる)
  • 鍵アイテムを削除する

そうじゃなかったら

  • ドアが開かないことを表現(`iTween.ShakeRotation()でガタガタ揺らしている)

TestManager クラス

ところどころ前回から変更しているのでまとめてみてみる。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class TestManager : MonoBehaviour {

    public EventSystem eventSystem;

    // カメラ関係
    public Camera mainCamera;

    // ray関係
    public Ray ray;
    public RaycastHit hit;
    public GameObject selectedGameObject;

    // controllers
    public ItemListController itemListController;
    public ItemDetailController itemDetailController;
    public CameraController cameraController;
    public EventController eventController;
    

    
    // Use this for initialization
    void Start () {
        eventSystem = GameObject.Find("EventSystem").GetComponent<EventSystem>();
        mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();

        // アイテムリストの操作
        itemListController = ItemListController.getInstance();
        // アイテム詳細画面の操作 + アイテムごとの処理
        itemDetailController = ItemDetailController.getInstance();
        itemDetailController.createItemInstances();
        // カメラの操作
        cameraController = CameraController.getInstance();
        // ゲーム画面でのイベント操作 + イベントの処理
        eventController = EventController.getInstance();

    }
    
    // Update is called once per frame
    void Update () {
        if(Input.GetMouseButtonUp(0)){

            selectedGameObject = eventSystem.currentSelectedGameObject;

            if(selectedGameObject == null){
                searchRoom();
            } else {
                switch(selectedGameObject.tag){
                    case "ItemListButton":
                        itemListController.click(selectedGameObject);
                        break;
                    
                    case "ItemDetail":
                        itemDetailController.main(selectedGameObject);
                        break;

                    case "CameraUI":
                        cameraController.main(selectedGameObject);
                        break;
                }
            }
            
        }

        // dキーで詳細画面へのショートカット
        if(Input.GetKey(KeyCode.D)){
            selectedGameObject = GameObject.Find("DetailButton");
            itemDetailController.main(selectedGameObject);
        }
    }

    public void searchRoom(){
        selectedGameObject = null;
        ray = mainCamera.ScreenPointToRay(Input.mousePosition);
        if(Physics.Raycast(ray, out hit, 10000000, 1 << 9 | 10)){

            selectedGameObject = hit.collider.gameObject;
            Debug.Log("Selected object is " + selectedGameObject.name);

            switch(selectedGameObject.layer){
                case 9:  // Clickable
                    eventController.main(selectedGameObject);
                    break;
                
                case 10: // Getable
                    itemListController.add(selectedGameObject);
                    Destroy(selectedGameObject);
                    break;
            }

        }
    }

}

メンバ変数

public CameraController cameraController;
public EventController eventController;

今回は新しくCameraControllerEventControllerというクラスを作成するので、それらのインスタンスを格納しておくための変数を追加した。

Start関数

void Start () {

    ... 略 ...

    // カメラの操作
    cameraController = CameraController.getInstance();
    // ゲーム画面でのイベント操作 + イベントの処理
    eventController = EventController.getInstance();

}

ゲームが始まると同時にインスタンスを取得。

Update関数

void Update () {
    if(Input.GetMouseButtonUp(0)){

        selectedGameObject = eventSystem.currentSelectedGameObject;

        if(selectedGameObject == null){
            searchRoom();
        } else {
            switch(selectedGameObject.tag){
                case "ItemListButton":
                    itemListController.click(selectedGameObject);
                    break;
                
                case "ItemDetail":
                    itemDetailController.main(selectedGameObject);
                    break;

                case "CameraUI":
                    cameraController.main(selectedGameObject);
                    break;
            }
        }
        
    }

    ... 略 ...
}

ゲームで何かがクリックされたときの処理。

クリックされたのがUIだった場合は、selectedGameObjectにそのオブジェクトが入っているので、else {}の処理を実行することになる。

UIのタグ名で分岐し、今回新しくCameraUIというタグ名を導入しているので、その部分を追加した。

searchRoom メソッド

public void searchRoom(){
    selectedGameObject = null;
    ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    if(Physics.Raycast(ray, out hit, 10000000, 1 << 9 | 10)){

        selectedGameObject = hit.collider.gameObject;
        Debug.Log("Selected object is " + selectedGameObject.name);

        switch(selectedGameObject.layer){
            case 9:  // Clickable
                eventController.main(selectedGameObject);
                break;
            
            case 10: // Getable
                itemListController.add(selectedGameObject);
                Destroy(selectedGameObject);
                break;
        }

    }
}

クリックできるアイテムの場合は、eventController.mainを実行し、取得できるアイテムの場合はアイテムリストに追加している。

TestManagerクラス全般の処理は以下を参考にしてる。

Unityで脱出ゲームの作り方(5)「3Dオブジェクトをクリックで取得」 | 閃光絵巻ラボ

これにて、ゲーム画面で物体を動かすことに成功した。

Tips

スクリプト的な部分よりもビジュアル部分が苦労する。

知らなかったセッティングの一つとして、環境光がある。

今回はLighting > Scene > Environment Lighting をSkybox から Colorにした。

今回はUnityアセットストアにあったSkyboxを使用したが、以下のサイトから作るのも良いらしい。

www.hdrlabs.com