RPGって難しい
自分は趣味でRPGの開発をしています。ゲーム開発・Unity歴はちょうど一年くらいで、去年の年末ごろから本格的にRPGの制作をしています。しかし、RPGの開発はとても難しい。なぜかというと「自由度が高い」からでしょう。大ボリュームのゲームなら何でもかんでもRPGとジャンル分けされている気がするくらいに種類がたくさんあります。だからこそ他にはない難しい機能を実装したいと考えるため、ほぼAIに任せきりの処理もあります。これからは自分の実装力を上げるためにも自分で考えることが重要だと思いました。
もうパクれ
今まではオリジナリティを重視しすぎて、制作の途中段階で迷走したりしていました。しかし、いまは何万ものゲームがリリースされています。どれだけ自分自身でアイデアを絞り出したものでも、最終的にはとあるゲームに似通ってしまうことは仕方のないものです。なので、どうせそうなるならもういっそパクってしまおうと思います。その方が開発のハードルが下がるし、あとで自分なりにアレンジしたりもできるので...
ミートピアのようなステージ制をパクる
本題です。今回パクるのは、任天堂から「ミートピア」のステージ制の仕組みです。
①自動で次のエリアに向かって移動。
②キャラ同士の会話やつぶやきが展開。
③敵がいきなり出現して、戦闘開始。
④ゴール地点で宿屋に到着。
という流れです。今回はこれを自分なりにアレンジしながら実装したいと思います。
+α(アレンジ)
ミートピアとの差別化は、主に三つです。
①会話が分岐する。
②キャラが乗り物に乗っている。
③宿屋ではなく、自由に動き回れるエリアへ向かう。
これならかなりオリジナリティーのあふれたゲームになるでしょう。
どんな処理が必要か
まずは、どんな処理が必要か考えて優先順位を立てました。
①移動処理
②キャラ管理処理
③モンスター出現処理
④トーク処理
⑤エリア処理
このような優先順位にしました。
①の詳細化
この記事では①の実装について記載します。具体的に何をするのか詳細化をしましょう。
①は一言で表すと「移動状態に切り替え」です。
・カメラ追従処理
カメラ追従処理は操作キャラに依存する処理になっているので、追従対象を変更できるように修正して、乗り物オブジェクトを追従させられるようにします。
・キャラ
操作キャラのオブジェクトにはもうすでにたくさんの処理が実装されているので、別で乗り物に乗っている状態のスプライトを持ったオブジェクトを用意します。ScriptableObjectを利用してキャラクター共通のInterfaceで定義した名前文字列と対応付けて管理します。
・スタート&ゴール
スタート地点のオブジェクトとゴール地点のオブジェクトまで乗り物オブジェクトを移動させます。
カメラ追従処理の修正
まずは依然実装したカメラ追従処理を修正して追従対象を変更できるようにします。
(修正前のコードはこの記事を作ろうと思いついたころには修正してしまいましたので記載なしです。)
//変更後
using UnityEngine;
public class CameraCtrl : MonoBehaviour
{
public float smoothTime = 0.2f;
public Vector3 offset;
//これで変更可能にする
public static Transform target;
public float zFollowFactor = 0.3f;
public float yFollowFactor = 0.5f;
private Vector3 velocity = Vector3.zero;
void LateUpdate()
{
//エラー防止
if (target == null) return;
// プレイヤー位置を丸めて微振動を防ぐ
Vector3 p = target.position;
// Vector3 p = Character.Current.tf.position;
p.x = Mathf.Round(p.x * 100f) / 100f;
p.y = Mathf.Round(p.y * 100f) / 100f;
p.z = Mathf.Round(p.z * 100f) / 100f;
Vector3 t = p + offset;
// 奥行き追従(HD2D風)
t.z = offset.z + (p.z * zFollowFactor);
// 高さ追従(弱め)
t.y = Mathf.Lerp(transform.position.y, p.y + offset.y, yFollowFactor);
//滑らかに追従
transform.position = Vector3.SmoothDamp(transform.position, t, ref velocity, smoothTime);
}
}
状態によって追従対象を変更
状態はenumで定義します。
using UnityEngine;
public class TravelCtrl : MonoBehaviour
{
public enum VehicleState
{
Idle,
Moving
}
public VehicleState currentVehicle;
[SerializeField, Header("デバッグ用乗り物")]
private GameObject debugVehicle;
void SetCameraTarget(VehicleState newState)
{
currentVehicle = newState;
if (currentVehicle == VehicleState.Moving)
{
//デバッグ用(本来はエリアごとに別の乗り物を設定)
CameraCtrl.target = debugVehicle.transform;
}
else
{
//エリア移動以外はキャラクターに追従(Interfaceから参照)
CameraCtrl.target = Character.Current.tf;
}
}
}
最初はUpdateで変更処理をしていましたが、長期的にみると逐一呼び出す方がいいと思ったのでこのような形に修正しました。
(※Updateで毎フレーム切り替えると、状態管理とカメラ制御の責務が混ざってしまうため)
AIからの提案
このようにして実装した追従対象変更処理ですが、AIさんに見せたところ、以下のようなコードを提案されました。
private VehicleState _state;
public VehicleState State {
get => _state;
set {
_state = value;
OnStateChanged(value);
}
}
void OnStateChanged(VehicleState state)
{
if (state == VehicleState.Moving)
CameraCtrl.target = debugVehicle.transform;
else
CameraCtrl.target = Character.Current.tf;
}
こうすると:
travelCtrl.State = VehicleState.Moving;
と記述するだけで自動的に切り替わるそうです。
ただ、「これではコーディングをした自分以外の人間は追従対象が変わったことがわからないのでは?」
と思いました。しかし、「この処理はTravelCtrlに全責任を持たせるべきで、外から見せる必要がないように設計するべき。」とAIに論破されました。設計が崩壊すると脅されたので怯えながら以下のように修正しました。
using Unity.VisualScripting;
using UnityEngine;
public class TravelCtrl : MonoBehaviour
{
public enum VehicleState
{
Idle,
Moving
}
public VehicleState currentVehicle;
public VehicleState State
{
get => currentVehicle;
set
{
currentVehicle = value;
SetCameraTarget(value);
}
}
[SerializeField, Header("デバッグ用乗り物")]
private GameObject debugVehicle;
void SetCameraTarget(VehicleState newState)
{
if (newState == VehicleState.Moving)
{
//デバッグ用(本来はエリアごとに別の乗り物を設定)
CameraCtrl.target = debugVehicle.transform;
}
else
{
CameraCtrl.target = Character.Current.tf;
}
}
}
これで完成です。
初めて書いた記事の感想
かなり長くなってしまったので今回はこれで終わりにします。初めて記事を書いてみて、理解がとても深まるように感じました。これからもRPG開発にかかわらず、様々な開発や研究に関する記事を書いていこうと思いました。次回は「乗り物の移動処理について」について書きます。Thank You For Reading!