我用HTML画布制作了一个游戏,用户可以在其中控制角色的移动。
链接:live website / GitHub repo
我的目标是在游戏结束时显示的模式打开时禁用箭头键。我通过在showModal()
函数中添加以下内容来进行尝试,该函数与在JavaScript末尾添加的事件侦听器的版本相同(然后我会在游戏结束时重新添加此事件侦听器重启)。它什么也没做。
document.removeEventListener('keydown', function(e) {
let allowedKeys = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
player.handleInput(allowedKeys[e.keyCode]);
});
此文件中的完整JS代码(还有其他两个JS文件,但我认为它们不相关):
"use strict"; // Enables strict mode to catch common bloopers
// TODO: Disable player movement when modal opened? Also, set 3 tries before modal opened (change to game over). Restart button.
// TODO: Start game on enter when modal opened
const playAgainButton = document.querySelector('.play-again');
const restartButton = document.querySelector('.restart');
// Calls playAgain() function when user clicks reset icon in sidebar
restartButton.addEventListener('click', playAgain);
// Starts lives at 3
let lives = 3;
let sidebarLives = document.querySelector('.lives-left');
sidebarLives.innerHTML = lives;
// Sets an initial player score of 0.
let score = 0;
// Sets score shown in sidebar
// document.getElementsByClassName('score')[0].innerHTML = score;
let sidebarScore = document.querySelector('.score');
sidebarScore.innerHTML = score;
let modalScore = document.querySelector('.modal-score');
modalScore.innerHTML = score;
// Called when user clicks restart button in sidebar or play again button in modal. Sets modal to display: none, resets lives and score
function playAgain() {
// Hides modal if present (if opened by game ending)
modal.classList.remove('modal-visible');
lives = 3;
sidebarLives.innerHTML = lives;
score = 0;
sidebarScore.innerHTML = score;
}
// Calls playAgain() function (hides modal and restarts game) with user clicks "play again" button in modal
// TODO: remove? just one event listener for both buttons?
// modalPlayAgainButton.addEventListener('click', playAgain);
// Note: In a constructor function "this" does not have a value. It is a substitute for the new object. The value of this will become the new object when a new object is created
// Note commas not used to separate methods and properties in a class
class Player {
// Constructor function, a special function just for initializing new objects, will automatically run when a new object is constructed (with keyword "new") from this class. Contains data needed to create it
constructor(x, y, speed) {
this.sprite = 'images/char-boy.png';
this.x = x;
this.y = y;
this.speed = speed;
}
// Methods that all objects created from class will inherit. Would exist on prototype in pre-class way of writing it, but effect is the same (the following methods still exist on Player prototype [for example would be Player.prototype.update = function(dt)...])
// When player reaches water, moves player back to starting position, and increase score by 1
update(dt) {
if (this.y === 25) {
this.x = 200;
this.y = 400;
score++;
sidebarScore.innerHTML = score;
}
}
// Draws player on screen
render() {
ctx.drawImage(Resources.get(this.sprite), this.x, this.y)
}
// Connects keyboard input to player movement. If statements prevent player movement off screen
handleInput(allowedKeys) {
if (allowedKeys === 'down' && this.y < 425) {
this.y += 25;
}
if (allowedKeys === 'up') {
this.y -= 25;
}
if (allowedKeys === 'left' && this.x > 0) {
this.x -= 25;
}
if (allowedKeys === 'right' && this.x < 400) {
this.x += 25;
}
}
}
class Enemy {
// Sets enemy's initial location
constructor(x, y, speed) {
this.x = x;
this.y = y;
// Sets speed of enemy
this.speed = speed;
// The image/sprite for our enemies
this.sprite = 'images/enemy-bug.png';
}
update(dt) {
// Multiplies enemy's movement by time delta to ensure game runs at same speed for all computers
this.x += this.speed * dt;
// Once enemy finished moving across screen, moves it back so it can cross screen again and randomizes its speed
if (this.x > 500) {
this.x = -75;
// Math.random() function returns random number between 0 (inclusive) and 1 (exclusive). Math.floor() returns the largest integer less than or equal to a given number
this.speed = 70 + Math.floor(Math.random() * 450);
}
// When collission occurs, subtracts a life, updates lives displayed in sidebar and updates score that will be displayed in modal if no lives remaining
if ((player.x < (this.x + 70)) && ((player.x + 17) > this.x) && (player.y < (this.y + 45)) && ((30 + player.y) > this.y)) {
player.x = 200;
player.y = 400;
lives--;
sidebarLives.innerHTML = lives;
modalScore.innerHTML = score;
if (lives === 0) {
// Calls function that adds class that sets modal to display: block
showModal();
}
}
}
// Draws enemy on the screen
render() {
ctx.drawImage(Resources.get(this.sprite), this.x, this.y);
}
};
// ENEMY/PLAYER OBJECT INSTANTIATION
let enemyPosition = [60, 140, 220];
let allEnemies = [];
let player = new Player(200, 400, 50);
enemyPosition.forEach(function(posY) {
let enemy = new Enemy(0, posY, 70 + Math.floor(Math.random() * 450));
allEnemies.push(enemy);
});
// Modal
const modal = document.getElementById('myModal');
const closeIcon = document.querySelector('.close');
// When called, adds class that sets modal to display: block when player reaches water
function showModal() {
modal.classList.add('modal-visible');
// Goal: Disable arrow keys while the modal is open (doesn't work). If I can get this to work, then I'd re-add the arrow key event listener when the game is reset
document.removeEventListener('keydown', function(e) {
let allowedKeys = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
// Not sure why "player" needs to be lowercase, given the class name is uppercase
player.handleInput(allowedKeys[e.keyCode]);
});
// Calls playAgain() function when user clicks play again button in modal
playAgainButton.addEventListener('click', playAgain);
// If esc is pressed, closes modal and restarts game (note: keydown used instead of keypress because keypress only works for keys that produce a character value)
document.addEventListener('keydown', function(e) {
let keyCode = e.keyCode;
if (keyCode === 27) {
modal.classList.remove('modal-visible');
playAgain()
}
});
// If enter is pressed, closes modal and restarts game
document.addEventListener('keydown', function(e) {
let keyCode = e.keyCode;
if (keyCode === 13) {
modal.classList.remove('modal-visible');
playAgain()
}
});
// If user clicks modal's close icon, closes modal and restarts game
closeIcon.addEventListener('click', function() {
modal.classList.remove('modal-visible');
playAgain();
});
}
// Listens for keydown event (fired when a key is pressed down [regardless of whether it produces a character, unlike keypress]) and sends the keys to Player.handleInput() method
document.addEventListener('keydown', function(e) {
let allowedKeys = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
// Not sure why "player" needs to be lowercase, given the class name is uppercase
player.handleInput(allowedKeys[e.keyCode]);
});
答案 0 :(得分:1)
当游戏状态更改为(例如,角色死亡时)时,代替删除和/或过滤掉特定的按键事件,对于初学者来说,标记 (一个布尔值)以指示您的角色是否应该移动。
let isDead = false;
角色死亡时,将标志设置为false
:
if (lives === 0) {
isDead = true;
// Calls function that adds class that sets modal to display: block
showModal();
}
在输入处理功能中,使用标志允许/禁止字符移动:
// Connects keyboard input to player movement. If statements prevent player movement off screen
handleInput(allowedKeys) {
if (isDead) {
return;
}
if (allowedKeys === 'down' && this.y < 425) {
this.y += 25;
}
if (allowedKeys === 'up') {
this.y -= 25;
}
if (allowedKeys === 'left' && this.x > 0) {
this.x -= 25;
}
if (allowedKeys === 'right' && this.x < 400) {
this.x += 25;
}
}
或者直接避免在事件处理程序中调用handleInput(...)
函数:
document.addEventListener('keydown', function(e) {
let allowedKeys = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
// notice that we only want to call the handleInput function
// when the player is alive, hence !isDead
if (!isDead) {
player.handleInput(allowedKeys[e.keyCode]);
}
});
然后重新启动游戏时,将标志设置为其默认值:
function playAgain() {
modal.classList.remove('modal-visible');
isDead = false;
lives = 3;
sidebarLives.innerHTML = lives;
score = 0;
sidebarScore.innerHTML = score;
}
由于您正在开发游戏,即使只是简单的游戏,我也强烈建议您使用game states来管理游戏行为。
答案 1 :(得分:0)
如果无法解决问题,您可以尝试几种不同的方法。 假设您有一个布尔变量,该变量在模式打开时为true,在模式关闭时为false。这会起作用
th = np.linspace(0,np.pi/3,100)
def find_v0(theta):
v0=8
while(True):
v0x = v0 * np.cos(theta)
v0y = v0 * np.sin(theta)
z0 = np.array([0, v0x, ystar, v0y])
# Calculate solution
t, z = explicit_midpoint(rhs, z0, 5, 1000)
for k in range(1001):
if z[k,0] > xstar:
z[k,0] = 0
z[k,2] = 0
x = np.trim_zeros(z[:,0])
y = np.trim_zeros(z[:,2])
diff = difference(x[-1],y[-1])
if diff < 0.1:
break
else: v0+=0.01
return v0#,x,y[0:]
v0 = np.zeros_like(th)
from tqdm import tqdm
count=0
for k in tqdm(th):
v0[count] = find_v0(k)
count+=1
v0_interp = interpolate.interp1d(th,v0)
plt.figure()
plt.plot(th,v0_interp(th),"g")
plt.grid(True)
plt.xlabel(r"$\theta$")
plt.ylabel(r"$v_0$")
plt.show()
如果模式已打开,则将键码设置为true而不是数字。 这种方式是非正统的,但是可以。