使用setInterval

时间:2018-01-05 14:42:37

标签: javascript game-engine 2d-games pixi.js topdown

如果您对该问题进行投票或投票,请评论原因。我愿意改变它,但如果你告诉我什么是错的话,我只会这样做。

我目前正在尝试为我正在写的类似炸弹人的游戏实施玩家动作。布局与此非常相似:
enter image description here

运动方向优先级
基本上,当你按下其中一个箭头键时,玩家应该开始朝那个方向移动,直到他碰到一个阻挡。 (这已经有效了)

但它更复杂。例如,当你按住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
    }

}

2 个答案:

答案 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)

使用链接的地图单元格。

与链接列表一样,链接的地图可以轻松导航复杂的迷宫。

墙壁碰撞(包括墙壁跳跃)和您列出的所有其他问题都是非问题,因为它们根本不会发生。

细胞。

首先,您定义每个地图单元格以保存单元格的位置xy

对于每个免费单元格,玩家可以占用一个,该单元格保存对玩家可以通过名称,左,右,上,下移动的单元格的引用。

因此,单元格可能看起来像

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"],
}

因此,如果球员最后一次移动被留在下一个单元格,则按顺序向上,向下,向右,向左检查按键。如果侧方向键关闭,玩家将始终转动。

还有更多好处。

  • 完全消除了玩家地图碰撞测试的需要。

  • 解决玩家对齐的问题,因为您确保始终拥有对齐的单元格以及距其移动的距离

  • 让投球手的位置很容易及时前进。只需按照播放器下方的单元格中的单元格即可。

  • 除了玩家移动之外,它还可以帮助非玩家角色,大大简化了寻路解决方案。

  • 您可以使用固定时间步或随机时间步,只要您只从一个单元格到另一个单元格。

  • 玩家永远不会被卡住,因为你无法移动到没有出路的位置。

相关问题