T=>

3D脱出ゲームでuGUIとカメラ移動

ちょっとした脱出ゲームみたいなのを作ろうとして、よくある2Dの部屋の画像をつなぐんでなくそのまま3D空間でやったら楽じゃないって思って、 どうせならカメラの切り替えもぱっと切り替わるんじゃなくてぐいっと動いたらいい感じじゃないかな、と思ってやってみました。

プラスせっかくなのでタップ判定は透明なUGUIボタンで拾えばいろいろ微調整しやすそうだし、Editorからの連携うまくさせたいなーなんて思ったり。 エディタ操作でいければ非エンジニアも触りやすいような気がして。ただし結構複雑になっちゃったのでアレですが。

とりあえずやってみた

f:id:taro-furuya:20151103203046g:plain

github.com

中身

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

// control camera transition
public class CameraTransition : MonoBehaviour {
    // timespan to transition
    [SerializeField]float transitionSecond = 1;

    Dictionary<int, Camera> cameras = new Dictionary<int, Camera>();
    int currentId;

    // default camera id
    const int DefaultId = 0;
    bool transiting;

    void Start () 
    {
        // ensure that camera id is unique each other
        cameras = GameObject.FindObjectsOfType<CameraId>()
            .ToDictionary(x => x.Id, x => x.GetComponent<Camera>());
        
        // ensure default id camera exist
        cameras.First(x => x.Key == DefaultId)
            .Value
            .gameObject
            .SetActive(true);
        
        cameras.Where(x => x.Key != DefaultId)
            .Select(x => x.Value)
            .ToList()
            .ForEach(x => x.gameObject.SetActive(false));
    }

    public void Transit(int nextId)
    {
        if (nextId == currentId) return;
        if (transiting) return;
        
        StartCoroutine(TransitCore(nextId));
    }
    
    private IEnumerator TransitCore(int nextId)
    {
        transiting = true;
        
        var currentCamera = cameras[currentId];
        var originPosition = currentCamera.transform.position;
        var originRotation = currentCamera.transform.rotation;
        
        var nextCamera = cameras[nextId];
        
        var elapsedTime = 0f;
        while (elapsedTime < transitionSecond)
        {
            elapsedTime += Time.deltaTime;
            currentCamera.transform.position = Vector3.Lerp(originPosition, nextCamera.transform.position, elapsedTime / transitionSecond);
            currentCamera.transform.rotation = Quaternion.Slerp(originRotation, nextCamera.transform.rotation, elapsedTime / transitionSecond);
            yield return null;
        }
        transiting = false;
        
        currentId = nextId;
        nextCamera.gameObject.SetActive(true);
        currentCamera.gameObject.SetActive(false);
        currentCamera.transform.position = originPosition;
        currentCamera.transform.rotation = originRotation;
    }
}

対象カメラをStart()で拾ってくる→Transition()がきたらpositionとrotationをLerpする ってだけです。 Prefabsの中のCameraTransitionヒエラルキーに入れればOKになってます。

でTransitionに使いたいカメラにCameraId.csをアタッチ。Idを0から振れば準備OK。 重複したりDefaultIdのカメラがないとエラーでます。 Tagつかったりとかもっと上手いこと出来そうですが…

using UnityEngine;

// Attach to camera to be transitable
public class CameraId : MonoBehaviour {
    [SerializeField]int id;
    public int Id { get { return id; } }
}

あとはButtonのOnClick()にエディタからCameraTransitionのTransition()を選択して、遷移先のカメラIDを設定すると動きます。 f:id:taro-furuya:20151103204422j:plain

ポイント

各カメラをあらかじめ配置しておいて、遷移するときに次のカメラの位置まで線形で移動→次のカメラActivate、位置戻してdeactivate。

uGUIのCanvasでScreenSpace camera 選択したものと組み合わせた脱出ゲーム的ゲーム前提での話にすると

  • カメラをいっぱい配置してスクリプトつけてID振る、遷移もButton配置してエディタで設定→スクリプト自体をいじらなくて済むので、例えばちょっとUnity触ったことある非エンジニアがレベルデザインできそう。
  • UIをカメラごとに調整できるので該当カメラでの画面の調整がしやすいはず(たぶんRayとかでやると特定カメラだけ反応する、みたいなのの制御が逆にやっかいそう)