amustallホーム

RPGMakerUniteでActionRPGを作ろう(part1)

目次

part1
・playerの攻撃を作成(当たり判定なし)までを解説
part2
・playerの常時ステータス表示windowとステータスバー(Hp,MP)までを解説
part3
・敵にHpとGoldを付与して、敵用のHpバーも作成するまでを解説
part4
・敵への攻撃判定までを実装するまでを解説
part5
・playerの攻撃の種類を増やして複数攻撃を実装までを解説
part6
・敵のAIや表示UIの調整から完成まで!!(最終回)

この講座を最後まで呼んでいただけると出来るゲームの内容(このゲームようなアクションRPGをつくります)
ALL YOU NEED IS GOLD 0

RPGMakerUnite参上!

さて、Unityをこよなく愛する僕に朗報がとどきました! RPGMakerUnite(以下Unite)の登場です!!テンションあがりますね!! UnityでついにRPGが作れる日が来たのです(^^) もちろんRPGを1から作成するのも素晴らしいですが、これは結構というか、信じられないレベルで大変です。 その大変な部分をUniteが準備してくれているのです!! すばらしいですね!ということでRPGを作って行きたいんですが・・・ 普通にRPGMakerでRPG作っても、オリジナリティーを出すのは難しいですよね。 自分でドット絵がかけたりできれば別ですが、私は書けません。 でもコードは少し書けます! ということで、今回はUniteでアクションRPGを作って行くことにしました!! といってもRPGMakerの達人達のようにAdd Onでは改造しません。 今回は初心者向けに(というか、僕がAdd Onでアクションゲームにまで改造しきれないので><) Unityらしく、普通にバリバリ改造していきましょう。 対象としてはある程度unityの基本操作はわかっている人向けになります。 方針としては出来るだけソースコードの改変は少なめに、そして出来るだけ Uniteの機能にそって改造していきます! 一応、もうunite製のアクションRPGは作り終わっています、このブログ記載時(2023/8)の時点で ふりーむに投稿しており審査中です。 作りながらブログを書いている分けではないので、せっかくブログ読んだのに改造できなかったジャン! という事にはなりません。(僕がブログ自体を最後まで書ききれるかはわかりませんが・・・がんばります!!) そして、part1としているように、かなり長くなりそうなので、何回かに分けて投稿していきます。 また今回つくったアクションRPGと同じものを1から説明するのはモチベーションが続かないので、 今回作ったアクションRPGの次回作の基礎を作っていく過程を説明してきます。 そしてコードも基本的に全部添付する予定なので、がんがんコピペできるようにします! そして参照できる図も沢山いれて、みやすく、分かりやすくするように頑張ります! この基礎の組み合わせでちゃんとアクションRPGを作れるところまで、ちゃんと説明する予定なので 安心してください!!では早速開始していきましょう! そして、この記事はあくまで比較的初心者向けの記事で、筆者もとくにプログラムの正式な 教育は受けておりません。動けばいいやの精神の雑魚アマチュアです。あくまで参考程度に、 そして僕より初心者に向けて書いておりますので、つたないコードが頻発しますがご容赦ください。

コードを書く前に、Uniteの基本を幾つか確認

さてまずUniteの画面構成です。UniteはUniteのみの画面とunite+unityの画面が選べますが、 unite+unityの画面にして、最終的にはunityの画面側にuniteのタブを入れ込んでしまって 1画面にするのが開発として楽ちんなのでそのようにしてしまいましょう!
ウィンドウ→RPGMaker→モード→RPGMaker + unity Editorを選びましょう
そして、タブを使いやすいようにどちらかの画面に全部いれてしまいましょう。僕は以下の 様に設定しています。

photo

↑の画像はクリックで拡大できます(以下の画像も同様です)

僕は上記のように設定しました。これは個人の好みなので好きなようにwindowを配置してください。 そして忘れないようにこの画面設定を保存しておきましょう。

さて画面構成ができたらUniteがどうやって動いていくかを解明していきましょう。 僕もuniteがどうやって動いているのかさっぱり分からなかったので、ソースコード死ぬほど読みながら、 すこしずつ色んな機能を自分で追加していきました。これはとても大変でしたが、ソースコードをめちゃくちゃ 読んだので、純粋にプログラミングの勉強になりました(現に僕は今回のソースコードをみてやっと継承を少し理解して 実際に使う事ができました。すいません筆者のレベルはその程度です。。)、また例えばイベントどうやって発火させるの? プレイヤーのHPどうやって参照するの?などなど沢山でてくる疑問を解決していくのは純粋にクイズを 解いているみたいで楽しかったです。脇道にそれましたが、、、、まずはUniteで最小のアセットのみでプロジェクト をビルドしましょう。ここで全部詰まったやつをでプロジェクトを開くと、とっても重くなってしまいます。

photo

上の図のようにメニューの赤で囲った部分をクリックして、基本アセットのみで新しいプロジェクトを作りましょう。 これで起動の時間も大分早くなります。さてこれで新しい軽量なプロジェクトが出来たので、 この新しく作成したプロジェクトでここから説明していきます。

まずは初めからシンプルなゲームが入っているので、uniteのゲーム起動ボタンを押してゲーム画面にしましょう。 そしてuniteのゲームのsceneがどうなっているのか確認してみましょう。するとゲームを起動するとまずは titleというsceneになり、ニューゲームをメニューから選んで ゲームを開始するとsceneMapというsceneになります。 そして、別のマップを移動してもsceneはsceneMapのままなので、移動するたびにsceneの移動はなく、 同じsceneでマップを入れ替えている事が分かります。そのほかにバトル用のsceneもありますが、 これはUniteで普通にエンカウント式のRPGを作成するとに使用するsceneです。今回は敵はシンボルとして マップを動き、こちらはマウスをクリックして必要な攻撃を出して戦闘していくアクションRPGにするので、 このバトル用のsceneは使用しません。titleもタイトル画面用なので、メインで使用するのはsceneMapになります。

しかし、このsceneMapをsceneとして選んでいる状態でゲームを初めてもエラーになります。 titleでDontDestoryOnloadのオブジェクトが生成されているので、必ずゲームを起動するときには titleをsceneとして選んでおきましょう。

いよいよ改造開始です。マウスでボタンをクリックできるようにしましょう!!

このゲームでは攻撃はボタンをクリックして行います。しかし現状のゲームには攻撃ボタンはありません。 まずはボタンを配置していきましょう。ということでcanvasを作成していきましょう。 今後自分でオブジェクトやスクリプトを沢山作成していきますが、uniteに元からある物と区別をするために、 自分で作成した物の名前には頭にMy_をつけることにします。これは個人の好きでいいと思いますが、 自分はこれが非常に役にたちました。

さてそれではSceneMapプロジェクトタブ検索欄から探して、sceneをSceneMapに移動してください。 そこでcanvasとbuttonを作成します。buttonは今後ボタンをふやして行くときに管理がしやすいので、 空のオブジェクトでbuttonManagerを作成して、そのbuttonManagerにContent Size FilterVertical layout Groupをつけて置きましょう。 以下に参照用画像を示しておきます。

photo

上記はcanvasの設定です。多分のこの設定でいいと思います。ソート順はこんなに大きくなくていいかもしれません。

photo
photo
photo

変更している所は赤で示しています。ボタンの名前や大きさやfontは各自で自由に決めてください。 さて、ここでボタンをクリックしてみてください。するとどうでしょうキャラクターが移動してしまいますね。 Uniteではクリックでキャラクターが移動するで今のままでは、ボタンを押すたびにplayerが移動してしまます。 そこで、ボタンを押した時はプレイヤーの移動が起こらない様にしましょう。

buttonを押したときの制御はソースを探したところ、InputHandler.csにありました。 コードを以下の様に書き換えます。まずはフラグを追加します。


//改変コード 元はなし----------------------------------------------------
public static bool canMoveMouseClick;
//--------------------------------------------------------------改変コード
            

一応ソースを変更している時はあとで困らないように出来るだけコメントを残しておきます。


        public static void Watch() {
            // InputSystemからのインプットのハンドリング
            if (_currentInputSystemState == null) return;

            // 連続でキーを受け付けるもの
            if (_currentInputSystemState.CurrentInputSystemState(HandleType.Left))
            {
                Handle(HandleType.Left);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.Right))
            {
                Handle(HandleType.Right);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.Up))
            {
                Handle(HandleType.Up);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.Down))
            {
                Handle(HandleType.Down);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.Decide))
            {
                Handle(HandleType.Decide);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.Back))
            {
                Handle(HandleType.Back);
            }

            // 1回しかキーを受け付けないもの
            if (_currentInputSystemState.CurrentInputSystemState(HandleType.LeftKeyDown))
            {
                Handle(HandleType.LeftKeyDown);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.RightKeyDown))
            {
                Handle(HandleType.RightKeyDown);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.UpKeyDown))
            {
                Handle(HandleType.UpKeyDown);
            }
            else if (_currentInputSystemState.CurrentInputSystemState(HandleType.DownKeyDown))
            {
                Handle(HandleType.DownKeyDown);
            }

            /*
            // マウスイベント系
            // 左クリックのみ、マップ移動等で利用しているため、Decideとは別途で発火する
            if (Input.GetMouseButtonUp(0))
            {
                Handle(HandleType.LeftClick);
            }
            */
            //改変、↑のコメントアウトが元のコード-----------------------------------------
            if (canMoveMouseClick)
            {
                if (Input.GetMouseButtonUp(0))
                {
                    Handle(HandleType.LeftClick);
                }
            }
            //----------------------------------------------------------------------------
        }
            

出来るだけ前後関係が分かるようにもとのコードが沢山入っていますが、変更している場所は コメントの部分のみ、やっていることはシンプルです、まずは参照できるようにstaticで playerがクリックで移動するかどうかのFlagをcanmoveMouseClickとして宣言しています。 そして、Uniteではinput sysytemとinput managerを両方使用しているので、 Input.GetMouseButtonUpが使える設定になっていますが、ソースをみるにこの処理で playerの移動を呼んでいるので、flagがtureの時のみその指示が呼ばれるように変更しています。

これを記載したら、次はボタンが押された瞬間にFlagをfalseにして、ボタンが離された0.01秒後に flagをtureにすればいいですね。先ほど作成したMy_sceneMapCanvasにMy_SceneMapCanvasScript というC#スクリプトつけて以下のコードを書きましょう。


    //buttonのボタンが押された時と離された時を監視----------------------------------
    //以下がないとボタンを離した時にプレイヤーが移動してしまう
    public void PointerDown() {
        InputHandler.canMoveMouseClick = false;

    }

    public void PointerUp() {
        StartCoroutine(CanMoveMouseClickTrue());

    }

    IEnumerator CanMoveMouseClickTrue() {
        yield return new WaitForSeconds(0.01f);
        InputHandler.canMoveMouseClick = true;
    }
    //-------------------------------------------------------------button監視
            

上記ではInputhandlerが参照できませんとエラーがでますので、スクリプトの先頭に
using RPGMaker.Codebase.Runtime.Common;
を入れておきましょう。今後もソースコードを参照する機会は多いので、 きちんとusingにパスを記載して参照できるようにしましょう。 そして、ボタンの押したタイミングと離したタイミングが取れるように、ボタンに 必要なEvent trigerをつけます。Event TriggerはAdd Componentボタンを押してつけます。 そして以下の画像を参照して必要なeventを追加してください。

photo

pointer upの下の+を押して、My_SceneMapCanvasをアタッチ(赤線部)して、pointer upには 先に記載したPointerUp関数を関連させております(青線部)。pointer downも同様です。 これでゲームを起動してボタンを押してみてください、playerは移動しなくなっています。 最後にこのボタンが動くかまずはテストコードを書いて確認しましょう。


    public void ClickAttackButton() {
        Debug.Log("Button Clicked");
    }
            

上記のコードをMy_SceneMapCamvasScriptに追加して、ボタンのクリック時に関連づけてください。 これでゲームを起動して、ボタンをおしてコンソールにButton Clickedが表示されて、 かつplayerが移動しなければボタンは完成です。

ボタンを押した時にplayerから攻撃を出るようにしよう!

さてボタンが出来たので攻撃に必要な入力をUniteに渡すことができるようになりました。 それではボタンを押したらplayerから攻撃objectを発射できるようにましょう。それにはどんな情報が必要でしょうか?
1.playerの位置情報
攻撃objectの発射位置として必要ですね
2.playerの向き
攻撃を発射する方向を取得するために必要です
まずは上記の2つが必要そうですね、それではこの二つの取得方法を考えていきましょう。

ゲームを起動してplayerを操作可能な画面にしてヒエラルキーを見てみましょう。 するとrootの下にいくつかのnew game objectがありますが、この中で一つmain cameraが ついているものがありますが、これがplayerになります。そしてこのplayerのインスペクターをみると いかにもな項目がありますね。X_now,Y_nowがありこれは画面をみるにActorOnMapで宣言されているようです。 そしてこのActorOnMapを検索して中をみてみると、どうやらCharacterOnMapを継承しています。 そしてこのcharacterOnMapの中にGetCurrentPositionOnTile()という関数があり、 コメントにも現在のタイル位置を取得と記載があります。このようにUniteのソースはかなり コメントが丁寧に記載されているので、ソースはかなり読みやすく、またこのゲームが動いているので 自分が必要と思われる値を取得する関数はほぼこのソースに、使用しやすい形で用意されています。 これを探すのはかなり大変でしたが、これらを利用してどんどん自分で作りたい機能を作っていきましょう。 そしてGetCurrentDirection()という、playerの向きを取得する関数もcharacterOnMapに見つかります。

photo
photo

これで1と2が達成できそうですが、まだできません、そもそもplayerのオブジェクトを取得しないと いけません。これはやり方としては、playerにはmain Cameraがあるので、main camaraの親オブジェクト を取得すればplayerがとれます。それでもいいのですが、caracterOnMapの関数を簡単に使用する方法 があります。それはMapManagerというスクリプトを利用します。これを利用すれば簡単にplayerの 位置を取得できるのでその方法で行きましょう。 まずは、playerの処理関係を一つのスクリプトにしておくと分かりやすいと思いますので、My_PlayerManagerScriptと いうスクリプトを作成、またSceneMapのsceneの中にMy_SceneMapManagerという空のオブジェクトを作成して、 My_PlayerManagrScriptをアタッチしてください。

photo

上記ができたら、My_PlayermanagerScriptに以下を書きましょう。
using RPGMaker.Codebase.Runtime.Map;
上記はMapManager使用ために宣言しておきます。


    public void SordAttack() {
        Debug.Log( MapManager.OperatingCharacter.GetCurrentPositionOnTile());
        Debug.Log(MapManager.OperatingCharacter.GetCurrentDirection());
        Debug.Log("sordAttack");
    }
            

まずは機能の確認のためにplayerの位置向きをコンソールに出力してみるコードです。 あとはこれをボタンから呼べればいいですね。 それではMy_SceneMapCanvasScriptを以下の様に改変してください。そしてこの改変で もとのボタンにつけていた関数の名前を変更しているので、ボタンに再度ClickSordAttackButton()関数を 関連つけておいてください。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using RPGMaker.Codebase.Runtime.Common;

public class My_SceneMapCanvasScript : MonoBehaviour
{
    public GameObject my_SceneMapManager;
    private My_PlayerManagerScript my_PlayerManagerScript;
    // Start is called before the first frame update
    void Start()
    {
        my_PlayerManagerScript = my_SceneMapManager.GetComponent<My_PlayerManagerScript>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void ClickSordAttackButton() {
        my_PlayerManagerScript.SordAttack();
    }

    //buttonのボタンが押された時と離された時を監視----------------------------------
    //以下がないとボタンを離した時にプレイヤーが移動してしまう
    public void PointerDown() {
        InputHandler.canMoveMouseClick = false;

    }

    public void PointerUp() {
        StartCoroutine(CanMoveMouseClickTrue());

    }

    IEnumerator CanMoveMouseClickTrue() {
        yield return new WaitForSeconds(0.01f);
        InputHandler.canMoveMouseClick = true;
    }
    //-------------------------------------------------------------button監視
}
            

上記でやっていることを簡単に解説します。まずはunityを使う上で必須のテクニックがあります。 他のオブジェクトにあるスクリプトを参照する方法です。これはやり方は沢山ありますが、 僕はこのやり方が一番好きなので使っています。まずはスクリプトがついているオブジェクトを 取得します。今回はpublicで宣言したところにアタッチの方法を取っていますが、これは別に findしても何でもいいです。そしてスクリプトとして使用する変数をそのスクリプト型で 宣言します。
private My_PlayerManagerScript my_PlayerManagerScript;
上記の部分です。あとはstartでお馴染みのGetComponentしてあげれば、スクリプトを参照 できます。そしてClickSordAttackButton()関数で目的を関数を呼びます。 ゲームを実行してボタンを押してみてください、コンソールにplayerの位置と向きが表示 されれば成功です。

続いてplayerから発射する武器をを作成しましょう!
Uniteの素晴らしい点はあらかじめ多くのspriteが用意されている点です。
プロジェクトのRPGmaker→Strage→images→objectsの中から剣のスプライトを選んでctrl+dで複製しましょう。 それが出来たら、自分でMy_Spritesフォルダーを作成してそこに移動して、名前をsordに変更しましょう。

photo

スプライトが出来たので、SordObjectという空のオブジェクトを作成、transformを(0,0,0)にしてから、 (これはこの後でprefabにします。prefabにする場合は生成時の位置に問題が出る場合があるので、あらかじめ transformをにしておく方がいいです) このオブジェクトの子オブジェクトとして作成したスプライトをつけておきましょう。 そして、この作成したsordObjectの挙動を制御するスクリプトを作成しましょう。
My_AttackBaseScriptという名前のスクリプトを作成して、内容を以下のようにしてください。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPGMaker.Codebase.Runtime.Map;
//CharacterMoveDirectionEnumを使用できる様に宣言
using RPGMaker.Codebase.CoreSystem.Knowledge.Enum;

public class My_AttackBaseScript : MonoBehaviour
{
    //ソードのspeed
    private float speed = 4.0f;
    //characterの方向はCharacterMoveDirectionEnumで返されるので宣言
    private CharacterMoveDirectionEnum characterDirection;
    // Start is called before the first frame update
    void Start()
    {
        //生成時のプレイヤーの向きを取得
        characterDirection = MapManager.OperatingCharacter.GetCurrentDirection();
        RotetionChange();
    }

    // Update is called once per frame
    void Update()
    {
        SordMove();
    }
    //生成時の向きに応じてsordの進む方向を決める
    public void SordMove() {
        if (characterDirection == CharacterMoveDirectionEnum.Up)
        {
            transform.position = (Vector2) transform.position + new Vector2(0, 1) * speed * Time.deltaTime;
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Down)
        {
            transform.position = (Vector2) transform.position + new Vector2(0, -1) * speed * Time.deltaTime;
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Right)
        {
            transform.position = (Vector2) transform.position + new Vector2(1, 0) * speed * Time.deltaTime;
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Left)
        {
            transform.position = (Vector2) transform.position + new Vector2(-1, 0) * speed * Time.deltaTime;
        }
    }

    //プレイヤーの向きに応じて生成時にスプライトを回転
    private void RotetionChange() {
        if (characterDirection == CharacterMoveDirectionEnum.Up)
        {
            transform.Rotate(0, 0, 180);
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Down)
        {
            transform.Rotate(0, 0, 0);
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Right)
        {
            transform.Rotate(0, 0, 90);
        }
        else if (characterDirection == CharacterMoveDirectionEnum.Left)
        {
            transform.Rotate(0, 0, -90);
        }
    }
}
            

コードの説明は上記コードのコメントを参照してください。これが出来たらSordObjectにアタッチして SordObjectをプレハブ化してください。この後で重要な事があります。それはUniteではspriteの ソートが指定されています。これを変更しないと表示がマップの下になったりして表示がみえません。 以下の画像を参考にして変更してください。画像では全部見えませんが選択するソートレイヤーは
Runtime_MapCharacterPriorityNormalにしています。

photo

上記ができたら、My_PlayerManagerScriptのSordAttack関数を以下の様に変更しましょう。


    public void SordAttack() {
        Debug.Log( MapManager.OperatingCharacter.GetCurrentPositionOnTile());
        Debug.Log(MapManager.OperatingCharacter.GetCurrentDirection());
        Instantiate(sordPrefabObject, MapManager.OperatingCharacter.GetCurrentPositionOnTile(), Quaternion.identity);
    }
            

そして、スクリプトの冒頭で以下のようにsordprefabObjectを宣言して、先ほど作成したprefabをアタッチしましょう。
public GameObject sordPrefabObject;
さぁこれで出来ました!ゲームを起動して攻撃ボタンを押してみてください。playerから攻撃が発射されます。 あとはこのobjectは発射されたらずっと存在してしまうので、一定時間経過したら破壊されるようにしましょう。

攻撃objectを破壊するためのMy_DestroyAttackObjectという名前にスクリプトを作成しましょう。 以下の内容でスクリプトを記載してください。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class my_DestroyObjectScript : MonoBehaviour
{
    private float time;
    //prefab毎にunity側で指定
    public float lifeTime;
    // Start is called before the first frame update
    void Start() {
        time = 0;

    }

    // Update is called once per frame
    void Update() {
        time += Time.deltaTime;
        if (time > lifeTime)
        {
            DestroyObject();
        }
    }

    private void DestroyObject() {
        Destroy(this.gameObject);
    }
}
            

上記が出来たら、このスクリプトをプレハブにした、SordObjectにアタッチしましょう。そして publicで宣言したlifeTimeにと入力しておきましょう。

photo

上記が完成したら攻撃オブジェクトは生成後5秒で破壊されます。この方法ならpublicなので 他の攻撃オブジェクトを作成したときに、それぞれ異なるlifeTimeを指定できるので汎用性がありますね!!

photo

さて無事に見かけ上は攻撃することが出来るようになりました。part1ではここまでとします。 さて今後の予定ですが、part2では攻撃から一旦はなれて、playerや敵のUI関係、HP等の表示関係を実装します。 そしてpart3で作った攻撃の当たり判定を作成して実際に攻撃してみましょう! ここまで読んでい頂いてありがとうございました!次回part2にもご期待ください!!

ブログ一覧へ戻る