【SvelteでRPGゲーム開発】requestAnimationFrameを使いながらアニメーションを意識したスプライトを操作する
※ 当ページには【広告/PR】を含む場合があります。
2023/01/26
1. Svelteのリアクティブ構文を理解する
2. setTimeoutとリアクティブを組み合わせてSvelte版再帰ループを扱う
3. Svelteでのキーボード入力イベントの使い方
4. requestAnimationFrameと組み合わせたSvelteでのアニメーションの実装方法
説明
RPG風マップでネコ(?)を動かすだけの実験プログラムです。
ゲームの枠内をクリックして、キーボードからの操作が有効になります。
キーボードの方向キーで上下左右に動きます。
Svelteでスプライトを操作する
☆
○
.
├── index.html
└── src
├── App.svelte
├── app.scss
├── main.ts
├── components
│ ├── player.svelte☆
│ └── tile.svelte
└── lib
├── models.ts☆
└── GameStage.svelte○
#(その他のファイルは省略)
player.svelte
<script lang="ts">
export let top: number;
export let left: number;
const neko = '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UCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOmlwdGNFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0yOS8iCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpwbHVzPSJodHRwOi8vbnMudXNlcGx1cy5vcmcvbGRmL3htcC8xLjAvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDo2YTc1YjkxMS1kZjFhLTQ3ZmUtOTJlNi0yZDlhZWRjZmYzOWQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZDA2N2EzMzktMGYyZS00N2EzLWJiZGQtYmI5NThlMmNmMmUwIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZGUyNTBjYjMtNGZlZi00Mzc4LWFkMzMtOTZjNTYzYzU3NDIxIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzQzODk4MTk3OTg2NzQiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4yMiIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPGlwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8aXB0Y0V4dDpMb2NhdGlvblNob3duPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgPGlwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICA8aXB0Y0V4dDpSZWdpc3RyeUlkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6UmVnaXN0cnlJZD4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZDA0NGZhMjYtMDYyNS00YzI0LTg5N2MtMjFkZDU3YzJmNGI0IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iKzA5OjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICAgPHBsdXM6SW1hZ2VTdXBwbGllcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlU3VwcGxpZXI+CiAgIDxwbHVzOkltYWdlQ3JlYXRvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlQ3JlYXRvcj4KICAgPHBsdXM6Q29weXJpZ2h0T3duZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpDb3B5cmlnaHRPd25lcj4KICAgPHBsdXM6TGljZW5zb3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpMaWNlbnNvcj4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PoYCEdYAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfnARYMEDt9VKJ4AAABS0lEQVRIx81VMW7DMAw8GaxfUGWIp76lW/ocTXlBJn0nW9+SyR3iF9QQwA42HVmmZQV1gRLQIJm6o6g7GfjjMNoie+IpwQWztrkkr1rb3LiAxoUZyFpeSpYl2ALU8oREC8pVNQN8x6Mdn0Nrc8CbLQKA1pN6KpnL91zQFjgA4JpUeg3AiVBCUm2CAwPYiebzwqhKkg7nsCCa1vYg+E0UEXx8c9GaCECGSpBzbmnE8l2A2Rqz0u6Xx4W+jRtvkQjkLrp+wGJP3LiA1hOMC4Y08K6H0Rx9UyR5vxAO57AoTPWBVGFrsFQWn2BNYZIztpezr6lUEgNLT8Uf6TyW7EsP23rqhFAl0FqUvjuag40L5gi8ZgmeeVFXFDQ7we5GE/D/5eTd/8m2BmsqSi9XDJXKVuRefIKvwr/eU2FrsIzRPNOcPfERmIY8blH+FD95YrOV1qX1VgAAAABJRU5ErkJggg==';
</script>
<object title="" data="data:image/png;base64,{neko}" type="image/png" style="left: {left}px; top: {top}px"></object>
<style lang="scss">
object {
display: block;
width: 24px;
height: 24px;
position: absolute;
}
</style>
24x24
models.ts
export type TSpliteBase = {
id: number,
x: number,
y: number,
width: number,
height: number
}
/**
* #### [type] TPlayer
* - - -
* @param id `{number}` スプライトID値
* @param x `{number}` マップ上の水平方向の座標番号
* @param y `{number}` マップ上の垂直方向の座標番号
* @param width `{number}` 画像の幅
* @param height `{number}` 画像の高さ
* @param life `{number}` HPを記録
*/
export type TPlayer = TSpliteBase & {
life?: number,
};
export class PlayerModel {
private player: TPlayer;
constructor(player: TPlayer) {
this.player = player;
}
//👇値の設定・取得につかうショートカットアクセッサ
get Id(): number { return this.player.id; };
set Id(val: number) { this.player.id = val; };
get X(): number { return this.player.x; };
set X(val: number) { this.player.x = val; };
get Y(): number { return this.player.y; };
set Y(val: number) { this.player.y = val; };
get W(): number { return this.player.width; };
get H(): number { return this.player.height; };
get T(): number { return this.player.y * this.player.height; };
get L(): number { return this.player.x * this.player.width; };
get B(): number { return (this.player.y + 1) * this.player.height; };
get R(): number { return (this.player.x + 1) * this.player.width; };
}
GameStage.svelte
<script lang="ts">
import Tile from '../components/tile.svelte';
//☆プレイヤーのコンポーネントを追加
import Player from '../components/player.svelte';
//☆モデルの定義ファイルも追加
import { PlayerModel } from './models';
//☆移動方向(上下左右と静止状態)のリテラル型
type TDirection = 'u' | 'd' | 'l' | 'r' | 'n';
const _arr = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,2,1,2,2,1,1,2,2,2,1,1,2,1,2,1,1,1],
[1,1,2,2,1,2,2,2,1,2,2,2,1,2,2,1,2,2,1,1],
[1,1,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1],
[1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],
[1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,1,1,1,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
[1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1],
[1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,1,2,2,2,2,2,1,2,1,2,2,1,2,2,1],
[1,1,2,2,1,1,1,1,2,2,1,1,1,2,2,2,1,1,2,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
const _map = _arr.map((r: number[], i: number) => {
return r.map((c: number, j: number) => {
return {tile_index: c, x: 24*i, y: 24*j}
})
});
//☆1コマあたりの移動にかかる時間(ms)
const moveTerm = 200;
//☆プレイヤーのモデルを初期化
const player = new PlayerModel({
id: 0,
x: 9,
y: 9,
width: 24,
height: 24
life: 4,
});
//①リアクティブ構文による変数のサブスクリプションを登録
let movingLock = false;
$: if (!movingLock) {
if (movingFlg != 'n') {movePlayer();}
}
//☆プレイヤーの水平方向への移動
const moveX = (x:number) => {
player.X = x;
if (player.X < 0) {player.X += 1;}
else if (player.X > 19) {player.X -= 1;}
};
//☆プレイヤーの垂直方向への移動
const moveY = (y:number) => {
player.Y = y;
if (player.Y < 0) {player.Y += 1;}
else if (player.Y > 19) {player.Y -= 1;}
};
//②入力キーに応じてプレイヤー動かすメソッド
function movePlayer() {
if (key != 'n') {
const _dir = movingFlg;
if (!movingLock) {
movingLock = true;
let _tid = setTimeout(function repeat() {
slipX = 0;
slipY = 0;
if (_dir == 'u') {moveY(player.Y-1);}
else if (_dir == 'd') {moveY(player.Y+1);}
else if (_dir == 'l') {moveX(player.X-1);}
else if (_dir == 'r') {moveX(player.X+1);}
movingLock = false;
}, moveTerm);
}
}
}
//☆現在の入力キーを記録
let key: TDirection = 'n';
//☆一つ前の入力キーを記録
let oldKey: TDirection = 'n';
//☆キーの連続入力(押しっぱなし)に対応するためのキー状態を記録
let movingFlg: TDirection = 'n';
//③キーダウンイベント ... up/38 down/40 right/39 left/37
function onKeyDown(e: any) {
if (movingLock) {return;}
switch(e.keyCode) {
case 38:
key = 'u';
break;
case 40:
key = 'd';
break;
case 37:
key = 'l';
break;
case 39:
key = 'r';
break;
default:
key = 'n';
break;
}
if (key != oldKey) {
//👇押されたキーが前回と違うときだけキーを記録し、移動を開始させる
movingFlg = key;
movePlayer();
}
oldKey = key;
}
//③キーアップイベント
function onKeyUp(e: any) {
//キーを離すタイミングで、静止状態にして、スプライトの動きを止める
switch(e.keyCode) {
case 38:
case 40:
case 37:
case 39:
key = 'n';
if (!movingLock) {movePlayer();}
break;
}
oldKey = key;
}
</script>
<!-- 👇③Svelteでキー入力するためのおまじない -->
<svelte:window on:keydown|preventDefault={onKeyDown} on:keyup|preventDefault={onKeyUp}/>
<section>
{#each _map as mapx}
{#each mapx as mapxy}
<Tile pattern={mapxy.tile_index} left={mapxy.x} top={mapxy.y}/>
{/each}
{/each}
<!-- 👇プレイヤーコンポーネントを追加 -->
<Player left={player.L} top={player.T}></Player>
</section>
<!-- 👇デバッグ用のパラメーター確認 -->
<p>x={player.X} y={player.Y} left={(player.L).toFixed(1)} top={(player.T).toFixed(1)}</p>
<p>key={key} movingFlg={movingFlg}</p>
<style lang="scss">
//..前回より変更なし
</style>
Svelteのリアクティブ構文を使いこなす
「$:」
//外部のコンポーネントからの入力するためのエクスポート
export let title;
export let person
//👇変数titleの値が変わるたびに、それに依存するdocument.titleも自動で変更される
$: document.title = title;
//👇変数titleの値が変わるたびに、リアクティブ化したスクリプト全体が実行される
$: {
console.log(`複数のステートメントをまとめることができます`);
console.log(`現在のタイトルは ${title}`);
}
//👇personというインスタンスが変わるたびに、{name: string}型のオブジェクトも更新される
$: ({ name } = person);
①
...
//👇リアクティブ構文による変数のサブスクリプションを登録
let movingLock = false;
$: if (!movingLock) {
if (movingFlg != 'n') {movePlayer();}
}
...
movingLock
movingFlg
movingFlg
setTimeoutメソッドで移動完了を検知しながら処理ループさせる
setTimeout
//👇入力キーに応じてプレイヤー動かすメソッド
function movePlayer() {
if (key != 'n') {
const _dir = movingFlg;
if (!movingLock) {
movingLock = true;
let _tid = setTimeout(function repeat() {
slipX = 0;
slipY = 0;
if (_dir == 'u') {moveY(player.Y-1);}
else if (_dir == 'd') {moveY(player.Y+1);}
else if (_dir == 'l') {moveX(player.X-1);}
else if (_dir == 'r') {moveX(player.X+1);}
movingLock = false;
}, moveTerm);
}
}
}
movingLock
movingLock
movePlayer
Svelteでのキーボード入力処理
window
document
<script>
...
//③キーダウンイベント ... up/38 down/40 right/39 left/37
function onKeyDown(e: any) {
if (movingLock) {return;}
switch(e.keyCode) {
case 38:
key = 'u';
break;
case 40:
key = 'd';
break;
case 37:
key = 'l';
break;
case 39:
key = 'r';
break;
default:
key = 'n';
break;
}
if (key != oldKey) {
//👇押されたキーが前回と違うときだけキーを記録し、移動を開始させる
movingFlg = key;
movePlayer();
}
oldKey = key;
}
//③キーアップイベント
function onKeyUp(e: any) {
//キーを離すタイミングで、静止状態にして、スプライトの動きを止める
switch(e.keyCode) {
case 38:
case 40:
case 37:
case 39:
key = 'n';
if (!movingLock) {movePlayer();}
break;
}
oldKey = key;
}
</script>
<!-- 👇③Svelteでキー入力するためのおまじない -->
<svelte:window on:keydown|preventDefault={onKeyDown} on:keyup|preventDefault={onKeyUp}/>
...
<svelte:window>
Svelteゲームの動作確認(ただしアニメーションなし)
アニメーション対応のSvelteコンポーネントに修正する
requestAnimationFrameメソッドとは?
ゲームエンジンのコード修正
GameStage.svelte
<script lang="ts">
//☆onMountメソッドを実装
import { onMount } from 'svelte';
import Tile from '../components/tile.svelte';
import Player from '../components/player.svelte';
import { PlayerModel } from './models';
type TDirection = 'u' | 'd' | 'l' | 'r' | 'n';
const _arr = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,2,1,2,2,1,1,2,2,2,1,1,2,1,2,1,1,1],
[1,1,2,2,1,2,2,2,1,2,2,2,1,2,2,1,2,2,1,1],
[1,1,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1],
[1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],
[1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,2,2,2,2,1,1,1,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,1,1],
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,1],
[1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1],
[1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,2,2,2,1,2,2,2,2,2,1,2,1,2,2,1,2,2,1],
[1,1,2,2,1,1,1,1,2,2,1,1,1,2,2,2,1,1,2,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
const _map = _arr.map((r: number[], i: number) => {
return r.map((c: number, j: number) => {
return {tile_index: c, x: 24*i, y: 24*j}
})
});
const moveTerm = 200;
//⑤フレッシュレートによる更新周期の割合progressの1つ前の値を記録する
let _oldProg: number;
//⑤アニメーションさせる移動時の変化量
let slipX: number = 0, slipY: number = 0;
//⑤requestAnimationFrameで更新させるメインの描画関数
const draw = (progress: number) => {
const _delta = progress > _oldProg ? progress - _oldProg : 1 + progress - _oldProg;
_oldProg = progress;
if (movingLock) {
if (movingFlg == 'u') {
if (player.Y == 0) {slipY = 0;}
else if (slipY > -player.H) {slipY = slipY - player.H*_delta;}
slipX = 0;
}
else if (movingFlg == 'd') {
if (player.Y == 19) {slipY = 0;}
else if (slipY < player.H) {slipY = slipY + player.H*_delta;}
slipX = 0;
}
else if (movingFlg == 'l') {
if (player.X == 0) {slipX = 0;}
else if (slipX > - player.W) {slipX = slipX - player.W*_delta;}
slipY = 0;
}
else if (movingFlg == 'r') {
if (player.X == 19) {slipX = 0;}
else if (slipX < player.W) {slipX = slipX + player.W*_delta;}
slipY = 0;
}
}
};
const player = new PlayerModel({
id: 0,
x: 9,
y: 9,
width: 24,
height: 24
life: 4,
});
let movingLock = false;
$: if (!movingLock) {
if (movingFlg != 'n') {movePlayer();}
}
const moveX = (x:number) => {
player.X = x;
if (player.X < 0) {player.X += 1;}
else if (player.X > 19) {player.X -= 1;}
};
const moveY = (y:number) => {
player.Y = y;
if (player.Y < 0) {player.Y += 1;}
else if (player.Y > 19) {player.Y -= 1;}
};
function movePlayer() {
if (key != 'n') {
const _dir = movingFlg;
if (!movingLock) {
movingLock = true;
let _tid = setTimeout(function repeat() {
slipX = 0;
slipY = 0;
if (_dir == 'u') {moveY(player.Y-1);}
else if (_dir == 'd') {moveY(player.Y+1);}
else if (_dir == 'l') {moveX(player.X-1);}
else if (_dir == 'r') {moveX(player.X+1);}
movingLock = false;
}, moveTerm);
}
}
}
let key: TDirection = 'n';
let oldKey: TDirection = 'n';
let movingFlg: TDirection = 'n';
function onKeyDown(e: any) {
if (movingLock) {return;}
switch(e.keyCode) {
case 38:
key = 'u';
break;
case 40:
key = 'd';
break;
case 37:
key = 'l';
break;
case 39:
key = 'r';
break;
default:
key = 'n';
break;
}
if (key != oldKey) {
movingFlg = key;
movePlayer();
}
oldKey = key;
}
function onKeyUp(e: any) {
switch(e.keyCode) {
case 38:
case 40:
case 37:
case 39:
key = 'n';
if (!movingLock) {movePlayer();}
break;
}
oldKey = key;
}
//④ onMountでコンポーネントの生成時にアニメーションを開始
onMount(() => {
let frame: number;
let timer: number = performance.now();
function loop() {
let start = performance.now();
const progress = (start - timer) / moveTerm;
if (progress > 1) {timer = performance.now();}
draw(progress);
frame = requestAnimationFrame(loop);
}
loop();
return () => cancelAnimationFrame(frame);
});
</script>
<svelte:window on:keydown|preventDefault={onKeyDown} on:keyup|preventDefault={onKeyUp}/>
<section>
{#each _map as mapx}
{#each mapx as mapxy}
<Tile pattern={mapxy.tile_index} left={mapxy.x} top={mapxy.y}/>
{/each}
{/each}
<!-- ☆コンポーネントにアニメーション変化量(slipX/slipY)を加味 -->
<Player left={player.L + slipX} top={player.T + slipY}></Player>
</section>
<!-- ☆デバッグ用のパラメーター確認 -->
<p>x={player.X} y={player.Y} left={(player.L + slipX).toFixed(1)} top={(player.T + slipY).toFixed(1)} slipX={slipX.toFixed(2)} slipY={slipY.toFixed(2)}</p>
<p>key={key} movingFlg={movingFlg}</p>
<style lang="scss">
//..前回より変更なし
</style>
☆
④
⑤
requestAnimationFrameをループ化して呼び出す
④
GameStage.svelte
//👇OnMountからコンポーネントが最初にDOMレンダリングされた後にアニメーション登録
onMount(() => {
let frame: number;
let timer: number = performance.now();
//👇requestAnimationFrameをループ化し連続的に画面を更新する
function loop() {
const start = performance.now();
const progress = (start - timer) / moveTerm;
if (progress > 1) {
//👇指定したmoveTermが一周したらtimerの時間を更新
timer = performance.now();
progress = 1;
}
//👇メインの描画関数
draw(progress);
frame = requestAnimationFrame(loop);
}
//👇ディスプレイフレッシュレートによるアニメーションの開始
loop();
//👇このコンポーネントが破棄されたタイミングでアニメーションも破棄
return () => cancelAnimationFrame(frame);
});
draw
progress
progress
moveTerm
progress = 0
progress = 1
フレッシュレートに合わせたアニメーションを更新する
⑤
//👇フレッシュレートによる更新周期の割合progressの1つ前の値を記録する
let _oldProg: number;
//👇アニメーションさせる移動時の変化量
let slipX: number = 0, slipY: number = 0;
//👇requestAnimationFrameで更新させるメインの描画関数
const draw = (progress: number) => {
//👇前回の進捗率と現在の進捗率の差分(=アニメーションの移動量)
const _delta = progress > _oldProg ? progress - _oldProg : 1 + progress - _oldProg;
//👇次のループでの1つ前の進捗率として使うために保存
_oldProg = progress;
//👇movingLock = trueのときだけアニメーションが有効
if (movingLock) {
//👇入力されたキーによってスプライトの位置を更新
if (movingFlg == 'u') {
if (player.Y == 0) {slipY = 0;}
else if (slipY > -player.H) {slipY = slipY - player.H*_delta;}
slipX = 0;
}
else if (movingFlg == 'd') {
if (player.Y == 19) {slipY = 0;}
else if (slipY < player.H) {slipY = slipY + player.H*_delta;}
slipX = 0;
}
else if (movingFlg == 'l') {
if (player.X == 0) {slipX = 0;}
else if (slipX > - player.W) {slipX = slipX - player.W*_delta;}
slipY = 0;
}
else if (movingFlg == 'r') {
if (player.X == 19) {slipX = 0;}
else if (slipX < player.W) {slipX = slipX + player.W*_delta;}
slipY = 0;
}
}
};
progress
progress
まとめ
1. Svelteのリアクティブ構文を理解する
2. setTimeoutとリアクティブを組み合わせてSvelte版再帰ループを扱う
3. Svelteでのキーボード入力イベントの使い方
4. requestAnimationFrameと組み合わせたSvelteでのアニメーションの実装方法
☆Javascriptでオリジナルゲームアプリを作ろう!☆ JavaScriptとCanvasでアニメーションとゲーム制作!~描き、動かし、操作する~
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー