Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Custom TBS Attack Range Optimization

I'm creating a Fire Emblem-like system in XP and having trouble displaying attack ranges as FE does. I've got the range showing up right:
http://img.photobucket.com/albums/v194/ ... _Thing.png[/img]
(First image is range displayed, second is base map for reference)
However, for characters with very long range attacks (Bolting, Purge, Eclipse) the method I created that checks for locations in range simply takes too long; at 3-10 range, it makes some 9000 extraneous checks for the 200 valid locations, taking about a second when it shouldn't drop the framerate. (At least somehow it doesn't in FE itself D: ) I'm hoping someone has a way to do this that's more efficient.
Also, for the reason it makes so many checks, look at the first image; it displays the attack range compared to every square the character can move to. So for a character that can move to some 40-70 squares, it has to check every square in range of all those locations.
Code:
    move_range = []
      #move_range is an array holding a list of locations in range of the character in [x,y] form
      for i in @move_range
        x = i[0]
        y = i[1]
        # Checks the current location to see if another event (will always be allied or neutral) is there already
        blocked = false
        for j in @events.values
          if j.x == x and j.y == y and j.is_a_unit
            blocked = true unless j.id == id
          end
        end
        unless blocked
          move_range.push(i)
        end
      end
      for i in move_range
        x = i[0]
        y = i[1]
        # Create the outer range
        temp_range = []
        # I had some reason for making this 1..max range and delete the ones too close later instead of
        # min range.. max range, but I can't remember it right now. Either way that part doesn't slow it down
        # much, but feel free to redo it in a rewrite if you feel it would be better
        for range in 1..5#10 # reduced to 5 here since 10 covers 95% of the map
          for i in (-1 * range)..range
            xx = x + i
            k = i.abs
            for j in [(-1 * (range - k)),(range - k)]
              yy = y + j
              unless temp_range.include?([xx,yy])
                temp_range.push([xx,yy])
                #@l += 1 # recorded how many extra checks were being made
              end
            end
          end
        end
        # Create an array of locations under the minimum
        temp_range2 = []
        for range in 1..2
          for i in (-1 * range)..range
            xx = x + i
            k = i.abs
            for j in [(-1 * (range - k)),(range - k)]
              yy = y + j
              unless temp_range2.include?([xx,yy])
                temp_range2.push([xx,yy])
              end
            end
          end
        end
        # Delete locations under minimum
        for i in temp_range2
          temp_range.delete(i)
        end
        # Add found locations to attack/staff range
        # This is separate instead of writing to @attack/staff_range directly because they can have locations
        # recorded into them separately for other ranges (1, 2, etc)
        for i in temp_range
          if staff
            @staff_range.push(i) unless @staff_range.include?(i)
          else
            @attack_range.push(i) unless @attack_range.include?(i)
          end
        end
      end
 
Bump.

Something I forgot to mention, that only showed up in that code snippet:
http://img.photobucket.com/albums/v194/ ... _Range.png[/img]

There are minimum ranges too, so something like... just finding the edges and filling it in wouldn't work, it would need to then go back and make sure all the possibilities are actually possible.
And in addition, squares that are occupied by an ally or neutral character (who can be moved through) don't count toward the attack range, so something like
http://img.photobucket.com/albums/v194/ ... Range2.png[/img]
can happen because of blocked squares.

:EDIT:
Well, I found a slightly faster way... taking all the end points based on the edges of the move range
http://img.photobucket.com/albums/v194/ ... olved2.png[/img]
and then draw triangles sticking out of the endpoints (circled in purple) with the bottom cut out equal to the size of the minimum range.
http://img.photobucket.com/albums/v194/ ... olved1.png[/img]
As long as the endpoints are correct (taking squares blocked by allies into account) then it always comes out right.

Code:
      max_range = 5
      min_range = 3
      temp_range = []
      x_maxes = []
      x_mins = []
      y_maxes = []
      y_mins = []
      # Find the edges of the move range
      for i in @move_range
        x = i[0]
        y = i[1]
        # This basically checks if the current square is available to move to, and if the squares on one side of it (next to it and on diagonals)
        # aren't in the move range or are blocked; If so, it's an edge to the move range
        unless blocked(x,y,id)
          if (!@move_range.include?([x+1,y]) or blocked(x+1,y,id))# and
                #((!@move_range.include?([x+1,y+1]) or blocked(x+1,y+1,id)) or
                #(!@move_range.include?([x+1,y-1]) or blocked(x+1,y-1,id)))
            x_maxes.push([x,y])
          end
          if (!@move_range.include?([x-1,y]) or blocked(x-1,y,id))# and
                #((!@move_range.include?([x-1,y+1]) or blocked(x-1,y+1,id)) or
                #(!@move_range.include?([x-1,y-1]) or blocked(x-1,y-1,id)))
            x_mins.push([x,y])
          end
          if (!@move_range.include?([x,y+1]) or blocked(x,y+1,id))# and
                #((!@move_range.include?([x+1,y+1]) or blocked(x+1,y+1,id)) or
                #(!@move_range.include?([x-1,y+1]) or blocked(x-1,y+1,id)))
            y_maxes.push([x,y])
          end
          if (!@move_range.include?([x,y-1]) or blocked(x,y-1,id))# and
                #((!@move_range.include?([x+1,y-1]) or blocked(x+1,y-1,id)) or
                #(!@move_range.include?([x-1,y-1]) or blocked(x-1,y-1,id)))
            y_mins.push([x,y])
          end
        end
      end
      # Expand the move range edges out by max_range
      for i in 0...x_maxes.length
        x_maxes[i] = [x_maxes[i][0]+max_range,x_maxes[i][1]]; end
      for i in 0...x_mins.length
        x_mins[i] = [x_mins[i][0]-max_range,x_mins[i][1]]; end
      for i in 0...y_maxes.length
        y_maxes[i] = [y_maxes[i][0],y_maxes[i][1]+max_range]; end
      for i in 0...y_mins.length
        y_mins[i] = [y_mins[i][0],y_mins[i][1]-max_range]; end
      # Adds locations closer to the move range than the endpoints
      for range in 0..max_range
        for width in (-range)..(range)
          # Unless they're too close
          unless width.abs < min_range - (max_range - range)
            for i in x_maxes
              unless temp_range.include?([i[0]-range,i[1]+width])
                temp_range.push([i[0]-range,i[1]+width])
              end
            end
            for i in x_mins
              unless temp_range.include?([i[0]+range,i[1]+width])
                temp_range.push([i[0]+range,i[1]+width])
              end
            end
            for i in y_maxes
              unless temp_range.include?([i[0]+width,i[1]-range])
                temp_range.push([i[0]+width,i[1]-range])
              end
            end
            for i in y_mins
              unless temp_range.include?([i[0]+width,i[1]+range])
                temp_range.push([i[0]+width,i[1]+range])
              end
            end
          end
        end
      end
      # Adds the endpoints to the range
      for i in x_maxes; temp_range.push([i[0],i[1]]); end
      for i in x_mins; temp_range.push([i[0],i[1]]); end
      for i in y_maxes; temp_range.push([i[0],i[1]]); end
      for i in y_mins; temp_range.push([i[0],i[1]]); end
      # Add found locations to attack/staff range
      for i in temp_range
        if staff
          @staff_range.push(i) unless @staff_range.include?(i)
        else
          @attack_range.push(i) unless @attack_range.include?(i)
        end
      end
Code:
  # This checks if an event is in the x,y location provided, and returns true unless that event is the one being checked for (based on id)
  def blocked(x, y, id)
    for i in @events.values
      if i.x == x and i.y == y and i.is_a_unit
        return true unless i.id == id
      end
    end
    return false
  end

This makes about a quarter the excess checks as the other method.
 

e

Sponsor

So your bug is that when the range is over n squares, the algorithm just takes too long?

A way to accelerate this would be to switch your data structure from Arrays to Graphs. That would probably give you a good boost of speed, although to what extent, I don't know.

If you provided the whole script or a demo, I'd gladly take it up and try a buncha things to get the speed up.
 
I sent a link to the demo in a PM.
Much thanks, for anything you can think of that could help.
The method that's lagging is setup_attack_range on line 233 of Moverange Thing.

-EDIT-
I doubt anyone will see this since I'm editing a two month old topic, but I got it working. Didn't improve on the method, I just made a C dll to do the same thing except infinity faster.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top