kotonagaのブログ

Unityとか

Unityで2DRPGを作ってみる

まだUnityを触り始めて日が浅い(1週間)ものの、勉強のログも兼ねてブログを始めてみる事にしました。
なにぶん、日本語の情報がまだまだ少ないので、少しでも人の役に立てば幸いです。
初心者なので間違っているところがあればご指摘頂けると勉強になります。

と言うことで、いきなり2DRPGを作り始めています。いきなりと言っても、公式のチュートリアルは一応やりました。

最近は結構、素材も無料で提供しておられる方が多いみたいで、今回は無料の素材を、ライセンスに気を付けながら使います。

今回使う素材

こんな素晴らしい素材を、無料で、ほぼ制限なしに配布されるのは凄いことですね。

キャラクター素材読み込み用のプレハブの作成

素材は、それぞれ目的のソフトウェアの規格に従って作られています。今回は、素材提供者の多い、ウディタ用の素材を使うので、ウディタの素材仕様に則った処理をするようにします。
WOLF RPGエディター公式サイト 【RPG作成フリーソフト】

スプライトの作成

Unity関連の記事などをみていると、画像をアセットとして登録したら、スライサーを使って分割し、アニメーションなどの設定をGUIから行っている例が多いです。 f:id:kotonaga:20141228033633j:plain もちろんこの方法でも良いと思いますが、2DRPGで、しかも素材の規格が既に決まっている場合には、沢山のキャラクターに対して、同じ設定をいくつもする必要があり、煩雑です。

そこでまずは、キャラクターとしての基本的な機能を持っている、Characterクラスを作成します。
このクラスは、登録された素材データを読み込み、内部で分割し、アニメーションの設定などをAwakeで行います。

/*
The MIT License (MIT)

Copyright (c) 2014 Kotonaga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

[ExecuteInEditMode()]
public class Character : MonoBehaviour
{
    /// 
    /// ウディタ用キャラクタ画像素材
    /// 
    public Texture2D m_image;
    /// 
    ///  1セルの幅
    /// 
    public int m_cropWidth;
    /// 
    /// 1セルの高さ
    /// 
    public int m_cropHeight;

    void Awake ()
    {
        if(this.m_image==null)
            return;

        // スプライトレンダラの取得
        SpriteRenderer spriteRenderer = gameObject.GetComponent ();
        // アニメータの取得
        Animator animator = gameObject.GetComponent ();
        // ぼやけを除去
        this.m_image.filterMode = FilterMode.Point;

        // 15フレームに一回アニメーションする
        float frameLength = 15f / 60f;
        // 画像は横にアニメーションする
        int frameCount = this.m_image.width / this.m_cropWidth;
        // 画像は縦に種類が並んでいる
        int stateCount = this.m_image.height / this.m_cropHeight;
        bool extendedCharaChipSet = false;

        // ただし、8方向のチップセットが存在するので、それに対処する
        if(frameCount==6 || frameCount==10){
            frameCount >>= 1;
            stateCount <<= 1;
            extendedCharaChipSet = true;
        }

        // スプライト配列の作成
        Sprite[,] sprites = new Sprite[stateCount, frameCount];

        // アニメータコントローラの作成
        AnimatorController animatorController = new AnimatorController ();
        // 名前はテクスチャ名にする
        animatorController.name = this.m_image.name;
        //向きと状態指定用のパラメータを追加
        animatorController.AddParameter ("DirectionX", AnimatorControllerParameterType.Float);
        animatorController.AddParameter ("DirectionY", AnimatorControllerParameterType.Float);
        animatorController.AddParameter ("Walking", AnimatorControllerParameterType.Bool);

        // アニメータコントローラにベースレイヤを追加
        AnimatorControllerLayer animatorContorollerLayer = animatorController.AddLayer ("Base");
        // ステートマシンの取得
        StateMachine stateMachine = animatorContorollerLayer.stateMachine;

        // 歩き状態を追加する
        State walkState = stateMachine.AddState ("Walk");
        BlendTree walkBlendTree = walkState.CreateBlendTree ();
        walkBlendTree.name = "Blend Tree";

        // 歩き状態は歩く方向によってアニメーションが変化する
        walkBlendTree.blendType = BlendTreeType.SimpleDirectional2D;
        walkBlendTree.blendParameter = "DirectionX";
        walkBlendTree.blendParameterY = "DirectionY";

        // 状態の数だけループ
        for (int y=0; y=(stateCount>>1)){
                offsetX = frameCount;
                offsetY = -(stateCount>>1);
            }
            // スプライトの切り出しと普通のアニメーションの作成
            for (int x=0; x1枚目とアニメーションする
            for (int x=1; x>=0; x--)
            {
                int index = frameCount + 1 -x;
                keyFrames [index] = new ObjectReferenceKeyframe{time=index*frameLength, value=sprites[y,x]};
            }

            // アニメーションクリップの作成
            // 名前は状態のインデックスにする
            AnimationClip clip = new AnimationClip ();
            clip.name = y.ToString ();
            // はみ出た部分は消す
            clip.wrapMode = WrapMode.Clamp;
            // よくわかっていないがアニメーションのタイプを指定する
            AnimationUtility.SetAnimationType (clip, ModelImporterAnimationType.Generic);
            // カーブの作成
            EditorCurveBinding curveBinding = new EditorCurveBinding ();
            // ファイルは存在しない
            curveBinding.path = string.Empty;
            // スプライトをアニメーションするのでtypeにSpriteRenderer
            // propertyNameにはm_Spriteを指定
            curveBinding.type = typeof(SpriteRenderer);
            curveBinding.propertyName = "m_Sprite";
            // クリップとカーブにキーフレームをバインド
            AnimationUtility.SetObjectReferenceCurve (clip, curveBinding, keyFrames);
            // LoopTimeが直接設定出来ないので一旦シリアライズする
            SerializedObject serializedClip = new SerializedObject (clip);
            // アニメーションクリップ設定の取り出し
            AnimationClipSettings animationClipSettings = new AnimationClipSettings (serializedClip.FindProperty ("m_AnimationClipSettings"));
            // LoopTimeを有効にする
            animationClipSettings.LoopTime = true;
            // 設定を書き戻す
            serializedClip.ApplyModifiedProperties ();
            // 状態を歩き状態のブレンドツリーに追加
            walkBlendTree.AddAnimationClip (clip);
        }
        if(extendedCharaChipSet){
            // 上向き
            walkBlendTree.SetChildPosition (0, new Vector2 (0, 1));
            // 右向き
            walkBlendTree.SetChildPosition (1, new Vector2 (1, 0));
            // 左向き
            walkBlendTree.SetChildPosition (2, new Vector2 (-1, 0));
            // 下向き
            walkBlendTree.SetChildPosition (3, new Vector2 (0, -1));
            // 右上向き
            walkBlendTree.SetChildPosition (4, new Vector2 (1, 1));
            // 左上向き
            walkBlendTree.SetChildPosition (5, new Vector2 (-1, 1));
            // 右下向き
            walkBlendTree.SetChildPosition (6, new Vector2 (1, -1));
            // 左下向き
            walkBlendTree.SetChildPosition (7, new Vector2 (-1, -1));
        }else{
            // 上向き
            walkBlendTree.SetChildPosition (0, new Vector2 (0, 1));
            // 右向き
            walkBlendTree.SetChildPosition (1, new Vector2 (1, 0));
            // 左向き
            walkBlendTree.SetChildPosition (2, new Vector2 (-1, 0));
            // 下向き
            walkBlendTree.SetChildPosition (3, new Vector2 (0, -1));
        }
        
        // 止まり状態を追加する
        State waitState = stateMachine.AddState ("Wait");
        BlendTree waitBlendTree = waitState.CreateBlendTree ();
        waitBlendTree.name = "Blend Tree";

        // 止まり状態はアニメーションしないが
        // 方向によってスプライトが変化する
        waitBlendTree.blendType = BlendTreeType.SimpleDirectional2D;
        waitBlendTree.blendParameter = "DirectionX";
        waitBlendTree.blendParameterY = "DirectionY";
        
        // 状態の数だけループ
        for (int i=0; i歩きのトランジションを作成
        Transition waitToWalk = stateMachine.AddTransition (waitState, walkState);
        // 最初からExitTimeの条件が設定されているので、これを変更する
        AnimatorCondition waitToWalkCondition = waitToWalk.GetCondition (0);
        // Walkingフラグがtrueの時
        waitToWalkCondition.parameter = "Walking";
        waitToWalkCondition.mode = TransitionConditionMode.If; // Ifがtrue
        
        // 歩き->止まりのトランジションを作成
        Transition walkToWait = stateMachine.AddTransition (walkState, waitState);
        // 最初からExitTimeの条件が設定されているので、これを変更する
        AnimatorCondition walkToWaitCondition = walkToWait.GetCondition (0);
        // Walkingフラグがfalseの時
        walkToWaitCondition.parameter = "Walking";
        walkToWaitCondition.mode = TransitionConditionMode.IfNot; // Ifがfalse

        // アニメータコントローラに設定
        animator.runtimeAnimatorController = (animatorController as RuntimeAnimatorController);

        // とりあえず0,0のスプライトをデフォルトにセット
        spriteRenderer.sprite = sprites[0,0];
    }
}

/// 
/// AnimationClipSettingsの設定用クラス
/// 
class AnimationClipSettings
{
    SerializedProperty m_property;
    
    public AnimationClipSettings (SerializedProperty prop)
    {
        this.m_property = prop;
    }

    private SerializedProperty Get (string property)
    {
        return this.m_property.FindPropertyRelative (property);
    }

    public bool LoopTime
    {
        get
        {
            return this.Get ("m_LoopTime").boolValue;
        }
        set
        {
            this.Get ("m_LoopTime").boolValue = value;
        }
    }
}
#endif

AnimatorControllerLayerとかStateMachineとかをスクリプト側で生成する例が見つからなかったので、結構苦労しました。

とりあえず今日はここまで。明日はGameObjectの作成とスクリプトの登録をします。
あとはこのスクリプトを登録したGameObjectに素材の画像を登録すれば、勝手にアニメーションなどを生成します。便利ですね。