今回はUnityでRPGやアクションゲーム等のスキルツリーのシステムを作ってみたいと思います。
スキルツリーシステムと言っても管理するスクリプトはアイテム管理と同じような感じで作る事が出来るので、UIのデザインの部分が重要になってきます。
が、わたくしにデザインの才能はないのでUIはとりあえず、といった感じになります。(^_^;)
スキルツリーのUIを作成
Canvasの作成
まずはスキルツリーを表示するキャンバスを作成します。
ヒエラルキー上で右クリック→UI→Canvasで作成し、名前をSkillUIとします。
インスペクタでCanvas ScaleのUI Scale ModeをScale With Screen Sizeを選択し、Screen Match ModeをExpandにします。
これはゲームの画面サイズを変更した時にある程度UIがスクリーンサイズに合わせて縮小するようにする為です。
この辺りは
の記事のキャンバス(Canvas)項目を参照してください。
この後、作成したSkillUIの子要素に3つのPanelを作成し『スキルポイント表示部』『スキルツリー部』『説明表示部』の3つの空間を作成していきます。
スキルポイント表示部
まずはスキルポイント表示部を作成していきます。
SkillUIを選択した状態で右クリック→UI→Panelを選択し名前をSkillPointとします。
SkillPointパネルのサイズをSkillUIの上から10%のサイズに変更します。
↑のSkillPointの四隅にある三角形のアイコン(今回は左下のもの)をドラッグしSkillPointの上から10%部分まで移動させます。
SkillPointのインスペクタは
↑のようになります。
次にSkillPointを選択した状態で右クリック→UI→Textを選択し、SkillPointの子要素にTextを作成します。
TextのAnchorを選択します。
↑のようにアイコンをクリックしShiftキーとAltキーを押しながら右下の縦と横をStretch(親の要素に合わせる)を選択します。
ShiftとAltを押しながら選択する事ですぐに親に合わせて自身(Text)が調整されます。
Textは↑のようになりました。
テキストに使用するフォントの変更、フォントサイズ、テキストの表示位置(中央に表示されるように)を変更しておきます。
これでスキルポイント表示部が出来ました。
説明表示部の作成
『スキルツリー部』は一番大変なので最後に回して先に『説明表示部』を作成していきます。
SkillUIを選択した状態で右クリック→UI→Panelを選択し名前をSkillInformationとします。
その子要素にUI→Textを作成します。
Textの表示位置は左上となるようにTextインスペクタのParagraphのAlignmentを変更します。
↑のようにSkillPointと同じですね。
SkillInformationのサイズを変更し↑のように上から80%の部分までドラッグし下の方に表示されるようにしておきます。
これで説明表示部が出来ました。
スキルツリー部
最後にスキルツリー部を作成していきます。
SkillUIを選択した状態で右クリック→UI→Panelを選択し名前をSkillPanelとします。
スキルツリー部のサイズをスキルポイント部の下と説明表示部の上にピッタリ合うように調整します。
↑のように真ん中にスキルツリー部がある形になります。
スキルひとつひとつを表現するのにUIのボタンを使用します。
スキル毎のボタンを押して条件が整えばスキルを覚える事が出来るようにします。
スキルツリーなので『スキル毎のボタン』と『スキルを繋げる遷移』が必要になりますが、わかりやすくなるように『ボタンパネル』『遷移』のUIを分けます。
特に分ける必要はないのですが見た目上わかりやすく管理する為です。
でも、一緒にした方がちょっとした移動をさせる時便利かもしれないなぁ・・・・(-_-)
ボタンパネルの作成
SkillPanelを選択した状態で右クリック→UI→Panelを選択し名前をButtonPanelとします。
ButtonPanelのインスペクタでAnchor Presetsを縦横がStretchになるようにします。
これでButtonPanelはSkillPanelと同じ大きさの領域になります。
ButtonPanelを選択した状態で右クリック→UI→Buttonを選択します。
Buttonの子要素にUIのImageを1つ作成し名前をそのまま(もちろん変更してもOK)、Textを2つ作成し名前をTitleとInformationにします。
階層は↑のようになります。
Titleのインスペクタを表示しParagraphのAlignmentでテキストを上付きで表示するようにします。
Anchor Presetsは縦横Stretchです。
Imageはスキルのイメージを表示するものなのでSource Imageにスプライトを設定します。
Imageは中央に表示するのでAnchor Presetsはmiddle centerにします。
Imageのサイズを調整しボタンに合わせます。
InformationのインスペクタのParagraphのAlignmentでテキストを下付きにします。
設定が終わったらTitleのTextにスキルの名前、InformationのTextにスキルの簡易説明を設定します。
↑のように1つのスキルが出来上がりました。
ボタンの中にスキル名とスキルの簡易説明を表示していますが、もちろん表示するものは自由に設定してください。
出来上がったスキルをCtrl+Dでコピーし他のスキルを作成します。
↑のようにButtonをコピーし他のスキルを作成し配置していきます。
名前を変更していないので全てButtonの派生の名前になってますが・・・・・(^_^;)
↑がスキルを並べた画像です。
これでスキルボタンの配置が終わったので次はスキルの遷移を作成していきます。
スキル遷移UI
スキルボタンが出来たのでその遷移を表すUIを作成していきましょう。
スキルの遷移を表すアイコンは矢印で作成しますが、矢印アイコンをそのまま使うと
↑のように矢印の先が長く伸びてしまうので矢の本体部分と先でわけて画像を作成します。
矢印用の画像を作成
↑が矢印の本体用のImage画像。
↑が矢印の先用のImage画像になります。
上の2つの画像はダウンロードしてお使いください。
画像をダウンロードしたらUnityのAssetsフォルダ内で右クリック→Import New Asset…で画像を選択しUnityに取り込みます。
矢印本体のSenのImport Settingsを変更します。
↑のようにTexture TypeをTexture、Wrap ModeをClampにします。
矢印の先のyaのImport Settingsを変更します。
↑のようにTexture TypeをTexture、Wrap ModeをClampにします。
Wrap ModeをClampにしたのはRepeatだとサイズが足りない時に画像の繰り返しが行われて表示されてしまう為です。
これで矢印用の画像の取り込みが終了しました。
スキル遷移画像を設定
矢印用画像の取り込みが終了したのでスキルの遷移を作成していきます。
SkillPanelを選択した状態で右クリック→UI→Panelを選択し名前をRootPanelとします。
RootPanelのインスペクタは↑のようになりSkillPanelと同じ大きさにします。
次にRootPanelの子要素にUI→RawImageを作りその子要素にRawImageを作成します。
↑のような階層になります。
このRawImage以下が遷移の矢印の画像を形成します。
親のRawImageのTextureには矢印本体のSenを設定します。
子のRawImageのTextureには矢印の先のyaを設定します。
矢印の向きをスキルツリーの向きと逆に作ってしまった為RotationのZ軸を回転し向きを合わせています。
矢印本体と先の画像の色が黒で統一されているので問題ではありませんが、本体と矢の先の色が違う場合はそれぞれの位置を丁度に合わせないと重なってしまいます。
出来上がったら『攻撃UP1』から『攻撃UP2』への遷移の矢印を作成する為に親のRawImageを移動させます。
↑のような感じに遷移の矢印を移動させました。
他のスキルの遷移も同じように作成します。
矢印を斜めにする必要がある場所もあります。
↑のような感じですね。
UIは2DなのでRotationはZ軸部分だけ変更して回転させるようにします。
回転については他のゲームオブジェクトの回転と同じように出来ます。
↑のようにZ軸だけ回転させます。
全てのスキルの遷移を作成すると
のようになります。
矢印を斜めにするとギザギザが少し目立ちますね・・・・(-_-)
元の画像を引き延ばしているので仕方ないと言えば仕方ないですが・・・。
斜めに遷移させないで直角に遷移させるとギザギザを気にする必要はなくなりますね。
スキル管理スクリプト
SkillUIにスキルを管理するスクリプトSkillSystemを新しく作り設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | using UnityEngine; using System.Collections; using UnityEngine.UI; // スキルのタイプ public enum SkillType { Attack1, Attack2, Defense1, Defense2, Speed1, Speed2, Combo, Master }; public class SkillSystem : MonoBehaviour { // スキルを覚える為のスキルポイント [SerializeField] private int skillPoint; // スキルを覚えているかどうかのフラグ [SerializeField] private bool[] skills; // スキル毎のパラメータ [SerializeField] private SkillParam[] skillParams; // スキルポイントを表示するテキストUI public Text skillText; void Awake () { // スキル数分の配列を確保 skills = new bool[skillParams.Length]; SetText (); } // スキルを覚える public void LearnSkill(SkillType type, int point) { skills [(int)type] = true; SetSkillPoint (point); SetText (); CheckOnOff (); } // スキルを覚えているかどうかのチェック public bool IsSkill(SkillType type) { return skills [(int)type]; } // スキルポイントを減らす public void SetSkillPoint(int point) { skillPoint -= point; } // スキルポイントを取得 public int GetSkillPoint() { return skillPoint; } // スキルを覚えられるかチェック public bool CanLearnSkill(SkillType type, int spendPoint = 0) { // 持っているスキルポイントが足りない if (skillPoint < spendPoint) { return false; } // 攻撃UP2は攻撃UP1を覚えていなければダメ if (type == SkillType.Attack2) { return skills [(int)SkillType.Attack1]; // 防御UP2は防御UP1を覚えていなければダメ } else if (type == SkillType.Defense2) { return skills [(int)SkillType.Defense1]; // 速さUP2は速さUP1を覚えていなければダメ } else if (type == SkillType.Speed2) { return skills [(int)SkillType.Speed1]; // コンボは攻撃UP2と防御2を覚えていなければダメ } else if (type == SkillType.Combo) { return skills [(int)SkillType.Attack2] && skills [(int)SkillType.Defense2]; // マスタースキルは全てのスキルを覚えていなければダメ } else if (type == SkillType.Master) { return skills [(int)SkillType.Attack2] && skills [(int)SkillType.Defense2] && skills [(int)SkillType.Speed2] && skills [(int)SkillType.Combo]; } return true; } // スキル毎にボタンのオン・オフをする処理を実行させる void CheckOnOff() { foreach (var skillParam in skillParams) { skillParam.CheckButtonOnOff (); } } void SetText() { skillText.text = "スキルポイント:" + skillPoint; } } |
それぞれのスキルを識別する為に列挙型を使って判別出来るようにします。
skillPointはスキルを覚える時に使うポイント、skillsはそのスキルを覚えているかどうかを入れておくbool型の配列、skillParamsはスキルそれぞれに設定するSkillParamスクリプトを設定します。
skillTextはスキルポイントを表示するテキストUIを設定します。
Awakeメソッドでスキルの数分のskillsの領域を確保します。
bool型の変数(今回の場合配列)は初期値を指定しないとデフォルトでfalseが設定されるので個別に初期値を設定する事はしていません。
その後SetTextメソッドを呼び出しスキルポイントをテキストUIに表示しています。
SetSkillメソッドはスキルタイプと消費するスキルポイントを受け取りスキルを覚えさせます。
SkillType型の変数を(int)でキャストするとSkillTypeの順番に並べた数値を取得する事が出来ます。
SetSkillPointメソッドでスキルポイントを更新し、SetTextでテキストUIに新しいスキルポイントを表示しています。
SetSkillPointメソッドに処理を分離したのはスキルポイントを消費する機能だけでなくプラスにする処理を作る場合にSetSkillPointの処理を変更すれば楽に出来ると思ったのでそうしました。
今回の場合は消費する事しか念頭に置いていない為、skillPointから受け取ったポイント分を減らしているだけです。
CheckOnOffメソッドはスキルを覚えた時に他のスキルを覚えられる状態にする処理を実行します。
実際に実行するのはSkillParamスクリプトのCheckButtonOnOffメソッドになります。
foreach文を使いskillParamsに格納されている全てのCheckButtonOnOffメソッドを呼び出しています。
IsSkillは受け取ったスキルを覚えているかどうかを調べ、GetSkillPointはスキルポイントを返します。
CanLearnSkillメソッドはそのスキルを覚えられるかどうか?のbool値を返すメソッドで、spendPoint引数が渡ってこなかった場合オプション引数を使ってspendPointを初期化しています。
オプション引数に関しては
を参照してください。
CanLearnSkillメソッドで行っている事は覚える為に消費するスキルポイントがなければfalseをまず返します。
その後そのスキルを覚える為の条件をクリアしているかどうかを判断しbool値をreturnしています。
例えば『攻撃UP2』は『攻撃UP1』を覚えていないと覚えられないので『攻撃UP1』を覚えていなければfalseを返しています。
1 2 3 4 5 | // 攻撃UP2は攻撃UP1を覚えていなければダメ if (type == SkillType.Attack2) { return skills [(int)SkillType.Attack1]; |
return skills [(int)SkillType.Attack1];
と一気に記述しているのでわかり辛い人もいるかもしれませんが、skillsはbool型の配列でskills[(int)skillType.Attack1]でbool値が得られるのでそれをreturnしているだけです。
スキルパラメータスクリプト
スキル管理スクリプトでも出てきましたが個別のスキルに設定するスキルパラメータスクリプトSkillParamを作成していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | using UnityEngine; using System.Collections; using UnityEngine.UI; using System; public class SkillParam : MonoBehaviour { // スキル管理システム [SerializeField] private SkillSystem skillSystem; // このスキルの種類 [SerializeField] private SkillType type; // このスキルを覚える為に必要なスキルポイント [SerializeField] private int spendPoint; // スキルのタイトル [SerializeField] private string skillTitle; // スキル情報 [SerializeField] private string skillInformation; // スキル情報を載せるテキストUI [SerializeField] private Text text; // Use this for initialization void Start () { // スキルを覚えられる状態でなければボタンを無効化 CheckButtonOnOff (); } // スキルボタンを押した時に実行するメソッド public void OnClick() { // スキルを覚えていたら何もせずreturn if (skillSystem.IsSkill (type)) { return; } // スキルを覚えられるかどうかチェック if (skillSystem.CanLearnSkill (type, spendPoint)) { // スキルを覚えさせる skillSystem.LearnSkill (type, spendPoint); ChangeButtonColor (new Color(0f, 0f, 1f, 1f)); text.text = skillTitle + "を覚えた"; } else { text.text = "スキルを覚えられません。"; } } // 他のスキルを習得した後の自身のボタンの処理 public void CheckButtonOnOff () { // スキルを覚えられるかどうかチェック if (!skillSystem.CanLearnSkill (type)) { ChangeButtonColor (new Color (0.8f, 0.8f, 0.8f, 0.8f)); // スキルをまだ覚えていない } else if(!skillSystem.IsSkill (type)) { ChangeButtonColor (new Color (1f, 1f, 1f, 1f)); } } // スキル情報を表示 public void SetText() { text.text = skillTitle + ":消費スキルポイント" + spendPoint + "\n" + skillInformation; } // スキル情報をリセット public void ResetText() { text.text = ""; } // ボタンの色を変更する public void ChangeButtonColor(Color color) { // ボタンコンポーネントを取得 Button button = gameObject.GetComponent <Button> (); // ボタンのカラー情報を取得(一時変数を作成し、色情報を変えてからそれをbutton.colorsに設定しないとエラーになる) ColorBlock cb = button.colors; // 取得済みのスキルボタンの色を変える cb.normalColor = color; cb.pressedColor = color; // ボタンのカラー情報を設定 button.colors = cb; } } |
skillSystemは先ほど作成したSkillSystemスクリプトを設定します。
typeはこのスキルのタイプを設定します。
spendPointはこのスキルを覚える為に必要なスキルポイント。
skillTitleはスキルテキストUI情報に表示するスキルタイトル。
skillInformationはスキルテキストUI情報に表示するスキルの説明。
textはスキル情報を載せるテキストUI。
になります。
StartメソッドでCheckButtonOnOffメソッドを呼び出します。
CheckButtonOnOffメソッドはスキルをまだ覚えていない状態、覚えている状態等でボタンの色を変更する処理です。
実際に変更しているのはChangeButtonColorメソッドで受け取った色にボタンのNormalColorとPressdColorを変更しています。
NormalColorやPressdColorは直接変更出来ないので一時的にColorBlock変数を作成し、そのNormalColorやPressdColorを変更したのちにbutton.colorsに代入して変更します。
今回の処理はボタンの状態によって色合いを変える為ButtonのTransitionがColor Tintである必要があります。
その他のTransitionを設定している場合はそれぞれの対応が必要になってきます。
OnClickメソッドはスキルボタンが押された時に実行させるメソッドです。
自身のスキルを覚えていたらreturnですぐ処理をやめます。
それ以外の時でCheckメソッドでスキルを覚えられるか調べスキルを覚えさせスキルテキストUIにスキルを覚えた事を表示します。
スキルが覚えられない時はスキルを覚えられないと表示します。
SetTextメソッドはスキルボタンがアクティブ(マウスが上に来た時やフォーカスが移動してきた時)になった時にスキル情報をスキルテキストUIに表示するメソッドです。
ResetTextメソッドはスキルテキストUIを空にするだけです。
SetTextやResetTextメソッドを呼び出す為にはマウスがスキルボタンの上に来たや移動キーやゲームパッドでフォーカスがスキルボタンに来たというイベントを受け取り、
そのイベントを受け取ったらSetTextやResetTextメソッドを実行する必要があります。
それは後で作成します。
SkillSystemとSkillParamのインスペクタの設定
とりあえずSkillSystemスクリプトとSkillParamスクリプトが出来たので、SkillSystemをSkillUIゲームオブジェクトに、
SkillParamスクリプトをButtonPanelの子要素のそれぞれのボタンに設定します。
SkillSystemスクリプトではskill Point、スキルボタンに設定されているSkillParamをそれぞれ設定、Skill TextにスキルテキストUIを設定します。
↑のような感じです。
『攻撃UP1』のスキルボタンに取り付けたSkillParamの設定は
のようになります。
そのスキルのタイプの設定とスキル情報の記述等を設定しています。
それぞれのスキル毎に設定をします。
これでスクリプトの設定が終了しました。
スキルボタンのイベント処理を追加
スクリプトが出来上がったのでスキルボタンの上にマウスが来た時、押した時、移動キーやゲームパッドでフォーカスが当たった時のイベント処理を追加します。
例として『攻撃UP1』のスキルボタンで説明していきます。
スキルボタンゲームオブジェクトのAdd Component→Event→EventTriggerを追加します。
↑はすでにイベントタイプを追加していますが本来はAdd New Event Typeをクリックしイベントの種類を追加します。
またOn Clickは最初からButtonについているのでそれを使用します。
Add New Event Typeをクリックすると↑のようにイベントのタイプを選択出来るので、
Pointer Exit(マウスがボタン外に出た)、Pointer Enter(マウスがボタン内に入ってきた)、Select(フォーカスが当たった)、Deselect(フォーカスが外れた)を追加します。
それぞれのタイミングはカッコ内に記述していますが、今回のスキルツリーの操作はマウス、キーボード、ゲームパッド全てで操作出来るようにする為、色々なイベントを追加しています。
イベントタイプを追加したら
↑のように+を押して、それぞれのイベントタイプ毎にイベントを追加します。
Pointer Exitの設定をしてみます。
イベントを追加したらゲームオブジェクトを指定する箇所があるのでそこにSkillParamスクリプトを持つ自分自身のゲームオブジェクトをドラッグ&ドロップします。
ゲームオブジェクトを指定すると実行するメソッドを指定する事が出来るのでそこにSkillParamスクリプトのResetTextメソッドを設定します。
このようにPointer Exitイベントが発生した時にResetTextメソッドを実行するように指定する事が出来ます。
Pointer Exit、DeselectはResetTextメソッド、Pointer Enter、SelectはSetTextメソッドを指定します。
ButtonのOn ClickにはOnClickメソッドを指定しておきます。
スキルボタンそれぞれに同じように設定していきますが、イベントに設定するゲームオブジェクトは必ず自分自身を設定します。
これでイベントの追加も終了しました。
スキルツリー画面のオン・オフ
スキルツリーの画面は出来ましたが、通常であれば最初は見えない状態で何らかのキーやボタンを押す事でスキルツリー画面を表示したいところです。
そこでMain CameraにSkillSystemOnOffスクリプトを新しく作り取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class SkillSystemOnOff : MonoBehaviour { [SerializeField] public GameObject skillSystem; // 最初にフォーカスするゲームオブジェクト [SerializeField] public GameObject firstSelect; // Update is called once per frame void Update () { if (Input.GetKeyDown ("s")) { skillSystem.SetActive (!skillSystem.activeSelf); EventSystem.current.SetSelectedGameObject (firstSelect); } } } |
skillSystemはSkillUIに設定しているSkillSystemスクリプト、firstSelectはスキルボタンで最初にフォーカスする対象を指定します。
今回はfirstSelectには『攻撃UP1』のスキルボタンを設定します。
UpdateメソッドではキーボードのSキーを押した時にSkillUIのオン・オフをします。
本来であればGetButtonDownを使ってキーボードとマウス、ゲームパッドどちらを使っても操作できるようにしておくといいと思います。
EventSystem.current.SetSelectedGameObjectで最初にフォーカスしておくゲームオブジェクトを設定する事が出来ます。
これでSkillUIのオン・オフが可能になったのでSkillUIのインスペクタで名前の横のチェックボックスを外し、最初はオフの状態にしておきます。
これでスキルツリーの機能が完成しました!
スキルツリーUIの全体は
↑のようなヒエラルキーになります。
スキルツリー機能を確認する
スキルツリー機能が完成したのでマウスやゲームパッドで操作して確認してみます。
↑のようになりました。
最初マウスで操作して確認し一旦Unityを終了してからゲームパッドで操作しています。
今回の場合はゲームパッドで操作する場合でもSkillUIのオン・オフはキーボードのSキーを押す必要があります。
スキルを覚えたスキルボタン自体のinteractableをfalseにしボタン自体を機能させないようにする事も出来ますが、
ゲームパッドの操作を考えた場合に一度覚えたスキルボタンの上にフォーカスが移動出来なくなってしまう為やめました。
マウス操作のみを使う場合はボタンの色合いを変更するのではなくButtonのinteractableをスクリプトからfalseにする方が楽です。