Diagonal Movement and Character Interaction

Help spread the word!Share on FacebookShare on TumblrTweet about this on TwitterShare on Google+Share on LinkedInShare on RedditFlattr the author

EightDirMovement1

This is the second part of my tutorial on introducing diagonal movement into RPG Maker.

In the first part, we started with the default project: 4-directional movement and no support for diagonals movement. We modified the player input to handle 8-directional input, updated our characters to store diagonal directions, designed a format for our spritesheets to hold the diagonal sprites, and then implemented the new format in our sprite class so that it knows which sprite to draw.

If you haven’t read the first part, I would recommend doing so. You can visit it by clicking this link.

At the end of the article, we had characters that could move in all eight directions using the correct sprite. We then noticed that we couldn’t trigger events if we approached diagonally.

In this article, we see why this is happening, and how we can resolve it.

Event Triggering and Directions

Directions play a large part in event triggering.

This (kind of) makes sense: you usually check things that are in front of you or under you, rather than behind you or beside you. Well you could of course, but the “Action Trigger” in RM only supports in front or below.

We begin with the player trigger other events by pressing the action button.

Action Trigger

Currently, our diagonal movement script has no problem with checking for events under you. This is because it does not use a character’s direction property.

However, checking events in front of you does use direction. This is how it is defined in Game_Player

def check_event_trigger_there(triggers)
  x2 = $game_map.round_x_with_direction(@x, @direction)
  y2 = $game_map.round_y_with_direction(@y, @direction)
  start_map_event(x2, y2, triggers, true)
  return if $game_map.any_event_starting?
  return unless $game_map.counter?(x2, y2)
  x3 = $game_map.round_x_with_direction(x2, @direction)
  y3 = $game_map.round_y_with_direction(y2, @direction)
  start_map_event(x3, y3, triggers, true)
end

This is what this code means:

  1. Check the tile in front of you.
  2. If the tile in front of you is a counter, check the tile in front of that (this can be called “checking behind the counter”)

Hopefully it is clear what it means for a tile to be in front of you.

Incorporating diagonal directions

In the snippet I show above, the engine determines which position to check using these two methods defined in Game_Map:

def round_x_with_direction(x, d)
  round_x(x + (d == 6 ? 1 : d == 4 ? -1 : 0))
end

def round_y_with_direction(y, d)
  round_y(y + (d == 2 ? 1 : d == 8 ? -1 : 0))
end

You can ignore the rounding for now. That is just to take into consideration the possibility that your map loops.

If you look at how x_with_direction is defined, you see that it simply adds 1 or subtracts 1 depending on the direction you’re facing.

Notice also that it only checks two directions: left or right. It doesn’t consider the possibility that you’re looking towards the upper-left, upper-right, bottom-left, or bottom-right.

Realistically, this is what you need to consider:

EightDirMovement7

So when d is 3, 6, or 9, we need to increase x by 1, and when d is 7, 4, or 1, we need to decrease by 1.

A similar argument is made for the vertical direction: when d is 7, 8, or 9, then we need to increase y by 1. When d is 1, 2, or 3, we need to decrease y by 1.

Here are the new methods, that are adjusted to consider diagonals:

class Game_Map
  def x_with_direction(x, d)
    x + (d % 3 == 0 ? 1 : (d + 2) % 3 == 0 ? -1 : 0)
  end

  def y_with_direction(y, d)
    y + (d < 4 ? 1 : d > 6 ? -1 : 0)
  end

  def round_x_with_direction(x, d)
    round_x(x + (d % 3 == 0 ? 1 : (d + 2) % 3 == 0 ? -1 : 0))
  end

  def round_y_with_direction(y, d)
    round_y(y + (d < 4 ? 1 : d > 6 ? -1 : 0))
  end
end

Note that I do the same with the non-rounded versions for consistency.
For some, this might seem a bit strange, but all I’ve done is converted the observations explained above into formulas. You could conditional branches to check each case one at a time, but because there are only 8 directions, we can throw some math at it.

Add this to your script, and you should be able to trigger events diagonally now!

EightDirMovement8

Turning towards Player

If you have followed along the tutorials (or just look at the previous screenshot), you will see that while the event does respond to the player, it doesn’t actually turn towards you. I assure you that the event does turn towards you if you approach it from another direction:

EightDirMovement9

When you trigger an event, by default it will turn towards you if the direction is not locked. From Game_Event, there is the start method. From there, it calls the lock method.

def start
  return if empty?
  @starting = true
  lock if trigger_in?([0,1,2])
end

def lock
  unless @locked
    @prelock_direction = @direction
    turn_toward_player ###
    @locked = true
  end
end

We can see that it’s supposed to turn towards the player. Following the definition, we find ourselves in Game_Character:

def turn_toward_player
  turn_toward_character($game_player)
end

Which is simple enough: it turns towards a character, which happens to be the player.
And now we arrive at where we want to be:

def turn_toward_character(character)
  sx = distance_x_from(character.x)
  sy = distance_y_from(character.y)
  if sx.abs > sy.abs
    set_direction(sx > 0 ? 4 : 6) ###
  elsif sy != 0
    set_direction(sy > 0 ? 8 : 2) ###
  end
end

It should be apparent why the event wasn’t turning towards us diagonally. It only turns in 4 directions, none of which are diagonal!

Turning Towards a Character Diagonally

The turn_towards_character is meant for one character to turn towards another character in the general direction. This means that if you’re right beside the character, it’ll turn towards you correctly, but if you’re slightly further away, it will just look in your general direction.

This is how the default method works with 4 directions (play with some numbers if you’re not convinced)

EightDirMovement10

So if you’re anywhere inside those regions, that’s the direction the character will turn. If you’re exactly on the edge, it’ll take the region above that line. Each region gets 90 degrees of the circle, rotated 45 degrees along the x-axis.

What we are looking for is something more like this

EightDirMovement11

Each region covers 45 degrees, rotated 22.5 degrees to reflect where the target character could be. Because we are using a grid, and all of our distance calculations are based on grid coordinates, it would be a little difficult to accurately determine which direction the character should turn. At the very least, we should be able to handle the base case: when the characters are beside each other.

EightDirMovement12

At this point, you may want to look at how distance between two characters is calculated since who the point of reference is would affect your calculations.

I look at the problem by first breaking it down into two triangles: the upper-right triangle and the lower-left triangle.

EightDirMovement13

sx = distance_x_from(character.x)
sy = distance_y_from(character.y)
if sx > sy
  set_direction(1) # bottom
else
  set_direction(9) # top
end

It would be a bit easier to see if we extended our grid a little

EightDirMovement14

This is just how I plan to define the directions. It is a very basic setup and it really doesn’t look anything like what we would like it to ideally look, but it’ll work for starters. Here’s how it might look:

class Game_Character < Game_CharacterBase
  def turn_toward_character(character)
    sx = distance_x_from(character.x)
    sy = distance_y_from(character.y)
    if sx > sy      
      if sx == -sy
        set_direction(1)
      elsif sx.abs > sy.abs
        set_direction(4)
      else
        set_direction(2)
      end
    else
      if -sx == sy
        set_direction(9)
      elsif -sx < sy
        if sx == sy
          set_direction(7)
        else
          set_direction(8)
        end
      else
        if sx == sy
          set_direction(3)
        else
          set_direction(6)
        end
      end      
    end
  end
end

And the results

EightDirMovement15

Which doesn’t look too bad. But as I mentioned, the diagonal regions could be done better. And of course, now when you interact with an event, it will turn towards you properly.

There’s an analogous method, turn_away_from_character, which would also need to be updated. It’s basically just the opposite directions since instead of turning towards, you’re turning away.

Touch Events

Action triggered events now function correctly, but if you tried to walk up to a touch-triggered event diagonally, it won’t activate. However, if you walk up to it orthogonally, it works!

Looking at the two definitions, when you move_straight, it actually performs a check to see if there are any touch-triggered events in front of you, whereas when you move diagonally, no such check occurs. This is possibly because there is no concept of “in front” when you’re moving diagonally in the default engine.

Now that we have an actual definition for what it means for a tile to be in front of you even if you’re moving diagonally, we can simply add the check of the move_diagonal method as well. Note that we should only check if we couldn’t move, because that indicates something is blocking our way. At this point it might be better to simply overwrite the method.

Summary

Diagonal movements results in issues with event triggers and turning towards or away from characters. In this section we managed to implement

  • Diagonal event triggering
  • Turning towards and away from a character

This should cover most of the diagonal movement related issues, and you can now have eight directional movement in your game.

Credits

All of the sprites shown in the screenshots are taken from this website: http://usui.moo.jp/rpg_chadot.html

Spread the Word

If you liked the post and feel that others could benefit from it, consider tweeting it or sharing it on whichever social media channels that you use. You can also follow @HimeWorks on Twitter or like my Facebook page to get the latest updates or suggest topics that you would like to read about.

Help spread the word!Share on FacebookShare on TumblrTweet about this on TwitterShare on Google+Share on LinkedInShare on RedditFlattr the author

You may also like...

1 Response

  1. Arsist says:

    Let’s say I wanted to activate an event that is one tile above me and 1 tile to
    the right of me, and have an object blocking my upward movement and an object blocking my sideways movement. When the character is blocked, they can’t actually turn diagonal — they can only move diagonally, not explicitly turn diagonally. However, this can be achieved with a turn-in-place D8 script.

Leave a Reply

Your email address will not be published.

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax