見出し画像

(Unityアセット)Turn Based Strategy Framework チュートリアル徹底解説

Turn Based Strategy Frameworkのチュートリアルを詳細に解説していく

Yale(イエール)です。Unityを学んでいる者です。

UnityでタクティクスオウガやFFタクティクス、トライアングルストラテジーのようなタクティクスRPG(シミュレーションRPG)を個人で開発しようとした場合、0からフルスクラッチで作るのは非常に大変です。

そうした個人開発者の味方となるのが、Unityアセットです。さまざまなUnityアセットがある中で、私がタクティクスRPGを制作するにあたって、選んだものが本シリーズで紹介する「Turn Based Strategy Framework(以下、TBSF)」です。

本稿、またはこのシリーズの記事はTBSFの開発者であるCrooked Head様に一部のコードと公式ドキュメントの引用を許可のもと公開しています。また、筆者のUnityならびに本フレームワークの理解度が追いついていない場合、後日記事の訂正や補足を加える可能性があります。あらかじめご了承ください。


公式チュートリアルの日本語解説の概要

TBSFは開発者がかなり丁寧な英語のドキュメントを用意しています(外部リンク)。その中に最小構成でタクティクスRPGっぽいものを作れるようなチュートリアルがあります。先日公開した公式ドキュメントを全て日本語に翻訳した記事の中にもチュートリアルを掲載しています。

本稿ではそのチュートリアルをさらに詳細に解説していきます。チュートリアルで使用するUnityやTBSFの機能はもちろん、スクリプトの中身も一つずつ解説していくため、本フレームワークを購入していれば初心者の方でも真似ができる記事になっています。

チュートリアルスタート

このチュートリアルでは派手な演出や分かりやすいUI、勝利・敗北演出などは登場しません。
ゲームの舞台となるCubeを並べ、Player1とPlayer2を用意し、それぞれのユニットを準備し、障害物を追加していきます。そして、ちょっとしたGUI操作を付与するだけでユニットを動かして、攻撃するといった基本が出来上がります。
公式ドキュメントでは省略されている説明やステップを一つずつ拾い上げていきますので、冗長に感じる可能性もありますがよろしくお願いします。

まず、Unity3Dのプロジェクトを新規に作りましょう。そして、TBSFをWindow>Package Managerからインポートしてください(いつも通りUnityアセットのインポートをするべし)。

Unityエディタで新規シーンを作成します。
この際、シーン名はTutorialなど任意の名前で良いです。本番のプロジェクトではなく、チュートリアル用のプロジェクトとして用意した方が好きにカスタマイズできておすすめです。

ゲームの舞台である「Cell(セル)」を作ろう

UnityエディタでGameObject>3DObject>Cubeを作成します。
これがゲームの舞台、つまりユニットたちが移動する地面の働きをしてくれる、Cell(セル)のPrefabになります。CubeにはデフォルトでBoxColliderがアタッチされています。

次にCellのコーディングをしましょう。新しいスクリプトを作成して名前をつけましょう。ここでは例として、SampleSquareとします。

SampleSquareはSquareクラスを継承し、セルの外観に関わるいくつかのメソッドをオーバーライドします(後述)。
このCellはハイライトされると灰色になり、ユニットが到達可能な場合黄色になり、緑色はパス(経路)を示します。コードは下のようになります。

@継承ってなに?
似たような動きや性質を持ったスクリプトを書く際に用いられます。今回で言えば元々のSquareというクラスには本フレームワークを使って、セルを作るのに必要かつ便利な機能がすでに備わっています。それを利用しつつ、ユーザーの目的に合わせたセルを作る目的で継承します。継承には下のコードのように、「class SampleSquare : Square」というように書きます。デフォルトではMonoBehaviorとなっている部分を継承元のクラスにするといった認識で良いでしょう。
そして、継承元のメソッドをオーバーライドすることで継承先のオブジェクトの性質を微妙に変化させるなど、カスタマイズ性を保つことができます。

using System.Collections;
using System.Collections.Generic;
using TbsFramework.Cells;
using UnityEngine;

namespace TbsFramework.Tutorial
{

    class SampleSquare : Square
    {
        public override Vector3 GetCellDimensions()
        {
            //boundsとは三次元空間の範囲を表す。
            //bounds.sizeでボックスのサイズを取得している。
            return GetComponent<Renderer>().bounds.size;
        }

        public override void MarkAsHighlighted()
        {
            //マテリアルのカラーをColorを使って新たなカラーに変更している。今回は灰色。
            GetComponent<Renderer>().material.color = new Color(0.75f, 0.75f, 0.75f);
        }

        public override void MarkAsPath()
        {
            //Colorを使ってパス(経路)を緑色に変更している。
            GetComponent<Renderer>().material.color = Color.green;
        }

        public override void MarkAsReachable()
        {
            //上と同じくColorを使って、到達可能な場所を黄色にしている。
            GetComponent<Renderer>().material.color = Color.yellow;
        }

        public override void UnMark()
        {
            GetComponent<Renderer>().material.color = Color.white;
        }
    }
}

コード内のコメントで紹介していますが、GetCellDimensions()をオーバーライドした際にセルのボックスのサイズをbounds.sizeで取得しています。
以後のオーバーライドするメソッドはセルの状態によって色を変更するためのものです。全てコメントで記述している通りの内容になっています。
namespaceでTBSFrameworkと記述するのを忘れないようにしましょう。

SampleSquareスクリプトを先ほど作成したCubeにアタッチして、インスペクタ内のMovement Costパラメータを1に設定しましょう。Prefabを作成するためにプロジェクトエクスプローラにドラッグしてPrefabを作成します。

Window>Grid Helperを選択してGrid Helperを開きます(Grid Helperの説明は後述)。

Grid Helperのウィンドウでパラメータを入力します。入力するパラメータは画像をご覧ください。

Grid Helperの入力パラメータ。

ここでワンポイント! Grid Helperってなに?
TBSFでゲームを開発するとWindowからGrid Helperを呼び出すことが多々あります。ではGrid Helperとはなんでしょうか。
Grid Helperとはセルやユニットを生成して、必要なコンポーネントを自動で用意してくれる非常に重要なツールです。ユーザーの目的によって与えられたパラメータによってシーンを構成してくれます。

パラメータを入力したら、GridHelperウィンドウの「Generate Scene」ボタンをクリックしてください。Generatate Scene押下後のヒエラルキーは以下の画像の通りです。また2枚目の画像はシーンビューです。併せてご確認ください。

Generate Scene押下後のヒエラルキー。
シーンビュー

ゲームを動き回るUnit(ユニット)を作ろう

いよいよユニットをシーンに追加します。新しいスクリプトを作成し、名前をSampleUnitと名付け、Unitを継承させます。味方キャラユニットは薄緑色になるようにしましょう。
ここで作るユニットは選択されると、緑色に点灯し、攻撃可能な場合は赤色に点灯します。ターンが終了すると灰色になります。
子オブジェクトのRendererコンポーネントにアクセスしていることに注意してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TbsFramework.Units;

namespace TbsFramework.Tutorial
{
    public class SampleUnit : Unit
    {
        public Color LeadingColor;
        public override void Initialize()
        {
            //Initializeはそのまま
            //
            base.Initialize();
            //子オブジェクトのカラーをLeadingColorにする。
            GetComponentInChildren<Renderer>().material.color = LeadingColor;
        }

        public override void MarkAsFriendly()
        {
            //味方キャラユニットは薄緑にする
            GetComponentInChildren<Renderer>().material.color = new Color(0.8f, 1, 0.8f);
        }

        public override void MarkAsReachableEnemy()
        {
            //攻撃可能な敵ユニットのCOlorを赤色にする
            GetComponentInChildren<Renderer>().material.color = Color.red;
        }

        public override void MarkAsSelected()
        {
            //選択中のウニっとをColorで緑色にする
            GetComponentInChildren<Renderer>().material.color = Color.green;
        }

        public override void MarkAsFinished()
        {
            //行動を終了したユニットをColorで灰色にする
            GetComponentInChildren<Renderer>().material.color = Color.gray;
        }

        public override void UnMark()
        {
            GetComponentInChildren<Renderer>().material.color = LeadingColor;
        }
    }
}

Player1とPlayer2でユニットの色を分けたいので、新しいマテリアルを二つ作成しましょう。二つとも違う色(ここでは赤と青)にします。

ユニットのオブジェクトを作りたいところですが、まず空のゲームオブジェクトを二つ作成しましょう。その理由はすでに先ほど用意したセルの上にユニットを表示させるためです。ユニットはセルの中心にスナップされるため、CubeのYを上に上げてオフセットさせたいのです。後ほど、これらをPrefab化したものを画像にしますので、イメージがつかみやすくなるでしょう。

では先ほど作った二つの空のオブジェクトにSampleUnitスクリプトをアタッチします。空のゲームオブジェクトにCubeを子としてそれぞれ追加し、二つ用意したマテリアルも同様にアタッチします。このユニットをPrefab化しましょう。ここで間違えて子オブジェクトにSampleUnitスクリプトをアタッチすると後ほど、Unit Painterという機能が上手く働きませんので注意してください。

先述の子オブジェクトである、Cubeの立方体のYの位置をオフセットして、セルの内側ではなく、上に表示させましょう。Cubeの位置をY軸の0.9に設定します。

ユニットの設定として、最後に親ゲームオブジェクトにコライダーをつけます。BoxColliderを追加し、中心フィールドのY値を0.9にオフセットします。

Prefabにする。ちなみにBrainという子オブジェクトは次章で登場。

SampleUnitスクリプトにパラメータを入力します。選択した値は画像27の通りですが、自由に試してみてください。両ユニットのLeading Colorパラメータを異なる値に設定してください。

Unitのパラメータ。

Unit Painterでユニットを配置しよう

Unit Painterとは先ほどセルを作った際に使用したツール、Grid Helperに搭載されている機能の一つです。ユニットのPrefabをセットして、それがどちらのPlayerのユニットなのか指定し、Enter Unit Modeに入ることでTile Mapのようにシーン内にユニットを配置することができます。
これはただ、ユニットを配置するだけでなく、ターンベースストラテジーを作る上で必要なユニットの挙動を同時に追加するようになっています。
そのため、本チュートリアルではその感触を掴んでみましょう。

Unit Painterを使ってユニットをゲームに追加します。Grid HelperをWindow>Grid Helperから開きます。Unit Prefabフィールドに先ほど作ったユニットのPrefabをドラッグアンドドロップします。
Player Numberを0にして、Enter Unit Edit Modeボタンをクリックします。インターフェイスは画像の通りです。

Unit Painterのインターフェース。

Unit Edit Modeではシーンビューのセルをクリックすることで、ユニットをグリッド上に配置することができます。このモードはTile Mapのようにユニットを直感的に配置できるツールです。
さて、両方のPlayer陣営(Player0とPlayer1)をPlayer numberに指定しながら、いくつかのユニットを作成しましょう。下の画像は公式チュートリアルにおける、この時点でのシーンセットアップです。配置はセルの上であれば自由に置いて構いません。
Paintモードを終わるにはMacの場合CtrlとRキーを同時押しです(Grid Helper内にON/OFFの方法が書いてあるためそちらも確認してください)。

ゲームの舞台を彩る障害物を設置してみよう

それではシーンに障害物を追加してみましょう。やり方としては最初に用意したセルとユニットの作成方法を合わせたような感じで非常にシンプルです。

新しいCubeを用意します。これが障害物(Rock)の親のオブジェクトになります。次に子オブジェクトとしてCubeを追加します。
この子オブジェクトのCubeを岩に見立てたいので、新しいMaterialも作りましょう。真っ黒のMaterialを用意したら、それを子オブジェクトのCubeに貼り付けます。
先ほどのUnitの時と同様に子オブジェクトのCubeのYの位置を0.9にします。つまり、セルの上に障害物を出すための工夫ですね。
このPrefabを画像30に示します。公式では上の黒いCubeの大きさをX ,Y,Z共に0.9にしています、不自然でなければ数字は自由に変更可能です。
次に、この障害物の親オブジェクトのCubeに既に作成済みのスクリプトであるSampleSquareをアタッチしてください。同時に、インスペクタからIsTakenフィールドをTrueに設定(チェックマークを入れる)を忘れないようにしましょう。
ここまでできたらPrefab化してください。

障害物をフィールドに配置するために、Tile Painterを使って用意したオブジェクトをグリッドに追加します。
Tile PainterはUnit Painterと同様にGrid Helperからアクセスできます。障害物のPrefabをTile Prefabフィールドにドラッグアンドドロップし、Enter Tile Edit Modeボタンをクリックします。シーンビューを使って新しいセルを配置します。画像31にTile Painterのインターフェースを示します。

Tile Painterのインターフェース。

Grid Helperスクリプトは非常にシンプルなGUIコントローラースクリプトを自動でシーンにアタッチします。
このスクリプトはキーボードの「mキー」を押すことによってターンを遷移してくれます。
つまり、この時点で実は最小限のターンベースストラテジーゲームは完成しています。ゲームプレイしてみると、Player0のユニットが動かせるようになっており、Mキーを押下するとターンがPlayer1に移ります。どちらかが相手のユニットを全滅させると勝利となります。Debugログを注視すると、ターン遷移やどちらが勝ったかなどログを出してくれることに気がつくでしょう。

完成図。

終わりに

冒頭にも述べたようにこれは最小構成のターンベースストラテジーです。ここにUIを付け加えるだけでも雰囲気が出ますし、好きな3Dモデリングをユニットの置き換えるのも良いでしょう。少しずつ試して、できることの幅を広げてみてください。



この記事が気に入ったらサポートをしてみませんか?