0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tauriでエンジンからゲームを作ってみるAdvent Calendar 2024

Day 18

【Day18】良い加減主人公を配置しよう【QAC24】

Last updated at Posted at 2024-12-17

主人公とは?

ここでは、画面の真ん中で動いてるあのキャラクターの事を指します。

主人公の配置

主人公もマップなどと同じように、タイルセットに画像があります。
同じようにタイルを持ってくればよさそうです。

map.tstilesを模倣してactorを作りましょう。
また、そのactorの型も定義してしまいましょう。

Actor に必要なものとは?

型を作るときはその型の構造を考えておくのが便利というかなんというか...

画面に配置されてる主人公としては...まぁ上下左右分の画像は必要ですよね。
後は...止まってる時と動いてるときは画像違いますよね。
なんなら、動いてるときはアニメーションしてるべきですよね。

ということで、以下のような型を作ってみましょう。

type ActorTiles = {
  stand: FakeTile;
  moves: FakeTile[];
};

type Actor = {
  up: ActorTiles;
  down: ActorTiles;
  left: ActorTiles;
  right: ActorTiles;
};

マップ用のタイルと Actor 用のタイルは同じなのでFakeTileを流用しています。
Actorに上下左右それぞれ、止まった時用と動いてる時用のタイル情報を持たせています。

また、マップタイルの時と同様にDrawableなものも定義しておきましょう。

type DrawableActorTiles = {
  stand: RealTile;
  moves: RealTile[];
};

type DrawableActor = {
  up: DrawableActorTiles;
  down: DrawableActorTiles;
  left: DrawableActorTiles;
  right: DrawableActorTiles;
};

(もしかしてジェネリクス使えばもっと楽にかけた?)

これはGameMaprealactorとして定義しておきましょう。
(ただし、タイルが生成されるまではrealactornullなので、DrawableActor | nullとなる。)

描画をする

Day11 あたりにgenerateMapTilesみたいなのをなんか説明したような気がするのですが、それの Actor 版としてgenerateActorTilesを作りましょう。(長いので紹介なし)

それらを纏めたgenerateTilesでも作りましょうか。
といっても、それらを実行するだけですが。

export abstract class GameMap {
  ...

  protected async generateTiles() {
    await Promise.all([
      this.generateMapTiles(),
      this.generateActorTiles(),
    ]);
  }

  ...
}

んで...なんですが、realactorには全部の向きの情報が入ってますよね。
画面に描画するのはそのうちの 1 方向だけですよね。

もっと言えば、止まってるときのやつか動いてるときのやつのどちらかしか描画しませんよね。

というわけで、DrawableActorTilesのさらに中、描画を行うRealActorを型に取ったactortileとかでも作りましょうか。
これはベクタではないので、常にタイルが 1 つだけになります。

という訳でこのactortileに入れる処理...すなわち、「矢印キーを押したら主人公を動かす」の紹介になりますパチパチパチパチ

矢印キーを押したらマップを動かすんだってば(デジャヴ)

まず先に免責事項となっちゃいますが、このコードはあくまで私が無限思考に陥ってマズいって思ってひねり出したコードなので、もう...それはそれはなコードです。
(ある程度は)動いてますが、適時調節してください。私は後で適当に直すが、君は今のうちにやっておくんだよ!

まず、矢印キーを押したときにmove[XY]PriorityisArrow(Down|Up|Left|Right)を変える処理はかいたと思いますが、離したときなんもしてないのでそこは書きましょう。

src/lib/map.ts
export abstract class GameMap {
  ...

  protected keyup(e: KeyboardEvent) {
    if (e.code === "ArrowDown") {
      this.isArrowDown = false;
    } else if (e.code === "ArrowUp") {
      this.isArrowUp = false;
    } else if (e.code === "ArrowLeft") {
      this.isArrowLeft = false;
    } else if (e.code === "ArrowRight") {
      this.isArrowRight = false;
    }
  }

  ...
}

次に、mainメソッドの中で動かす処理を書きましょう。
長いので「下に動く処理(マップを上に動かす処理)」だけ書きますね...

他の方向の処理は同じように書けるのであなたのプログラミングセンスに任せます。

src/lib/map.ts
export abstract class GameMap {
  ...

  private actorinterval: number = 0; // Actorのアニメーション
  private actorindex: number = 0; // Actorのアニメーション画像のindex

  /// マップが動いているpx量 (マップと実座標のズレと言えば分かりやすいか?)
  /// (実際のActorの座標は`actor[xy]`だが、マップがどれだけアニメーションしているかの情報も必要)
  private animatedx: number = 0;
  private animatedy: number = 0;

  // マップを動かすアニメーション
  private moveDownInterval: number = 0;
  ...

  protected actorx: number = 0;
  protected actory: number = 0;

  protected async main() {
    if (this.moveYPriority === "down") {
      // 下方向への移動が優先されているなら...
      if (!(this.moveUpInterval || this.moveDownInterval)) {
        // この処理の中では`interval`を定義するので既に定義されているなら無視

        this.moveDownInterval = setInterval(() => {
          if (!this.actorinterval && this.realactor) { // Actorのアニメーションが動いてないとき
            this.actorindex = 0;
            this.actorinterval = setInteval(() => {
              // Actorのアニメーション処理 (バックグラウンド)
              if (this.realactor) { // TypeCheckのため
                if (this.realactor.down.moves.length === 0) {
                  // 移動用の画像がない場合は止まってる画像を使う
                  this.actortile = this.realactor.down.stand;
                } else {
                  // 移動用の画像を設定
                  this.actortile = this.realactor.down.moves[this.actorindex];
                  this.actorindex++; // 次の画像へ
                  if (this.actorindex >= this.realactor.down.moves.length) {
                    // 画像が最後まで行ったら最初に戻す
                    this.actorindex = 0;
                  }
                }
              }
            }, ((1000 / FrameRate) * 25) / this.realactor.down.moves.length);
            // ↑ 25Frame毎に移動用画像が1週する (25は適当な数字だよ)
          }

          // マップを動かす処理

          this.animatedy -= TileSize / (1000 / FrameRate); // マップを微妙(1タイルには満たない程度)に動かす

          if (this.animatedy <= -TileSize) {
            // マップのズレが1タイル分になったら、ズレを0にして、実座標を1変える
            this.animatedy = 0;
            this.actory--;

            if (!this.isArrowDown) {
              // もしその時、下矢印キーが押されていなかったら、下方向への移動を止める
              clearInterval(this.moveDownInterval);
              this.moveDownInterval = 0;
              clearInterval(this.actorinterval);
              this.actorinterval = 0;
              if (this.isArrowUp) {
                /// もし、下は押されてないが上は押されているなら`moveYPriority`を`up`に変えることで
                /// 次の`main`では上方向への移動を行う
                this.moveYPriority = "up";
              } else {
                // どちらも押されていない場合は、`moveYPriority`を`none`にして、止まってる画像を使う
                this.moveYPriority = "none";
                this.actortile = this.realactor?.down.stand as RealTile;
              }
            }
          }

          requestAnimationFrame(() => {
            // 描画処理
            clearCanvas();
            this.draw();
          });
        }, 1000 / FrameRate);
      }
    } else if (this.moveYPriority === "up") {
      // 上に動かす処理
    }

    if (this.moveXPriority === "left") {
      // 左に動かす処理
    } else if (this.moveXPriority === "right") {
      // 右に動かす処理
    }
  }

  ...
}

ほら、ゴチャゴチャやろ?
無駄なコードがありそうですがそれは一旦気にせず(もし見抜いたらそこは消そう)、これで多分動く...多分...
動かないなら自分で直してみてね。私もやったんだからサ...

上下方向と左右方向の移動を1つのif-elseにまとめず分けたのは、もし上矢印キーと右矢印キーを同時押しした時、上方向の移動のみになってしまうため、軸違いは分けた方がいいと思ったからです。

てかインデントって大事だよね?
ここまでのネストする方が悪いんだけど...

まとめ

ちなむと、私のプロジェクトのmainには次回やるテーマのコードが配合されているので、上のコードはそれの抜粋です。
(だから動くか不明なのです。)

次回は移動処理において大事なやつをやります。
が、正直まだ未完成なので、私は知恵だけ与えて皆さんに考えてもらうことにします...

覚悟しておいてほしいのは、次回のテーマを進めると、さっきのゴチャゴチャコードが更にゴチャゴチャします。
ごめんな。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?