【開発メモ】ステージオブジェクト「トランポリン」の実装

というわけで、世界中がリオ五輪で盛り上がっている今日この頃!
便乗するわけではありませんが、こんなものを実装してみました!

shot2ss20160820213023487

所謂「トランポリン」です!
上に乗ると自動的にジャンプし、ジャンプボタンを押すと大ジャンプします。
割とユニークなギミックなためか、最近のゲームでは見かけなくなった気がします。

既に似たようなシステムとして、「ジャンプエリア」というものを作ってあります。
エリア内に入った状態でジャンプすると大ジャンプができます。
トランポリンとの違いは「判定なのでゲーム上では見えない」「入ってもオートでジャンプせず、手動入力した場合に否応なく大ジャンプ」といった感じです。
トランポリンを作れば要らない気もしますが、消す必要もないのでとりあえず残しておく予定です。

トランポリンのモデリングとジャンプ制御

物がないと始まらないので、トランポリンをモデリングしてみます。

shot2ss20160820213813901

うーん・・・何だかただの机に見えなくもない微妙な感じです。
まあモデリングが下手なのは今に始まったことではないので、気にせずいきます!

shot2ss20160820213611949

Unityへインポート後、各種コライダーを設定します。
外枠部分に MeshCollider、中央部分に BoxCollider を追加し、大きさや位置を微調整します。
本当は円形に合わせてコライダーを設定できればよかったのですが、楕円形のコライダーを作る方法が分からなかったので、BoxCollider でなるべく合うように調整しました。
キャラクターとトランポリンの大きさ的にこれでも問題なかったです。

トランポリンを制御するためのスクリプトを作成します。
ひとまず、「上に乗ったときに自動でジャンプする」動きを目指してみます。

using UnityEngine;
using System.Collections;

public class Trampoline : MonoBehaviour {

    public float jumpHeight;
    public float enemyJumpHeight;

    void OnTriggerEnter(Collider c) {
        if (c.gameObject.tag == "Player") {
            CharacterMotor charMotor = c.GetComponent<Player>().getCharacterMotor();

            charMotor.movement.velocity.y = 0;
            iTween.Stop(c.gameObject);
            charMotor.movement.velocity.y = jumpHeight;
        }

        if (c.gameObject.tag == "Enemy") {
            Rigidbody rb = c.GetComponent<Rigidbody>();
            iTween.Stop(c.gameObject);
            rb.velocity = new Vector3(rb.velocity.x, enemyJumpHeight, rb.velocity.z);
        }
    }
}

プレイヤーキャラは CharacterMotor、敵キャラは Rigibody を使用して制御しているため、処理を分ける必要があります。
ジャンプ時に余計な加速度が掛かっている場合、一度リセットしないと想定以上に吹っ飛んでしまったりします。
「velocity.yを0に設定」「iTween.Stop()で余計なiTweenを停止」はそのためです。
前者はともかく、後者は移動関係以外の iTween も止まってしまったりするので、他に iTween を使っている場合は気を付ける必要がありそうです。

ジャンプボタン押下で大ジャンプする処理

トランポリンと言えば、ジャンプボタン押下時に大ジャンプするものが大半です。
ということで、上のスクリプトを少し修正します。

using UnityEngine;
using System.Collections;

public class Trampoline : MonoBehaviour {

    public float jumpHeight;
    public float inputJumpHeight;

    public float enemyJumpHeight;

    void OnTriggerEnter(Collider c) {
        if (TagUtility.getParentTagName(c.gameObject) == "Player") {
            CharacterMotor charMotor = c.GetComponent<Player>().getCharacterMotor();

            charMotor.movement.velocity.y = 0;
            iTween.Stop(c.gameObject);

            if (Input.GetButton("Jump")) {
                charMotor.movement.velocity.y = inputJumpHeight;
            } else {
                charMotor.movement.velocity.y = jumpHeight;
            }
        }

        if (TagUtility.getParentTagName(c.gameObject) == "Enemy") {
            Rigidbody rb = c.GetComponent<Rigidbody>();
            iTween.Stop(c.gameObject);
            rb.velocity = new Vector3(rb.velocity.x, enemyJumpHeight, rb.velocity.z);
        }
    }
}

超シンプルに「コライダー判定時にジャンプボタンが押されているか」で行きます。
また、自動ジャンプと入力時のジャンプでフィールドを分け、区別できるようにします。
敵には入力で大ジャンプなんてないのでそのままです。

20160820_01

実際に動かすとこんな感じに。
モーションとかはさておき、挙動は問題なさそう。

ただし問題があり、2段ジャンプが有効の場合は2段が優先されてしまい、トランポリンの大ジャンプがなくなってしまいます。
今の実装の2段ジャンプはバグの温床になりえるので、いっそ無効化してしまおうかと思っていたりします。
本格的にやるならトランポリンのジャンプ専用モーションを作り、その間2段ジャンプのステートには遷移しないようにすればいけそうです。

まとめ

そんなわけで、トランポリンと使用時のジャンプ制御について作成してみました!
これでステージを作る際に高低差を激しくしても大丈夫ですね。

【Unity】「ProBuilder Basic」を使ったシンプルなステージ作成

というわけで、今回はステージ作成についてのお話です。
アセット「ProBuilder Basic」を使ってみました!

https://www.assetstore.unity3d.com/jp/#!/content/11919

旧名では「Prototype」と呼ばれていたそうです。
所謂エディタ拡張系のものですが、非常によくできていて使いやすかったです。

ProBuilderに手を出した経緯

今までステージ作成は Terrain と Blender を組み合わせて行っていました。
・・・が、

・Unity と Blender を交互に操作する必要がある
・Blender 側を変えると Unity 側でリインポートするため、微調整だけでも時間が掛かる
・たまに Blender 側の変更が Unity 側に反映されない
・Blender はZ軸が上だけど、Unity はY軸が上で(ry
・Terrain で作った地形の微調整が難しい

などの点でやっていられなくなったので、グーグル先生に相談。
ぐぐると、テラシュールブログさんで「ProBuilder Basic」というアセットが紹介されていました。

機能はいろいろありそうですが、Unity 上でモデルをいじれるのが非常によろしいです。
複雑なモデルを作るのは厳しそうですが、シンプルなオブジェクトを作るのであれば十分。
配置後でも変更できるので、「この面だけちょっと上に・・・」といった場合でも問題ありません。
面だけでなく、辺や頂点を移動させることもできます。

現状ではゲームとして成立していないのが何よりも致命的な点です。
いくらシステムやキャラクターを作ってもテストプレイできないので、有用かどうかの妥当性を図ることができません。
「カンガルー作る前にゲーム出来るようにしろ」という知人のお言葉は正論だと思います。

そんなわけで、ビジュアル面をいじりたい気持ちを抑えつつ、ProBuilder を使ってシンプルなステージを作っていきます!
使い方はテラシュールブログさんで紹介されているので、この記事では割愛します。

ステージの作成

とりあえず適当に配置してステージっぽくしていきます。
大抵はキューブ型で十分ですが、時々階段や円状のオブジェクトも混ぜていきます。
どうせならチュートリアル的な面も兼ねていきたいので、二段ジャンプや壁ジャンプなどが必要となるよう構成してみます。
あとはセンスとイマジネーションで気合で作ります。

shot2ss20160619222553038

Vキーを押しながら左ドラッグするとピッタリくっ付けやすいです。
今までは視点変更をしながら頑張って配置していましたが、これを使うとラクラクです。
というかもっと早く知っておくべきでしたorz

配置後に特定の面を伸ばしたり縮めたりもできます。
その場合は Ctrl を押しながらドラッグすると、マス目の分だけ伸縮されるのでやりやすいです。
逆にオブジェクト生成後に Scale を変えてしまうとマス目の大きさが変わってしまうので注意します。

shot2ss20160621212214983

前に作っていた「ステージ上に配置するオブジェクト」も入れてみます。
「壊せる箱」とか「ダメージを受けるトゲ」とかですね。

shot2ss20160619222659087

壁ジャンプは専用の壁に密着している時にジャンプボタンで行えます。
詳しい実装は以下の記事をご参照下さい。

【開発メモ】アクション「壁ジャンプ」の実装

カメラワークの調整

3Dゲーム且つカメラワークはオートなものを目指しています。
ということは、ステージの構成や場面に応じてカメラを移動・回転させる必要がありますね。
このあたりの実装は以前やっていますが、ちょっと古いので再度載せておきます!

shot2ss20160619222834238

mainCamera の Transform を直接あれこれすると、「プレイヤーを中心にカメラを回す」という処理が困難です。
なので、カメラを空オブジェクトの子に設定し、その空オブジェクトにスクリプトを付けます。
また、空オブジェクトは常にプレイヤーの位置を参照・移動するようにします。

using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour {

    private GameObject mainCamera;
    private GameObject player;

    private Vector3 defaultAngle;
    private Vector3 defaultPosition;

    public float default_angle_y;

    void Start() {
        // パラメータに値を設定
        player = GameObject.FindGameObjectWithTag("Player");
        mainCamera = GameObject.FindGameObjectWithTag("MainCamera");

        defaultPosition = new Vector3(0, 25f, -70f);
        defaultAngle = new Vector3(20f, default_angle_y, 0f);

        initAngle(default_angle_y, defaultPosition, defaultAngle);
    }

    void Update() {
        // キャラクターを見失った場合再取得
        if (player == null) {
            player = GameObject.FindGameObjectWithTag("Player");
        }

        transform.position = player.transform.position;
    }

    /// <summary>
    /// カメラアングルの初期化を行う
    /// </summary>
    /// <param name="angle">カメラのY軸初期角度</param>
    /// <param name="position">カメラの位置</param>
    /// <param name="rotation">カメラの角度</param>
    private void initAngle(float angle_y, Vector3 position, Vector3 rotation) {
        mainCamera.transform.position = transform.position;
        mainCamera.transform.localPosition = position;
        mainCamera.transform.eulerAngles = rotation;

        transform.eulerAngles = new Vector3(0f, angle_y, 0f);
        transform.position = player.transform.position;
    }

    /// <summary>
    /// カメラアングルを切り替える
    /// </summary>
    public void changeAngle(Vector3 position, Vector3 angle) {
        // カメラ位置
        mainCamera.transform.position = transform.position;
        mainCamera.transform.localPosition = position;

        // カメラ本体角度
        iTween.RotateAdd(mainCamera, iTween.Hash(
            "rotation", angle,
            "time", 1f
        ));
    }

    /// <summary>
    /// プレイヤーを中心にカメラ位置を回転させる
    /// </summary>
    public void rotateCameraPosition(Vector3 camera_rotate_angle) {
        // カメラ回転
        iTween.RotateTo(gameObject, iTween.Hash(
            "rotation", camera_rotate_angle,
            "time", 1f,
            "onupdate", "rotateCameraAngle"
        ));
    }

    public void rotateCameraAngle() {
        mainCamera.transform.LookAt(gameObject.transform.position);
    }
}

ステージ中のカメラアングル切り替え用として、専用のスクリプトを作成します。
BoxCollider などで Trigger を持たせたオブジェクトに付与して使います。

using UnityEngine;
using System.Collections;

public class AngleChange : MonoBehaviour {

    [SerializeField]
    private Vector3 position;

    [SerializeField]
    private Vector3 angle;

    [SerializeField]
    private Vector3 camera_rotate_angle;

    private CameraController cameraController;

    void Start () {
        cameraController = GameObject.FindGameObjectWithTag("PlayerManager").GetComponent<CameraController>();
    }

    void OnTriggerEnter(Collider c){
        if (c.gameObject.tag == "Player") {
            cameraController.changeAngle(position, angle);
            cameraController.rotateCameraPosition(camera_rotate_angle);
        }
    }
}

スクリプト的にはどんな角度にも回転できますが、基本は以下の3つで行こうと思います!

・サイドビュー
横から見た視点です。
あまり奥行のない2D気味なエリアはこれでいきます。

shot2ss20160619223131086

shot2ss20160619223150495

・クォータービュー
斜め上から見下ろしたような視点です。
奥行のある3Dマップならこの視点が一番でしょうか。

shot2ss20160619223029231

shot2ss20160619223059855

・トップビュー
ほぼ真上から見たような視点です。
上2つと比べて使いどころは少なそうですが、狭い空間や迷路状ではこの視点が良いかも。

shot2ss20160619223437887

shot2ss20160619223546927

細かい部分の修正

その他ステージを作っていく上で、細かい点を修正していきます。

階段の上り下り

階段を作ったわけですが・・・。

20160621_01

あうっ(´Д⊂

ということで、CharacterController のパラメータをちょっといじります。
「Step Offset」の値を適度に上げ、階段程度の段差なら上れるようにします。

20160621_02

うーん・・・どうも減衰して上手く登れません。
一応「Slope Limit」を上げると登れるのですが、傾斜もぐんぐん登れてしまうためそれはそれで問題です。
「そもそも階段要るか?」という話ではありますが・・・。
現状でもジャンプ連打で登れるので、また暇なときに調査してみます。

水と滝

「Frineds of Ocean」というタイトル的に水は外すことができない要素です。
Unity5 から無料版でもリアルな水面のアセットが使えるようになったので、これを使ってそれっぽくしました。

shot2ss20160621211941143

また、段差があるので滝みたいのも欲しいですね。
これまた前に作ったのを配置してみました。
不自然ですが、これ以上となると流体シミュレーションとかしないときつい気もするので、まあ妥協できるレベルかなーと思います。

まとめ

今回作った部分までで、上から見るとこんな感じになりました!

shot2ss20160621212057963

今後はこのステージを作り込みつつ、いろいろと要素を盛り込めていければと思います!
とりあえずゲームとしての体裁を作ってしまいます!

【ステージメモ】CharacterMotorを使った滑る床の実装

というわけで、今回はタイトル通り、CharacterMotorを使って「滑る床」を実現してみようと思います!
寒い今日、季節的にもジャストな話題ではないでしょうか。

20160205

ゲーム的にはステージギミックの1つで、特定のブロックの上では滑るような移動になります。
この手のギミックはアクが強すぎると面倒なことになったりするので、過激な設定は避けるように気を付けていきます。
また、ジャンプ中は滑る床の影響を受けません。

滑る床と判定の作成

氷の床の名目で「IceFloor」というCube状のオブジェクトを作ります。
大きさは使用箇所によって変化させ、マテリアルは水色っぽくします。

shot2ss20160205210400610

Cubeを足場として使用するためBoxColliderが設定されていますが、これでプレイヤーとの接触判定を行うのはなかなか厳しいです。
なので「足場としての判定用」と「制御用スクリプトの判定用」の2つのBoxColliderを設定します。
足場用はCubeの形状そのままで、制御用はCenterをY軸に少しずらし、isTriggerにチェックを入れておきます。

CharacterMotorのパラメータ設定

上で作ったIceFloorに制御用スクリプトを付けます。

using UnityEngine;
using System.Collections;

public class IceFloor : MonoBehaviour {

    [SerializeField]
    private float MaxGroundAcceleration;

    private float defaultMaxGroundAcceleration;

    public void OnTriggerEnter(Collider c) {
        if (TagUtility.getParentTagName(c.gameObject) == "Player") {
            CharacterMotor charMotor = c.gameObject.GetComponent<Player>().getCharacterMotor();
            defaultMaxGroundAcceleration = charMotor.movement.maxGroundAcceleration;
            charMotor.movement.maxGroundAcceleration = this.MaxGroundAcceleration;
        }
    }

    public void OnTriggerExit(Collider c) {
        if (TagUtility.getParentTagName(c.gameObject) == "Player") {
            CharacterMotor charMotor = c.gameObject.GetComponent<Player>().getCharacterMotor();
            charMotor.movement.maxGroundAcceleration = defaultMaxGroundAcceleration;
        }
    }
}

予めPlayerクラスにCharacterMotorのgeterを作っておきます。
CharacterMotorの変数をpublicにしている場合は不要です。

肝となるのはCharacterMotorの「Max Ground Acceleration」です。
これはキャラクターが接地している場合の最大加速度になります。

OnTriggerEnterでPlayerクラスからCharacterMotorを取得し、その値をデフォルト値として取得した後、インスペクターから設定した値で上書きします。
これでIceFloorに接地している際にはツルツル滑ります。
どのくらいの値にすれば良いかはオブジェクトの大きさやゲームのスタイルによって変わります。

また、Exit時はしっかりデフォルト値に戻すようにします。
これが抜けているとずっと滑りっぱなしになってしまうので注意します。

まとめ

・滑る床として「IceFloor」を実装
・足場用と制御用のColliderを2つ設定する
・CharacterMotorの「Max Ground Acceleration」を変化させ、滑るような挙動にする

最近実装した要素の中ではサクっと出来た方です。
CharacterMotorもパラメータさえ理解できればいろいろと応用できます。

「敵が滑らない」という問題が残っていますが、Rigidbodyで行うのは難しそう・・・。
物理マテリアルで上手くできそうな気がしないでもないので、暇なときに検証してみます!

【ステージメモ】ステージギミックの作成

というわけで、本日はステージ上のギミックに関するお話です!
ステージの地形や敵配置も重要ですが、ステージギミックを合わせてこそアクションゲームというものです。

あまりにも鬱陶しいギミックを仕掛けても面倒な上、そのような代物を作る知識もありません。
ということで、オーソドックスなものから実装してみました!

ダメージトラップ

触れるとダメージを受けるものです。
オーソドックスなトゲや、高温地帯のマグマなど、いろいろ考えられそうです。

中でも代表的なのは「うに」です!

shot2ss20151202223931357

何故「うに」かと言えば、海が(一応)テーマのゲームだからです。
それ以上でも、それ以下でもありません。
純粋に痛そうでもあり、球状なので全方位をカバーできます。

ちなみにモデルはBlenderのDuplication機能を使って作成しました。
球状のオブジェクトの頂点にコーン状のオブジェクトを複製しています。

処理的には接触時にPlayerクラスのDamage()を呼び出すだけです。
が、谷底など落ちたら復帰できない場所に配置してあるものは、接触したら手前に戻すようにしたいですね。
なので、空の子オブジェクトとして「returnPoint」を設定し、接触時にプレイヤーの位置をそこに戻すようにします。

using UnityEngine;
using System.Collections;

public class Trap : MonoBehaviour {

    public float damage;

    private Transform returnPoint;

    void Start () {
        returnPoint = transform.FindChild("returnPoint");
    }

    private void OnTriggerEnter(Collider c) {
        if (c.gameObject.tag == "Player") {
            c.GetComponent<Player>().damage(damage);
            if (returnPoint) {
                StartCoroutine("playerReturn", c.gameObject);
            }
        }
    }

    private IEnumerator playerReturn(GameObject player) {
        yield return new WaitForSeconds(1f);
        player.transform.position = returnPoint.position;
    }
}

いちいちGetComponent()しているのが微妙なところです。
SendMessage()でも良いのですが、後々引数が増えそうなので直接呼んでいます。

returnPoint がないトラップはワープを行わないようにします。
if文で適当に制御してあげれば良いと思われます。

ジャンプエリア

該当の判定内に入っている状態でジャンプすると、大ジャンプが行えるものです。
ステージに高低差を付けたり、純粋なアクション操作を要求するギミックとして使います。

shot2ss20151202224351021

分かりやすいようにエフェクトを付けてみました。
Effekseerの描画方法をリングにし、頂点数を4にすることで正方形が作れます。
その正方形に合うようにBoxColliderを調整します。

using UnityEngine;
using System.Collections;

public class JumpArea : MonoBehaviour {

    public float height;
    private Player player;
    
    protected void OnTriggerEnter(Collider c){
        if(c.gameObject.tag == "Player"){
            player = c.GetComponent<Player>();
            player.getCharacterMotor().jumping.baseHeight = height;
        }
    }
    
    protected void OnTriggerExit(Collider c){
        if(c.gameObject.tag == "Player"){
            player.getCharacterMotor().jumping.baseHeight = 5f;
        }
    }
}

やっていることはすごく単純で、Enter時にCharacterMotorのジャンプ関係の値を上げるだけ。
Exitした時にデフォルト値に戻さないとずっと維持されてしまうので気を付けます。

Effekseerの良い点として、Unity側での拡大・縮小に合わせてエフェクトが拡縮するところが挙げられます。
様々な大きさのジャンプエリアを作る場合でも対応できます。

リフト

一定間隔で上下左右に動くリフトです。
リフトをコントロールするためのクラスを作成し、リフト用オブジェクトに付けます。

using UnityEngine;
using System.Collections;

public class Lift : MonoBehaviour {
    
    public float speed, repeat_dist, delay;
    public int moveDirection;

    private float radian;
    private Vector3 defaultPosition;
    private bool isMoveing;
    
    void Start () {
        defaultPosition = transform.position;
        radian = 0;

        if (delay > 0) {
            StartCoroutine("startDelay", delay);
        } else {
            isMoveing = true;
        }
    }
    
    void Update () {
        if(isMoveing){
            switch (moveDirection) {
                case 0:
                    transform.position = defaultPosition + new Vector3(Mathf.Sin(radian) * repeat_dist, 0, 0);
                    break;
                case 1:
                    transform.position = defaultPosition + new Vector3(0, Mathf.Sin(radian) * repeat_dist, 0);
                    break;
                case 2:
                    transform.position = defaultPosition + new Vector3(0, 0, Mathf.Sin(radian) * repeat_dist);
                    break;
            }
            radian += speed * Time.deltaTime;
        }
    }
    
    IEnumerator startDelay(float time){
        yield return new WaitForSeconds(time);
        isMoveing = true;
    }
}

配置位置からの反復距離をrepeat_distとして設定します。
また、XYZのどの軸に対して移動するかを選択できるようにします。
直打ちするのもアレなので、インスペクターからプルダウンで選択できるよう、エディタ拡張用のクラスを作成します。

 
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Lift))]
public class LiftEditor : Editor {

    private static readonly string[] Directions = {"X", "Y", "Z"};

    public override void OnInspectorGUI(){
        var lift = target as Lift;

        lift.speed = EditorGUILayout.FloatField
                ("Speed", lift.speed);
        lift.repeat_dist = EditorGUILayout.FloatField
                ("repeat_dist", lift.repeat_dist);
        lift.delay = EditorGUILayout.FloatField
                ("delay", lift.delay);
        lift.moveDirection = EditorGUILayout.Popup
                ("MoveDirection", lift.moveDirection, Directions);
    }
}

LifeEditorクラスのファイルはAssets/Editorの下に配置します。
これでLiftのmoveDirectionを設定する際、プルダウンでXYZを選択できるようになります。
他の変数も設定しておかないと、インスペクターに表示されなくなるので注意します。

20151202

動かすだけなら簡単ですが、3Dアクションでよくある「なめらかな動き」にするには工夫が必要らしいです。
位置を移動させる際にMathf.Sin()を使い、ラジアンを取って計算したりするそうですが・・・。
ここは知人に教えてもらって実装した部分なので、自分でもよく分かっていませんorz

また、CharacterMotorを使用している場合、MoveingPlatformの設定値が非常に重要です。
パラメータに関しては以下にまとめてあります。

【再編集】Unity開発メモまとめ「CharacterMotor」

まとめ

ということで、ちょっとしたステージギミックを作成してみました!
ただステージを進んで敵を倒すだけではつまらないので、適度に配置していきたいところです。

今日のイラスト

マリンパ、シルリスに続いてモグフィーです。
前から「暗い配色」と言われていましたが、描いてみると改めて実感します。
マフラーとか靴くらいは明るめの色にしてあげてもいいかもしれません。

mogffy11

【ステージメモ】ステージ構成とカメラアングルについて

というわけで、もうすっかり秋の気候な山梨です。
去年は忙しすぎて紅葉をまともに見れなかったので、今年はどこかに見に行きたいです。

shot2ss20151011011310415

今回は新ブログ最初のゲーム開発系の記事になります!
アクションゲームの土台でもあるステージを何とかしたいので、その話題です。
プレイする際に重要なカメラアングルについても修正しておきます!

ステージの構成

最初に作るサンプル的な役割も兼ねるステージということで、やはり海の雰囲気があるステージを作りたいところです。
ざっくりと組み込みたいセクションを書いてみます。
実現できるかどうかはやってみてから考えます!

前回の記事で書きましたが、ステージの難易度や順番は考慮しないことにしました!
1から順に難易度を上げていくことを考えると、どうしても構造が縛られてしまうためです。

・滝の入り江
滝つぼから滝をぐるっと回りながら登っていくイメージです。
地形を作るのが大変そうですが、漠然とした平地よりはアクション性が高くてよさそうです。
ジャンプ力と高低差のバランスに気をつけて作るようにします。

・洞窟
アクションゲームでは定番ですね。
どんな構造にするかは未定ですが、2Dアクションに近い感じになるかもです。
地形作成にTerrainは使わず、ほぼBlenderオンリーになると思われます。

・マングローブ
ステージ後半、夕焼けのようなライティングと合わせて作ってみたいです。
木の上に乗れたりすると面白いかも。
マングローブのモデリングが最大の難関になりそうです。

大きく3つに別れるわけですが、別シーンにするか同一シーンにするかは考え中です。
Unityの場合、同一シーン内の離れた地点に飛ばすような処理のほうが楽でしょうか。

shot2ss20151011005227774

そんなわけで、滝の入り江から作り始めています。
地形はTerrainで作り、所々にあるブロックはBlenderで作る予定です。
最後まで進めることに重点を置いているので、細かい部分は割と適当になっています。
途中で滝のパーティクルが上手くレンダリングされない問題が発生しましたが、パーティクルのSacleを1に直したら改善しました。

ちなみに水没したら「ダメージを受けて一定地点まで戻る」予定です。
「海の仲間たち()」になりそうですが、汚染された海という設定があるにはあるので、細かいところは気にしないで作ります!

カメラアングル

これまた前の記事で書きましたが、カメラアングルを自動化し、場面に合わせて自動で動かすようにします。
カメラアングル専用のクラスを用意し、以下のような関数を作ってカメラを操作するようにします。

public void changeAngle(Vector3 position, Vector3 angle) {
    // カメラ位置
    mainCamera.transform.position = transform.position;
    mainCamera.transform.localPosition = position;

    // カメラ本体角度
    iTween.RotateAdd(mainCamera, iTween.Hash(
        "rotation", angle,
        "time", 1f
    ));
}

public void rotateCameraPosition(float camera_rotate_y) {
    // カメラ回転
    iTween.RotateTo(gameObject, iTween.Hash(
        "y", camera_rotate_y,
        "time", 1f,
        "onupdate", "rotateCameraAngle"
    ));
}

public void rotateCameraAngle() {
    mainCamera.transform.LookAt(gameObject.transform.position);
}

上がカメラの位置と角度、下がカメラ回転用の関数です。

スクリプトはMainCameraに直接付与するのではなく、プレイヤーキャラと同じ位置に空のGameObjectを作り、そこからカメラを取得して操作します。
作成したオブジェクトの子としてMainCameraを設定します。
この状態でカメラ操作用のオブジェクトを回転させることで、カメラをキャラクターを中心として回転させることができます。
カメラ回転中はプレイヤーの方を見てもらわないと困るので、iTweenのonupdate内で指定しておきます。

直接プレイヤーキャラの子オブジェクトとしてしまうと、キャラが動く度に移動するので面倒なことになります。
面倒ですが、Update()内でプレイヤーの位置に合わせる処理を書いておきます。

カメラの回転を直接行うと一瞬で処理が行われてしまうので、見栄えがよろしくありません。
iTweenのRotateToやRotateAddを使いtimeを指定することで、慣性のある角度切り替えを実現できます。

あとはステージ上に透明の判定を用意し、触れた際にカメラの位置・角度を切り替えます。
パラメータとしてカメラ位置・角度・キャラを中心としたカメラの回転位置を設定しておきます。

using UnityEngine;
using System.Collections;

public class AngleChangeController : MonoBehaviour {

    [SerializeField]
    private Vector3 position;

    [SerializeField]
    private  Vector3 angle;

    [SerializeField]
    private float camera_rotate_y;

    private CameraController cameraController;

    void Start () {
        cameraController = GameObject.FindGameObjectWithTag("PlayerManager").GetComponent<CameraController>();
    }

    void OnTriggerEnter(Collider c){
        if (c.gameObject.tag == "Player") {
            cameraController.changeAngle(position, angle);
            cameraController.rotateCameraPosition(camera_rotate_y);
        }
    }
}

OnTriggerEnterでタグがPlayerのオブジェクトの場合のみ実行させます。
設定したパラメータを引数として渡します。

20150605_1

動き自体は問題なさそうです!
ただしCharacterMotorを使用しているため、切り替わった後がすごく操作しにくいです。
プレイヤーの移動に合わせて徐々に回転させるのが良いと思われますが、今の自分の知識で実現するのは難しそうです。
判定をいくつかに分け、小刻みに回転させるようにすると少しマシかも

まとめ

そんなわけで、ステージとカメラアングルに関してまとめてみました!
前よりはステージの構造がイメージしやすいので、そこそこはかどっています。
ひとまず1セクションの地形を作り上げ、システム面の改良も行っていきたいところです。

10/12 追記

カメラ回転時にY軸に限定せず、Vector3型を渡して回転させると汎用性が上がります。
上で記載したrotateCameraPositionをちょっと修正します。

 
public void rotateCameraPosition(Vector3 camera_rotate_angle) {
    // カメラ回転
    iTween.RotateTo(gameObject, iTween.Hash(
        "rotation", camera_rotate_angle,
        "time", 1f,
        "onupdate", "rotateCameraAngle"
    ));
}

public void rotateCameraAngle() {
    mainCamera.transform.LookAt(gameObject.transform.position);
}

AngleChangeControllerの方は型をVector3にするだけなので割愛させていただきます!

上手く調整すればサイドビューにもトップビューにもできます。
これにズームイン・アウトの処理を作れば大抵のアングルはカバーできそうですね。