『流流鍛』メイキング(アップデート編)

ブラウザ用アクション『流流鍛』のアップデートの
メイキング記事をお送りします。※前回はこちら

15秒PV

今回はアクションの追加なのでUnityらしさは控えめですが、Unityだからこの追加がスピーディーに行えたという点はお伝えしたいところです。

デザイン、モーション、組込みの3編に分けて紹介します

デザイン

はじまり

このゲームをシンプルに表現すると

  1. プレイヤーが前進する
  2. 左右から飛来する様々な敵を捌く

というあそびなので、『左右から飛来する敵の時間差を自分でコントロールできれば、面白さに広がりが出るかも?』ということでステップを追加することにしました。

単に「久々にアクションゲームのモーション作りたい」と思った部分もあります

またステップはプレイの加速にも使えるため『上手い人は自分で難易度を調整できる』という点も良さそうな気がします。

性能について

昨今のステップでは『無敵時間付き + 時間長め』が一般的ですが、流流ちゃんの『地に足の着いた格闘家っぽさ』を推したかったので、シンプルに移動手段として設定しました。
また細かい所ですが、ステップ中に攻撃を出すと慣性を乗せています。

モーション

基本部分

まずは前後に移動させます。

前後ステップ

攻撃モーションより少し短く全体22Fにしました。当たり判定に変化があるわけではないので、大きく姿勢を変えずに手足を誇張して入力が成立したことを早めに伝えています。

やっぱり追加

移動だけを考えるなら前後の2種類だけで良いのですが、前項通り『せっかく拳法キャラなので格好よくしたい!』という訳で

前向きから、後ろステップ

攻撃中は体捌きを感じさせるような回転系を別で用意することにしました。

後ろ向きから、前ステップ

通常の構えに対して重心が前方(後方)に移っているので、その反動で引きつつ回転…という流れにしています。

スローでもういちど

構えが前と後ろで別々になっているため、最終姿勢は反対側に合わせています。

表情

余裕のある表情などの可愛らしい部分も見せたかったので、一瞬入れています。

悪目立ちしないよう、気づくか気づかれないかのところで印象を良くしたいというせめぎあいがあります。

ついでにお化粧
前後ステップふたたび

予定に無かったのですが、遊んでいて物足りなかったので
足元の煙と通常ステップの時に効果音をつけました。

組込み

アクションの判定

コマンド【→】【→】を判定するためのキーロガーを作ります。

入力履歴のイメージ図

入力を毎フレーム配列の末尾に積んで、配列順にコマンドの完成を監視するシンプルな形です。とりあえず動けばいいという富豪的思考で組みました。※今になると普通に頭に積んでコマンド毎の検出範囲から逆に読む形でもいいなと気づきました

// キーのログを記録して
// 特定コマンドの成立を監視する
public class MyInputLogger
{
    const int FRAME_MAX = 50;
    const int COOL_DOWN_FRAME = 24;
    const int INPUT_BUFFER = 12;

    MyInput.ActionType _last_activate_action;
    int _cool_down_frame;

    // 圧縮した方が良いが面倒なのでベタ入力
    struct tagInput
    {
        internal MyInput.DirType dir;
        internal MyInput.HeightType height;
        internal MyInput.AttackType attack;
        internal tagInput(MyInput.DirType iDir, MyInput.AttackType iAttack, MyInput.HeightType iAttackHeight)
        {
            dir = iDir;
            height = iAttackHeight;
            attack = iAttack;
        }
    }
    RingBuffer<tagInput> _inputs;

    // コマンド
    internal class CommandAction
    {
        internal MyInput.ActionType _action;
        internal List<MyInput.DirType> _lst_command;
    }
    List<CommandAction> _lst_commandAction;

    public MyInputLogger()
    {
        _inputs = new RingBuffer<tagInput>(FRAME_MAX);
        _lst_commandAction = new List<CommandAction>();

        // StepL
        var ca = new CommandAction();
        var cmd = new List<MyInput.DirType>() { MyInput.DirType.None, MyInput.DirType.Left, MyInput.DirType.None, MyInput.DirType.Left};
        ca._action = MyInput.ActionType.Step_Left;
        ca._lst_command = cmd;
        _lst_commandAction.Add(ca);

        // StepL
        ca = new CommandAction();
        cmd = new List<MyInput.DirType>() { MyInput.DirType.None, MyInput.DirType.Right, MyInput.DirType.None, MyInput.DirType.Right};
        ca._action = MyInput.ActionType.Step_Right;
        ca._lst_command = cmd;
        _lst_commandAction.Add(ca);
    }

    public void Input(MyInput.DirType iDir, MyInput.AttackType iAttack, MyInput.HeightType iAttackHeight)
    {
        _inputs.Add(new tagInput(iDir, iAttack, iAttackHeight));
        _cool_down_frame = Mathf.Max(0, _cool_down_frame - 1);
    }

    public MyInput.ActionType GetAction()
    {
        // コマンド種類ループ
        foreach (var cmd in _lst_commandAction)
        {
            int command_index = 0;
            int frame = 0;
            // 入力判定ループ
            foreach (var input in _inputs)
            {
                if (_cool_down_frame > 0 && cmd._action == _last_activate_action) break; // クールダウン中
                ++frame;
                if (frame > INPUT_BUFFER) break; // 入力猶予
                if (input.dir != cmd._lst_command[command_index]) continue; // コマンド一致
                // 最後まで一致
                if (cmd._lst_command.Count - 1 == command_index)
                {
                    // Debug.Log(string.Format("Command : {0}", cmd._action));
                    _last_activate_action = cmd._action;
                    _cool_down_frame = COOL_DOWN_FRAME;
                    return cmd._action; 
                }

                // 次のコマンドへ
                command_index++;
                frame = 0;
            }
        }

        return MyInput.ActionType.None;
    }
}
反省

本来であれば、入力をハッシュ化して同一入力を圧縮したり、リングバッファ形式にして生成/破棄のコストを抑えた方が良いのですが
ゲームを早く遊びたいので、そういう部分は60フレーム動作を人質に取られた時に考えるようにしています。(こういう考え方だとすぐにそうなりますが…)

まとめ

ゲーム全体にほどよい変化を与えつつ、今後も色んな技を組み込めるようになりました。
昨今はコマンドも省略されがちですが、プレイヤー的にはかめはめ波的な気持ちを込められる要素ではあるので、ステップ系以外にも【↓↑】や【←→】あたりは生き残ってほしいなと思っています。

お疲れさまでした!
よいUnityライフを~

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA