Unityで2DRPGを作ってみる
まだUnityを触り始めて日が浅い(1週間)ものの、勉強のログも兼ねてブログを始めてみる事にしました。
なにぶん、日本語の情報がまだまだ少ないので、少しでも人の役に立てば幸いです。
初心者なので間違っているところがあればご指摘頂けると勉強になります。
と言うことで、いきなり2DRPGを作り始めています。いきなりと言っても、公式のチュートリアルは一応やりました。
最近は結構、素材も無料で提供しておられる方が多いみたいで、今回は無料の素材を、ライセンスに気を付けながら使います。
今回使う素材
こんな素晴らしい素材を、無料で、ほぼ制限なしに配布されるのは凄いことですね。
キャラクター素材読み込み用のプレハブの作成
素材は、それぞれ目的のソフトウェアの規格に従って作られています。今回は、素材提供者の多い、ウディタ用の素材を使うので、ウディタの素材仕様に則った処理をするようにします。
WOLF RPGエディター公式サイト 【RPG作成フリーソフト】
スプライトの作成
Unity関連の記事などをみていると、画像をアセットとして登録したら、スライサーを使って分割し、アニメーションなどの設定をGUIから行っている例が多いです。 もちろんこの方法でも良いと思いますが、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; x 1枚目とアニメーションする 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に素材の画像を登録すれば、勝手にアニメーションなどを生成します。便利ですね。