Skip to main content
added profiler output
Source Link
gaazkam
  • 977
  • 1
  • 7
  • 11

Edit: Sorry, I forgot to tell you. According to the profiler the game spends an unacceptably high amount of time in setMomMovement. 1/2 - 1/3 of this time is the function's self time, the rest is mainly Game_CharacterBase.canPass (RPG Maker's function... will I have to try to optimize it even more?) Around 1-2 ms is spent in my heap implementation:

enter image description here

Edit: Sorry, I forgot to tell you. According to the profiler the game spends an unacceptably high amount of time in setMomMovement. 1/2 - 1/3 of this time is the function's self time, the rest is mainly Game_CharacterBase.canPass (RPG Maker's function... will I have to try to optimize it even more?) Around 1-2 ms is spent in my heap implementation:

enter image description here

Source Link
gaazkam
  • 977
  • 1
  • 7
  • 11

How to efficiently implement Dijkstra's path finding algorithm?

As per this answer: How to devise an algorithm for a person being on a walk? I tried to implement a simple path finding algorithm.

The map is 60x60 tiles total, and walkable tiles are even fewer:

enter image description here

(The reddish tiles are walkable and have weight of 1 (road), the greenish tiles have weight of 2 (grass), the white tiles are unwalkable)

I hoped that for such a small map there shouldn't be any performance issues. I was wrong. The game is supposed to run at 60fps. With the profiler enabled path finding can take up to 50ms! This means a few frames. Even with the profiler disabled the jitter is very visible. In this case I believe the algorithm would be unusable on any larger map. What am I doing wrong?

Here is the code for one character (The engine: RPG Maker MV):

GZKM.P1MomWalkMovement = function() {
    var getTileWeight = function(tagId) {
        return 1+((tagId&0x10)>>1)
    }
    
    $gameVariables._data[3] = $gameVariables._data[3] || {}
    var state = ($gameVariables._data[3].P1MomWalkMovement = $gameVariables._data[3].P1MomWalkMovement || {})
    state.goal = state.goal || null
    
    
    var findgoal = function() {
        var arr = []
        for(var x = 0; x < $gameMap.width(); x++) for(var y = 0; y < $gameMap.height(); y++) if($gameMap.regionId(x,y)&0x1) arr.push({x:x,y:y})
        state.goal = arr[Math.floor(Math.random()*arr.length)]
    }
    
    var setMomMovement = function() {
        var mom = this;
        
        var evs = []
        for(var x = 0; x < $gameMap.width(); x++) {
            evs[x] = []
            for(var y = 0; y < $gameMap.height(); y++) {
                evs[x][y] = []
            }
        }
        $gameMap.events().filter(function(ev){return ev.isNormalPriority() && !ev.isThrough()}).forEach(function(ev){evs[ev.x][ev.y].push(ev)})
        
        // Optimization attempt. RPGMaker's default checks for passability seem to be linear in the number of all events on map per tile.
        // I tried to bring this down to O(1) per tile. 
        // No, I didn't profile this, b/c I don't have many events yet - but
        // I plan to have more, so I wanted to be on a safe side.
        var isCollidedWithEventsOld = Game_CharacterBase.prototype.isCollidedWithEvents;
        Game_CharacterBase.prototype.isCollidedWithEvents = function(x, y) {
            return evs[x][y].length
        }
        
        var h = new GZKM.Heap()
        var map = []
        for(var x = 0; x < $gameMap.width(); x++) for(var y = 0; y < $gameMap.height(); y++) if($gameMap.regionId(x,y)&0x1) if(x != mom.x || y != mom.y)
            map[[x,y]] = h.push(Infinity, {x:x,y:y})
        map[[mom.x, mom.y]] = h.push(0, {x:mom.x, y:mom.y})
    
        var curr; while((curr = h.pop()) !== undefined && curr.priority != Infinity && !(curr.elt.x == state.goal.x && curr.elt.y == state.goal.y)) {
            for(var d = 2; d <= 8; d+=2) { // directions
                var nx = $gameMap.roundXWithDirection(curr.elt.x, d)
                var ny = $gameMap.roundYWithDirection(curr.elt.y, d)
                if([nx,ny] in map && mom.canPass(curr.elt.x, curr.elt.y, d)) {
                    var candPriority = curr.priority + getTileWeight($gameMap.regionId(curr.elt.x,curr.elt.y))
                    if(candPriority < map[[nx,ny]].priority) {
                        h.reprioritize(map[[nx,ny]], candPriority)
                        map[[nx,ny]].elt.prev = curr
                    }
                }
            }
        }
        
        Game_CharacterBase.prototype.isCollidedWithEvents = isCollidedWithEventsOld;
        
        if(!('prev' in map[[state.goal.x, state.goal.y]].elt))
            state.goal = null
        else {
            var t = map[[state.goal.x, state.goal.y]]
            while(!(t.elt.prev.elt.x == mom.x && t.elt.prev.elt.y == mom.y))
                 t = t.elt.prev
            var dir;
            if(t.elt.x == mom.x+1) dir = 6
            else if(t.elt.x == mom.x-1) dir = 4
            else if(t.elt.y == mom.y+1) dir = 2
            else if(t.elt.y == mom.y-1) dir = 8
            this.moveStraight(dir)
        }
        
    }.bind(this)
    
    if(state.goal === null) findgoal()
        
    setMomMovement()
}

My heap implementation is here: https://codereview.stackexchange.com/questions/177124/binary-heap-priority-queue-implementation-in-javascript

What am I doing wrong? How to fix the code and remove the jitter?