主人公とは?
ここでは、画面の真ん中で動いてるあのキャラクターの事を指します。
主人公の配置
主人公もマップなどと同じように、タイルセットに画像があります。
同じようにタイルを持ってくればよさそうです。
map.ts
のtiles
を模倣して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;
};
(もしかしてジェネリクス使えばもっと楽にかけた?)
これはGameMap
のrealactor
として定義しておきましょう。
(ただし、タイルが生成されるまではrealactor
はnull
なので、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]Priority
とisArrow(Down|Up|Left|Right)
を変える処理はかいたと思いますが、離したときなんもしてないのでそこは書きましょう。
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
メソッドの中で動かす処理を書きましょう。
長いので「下に動く処理(マップを上に動かす処理)」だけ書きますね...
他の方向の処理は同じように書けるのであなたのプログラミングセンスに任せます。
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
には次回やるテーマのコードが配合されているので、上のコードはそれの抜粋です。
(だから動くか不明なのです。)
次回は移動処理において大事なやつをやります。
が、正直まだ未完成なので、私は知恵だけ与えて皆さんに考えてもらうことにします...
覚悟しておいてほしいのは、次回のテーマを進めると、さっきのゴチャゴチャコードが更にゴチャゴチャします。
ごめんな。