如果您对该问题进行投票或投票,请评论原因。我愿意改变它,但如果你告诉我什么是错的话,我只会这样做。
我目前正在尝试为我正在写的类似炸弹人的游戏实施玩家动作。布局与此非常相似:
运动方向优先级
基本上,当你按下其中一个箭头键时,玩家应该开始朝那个方向移动,直到他碰到一个阻挡。 (这已经有效了)
但它更复杂。例如,当你按住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
}
}
答案 0 :(得分:3)
我认为您需要为您的游戏设置适当的架构,并且只是逐个攻击问题。
首先也是最重要的:介绍游戏循环http://gameprogrammingpatterns.com/game-loop.html。不要发出任何" setIntervals"在游戏代码中。对于游戏中的每个帧,请执行以下操作:
读取控制器状态
向基础游戏对象发出命令
在所有游戏对象上调用update(timeMillis)以执行命令
作为游戏循环实施的一部分,您可能希望解决"延迟一秒钟的问题。问题。例如,通过将最小timeMillis设置为100ms(即,如果游戏低于10 FPS,则游戏玩法本身将减慢)。还有很多其他选项,只需阅读各种游戏循环方法。
然后攻击Controller实现。实现单独定义良好的控制器对象,可能类似于(TypeScript):
class Controller {
public update(timeMillis: number);
public getPrimaryDirection(): Vector;
public getSecondaryDirection(): Vector;
}
使用console.log在游戏循环中测试它,然后将其放在一边。
然后关注LevelGrid对象和碰撞问题。有很多方法可以解决您在碰撞和导航中提到的问题。以下是可能的解决方案的几点建议:
简单"固定大小的步骤"做法。选择小的固定大小增量(例如0.1像素或1像素)。在游戏循环中创建一个子循环,它将使玩家按固定步骤向正确方向移动,而LevelGrid.canMoveTo(x,y,w,h)返回true。从remainingTimeDelta减去0.1像素所需的时间。当remainingTimeDelta低于零时,退出子循环。正确检查" epsilon"实现LevelGrid.canMoveTo时的距离,以便浮点精度不会伤害你。
"物理铸造"做法。制作LevelGrid.castRectangle(x,y,w,h,dx,dy)函数。它将计算以下内容:如果具有给定大小的rect在给定方向上移动,那么它究竟会到达第一个墙壁?您可以在许多物理库或游戏引擎中找到实现。
我建议您考虑选择优秀的JS游戏引擎,它将为您提供架构。
答案 1 :(得分:1)
与链接列表一样,链接的地图可以轻松导航复杂的迷宫。
墙壁碰撞(包括墙壁跳跃)和您列出的所有其他问题都是非问题,因为它们根本不会发生。
首先,您定义每个地图单元格以保存单元格的位置x
,y
。
对于每个免费单元格,玩家可以占用一个,该单元格保存对玩家可以通过名称,左,右,上,下移动的单元格的引用。
因此,单元格可能看起来像
cell = {
x,y, // the cell pos
left : null, // left is blocked
right : cell, // a cell to the right that can be moved to
up : cell, // a cell above that can be moved to
down : cell, // a cell below that can be moved to
}
左侧的单元格也必须指向右侧的单元格。因此cell.left.right === cell
。或者左后两步cell.left.left.right.right === cell
玩家持有其下的单元格(始终直接在单元格上方开始)player.cell = ?
很容易检查玩家是否可以朝某个方向移动。假设按下左侧控件,只需选中if(keyboard.left && player.cell.left) { //can move left
。移动的位置存储在单元格中
由于您不希望玩家立即移动到下一个单元格,因此您保存该单元格并使用计数器开始移动到新单元格。计数器值从0到1,其中0表示当前单元格,1表示下一个单元格。
当计数达到1时,将播放器下的单元格设置为下一个单元格,并将计数器设置为0,然后重复该过程。检查输入,然后检查单元格在该方向上的单元格,如果是,则移动。
如果玩家反转方向,只需缩小计数器直到回零。
通过跟踪移动的最后一个方向,您可以使用数组来确定下一个移动的优先顺序。
const checkMoveOrder = {
left : ["up","down","right","left"],
right : ["up","down","left","right"],
up : ["left","right","down","up"],
down : ["left","right","up","down"],
}
因此,如果球员最后一次移动被留在下一个单元格,则按顺序向上,向下,向右,向左检查按键。如果侧方向键关闭,玩家将始终转动。
完全消除了玩家地图碰撞测试的需要。
解决玩家对齐的问题,因为您确保始终拥有对齐的单元格以及距其移动的距离
让投球手的位置很容易及时前进。只需按照播放器下方的单元格中的单元格即可。
除了玩家移动之外,它还可以帮助非玩家角色,大大简化了寻路解决方案。
您可以使用固定时间步或随机时间步,只要您只从一个单元格到另一个单元格。
玩家永远不会被卡住,因为你无法移动到没有出路的位置。