ひでぼ~blog

C#ときどきゲーム

RxJSで格ゲーのキーディスプレイ的なものを作った

この記事を見かけて、Rxでコマンド判定やるの面白そうやんけ…!と思ってやってみました。 ただ、コマンド判定を作る以前に斜め入力やらを画面に表示する部分の実装だけでそこそこのボリュームになったので、この投稿では格ゲーのトレーニングモードでよくあるキーディスプレイを作るところまでをやってみます。TypeScript、RxJS、WebPackを使ってビルドしてブラウザで表示します。 qiita.com

入力の定義など

レバー入力とボタン入力を定義しました。入力はキーボードを使い、WASDを上下左右、Uをパンチボタン、Jをキックボタンとします。

enum Arrow {
  Up = "↑",
  Down = "↓",
  Left = "←",
  Right = "→",
  LeftUp = "↖",
  LeftDown = "↙",
  RightUp = "↗",
  RightDown = "↘",
  None = " ",
}

enum Button {
  Punch = "👊",
  Kick = "🦶",
}

type Input = Arrow | Button;

const getInputFromKey = (key: string): Input => {
  switch (key) {
    case "a":
      return Arrow.Left;
    case "d":
      return Arrow.Right;
    case "w":
      return Arrow.Up;
    case "s":
      return Arrow.Down;
    case "u":
      return Button.Punch;
    case "j":
      return Button.Kick;
    default:
      return Arrow.None;
  }
};

キーの入力を検知する

keyupイベントからObservableを作り、event.keyを'↓'、'→'などの文字列に変換してコンソールに表示します。getInputKeyFromKey()は'a'と'←'、's'と'↓'などをマッピングしています。

fromEvent<KeyboardEvent>(document, "keyup")
  .pipe(map((event) => getInputFromKey(event.key)))
  .subscribe(console.log);

上下左右とパンチ、キックボタンの入力が表示されるようになりました。が、斜め入力ができていません。これでは波動拳も風神拳も出せないので修正していきます。 f:id:hideb3:20200624160815p:plain

入力の状態を持たせる

↓を押している状態で→を押したとき↘を入力したことにして欲しいので、直前の入力を覚えておく必要があります。そのために入力値と入力状態を持つInputInfoというクラスを作りました。

class InputInfo {
  public input: Input;
  public pressed: boolean;

  constructor(input: Input, pressed: boolean) {
    this.input = input;
    this.pressed = pressed;
  }
}

直前の入力を参照するために、BehaviourSubjectを用意しておき、getValue()でキャッシュされているオブジェクト(直前の入力)を取得します。 直前の入力が押下状態であれば、現在の入力と合成して斜め入力を作り、先ほどのBehaviourSubjectに流します。

const inputStream = new BehaviorSubject<InputInfo>(null);

fromEvent<KeyboardEvent>(document, "keydown")
  .pipe(map((event) => getInputFromKey(event.key)))
  .subscribe((input) => {
    // 押しているキーがあればミックスして斜め入力を作る
    // '↓' + '→' = '↘'
    const latestInput = inputStream.getValue();
    if (latestInput?.Pressed) {
      input = mixArrow(latestInput.Key, input);
    }

    inputStream.next(new InputInfo(input, true));
  });

fromEvent<KeyboardEvent>(document, "keyup")
  .pipe(map((event) => getInputFromKey(event.key)))
  .subscribe((input) => {
    const latestInput = inputStream.getValue();
    inputStream.next(new InputInfo(input, false));

    // 斜め入力していたら分解する
    // '↘' - '↓' = '→'
    if (diagonalArrows.includes(latestInput.input)) {
      inputStream.next(new InputInfo(deconstructKey(latestInput.input, input), true));
    }
  });

最終的にinputStreamをsubscribeしてコンソールに入力を表示します。

inputStream
  .pipe(
    filter((inputInfo) => inputInfo && inputInfo.input !== Arrow.None && inputInfo.pressed),
    map((inputInfo) => inputInfo.input)
  )
  .subscribe(console.log);

ちゃんと波動コマンドも竜巻コマンドも入力できるようになりました。 f:id:hideb3:20200624191115p:plain