ぐーるらいふ

底辺。

【Unity】第14回「逆」に参加。ファイアアンドアイスの開発について【unity1week】

今回も参加しました。

unityroom.com

今回のお題は「逆」ということで自分は王道の炎と氷で行くことにすんなり決めました。
みんなのゲーム見て「うおーすげぇ!その発想は無かった!」と毎回驚いています。

実は

実は直前にあほげーという24時間ゲームジャムがありまして、そこにも参加してたんです。
初めての参加でしたが、まぁまぁな結果でした。(喜ぶべき所なのか?w)
もっとこうさらーっと行くかと思いきや好成績でした。

f:id:ghoul_life:20200304002120p:plain

(特にリンクはなし。行きたい方は右のサイドバーからどうぞ)
(こっちやらなくていいんでファイアアンドアイスやってください…w)

実装の基盤について

ここからファイアアンドアイスの話題です。
ファイアアンドアイスは大きく分けて

  • UI(遷移)
  • 会話システム
  • キャラクター
  • ステージ
  • 魔法(固有アクション)


この5つで構成されています。

UI(遷移)

f:id:ghoul_life:20200304004914p:plain


ゲーム全体を管理するManagerがStateManagerを持っていて、
そのStateManagerが画面の切り替えを担当しています。
切替時にTrasitionを経由して自然に遷移するようにしてます。

TrasitionSystemはShader一個でやってまして、
パラメータとして渡したgrayscaleの画像のアルファ値を変化させることによって、
トランジションを表現してます。

f:id:ghoul_life:20200304115432p:plain

また、そのトランジションはalphaパラメータを0.0 - 2.0まで受け取ることが出来るようになっていて、
1.0以降は処理を反転させることにより、逆の表現になります。
(alpha1.0が透けて、0.0が透けなくなるイメージ)

f:id:ghoul_life:20200304115448p:plain

ちょっとわかりにくいですかね…?
詳しくは別の記事でコードごとまるっと紹介する予定。

会話システム

実は中身はM-1ボーイをまるっと持ってきてます。
なので、かなり実装はラクすることが出来ました。

f:id:ghoul_life:20200304113541p:plain

実際に作っていったのは、見た目のガワと、InGame部分のみです。
(多少調整はしてますけど)
まぁここはよくあるやーつなので割愛。
汎用的にしなくても一個だけ用意して共有にしてもよかった。

キャラクター

当初は主人公一人で、炎の魔法と氷の魔法を切り替えて...と考えていました。
そして早い段階で氷の上に乗っかりたいな、とは考えていました。
が、目の前に出す、といったものでは普通すぎるかなと。

「そうだ、キャラクターを凍らせちゃえばいいんじゃね」

と思いました。ここで2人に分裂するのですが…。

f:id:ghoul_life:20200304113956p:plain
(最初のラフ絵)

これが大変だった。切り替えたら操作も分けないとならないし、
カメラも動かさなきゃならないし、いろんな問題が発生。
必要なかったはずのCameraManager , CharacterManagerまで作る羽目に…。


立ち絵は三回書き直してます。が、ラフ線のまま塗ってます…。ペン入れする時間も惜しくて…。
でも一番楽しい時間です。まじで。時間さえあればもう少し線を綺麗にしてもっと塗りたい。

f:id:ghoul_life:20200304114105p:plain
エンディング絵です。見てくれた人もいるでしょうか。
最終日の18:00ぐらいまで描いてました...。

ステージ

今回の難関です。なんてったってステージを量産しないとなりません。
しかもステージだけではなく、ギミックまでないとゲームとしての面白さがありません。


そこでどうしたか?
そう、作るしかないのです。ステージエディタ。

ステージエディタの実装

LWRP(2D)なんで、Tilemapで行こうとは思ってました。
これでぽちぽち配置していって、ちょちょっと必要なものを置いて、ピッと1クリックで出力。
こんなものが無いとダメだなと。


まず、実際に動かすゲームとは別のステージ作成用のシーンを用意しました。

f:id:ghoul_life:20200304104934p:plain

そこにキャラ二人を置き、ギミックをD&Dで配置していって、ゴールを置いて、出力をMenuから押下。
これでStageが焼き上がります。

    [MenuItem("Stage/Export")]
    static void StageExportExecute()
    {
        var stageData = StageData.CreateStageData(); // ScriptableObjectを生成

        // Hierarchy内のオブジェクトを全部なめて目的のオブジェクトを探す
        foreach (GameObject obj in UnityEngine.Object.FindObjectsOfType(typeof(GameObject)))
        {
               // 略. GetComponentでもtagでもなんでもOK
        }

        // 適当な所に出力
        AssetDatabase.CreateAsset(stageData, path);
        AssetDatabase.Refresh();

    }

ちなみにこのシーンを実行すると即座にプレイ出来るようになってます。
まぁ細かいUIとかいらないですもんね。

ステージギミックについて

ただ、マップがあるだけではゲームとしてつまりません。
なので、ステージを彩る各種ギミックを用意しました。

  • 押せる岩
  • 燭台(ライトがわりにもなります)
  • 動く床左右
  • 動く床上下


この5つです。

実際にはこれ以外に

  • キャラ1(アイス)
  • キャラ2(ファイア)
  • ゴール


が入って8種類ですね。

ステージを作る際には「どのギミック」が「どの位置」にあるか?
を伝える事ができればいいので、StageDataはScriptableObjectでこんな感じになりました。
(これもし本当にサービスにするならテキストデータから流し込んだ方が良さげ。
JsonでもMessagePackでもいいんで。このままだとAssetBundle化して毎回ビルド必須みたいになりそ)
今回はWebGL埋め込みなんでScriptableObject直で保持します。

[CreateAssetMenu(menuName = "ScriptableObject/StageData")]
public class StageData : ScriptableObject
{
    [Header("ステージID")]
    public int StageId;

    [Header("プレイヤー1開始位置")]
    public Vector3 Player1StartPosition;

    [Header("プレイヤー2開始位置")]
    public Vector3 Player2StartPosition;

    [Header("ゴール位置")]
    public Vector3 GoalPosition;

    [Header("読み込むステージプレハブ")]
    public GameObject StagePrefab;

    [Header("ステージオブジェクト群")]
    public List<StageObjectData> StageObjectDatas;

    [Header("ステージ開始前会話")]
    public List<TalkData> _talkDatas;

    public static StageData CreateStageData()
    {
        return CreateInstance<StageData>();
    }
}

// ScriptableObjectでクラスを使うにはSerializableの設定が必要
[System.Serializable]
public class StageObjectData
{
    public int StageGimmickType; // どのオブジェクトをロードするか
    public float MoveValue = 3.0f; // 値
    public Vector3 Position; // どの位置に
}


開始前の会話データ(TalkData)もここに入ってます。
これは一つずつ手で打ってます。ここも楽しいw
二人ならどんな感じに話すかな〜と妄想を全力で膨らませられます。

f:id:ghoul_life:20200304105858p:plain

魔法

魔法はMagicManagerが管理します。教科書的なオブジェクトプログラミングです。
魔法クラスがあり、それを継承した氷魔法クラス、炎魔法クラスがあります。
当たった時の挙動をそれぞれオーバーライドで記述する事ができます。

public class Magic : MonoBehaviour
{
    // 発射処理
    public virtual void Shoot(PlayerCharacter playerCharacter)
    {
           // 発射した人を持っておいて、自分自身を移動開始
    }

    // 当たった時の処理
    public void OnTriggerEnter2D(Collider2D collision)
    {
        HitTest(collision);
    }

    // 当たった時の処理
    public void OnTriggerStay2D(Collider2D collision)
    {
        HitTest(collision);
    }

    private void HitTest(Collider2D col)
    {
          // 何かに当たってたら消す、処理を実行するオブジェクトならExecute
    }

    // 当たった時の処理
    public virtual void Execute(GameObject gameObject){}
}
public class FireMagic : Magic
{
    // 魔法によってSEを切り替える
    public override void Shoot(PlayerCharacter playerCharacter)
    {
           AudioManager.Instance.PlaySE("se_fire");
           base.Execute(player);
    }

    public override void Execute(GameObject gameObject)
    {
            // 炎魔法の処理
    }
}
public class IceMagic : Magic
{
    // 魔法によってSEを切り替える
    public override void Shoot(PlayerCharacter playerCharacter)
    {
           AudioManager.Instance.PlaySE("se_ice");
           base.Execute(player);
    }

    public override void Execute(GameObject gameObject)
    {
            // 氷魔法の処理
    }
}

炎魔法は即座に処理をするので良いのですが、
氷魔法はそうもいかないため、MagicManagerを用意して氷オブジェクトの作成と破棄を管理しています。

氷は何が凍ったか?を持っておいて、SetActiveを切っています。
そして壊すアニメを再生して、自分は破棄し、持っておいたオブジェクトをSetActiveしています。


最後に氷を破棄する時に持っておいたオブジェクトの参照をnullにしておくのを忘れないようにすること。
忘れるとメモリリークの原因になります。

おまけのLWRP(2D Light)

以前記事にしたものを使おうと思いました。

ghoul-life.hatenablog.com

ちょっとオシャレな見た目になりますし、やったことないことに
チャレンジしていかないとなりません。

特に特殊な事はやってないんですが、この氷の青い光表現がお気に入りです。

f:id:ghoul_life:20200304101340p:plain

時間が許せば炎の魔法で燭台に火をつけるオブジェクトとか用意したかったなぁ〜。
またGlobal Lightをちょーっとだけつけてます。真っ暗すぎるのも窮屈なんで。

反省点

いーっぱいあります。もう一度全部捨てて作り直したいレベル。

キャラ、オブジェクトの移動はフルKinematicsにすればよかったとか

二人乗るとおかしな挙動になる移動床バグとか

もっといろいろなギミック作りたかったとか

切り替え直後の速度変化が攻略法になっちゃってるとか
(普通にしてたら難しすぎるので切り替えた直後はゆっくり落ちるようにしてるんです。
それを利用して簡単にステージクリアした人も結構いるのではないでしょうか。
攻略法がいくつかあってもいいなーと。気づいた人へのご褒美として。)

お絵描きもっと頑張りたかったとか

ゲーム自体が長すぎるとか

もう少し尖ったアイデア出ないもんかとか…。

感想

今回もどうにか走りきりました。
とにかく時間が足りません。金曜日から半徹夜状態で
土日を駆け抜け日曜の19:00にリリース。

色んなものを削って出してもあんまり注目されずにひっそりと消えていく…。
これが実力だと受け入れなければなりません。
いつも通りですが、現実は厳しいですね。