如果您对问题投了赞成票或反对票,请评论原因。我愿意改变它,但只有你告诉我哪里出了问题,我才能这样做。
我目前正在尝试为我正在编写的类似轰炸机的游戏实现玩家运动。布局与此非常相似:
移动方向优先级
基本上,当您按下其中一个箭头键时,玩家应该开始朝那个方向移动,直到他碰到一个方块。(这已经有效)
但它更复杂。例如,当你按住left
和时up
,玩家应该移动up
直到他碰到一个方块,然后他应该尝试去left
直到他可以up
再次去或碰到一个方块。
所以最后一个键总是具有最高优先级,前一个键具有第二高优先级,依此类推。(我已经编写了代码来跟踪优先级,但由于以下问题,它不起作用。)
浮点数位置
玩家不会简单地从场地 (0|0) 移动到场地 (0|1)。玩家在变量中配置了固定速度(默认为每秒 1 个字段),并且他的位置将每 10 毫秒更新一次。滞后可能会导致位置更新延迟数秒。
所以这个位置几乎从来不是一个整数,而且几乎总是一个浮点数。
碰撞问题
玩家的宽度和高度与游戏中的所有其他元素完全相同。这意味着,为了从 (0|2.0001312033) 到 (1|2),您首先必须准确地到达 (0|2),这样玩家才不会与 (1|1) 上的块发生碰撞并且(1|3) 只有这样你才能真正到达 (1|2)。
问题是玩家几乎从未真正到达过如此理想的整数位置,因为该位置仅每 10 毫秒更新一次。
字段跳过
另一个明显的问题是,如果延迟导致位置更新延迟一秒,玩家可能会跳过一个字段,从而导致玩家跳过墙壁、物品和爆炸。
总结 如果自上次位置更新以来玩家移动了多个区域,他将跳过一个区域,并且几乎不可能绕过拐角,因为玩家必须处于完美的位置才能在转弯时不与任何块发生碰撞。
我只是想不出在不创建大量不可读代码的情况下解决这些问题的好方法。我真的很想保持它的整洁,并且当我将来再次查看它时能够理解代码。
是否有可以提供帮助的游戏动作库?还有其他想法吗?
我的代码
这些是我当前代码的关键部分。我试图删除所有不相关的部分。
"use strict";
class Player {
constructor(gameField, pos) { // gameField is an object containing lots of methods and properties like the game field size and colision detection fucntions
// this._accuratePos is the floating point position
this._accuratePos = JSON.parse(JSON.stringify(pos));
// this.pos is the integer posision
this.pos = JSON.parse(JSON.stringify(pos));
// this.moveSpeed is the movement speed of the player
this.moveSpeed = 3;
// this.activeMoveActions will contain the currently pressed arrow keys, sorted by priority. (last pressed, highest prio)
this.activeMoveActions = []
// this.moveInterval will contain an interval responsible for updating the player position
this.moveInterval;
}
// directionKey can be 'up', 'down', 'left' or 'right'
// newState can be true if the key is pressed down or false if it has been released.
moveAction(directionKey, newState=true) { // called when a key is pressed or released. e.g. moveAction('left', false) // left key released
if (this.activeMoveActions.includes(directionKey)) // remove the key from the activeMoveActions array
this.activeMoveActions = this.activeMoveActions.filter(current => current !== directionKey);
if (newState) // if the key was pressed down
this.activeMoveActions.unshift(directionKey); // push it to the first position of the array
if (this.activeMoveActions.length === 0) { // if no direction key is pressed
if (this.moveInterval) { // if there still is a moveInterval
clearInterval(this.moveInterval); // remove the moveInterval
return; // exit the function
}
let lastMoveTime = Date.now(); // store the current millisecond time in lastMoveTime
let lastAccPos = JSON.parse(JSON.stringify(this.accuratePos)); // store a copy of this.accuratePos in lastAccPos
this.moveInterval = setInterval(()=>{
let now = Date.now(); // current time in milliseconds
let timePassed = now-lastMoveTime; // time passed since the last interval iteration in milliseconds
let speed = (this.moveSpeed*1)/1000; // the movement speed in fields per millisecond
let maxDistanceMoved = timePassed*speed; // the maximum distance the player could have moved (limited by hitting a wall etc)
// TODO: check if distance moved > 1 and if so check if user palyer went through blocks
let direction = this.activeMoveActions[0]; // highest priority direction
// this.activeMoveActions[1] would contain the second highest priority direction if this.activeMoveActions.length > 1
let newAccPos = JSON.parse(JSON.stringify(lastAccPos)); // store a copy of lastAccPos in newAccPos
// (newAccPos will not necessarily become the new player posision. only if it's a valid position.)
if (direction === 'up') { // if the player pressed the arrow up key
newAccPos.y -= maxDistanceMoved; // subtract the maxDistanceMoved from newAccPos.y
} else if (direction === 'down') {
newAccPos.y += maxDistanceMoved;
} else if (direction === 'left') {
newAccPos.x -= maxDistanceMoved;
} else if (direction === 'right') {
newAccPos.x += maxDistanceMoved;
}
// if it is possible to move the plyer to the new position in stored in newAccPos
if (!this.gameField.posIntersectsMoveBlockingElement(newAccPos) && this.gameField.posIsOnField(newAccPos)) {
this.accuratePos = JSON.parse(JSON.stringify(newAccPos)); // set the new player position to a copy of the modified newAccPos
} else { // if the newly calculated position is not a possible position for the player
this.accuratePos = JSON.parse(JSON.stringify(this.pos)); // overwrite the players accurate position with a copy of the last rounded position
}
realityCheck(); // handle colliding items and explosions
lastMoveTime = now; // store the time recorded in the beginning of the interval function
lastAccPos = JSON.parse(JSON.stringify(newAccPos)); // store a copy of the new position in lastAccPos
}, 10); // run this function every 10 milliseconds
}
set accuratePos(newAccPos) {
let newPos = { // convert to rounded position
x: Math.round(newAccPos.x),
y: Math.round(newAccPos.y)
};
if (this.gameField.posIsOnField(newPos)) { // if the posision is on the game field
this._accuratePos = JSON.parse(JSON.stringify(newAccPos));
this.pos = newPos;
}
}
get accuratePos() {
return this._accuratePos;
}
realityCheck() {
// ignore this method, it simply checks if the current position collides with an items or an explosion
}
}