読者です 読者をやめる 読者になる 読者になる

夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【Unity5でレースゲーム】進む方向をナビってくれる空飛ぶ矢印を追加

Unity オープンワールドレーシングゲーム

今回は、次に進む方向を教えてくれる矢印を空中に表示してみました。
こういうやつです。

f:id:takujidev:20150725194405j:plain

矢印は進む方向に繰り返し動くので、かなり遠くからでも方向を視認する事ができます。

Directional Lightを消して矢印が見やすいようにして動画を撮ってみました。
夕方っぽい雰囲気になりました。

動画を見てもらうとわかりますが、次に向う方向は青い矢印、その次に向う方向は黄色い矢印で示しており、それより先の矢印は見えないようにしています。
コーナーをクリアする毎に次のコーナーに切り替わって行きますが、ショートカットなどをしてコーナーをきちんとクリアしなかった場合は次に黄色の矢印が出て来るので失敗した事がわかります。
動画ではあえてそういう茶番を入れています。

矢印はテクスチャーとして描き、Quadに貼り付けています。光らせるためにシェーダーはParticles/Additiveを使っています。

ビルボード的にtransformLookAt()を使ってカメラの方向を常に向くようになっていますが、通常のビルボードと違い、矢印の方向をz軸、面の方向をy軸に合わせ、LookAtする座標を次のコーナー、upの方向をメインカメラに向うベクトルにしています。こうする事で常に矢印が次のコーナーに向いつつもカメラの方向に面を向ける事ができます。

矢印を動かすスクリプトです。

using UnityEngine;
using System.Collections;

public class NavigationMaker : MonoBehaviour {

    public float height = 15f; // 高さ
    public float length = 15f; // 移動距離
    public GameObject flyingMarker; // マーカー用プレファブを指定

    private Vector3 _start; // 初期位置
    private Vector3 _dstMarker; // 次のマーカーの位置
    private Vector3 _toDestination; // 進行方向のベクトル

    private float _progress; // 進行状況(0f 〜 1f)
    private const float _timeFactor = 0.5f; // 全行程の時間
    private const float _wait = 0f; // 最終地点で待つ時間
    private const int _numLen = 3; // コーナーマーカーのインデックスの桁数
    private const float _fadeInIime = 0.3f; // フェードインする時間(全行程1fに対する割合)
    private const float _fadeOutTime = 0.5f; // フェードアウトする時間(全行程1fに対する割合
    private const float _startFadeOutTime = 1f - _fadeOutTime; // フェードアウト開始プログレス地点
    private Color _blue = new Color(0f, 0.9f, 0.8f, 1f);
    private Color _yellow = new Color(1f, 1f, 0f, 1f);
    private Color _transparent = new Color(0f, 0f, 0f, 0f);
    private Color _currentColor;
    private const float _maxAlpha = 0.5f;

    private Transform _naviMarker; // ナビマーカー向き調整用親オブジェクト
    private Transform _mainCamera;

    private Renderer _renderer;

	void Start () {
        int nameLen = gameObject.name.Length;
        int number = int.Parse(gameObject.name.Substring(nameLen - _numLen, _numLen));
        string nameBody = gameObject.name.Substring(0, nameLen - _numLen);

        // 自分の親のRaceTrackスクリプトがコース全体の長さを知っている
        RaceTrack raceTrack = transform.parent.GetComponent<RaceTrack>();

        number++;
        if (number >= raceTrack.raceTrack.Length) {
            number = 0;
        }
        string paddedNumber = string.Format("{0:D3}", number);
        Transform destination = GameObject.Find(nameBody + paddedNumber).transform;
        _dstMarker = destination.position;
        _dstMarker.y = height;

        _toDestination = destination.position - transform.position;
        _toDestination.Normalize();
        _toDestination *= length;

        _start = transform.position - _toDestination * 0.5f; // 自分の位置が中心に来るようにスタート位置を設定
        _start.y = height;

        GameObject markerObject = Instantiate(flyingMarker, _start, Quaternion.identity) as GameObject;
        _naviMarker = markerObject.transform;
        _renderer = markerObject.GetComponentInChildren<Renderer>();
        _mainCamera = GameObject.Find("Main Camera").transform;
        _progress = 0f;

        StartCoroutine(UpdatePosition());
	}
	
    IEnumerator UpdatePosition () {
        while (true) {
            // マーカーの位置を動かす
            _progress += Time.deltaTime / _timeFactor;
            _naviMarker.position = _start + _toDestination * _progress;

            // マーカーの位置によって透明度を設定する
            Color color = _currentColor;
            if (_progress >= 1f) {
                color.a = 0f;
                yield return new WaitForSeconds(_wait);
                _progress = 0f;
                _naviMarker.position = _start;
            } else if (_progress < _fadeInIime) {
                color.a = (_progress / _fadeInIime) * _maxAlpha;
            } else if (_progress >= _startFadeOutTime) {
                color.a = (1f - ((_progress - _startFadeOutTime) / _fadeOutTime)) * _maxAlpha;
            } else {
                color.a = 1f * _maxAlpha;
            }
            _renderer.material.SetColor("_TintColor", color);

            // 矢印の先を次のマーカーの方向、面をメインカメラの方向へ向ける
            Vector3 toCamera = _mainCamera.position - _naviMarker.position;
            _naviMarker.LookAt(_dstMarker, toCamera);
            yield return null;
        }
	}

    // これらのメソッドはPlayerProgressTrackerから呼ばれる
    public void SetCurrentTarget()
    {
        _currentColor = _blue;
    }

    public void SetNextTarget()
    {
        _currentColor = _yellow;
    }

    public void SetInvisibleTarget()
    {
        _currentColor = _transparent;
    }
}

次に向うコーナーの切り替えは自車がコーナーに設置したコライダーに接触しOnTriggerEnterしたタイミングで行っています。

using UnityEngine;
using System.Collections;

public class PlayerProgressTracker : MonoBehaviour {

    private Transform[] _raceTrack;
    private NavigationMaker[] _navMarkerScript;
    private int _progress;
    private int _progressNext;
    private Transform _currentTarget;

	// Use this for initialization
	void Start () {
        string trackName = GameObject.Find("TrackInformation").GetComponent<TrackInformation>().trackName;
        _raceTrack = GameObject.Find(trackName).GetComponent<RaceTrack>().raceTrack;

        // NavigationMarkerスクリプトへの参照をキャッシュしておく
        _navMarkerScript = new NavigationMaker[_raceTrack.Length];
        for (int i = 0; i < _raceTrack.Length; i++) {
            _navMarkerScript[i] = _raceTrack[i].GetComponent<NavigationMaker>();
            _navMarkerScript[i].SetInvisibleTarget();  // 非表示のターゲットとして初期化
        }
        _progress = 0;
        _progressNext = _progress + 1;
        _currentTarget = _raceTrack[_progress];
        _navMarkerScript[_progress].SetCurrentTarget();
        _navMarkerScript[_progressNext].SetNextTarget();
	}

    public void IncrementTarget()
    {
        _navMarkerScript[_progress].SetInvisibleTarget();
        _progress++;
        if (_progress >= _raceTrack.Length) {
            _progress = 0;
        }
        _currentTarget = _raceTrack[_progress];
        _navMarkerScript[_progress].SetCurrentTarget();

        _progressNext++;
        if (_progressNext >= _raceTrack.Length) {
            _progressNext = 0;
        }
        _navMarkerScript[_progressNext].SetNextTarget();
    }

    public Transform GetTarget()
    {
        return _currentTarget;
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.transform == _currentTarget) {
            IncrementTarget();
        }
    }
}