Diagonal Movement and Character Interaction
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:
- Check the tile in front of you.
- 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:
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!
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:
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)
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
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.
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.
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
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
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.
Wow, that’s what I wwas looking for, what a data!
present heee at this website, tuanks armin of tnis website.
Excellent items from you, man. I’ve understand your stuff prior to and you are simply too wonderful.
I actually like what you’ve bought here, really like what you’re saying and the best way wherein you are saying it.
You make it enjoyable and you still care for to stay it sensible.
I cant wait to learn much more from you. That is really a terrific
website.
This website really has all of the info I wanted about this subject and didn’t know who to ask.
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.