ChatGPT4に作ってもらったRPGゲームの素その1。~入力~
テトリスのrequestAnimationFrameのフォーマットで作ってもらったRPGゲームの素。
見れば分かるがゲームの素であってゲームでない。このコードは見れば分かる状態を保持している。これ以上作りこむと見れば分かる状態をちょっとずつ超え始める。
この素は、キーボードのテンキーないし矢印キーによってプレイヤーがグリッドマップ上を移動できるところまでChatGPTにつくってもらった。
前回のテトリスのグリッド表示機能、およびrequestAnimationFrameを用いたゲームループ部分を使用。
テトリスにおいて左右だけだった入力処理を8方向に増加させ、またキーの入れっぱなしで連続入力されないようにした。
つまり今回の主題はキー入力であってそれ以上でない。
単発移動ver
keydownイベントでキーの状態をpressedにし、
キャラクターの移動後、対応キーの状態をholdingにする。
キャラクターはpressed状態でしか移動しないため、holding状態に入ったキャラクターは連続移動しない。
keyupイベントで状態を解除する。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="320" height="320"></canvas>
<script>
class MapObject {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(context2d) {
// 基本の描画処理は何もしない
}
}
class Player extends MapObject {
constructor(x, y) {
super(x, y);
}
move(direction) {
switch (direction) {
case 1:
this.x -= 1;
this.y += 1;
break;
case 2:
this.y += 1;
break;
case 3:
this.x += 1;
this.y += 1;
break;
case 4:
this.x -= 1;
break;
case 6:
this.x += 1;
break;
case 7:
this.x -= 1;
this.y -= 1;
break;
case 8:
this.y -= 1;
break;
case 9:
this.x += 1;
this.y -= 1;
break;
default:
console.log("Invalid direction. Please enter a valid number (1-9, excluding 5).");
break;
}
}//move
draw(context2d) {
context2d.fillStyle = 'blue';
context2d.fillRect(this.x * 32, this.y * 32, 32, 32);
}
}//player
class Map {
constructor(w, h) {
this.Width = w;
this.Height = h;
this.GameBoard = Array.from({ length: this.Height }, () => Array(this.Width).fill(null));
this.MapObjects = [];
}
addObject(mapObject) {
if (mapObject instanceof MapObject) {
this.MapObjects.push(mapObject);
this.GameBoard[mapObject.y][mapObject.x] = mapObject;
}
}
removeObject(mapObject) {
if (mapObject instanceof MapObject) {
const index = this.MapObjects.indexOf(mapObject);
if (index > -1) {
this.MapObjects.splice(index, 1);
this.GameBoard[mapObject.y][mapObject.x] = null;
}
}
}
draw(context2d) {
// Draw background
context2d.fillStyle = 'white';
context2d.fillRect(0, 0, this.Width * 32, this.Height * 32);
// Draw grid
context2d.strokeStyle = 'black';
context2d.lineWidth = 1;
for (let y = 0; y <= this.Height; y++) {
context2d.beginPath();
context2d.moveTo(0, y * 32);
context2d.lineTo(this.Width * 32, y * 32);
context2d.stroke();
}
for (let x = 0; x <= this.Width; x++) {
context2d.beginPath();
context2d.moveTo(x * 32, 0);
context2d.lineTo(x * 32, this.Height * 32);
context2d.stroke();
}
// Draw objects
for (const obj of this.MapObjects) {
obj.draw(context2d);
}
}//draw
}//Map
const canvas = document.getElementById('gameCanvas');
const context2d = canvas.getContext('2d');
const gameMap = new Map(10, 10);
const player = new Player(4, 4);
gameMap.addObject(player);
let lastFrameTime = performance.now();
const targetFrameDuration = 1000 / 60; // 60FPSの場合のフレーム間隔(ミリ秒)
const keyStates = {};
function gameLoop(currentTime) {
const deltaTime = currentTime - lastFrameTime;
if (deltaTime >= targetFrameDuration) {
update(deltaTime);
draw();
lastFrameTime = currentTime;
}
requestAnimationFrame(gameLoop);
}
function update(deltaTime) {
for (const key in keyStates) {
const state = keyStates[key];
if (state === 'pressed') {
const direction = directionKeyMap[key];
if (direction) {
player.move(direction);
}
keyStates[key] = 'holding';
}
}
}
function keydownHandler(event) {
if (!keyStates[event.code]) {
keyStates[event.code] = 'pressed';
}
}
function keyupHandler(event) {
delete keyStates[event.code];
}
function draw() {
gameMap.draw(context2d);
}
const directionKeyMap = {
'Numpad1': 1,
'Numpad2': 2,
'Numpad3': 3,
'Numpad4': 4,
'Numpad6': 6,
'Numpad7': 7,
'Numpad8': 8,
'Numpad9': 9,
'ArrowDown': 2,
'ArrowUp': 8,
'ArrowLeft': 4,
'ArrowRight': 6,
};
document.addEventListener("keydown", keydownHandler);
document.addEventListener("keyup", keyupHandler);
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
event.keyとevent.code
event.key と event.code は、キーボードイベント(keydown、keyup、keypress)の際に、イベントオブジェクト内で提供される異なるプロパティです。それぞれのプロパティは、キーに関する異なる情報を提供します。
event.key:
このプロパティは、押されたキーに対応する文字や動作を表します。例えば、アルファベットキー "A" を押すと、event.key は "a" になります。また、矢印キーを押すと、それぞれ "ArrowUp"、"ArrowDown"、"ArrowLeft"、"ArrowRight" になります。
event.key は、キーボードの物理的な位置には依存せず、キーが生成する文字や動作に基づいています。
event.code:このプロパティは、押されたキーの物理的な位置を表します。例えば、アルファベットキー "A" を押すと、event.code は "KeyA" になります。また、矢印キーを押すと、それぞれ "ArrowUp"、"ArrowDown"、"ArrowLeft"、"ArrowRight" になります。
event.code は、キーが生成する文字や動作には依存せず、キーボード上の物理的な位置に基づいています。
event.key と event.code の主な違いは、event.key がキーが生成する文字や動作に基づいているのに対し、event.code がキーボード上の物理的な位置に基づいていることです。このため、キー入力に対応する文字や動作に基づいて処理を行いたい場合は、event.key を使用することが推奨されます。
連続移動ver
キーの状態を保存しないので、押してる分だけ動く。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="320" height="320"></canvas>
<script>
class MapObject {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(context2d) {
// 基本の描画処理は何もしない
}
}
class Player extends MapObject {
constructor(x, y) {
super(x, y);
}
move(direction) {
switch (direction) {
case 1:
this.x -= 1;
this.y += 1;
break;
case 2:
this.y += 1;
break;
case 3:
this.x += 1;
this.y += 1;
break;
case 4:
this.x -= 1;
break;
case 6:
this.x += 1;
break;
case 7:
this.x -= 1;
this.y -= 1;
break;
case 8:
this.y -= 1;
break;
case 9:
this.x += 1;
this.y -= 1;
break;
default:
console.log("Invalid direction. Please enter a valid number (1-9, excluding 5).");
break;
}
}//move
draw(context2d) {
context2d.fillStyle = 'blue';
context2d.fillRect(this.x * 32, this.y * 32, 32, 32);
}
}//player
class Map {
constructor(w, h) {
this.Width = w;
this.Height = h;
this.GameBoard = Array.from({ length: this.Height }, () => Array(this.Width).fill(null));
this.MapObjects = [];
}
addObject(mapObject) {
if (mapObject instanceof MapObject) {
this.MapObjects.push(mapObject);
this.GameBoard[mapObject.y][mapObject.x] = mapObject;
}
}
removeObject(mapObject) {
if (mapObject instanceof MapObject) {
const index = this.MapObjects.indexOf(mapObject);
if (index > -1) {
this.MapObjects.splice(index, 1);
this.GameBoard[mapObject.y][mapObject.x] = null;
}
}
}
draw(context2d) {
// Draw background
context2d.fillStyle = 'white';
context2d.fillRect(0, 0, this.Width * 32, this.Height * 32);
// Draw grid
context2d.strokeStyle = 'black';
context2d.lineWidth = 1;
for (let y = 0; y <= this.Height; y++) {
context2d.beginPath();
context2d.moveTo(0, y * 32);
context2d.lineTo(this.Width * 32, y * 32);
context2d.stroke();
}
for (let x = 0; x <= this.Width; x++) {
context2d.beginPath();
context2d.moveTo(x * 32, 0);
context2d.lineTo(x * 32, this.Height * 32);
context2d.stroke();
}
// Draw objects
for (const obj of this.MapObjects) {
obj.draw(context2d);
}
}//draw
}//Map
const canvas = document.getElementById('gameCanvas');
const context2d = canvas.getContext('2d');
const gameMap = new Map(10, 10);
const player = new Player(4, 4);
gameMap.addObject(player);
let lastFrameTime = performance.now();
const targetFrameDuration = 1000 / 60; // 60FPSの場合のフレーム間隔(ミリ秒)
const pressedKeys = new Set();
function gameLoop(currentTime) {
const deltaTime = currentTime - lastFrameTime;
if (deltaTime >= targetFrameDuration) {
update(deltaTime);
draw();
lastFrameTime = currentTime;
}
requestAnimationFrame(gameLoop);
}
function update(deltaTime) {
let moveDirection = 0;
if (pressedKeys.has("ArrowUp") && pressedKeys.has("ArrowLeft")) {
moveDirection = 7;
} else if (pressedKeys.has("ArrowUp") && pressedKeys.has("ArrowRight")) {
moveDirection = 9;
} else if (pressedKeys.has("ArrowDown") && pressedKeys.has("ArrowLeft")) {
moveDirection = 1;
} else if (pressedKeys.has("ArrowDown") && pressedKeys.has("ArrowRight")) {
moveDirection = 3;
} else {
for (const key of pressedKeys) {
const direction = directionKeyMap[key];
if (direction) {
moveDirection = direction;
break;
}
}
}
if (moveDirection) {
player.move(moveDirection);
}
}
function draw() {
gameMap.draw(context2d);
}
function keydownHandler(event) {
pressedKeys.add(event.code);
}
function keyupHandler(event) {
pressedKeys.delete(event.code);
}
const directionKeyMap = {
'Numpad1': 1,
'Numpad2': 2,
'Numpad3': 3,
'Numpad4': 4,
'Numpad6': 6,
'Numpad7': 7,
'Numpad8': 8,
'Numpad9': 9,
'ArrowDown': 2,
'ArrowUp': 8,
'ArrowLeft': 4,
'ArrowRight': 6,
};
document.addEventListener("keydown", keydownHandler);
document.addEventListener("keyup", keyupHandler);
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
Set
JavaScript の Set は、ES6 (ECMAScript 2015) で導入された組み込みオブジェクトで、値の集合を格納するためのものです。Set は、各値を一度だけ保存し、重複を許さないのが特徴です。これは、配列とは異なり、同じ値を2回追加しても、実際には1つのエントリしか持たないということを意味します。
また、順番は入力した順番で保証されます。これはその他のプログラミング言語におけるセット実装とは異なる振る舞いです。
以下は Set の基本的な特徴と使用方法です:
1.ユニーク性:Set に同じ値を複数回追加しても、その値は1回しか保存されません。
let uniqueNumbers = new Set();
uniqueNumbers.add(1);
uniqueNumbers.add(2);
uniqueNumbers.add(2); // 重複
uniqueNumbers.add(3);
console.log(uniqueNumbers.size); // 3
2.メソッド:
add(value): 値を Set に追加します。
delete(value): 指定された値を Set から削除します。
has(value): 指定された値が Set に存在する場合、true を返します。
clear(): Set のすべての値を削除します。
3.イテレーション: Set はイテラブルで、その要素は挿入の順序でアクセスできます。
for (let number of uniqueNumbers) {
console.log(number);
}
4.size プロパティ:Set に含まれる要素の数を取得できます。
5.配列との相互変換:Set と配列の間で変換することも簡単です。
let array = [1, 2, 3, 4, 4, 5];
let uniqueSet = new Set(array);
let backToArray = [...uniqueSet];
この記事が気に入ったらサポートをしてみませんか?