Pixel Movement

by Forum_account
Pixel Movement
A pixel movement library for isometric and top-down maps.
ID:839165
 
I've been brainstorming on how I should go about a few things, and it's bothering me.



Problem 1:

I am simply trying to make a little shadow icon follow around underneath it's owner. Normally I would go with "Attach it to the icon!", but that doesn't look very well when you incorporate jumping. I tried some simple tricks but nothing seems to come out quite right. Right now I'm getting desperate, and have gone so far as to do this:
shadow
proc
at_owner()
if(x != owner.x) return 0
if(y != owner.y) return 0
if(z != owner.z) return 0
if(step_x != owner.step_x) return 0
if(step_y != owner.step_y) return 0
return 1
action(t)
#ifdef LIBRARY_DEBUG
if(trace) trace.event("[world.time]: start action:")
#endif

if(path || destination)
follow_path()
else
owner << at_owner()
if(!src.at_owner())
src.move_to(owner) // Note: This doesn't even work. The shadow doesn't move.

else
if(moved)
moved = 0
else
slow_down()

#ifdef LIBRARY_DEBUG
if(trace) trace.event("[world.time]: end action:")
#endif

I could really use some guidance here on how to keep things smooth without needlessly overriding the mob's movement. I'd much rather have the shadow take care of itself than have to be constantly accounting for it throughout my code.



Problem 2:

There is a strange collision problem with your movement that irks me. I mentioned this before on the forums. I have a large, dense object (In this case, a building), specifically:
The body of the building has a pwidth of 223 and a pheight of 105. It has a pdepth of 96.
The roof of the building has a pwidth of 239, a pheight of 120, and a pdepth of 150.

When I try to run into the sides, I can go partway into the building before being automatically shoved out. This isn't a high priority in the gameplay department, but in aesthetics it is quite high on the list.



Problem 3:

This ties into the last problem, referring to the building. I'm looking for the easiest way to make the building "slippery", so the mob won't land on the face of the building, and will keep falling instead. I'm sure this is easy, but after the previous two problems my brain is a bit fried.



Lastly, Problem 4:

I'm having some pdepth issues. I changed all of my custom elevation things over to pdepth, and I just want to make it do some simple tasks:
  • If there is something in your next step that has a higher pdepth than you, you can't step! (Somewhat works - refer to problem 2. Movement shouldn't happen, but instead it happens and then the system realizes that it shouldn't have and pushes them back out)
  • If you are on an object, you should obviously inherit their pdepth. (Works)
  • If your pdepth is greater than or equal to the pdepth of the object beneath you, you can still move. (Doesn't work. I can jump onto the roof of my building (150 pdepth), but I can't move anymore without jumping again.)
  • If you fall, a proc should be called when you land so you can easily incorporate things like fall damage. (I have not seen this, if this exists already or is easily implemented, please tell me where.)


Thank you for your time, and thank you for reading to this.
Problem 1: There could be timing issues because of how things tick. If the main PixelMovement game loop loops through your shadow before your mob, your shadow is updated before your mob moves. One solution to this is to make sure that the shadow ticks after the mob. You can probably do this by creating a movers list and looping through that instead of every mob in the world, as shown in the optimization demo. It should loop in order of the list, so if you add the shadow after the mob, the shadow should update after the mob in the same game-tick.

Also, you should probably use set_pos() instead of move_to(). The move_to() proc has some crazy pathfinding stuff involved that you definitely don't need. If you would use the mob to update the shadow, you'd do this:
mob/set_pos(nx, ny, nz=0, map_z=-1)
. = ..()
if(.) // if the mob's position was set successfully
// note: I'm not sure about this part
// what I'm trying to do here is put the shadow on top of anything that
// the player is standing on top of.
var shadow_z = 0
for(var/atom/o in below(src.pz))
shadow_z = max(shadow_z, o.pz + o.pdepth)
// ---
src.shadow.set_pos(nx, ny, shadow_z, src.z)

But if you want to keep it on the shadow, you'll have to do the other thing.

Problem 2: I don't see why what's happening to you. What you say makes me think the collision is squishy, but it's not meant to be squishy. Things have solid edges and collide precisely. Do you see this problem in the top-down-demo? (note to F_a: top-down is birds-eye-view, that demo is side-view)

Problem 3: Again, this doesn't happen to me. This library's pixel movement and collision has smooth edges in all axes. That is, you can slide along all sides of a box in all directions. There's nothing catching or causing you to stick.

Problem 4:
a. If there's something with pdepth>0 in your next step, normally you'll bump into it. If you're asking to have something like stair physics where you'll automatically hop up to step on it instead of bump into it, you can probably do that like this:
mob/bump(atom/a, d)
// if your top is above a's top, get on top
if(pz + pdepth > a.pz + a.pdepth)
pz = a.pz + a.pdepth

// it doesn't return a value, but ..() causes you to stop
else return ..()


b. I don't see why you'd want to do this, but if it works for you, that's good.

c. Normally, you can walk on top of things like this. It actually shouldn't be much different from walking on turfs, which act as an actual floor. By default, can_bump() returns 1 for turfs because you'd fall through the ground otherwise.

d. I'm not sure, but is bump() called? If so, I'd expect d=VERTICAL and a=whatever you landed on (floor, top of a building, etc)



Basically, there's some things that are happening for you that aren't standard in the Pixel Movement library. If you haven't already, you should look at the top-down-demo that comes with the library. If you're not sure about what collision's supposed to look like, all of the demos will show you that what you're seeing shouldn't be happening.
Albro1 wrote:
Problem 1:

Here's how I'd create shadows:
mob
var
obj/shadow/shadow = new()

set_pos()
..()

var/shadow_z = 0
for(var/turf/t in locs)
if(t.pz + t.pdepth > shadow_z)
shadow_z = t.pz + t.pdepth

shadow.loc = loc
shadow.pixel_x = step_x
shadow.pixel_y = step_y
shadow.pixel_z = shadow_z

obj
shadow
layer = TURF_LAYER + 0.1
icon_state = "mob-shadow"

Problem 2:

I'll look into this. I'm not sure why it'd happen because it uses obounds() to check for obstacles. Maybe there's some collision handling stuff that still assumes objects are small.

Problem 3:

I'm not sure what you mean. If you're using 3D movement then you just set the building's pdepth. When the player's icon is over the side of the building they won't actually be on the building's turf at all - their pixel_z just makes it look like they are.

If your pdepth is greater than or equal to the pdepth of the object beneath you, you can still move. (Doesn't work. I can jump onto the roof of my building (150 pdepth), but I can't move anymore without jumping again.)

This could also be caused by the object being large. Have you seen this problem with other objects?

If you fall, a proc should be called when you land so you can easily incorporate things like fall damage. (I have not seen this, if this exists already or is easily implemented, please tell me where.)

The bump() proc takes two parameters, an atom you bumped into and a direction. For vertical bumps the direction is VERTICAL. I don't think it distinguishes between moving upwards or downwards in the z direction, but you can check the vel_z to see what direction they were moving in.
problem 1: Personally, I use mobs as the turrets in the new version of Tanx I'm working on. What I do is just make a mob/owner variable and then call set_pos(owner.px,owner.py) in movement() for the turret. You could dot he same for a shadow. Works fairly well.

Problem 2: You only bump on objects that are within 1-2 tiles of you, so if it's a very large object you'll need to cut it up into smaller pieces for proper collision detection. This happens because an atoms location is based on the bottom left portion of it. so a large 320x320 box would have a position of 1,1 even if it stretches to 10,10. At 10,10 if you try to get all the objects in view(3) the box will not be detected even if you can see it on screen, since it's based entirely on where the "center" of the object is.

problem 3: Use a separate entity for both the "face" and "top" of the building and make the face object slippery/ unable to land on. Simplest way is using something that just adds vel_x or vel_y based on how close the mob is to the "slippery" object.

problem 4: You aren't supposed to modify pdepth, pdepth is how "tall" it is (in pixel movement anyway). You should be looking to modify "pz" which is your vertical position in pixel_movement. If you're changing pdepth then all you're doing is making yourself taller or shorter based on what you touch.
In response to Bravo1
Bravo1 wrote:
problem 1: Personally, I use mobs as the turrets in the new version of Tanx I'm working on. What I do is just make a mob/owner variable and then call set_pos(owner.px,owner.py) in movement() for the turret. You could dot he same for a shadow. Works fairly well.

Either movement() or set_pos() would work, as long as you call ..() before you set the position of the shadow (or turret). In the movement() proc, action() is called before you actually move, so if you set the position of the shadow there it'll be in the wrong spot after the move is performed.

Problem 2: You only bump on objects that are within 1-2 tiles of you, so if it's a very large object you'll need to cut it up into smaller pieces for proper collision detection. This happens because an atoms location is based on the bottom left portion of it. so a large 320x320 box would have a position of 1,1 even if it stretches to 10,10. At 10,10 if you try to get all the objects in view(3) the box will not be detected even if you can see it on screen, since it's based entirely on where the "center" of the object is.

Is that a BYOND thing?

problem 4: You aren't supposed to modify pdepth, pdepth is how "tall" it is (in pixel movement anyway). You should be looking to modify "pz" which is your vertical position in pixel_movement. If you're changing pdepth then all you're doing is making yourself taller or shorter based on what you touch.

You shouldn't have to modify pz either. It's automatically modified when you jump or fall.
In response to Forum_account
I'm pretty sure that if the large object had bounds set properly, its locs would include every tile it covers. Consequently, it should show up in view().
Is that a BYOND thing?

Yeah, unfortunately it does do that. All view(), range(), etc checks are based from the bottom left of the atom as it's position is. So, the best option is to cut it down into smaller bits.
bounds does work properly, but pixel_movement will only check the bounds of objects within view(1) of the mob, since it's based on the left(),right(),etc procs which only check 1 tile over initially.

It's mainly based on the fact that, for performance sake, pixel_movement doesn't check every atom in the world to see if you bump into it, only the ones nearby. Since Very large atoms are "centered" at the bottom left, trying to bump against the top right can become glitchy.
The library uses obounds() to check for obstacles, so that should be finding objects whose loc is multiple tiles away but whose bounds puts them nearby. But if it's BYOND that's not reporting the object is in obounds, there's not much I can do about that.

Either way, I'd split the object into multiple pieces or use turfs to represent it instead of objs.
I'm pretty sure that BYOND changed it in the native pixel-movement update. When an object is located on multiple tiles due to a bigger bounding box, it is in each tile's contents and each tile is in its locs.

Because of that, and that view() returns every visible turf in a specified range, along with the turfs' contents, big atoms will be found. It's also why there's been a couple bugs fixed to make sure an object is only in view() once.
In response to Forum_account
Forum_account wrote:
Here's how I'd create shadows:
Thanks a lot! I'll try it out.

I'll look into this. I'm not sure why it'd happen because it uses obounds() to check for obstacles. Maybe there's some collision handling stuff that still assumes objects are small.
Please do.

I'm not sure what you mean. If you're using 3D movement then you just set the building's pdepth. When the player's icon is over the side of the building they won't actually be on the building's turf at all - their pixel_z just makes it look like they are.
What I mean is that I don't want mobs landing on the face of the building, I want them shoved down if they try to land on it.

This could also be caused by the object being large. Have you seen this problem with other objects?
I have a tree with a pdepth of 64 that works just fine (It's somewhere between 64x64 and 96x96). So yeah, it must be the large object.

The bump() proc takes two parameters, an atom you bumped into and a direction. For vertical bumps the direction is VERTICAL. I don't think it distinguishes between moving upwards or downwards in the z direction, but you can check the vel_z to see what direction they were moving in.
Thanks a lot!

Sorry, but I have one more rather complex (at least for me) question.

My goal:

"Soundwaves"
  • When a sound, like a gunshot, happens, a sound_center object should be created.
  • sound_center will store a list of mobs created by it.
  • From sound_center, small, invisible mobs should expand in a enclosed circle. This means that new mobs will have to be created to keep the expanding circle closed.
  • However, if the sound hits something (atoms will have a soundproof variable that cuts the sound's velocity by a percentage, things like walls cutting them by 100%) like a wall, they are deleted.
  • The circle should not create new mobs to replace deleted ones. If the sound hits something like a door, the sound will go through (although considerably slower) and keep expanding from there. If you have a vivid imagination, you can see this. Imagine a ripple in water, that is stopped in all but a small place - the ripple goes through the small place and continues to expand.

I can handle the rest, like alerting things and the other specifics. It's creating and managing this circle that has me stumped.
In response to Albro1
Albro1 wrote:
What I mean is that I don't want mobs landing on the face of the building, I want them shoved down if they try to land on it.

I'm not sure I understand what you mean. If you're using 3D movement (the #define TWO_DIMENSIONAL statement in _flags.dm is commented out) this happens automatically. Mobs won't land on the face of the building, they'll land on the top. When they walk off the edge, they'll fall. They never stand on the side of the building. They may appear to be over top of the side, but only because of their pz coordinate.
In response to Forum_account
Forum_account wrote:
I'm not sure I understand what you mean. If you're using 3D movement (the #define TWO_DIMENSIONAL statement in _flags.dm is commented out) this happens automatically. Mobs won't land on the face of the building, they'll land on the top. When they walk off the edge, they'll fall. They never stand on the side of the building. They may appear to be over top of the side, but only because of their pz coordinate.

The building is in two different parts (as explained above) - the roof (The part you can walk on) and the face. A 3/4ths perspective view.
Here's an example of a building that's 64x32x48:
+---+---+---+---+---+
| | | | | |
| | +-------+ <------ py = 96, pz = 48
+---+---| |---+ <-- py = 128, pz = 0
| | | top | |
| | +-------+ <------ py = 64, pz = 48
+---+---| |---+ <-- py = 96, pz = 0
| | | face | |
| | | | |
+---+---+-------+---+ <-- py = 64, pz = 0
| | | | | |
| | | X | X | |
+---+---+---+---+---+ <-- py = 32, pz = 0

You can't stand on its face. When you're standing on the front edge of the top, you're at py = 64 and pz = 48. When you move down to py = 32 you'll begin to fall because the turf below you then has a lower pz + pdepth.

You're not just moving in two dimensions. When your mob is in a position that it looks like they're standing on the face of the building, they're actually not. They're on the turf's marked with Xs and have a pixel_z offset that puts them higher on the screen but they're not touching the building at all.