The Daily Click ::. Forums ::. Klik Coding Help ::. 8-Direction Grid Movement Collision Problems.
 

Post Reply  Post Oekaki 
 

Posted By Message

The_Antisony

At least I'm not Circy

Registered
  01/07/2002
Points
  1328

VIP MemberStarSnow
2nd October, 2019 at 02/10/2019 10:04:06 -

I'm not sure if I solved this, but I am sure there's probably a more optimal and less janky way to do this and I'm having a brain fart. So, I started out with basic 4-directional grid movement set up like this:

I have a 16x16 Player active object with these alterable values.
MoveUp, MoveDown, MoveLeft, MoveRight, and Stopped.
I have a 16x16 Detector active object.

In the event editor:

MoveUp of Player = 0 + Repeat While Up Arrow is Pressed:
Set Detector Position at (0,-16) of Player, Set Detector Direction to Up, Set Player MoveUp to 16.

MoveDown of Player = 0 + Repeat while Down Arrow is Pressed:
Set Detector Position at (0,16) from Player, Set Detector Direction to Down, Set Player MoveDown to 16.

MoveLeft of Player = 0 + Repeat while Left Arrow is Pressed:
Set Detector Position at (-16,0) from Player, Set Detector Direction to Left, Set Player MoveLeft to 16.

MoveRight of Player = 0 + Repeat while Right Arrow is Pressed:
Set Detector Position at (16,0) from Player, Set Detector Direction to Right, Set Player MoveRight to 16.

To make the player move, I have these events:
Player Stopped = 0 + MoveUp of Player > 0:
Set Player Y-Position to Player Y-Position - 1, Subtract 1 from Player MoveUp.

Player Stopped = 0 + MoveDown of Player > 0:
Set Player Y-Position to Player Y-Position + 1, Subtract 1 from Player MoveDown.

Player Stopped = 0 + MoveLeft of Player > 0:
Set Player X-Position to Player X-Position - 1, Subtract 1 from Player MoveLeft.

Player Stopped = 0 + MoveRight of Player > 0:
Set Player Y-Position to Player Y-Position + 1, Subtract 1 from Player MoveRight.

The Stopped variable in player basically just determines if the player is in the middle of a cut scene or otherwise shouldn't move freely.

I then created a few backdrop objects set up as obstacles and placed them on the same 16x16 level grid. To handle the collisions, I added these events:

Detector is facing direction Up + Detector is overlapping a backdrop:
Set Player MoveUp to 0.

Detector is facing direction Down + Detector is overlapping a backdrop:
Set Player MoveDown to 0.

Detector is facing direction Left + Detector is overlapping a backdrop:
Set Player MoveLeft to 0.

Detector is facing direction Right + Detector is overlapping a backdrop:
Set Player MoveRight to 0.

In testing that out, the collision worked, but if for any odd reason the detector bumped a backdrop object, sometimes it'd push the player out of the 16x16 grid, so I added one last event:

Player MoveUp = 0 + Player MoveDown = 0 + Player MoveLeft = 0 + Player MoveRight = 0:
Set Player Y-Position to Player Y-Position / 16 * 16, Set Player X-Position to Player X-Position / 16 * 16.

In other words, every time the player comes to a complete stop, it should snap back to the grid dimensions. When the player does get nudged out of place, it's not by more than a pixel or two, so snapping back into place on stop shouldn't be noticeable. Even though that's all a little event heavy, it worked like a charm for basic 4-directional grid movement, so I continued in adding 8-directional events.

MoveUp of Player = 0 + MoveLeft of Player = 0 + Repeat while Up Arrow is Pressed + Repeat while Left Arrow is Pressed:
Set Detector Position at (-16,-16) from Player, Set Detector Direction to Up-Left, Set MoveUp to 16, Set MoveLeft to 16.

MoveUp of Player = 0 + MoveRight of Player = 0 + Repeat while Up Arrow is Pressed + Repeat while Right Arrow is Pressed:
Set Detector Position at (16,-16) from Player, Set Detector Direction to Up-Right, Set MoveUp to 16, Set MoveRight to 16.

MoveDown of Player = 0 + MoveLeft of Player = 0 + Repeat while Down Arrow is Pressed + Repeat while Left Arrow is Pressed:
Set Detector Position at (-16,16) from Player, Set Detector Direction to Down-Left, Set MoveDown to 16, Set MoveLeft to 16.

MoveDown of Player = 0 + MoveRight of Player = 0 + Repeat while Down Arrow is Pressed + Repeat while Right Arrow is Pressed:
Set Detector Position at (16,16) from Player, Set Detector Direction to Down-Right, Set MoveDown to 16, Set MoveRight to 16.

That settled the basic diagonal movement, but then I had to handle collisions:

Detector is facing direction Up-Left + Detector is overlapping a backdrop:
Set Player MoveUp to 0, Set Player MoveLeft to 0.

Detector is facing direction Up-Right + Detector is overlapping a backdrop:
Set Player MoveUp to 0, Set Player MoveRight to 0.

Detector is facing direction Down-Left + Detector is overlapping a backdrop:
Set Player MoveDown to 0, Set Player MoveLeft to 0.

Detector is facing direction Down-Right + Detector is overlapping a backdrop:
Set Player MoveDown to 0, Set Player MoveRight to 0.

I tested collision again, and while 4-directional collision worked, the 8-directional collisions didn't work at all. They were super glitchy at first, but I figured that may have had something to do with event order. Since MMF was testing 4-direction first, it was likely firing those events when two directions were held at the same time, so I moved the 8-direction keytesting and collision information and ordered it to test before the basic 4-direction events. That worked a lot better, and the 8-direction collisions *almost* seem solid.

I still notice if I have two diagonally placed backdrop objects, the player can sneak right through the middle of them.
000X00
000X00
00X000
00X000
^^ If I have two backdrop objects placed like they're signified by 'X' in the ASCII diagram above, the player can walk right through the wall. That got me to thinking when two directions are held at once, I should probably be testing different positions depending on the player's direction.

So, in other words, if a player is moving Down-Right through the diagram, instead of testing (-16,16) from player, I should be testing (0,16) and (-16,0) from the player object instead, but there goes another 30 freakin' events just for movement and collision testing.

I have a feeling there's a much more simple way to achieve the same thing, but I'm just underthinking this. Does anybody have any ideas?

 
ChrisD> Employer: Say, wanna see a magic trick?
ChrisD> Employee: Uhh… sure, boss.
ChrisD> Employer: Your job! It just disappeared! Pack your things and leave! Pretty good trick, huh?

The_Antisony

At least I'm not Circy

Registered
  01/07/2002
Points
  1328

VIP MemberStarSnow
13th October, 2019 at 13/10/2019 09:47:09 -

Well, nobody seems to post around here anymore, but I answered my own question. I tried four different ways of implementing 8-direction grid movement and kept running into problems moving diagonally. Sometimes the detector would detect overlap properly, and sometimes it just wouldn't - allowing a player to move through diagonally set obstacles about 20% of the time.

It was too inaccurate and I couldn't find a reliable way to place the detector and test for overlap properly every time without creating eight separate detector objects always moving with the player. That's not very optimal and I don't like messing with unnecessary active objects if it's at all avoidable. I ended up settling for a four-direction grid movement, but the engine seems pretty solid, so I may as well post my events just in case somebody else needs it down the road.

So, I have the player active object and a detector active object. Both objects are 16x16. The Detector active contains most of the necessary variables. I'll list them below:
DisableMove: When set to 1, the player can't move. Useful during dialog and cutscenes.
GridX: The imension of the movement grid. In my case, it's set to 16.
GridY: The Y-dimension of the movement grid. In my case, it's set to 16 too.
CollideUp: If 0, detector has detected no overlap at 0,-16 from player active. If 1, overlap has occurred.
CollideDown: If 0, detector has detected no overlap at 0,16 from player active. If 1, overlap has occurred.
CollideLeft: If 0, detector has detected no overlap at -16,0 from player active. If 1, overlap has occurred.
CollideRight: If 0, detector has detected no overlap at 16,0 from player active. If 1, overlap has occurred.
MoveUp: If CollideUp = 0, MoveUp is set to GridY.
MoveDown: If CollideDown = 0, MoveDown is set to GridY.
MoveLeft: If CollideLeft = 0, MoveLeft is set to GridX.
MoveRight: If CollideRight = 0, MoveRight is set to GridX.
FullStop: Sets to 0 when MoveUp+MoveDown+MoveLeft+MoveRight all equal 0. Sets to one once a directional key is pressed and collision testing begins. Sets back to 0 once it's determined the player collides or has finished moving.

Originally, I had planned on working Detector's GridX and GridY variables into detector positioning, but all of the independent 'set player X pos/Y pos' events got to be excessive and I changed plans halfway through to set detector position relative to player position instead. If you have the patience, you could replace the events with something like 'Set Detector X Position to Player X position - Detector GridX' and 'Set Detector Y Position to Player Y position' as an example to move the detector to the left. That would allow for independent grid dimensions of 16x32 and other custom grid sizes for ISO engines and the like.

The below event is just a shortcut allowing me to test a single variable instead of testing that each of Detector's MoveVariables = 0 over and over again in multiple events. It saves a few cycles.

MoveUp = 0 + MoveDown = 0 + MoveLeft = 0 + MoveRight = 0 + Only One Action when Event Loops:
Detector - Set position at 0,0 from Player.
Detector - Set FullStop to 1.

Since I check that FullStop = 1 before MMF accepts any new keypress events, including "while key is pressed" isn't necessary. Each time FullStop changes from 1 to 0 and back to 1 again, MMF registers the same key press multiple times despite using "Upon pressing". That means the up arrow doesn't need to be tapped twice to move the player up two spaces. Holding the key down has the same effect. Eventually, I'd like to add joypad support and expecting players to tap D-pads multiple times to keep moving in a modern game is a hassle.

Detector FullStop = 1 + Detector DisableMove = 0 + Upon Pressing "Up Arrow":
Player - Set Y Position to Player Y Position / Detector GridY * Detector GridY.
Detector - Set position at 0,-16 from Player.
Detector - Set direction to "Up".
Player - Set direction to "Up".
Detector - Set FullStop to 0.
Activate Group "TestUp".

Detector FullStop = 1 + Detector DisableMove = 0 + Upon Pressing "Down Arrow":
Player - Set Y Position to Player Y Position / Detector GridY * Detector GridY.
Detector - Set position at 0,16 from Player.
Detector - Set direction to "Down".
Player - Set direction to "Down".
Detector - Set FullStop to 0.
Activate Group "TestDown".

Detector FullStop = 1 + Detector DisableMove = 0 + Upon Pressing "Left Arrow":
Player - Set X Position to Player X Position / Detector GridX * Detector GridX.
Detector - Set position at -16,0 from Player.
Detector - Set direction to "Left".
Player - Set direction to "Left".
Detector - Set FullStop to 0.
Activate Group "TestLeft".

Detector FullStop = 1 + Detector DisableMove = 0 + Upon Pressing "Right Arrow":
Player - Set X Position to Player X Position / Detector GridX * Detector GridX.
Detector - Set position at 16,0 from Player.
Detector - Set direction to "Right".
Player - Set direction to "Right".
Detector - Set FullStop to 0.
Activate Group "TestRight".

In plain English, each time a directional key is pressed, I reset either the X or the Y position of the Player active to ensure it's snapped to the correct grid position first, then I move the Detector in the position the player is attempting to move to. Before testing collisions by activating the corresponding inactive group, I set FullStop to 0 so MMF can't continue interpreting any more player key presses until FullStop = 0 again.

I then have four inactive group events; TestUp, TestDown, TestLeft, and TestRight. This is how they look:

TestUp:
Always + (NEGATE) Detector is overlapping a backdrop object:
Detector - Set CollideUp to 0.
Detector - Set MoveUp to Detector GridY.
Deactivate Group "TestUp".

Always + Detector is overlapping a backdrop Object:
Detector - Set CollideUp to 1.
Detector - Set MoveUp to 0.
Detector - Set FullStop to 1.
Deactivate Group "TestUp".

TestDown:
Always + (NEGATE) Detector is overlapping a backdrop object:
Detector - Set CollideDown to 0.
Detector - Set MoveDown to Detector GridY.
Deactivate Group "TestDown".

Always + Detector is overlapping a backdrop Object:
Detector - Set CollideDown to 1.
Detector - Set MoveDown to 0.
Detector - Set FullStop to 1.
Deactivate Group "TestDown".

Test Left:
Always + (NEGATE) Detector is overlapping a backdrop object:
Detector - Set CollideLeft to 0.
Detector - Set MoveLeft to Detector GridX.
Deactivate Group "TestLeft".

Always + Detector is overlapping a backdrop Object:
Detector - Set CollideLeft to 1.
Detector - Set MoveLeft to 0.
Detector - Set FullStop to 1.
Deactivate Group "TestLeft".

Test Right:
Always + (NEGATE) Detector is overlapping a backdrop object:
Detector - Set CollideRight to 0.
Detector - Set MoveRight to Detector GridX.
Deactivate Group "TestRight".

Always + Detector is overlapping a backdrop Object:
Detector - Set CollideRight to 1.
Detector - Set MoveRight to 0.
Detector - Set FullStop to 1.
Deactivate Group "TestRight".

I set each of these up in inactive group events so they're not constantly testing. I thought about using fastloops, and that likely could have resolved problems moving diagonally without including the limiting events below, but that's three separate OnLoop events for each directional collision test as opposed to two group events. I did some testing with fastloops and noticed I needed 3 onloop events and because I was testing so much in a fastloop, it caused other objects in game to visually "lag" for the lack of a better word. It was also causing player movement to look more glitchy and clumsy than fluid.

Controlling movement gets a bit hazy because without all the extra limiting events I'm about to get into below, MMF can end up grabbing two key presses at once adding 16 to two Move variables causing the player to move diagonally. I tried to make that work, but placing the detector properly to test for diagonal collisions wasn't working reliably even though my events made sense, so I eventually gave up.

Detector DisableMove = 0 + Detector MoveUp > 0:
Set Detector MoveLeft to 0.
Set Detector MoveRight to 0.
Set Detector MoveDown to 0.
Player set Y Position to Player Y + 1.
Subtract 1 from Detector MoveUp.

Detector DisableMove = 0 + Detector MoveDown > 0:
Set Detector MoveLeft to 0.
Set Detector MoveRight to 0.
Set Detector MoveUp to 0.
Player set Y Position to Player Y - 1.
Subtract 1 from Detector MoveDown.

Detector DisableMove = 0 + Detector MoveLeft > 0:
Set Detector MoveDown to 0.
Set Detector MoveRight to 0.
Set Detector MoveUp to 0.
Player set X Position to Player X - 1.
Subtract 1 from Detector MoveLeft.

Detector DisableMove = 0 + Detector MoveRight > 0:
Set Detector MoveDown to 0.
Set Detector MoveLeft to 0.
Set Detector MoveUp to 0.
Player set X Position to Player X + 1.
Subtract 1 from Detector MoveRight.

Effectively, when MMF notices one of the four MoveDirectional variables is larger than 0, it sets the three other move variables to 0, moves the player one pixel, and subtracts one from the corresponding MoveDirectional variable. Once all MoveDirection variables = 0 again, FullStop sets back to 1 and MMF can register another keypress.

With all the events out of the way, the only thing left over was to jump into the level editor and set the Detector's visual effect to Semi-Transparent with a coefficient of 255 to make it invisible.

I have a few ideas for expansion, but I didn't really know how to approach them. Since this is eventually going into an RPG engine I'm dinking around with, it'd be nice having a few events controlling speed from one grid position to the next so movement isn't always one pixel per game loop. That could be done with some kind of formula or by adding a 'Restrict Actions by calculation' event determining player speed based on stats or level.

Originally, I was planning on using the MoveIt extension, but after planning things out, I didn't see any sense in adding another extension to the project to do the same sort of thing I could do with basic events. If I decide to add followers or NPCs and Enemies with pathfinding or AI, that may become a necessity causing me to re-work the entire movement engine, but for now I'm settling on simple.

If I come up with more additions I feel are worth sharing, I'll keep posting - even though I'm basically talking to myself here.

Edited by The_Antisony

 
ChrisD> Employer: Say, wanna see a magic trick?
ChrisD> Employee: Uhh… sure, boss.
ChrisD> Employer: Your job! It just disappeared! Pack your things and leave! Pretty good trick, huh?
   

Post Reply



 



Advertisement

Worth A Click