ID:650496
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
As discussed in this feature request for per-pixel hit detection, the default functionality to step around the side of an object when using pixel movement would be generally beneficial (instead of coming to a complete stop when a single pixel from your hit-box catches the corner of a wall).
Agreed.
I agree, this would make a lot of sense, as the current functionality can be replicated easily, while the hopeful functionality would take a bit of work.
Wouldn't it be better to have an easier method of detecting which part of an object is colliding with another solid? Some reactive proc like PixelBump(atom/Obstacle, dir, x_left, x_right, y_bottom, y_top) where Obstacle is the thing you're colliding with, dir is the side you're colliding with, and the other four variables are used to determine the surface of the collision area.

From there you could easily slide it as you wanted, but it would also have the benefit of being used for other things.
Such a proc wouldn't be all that difficult to create ourselves (you already know what you're bumping into in Bump(), the rest is just simple math on step_x/y and bound_x/y), and it wouldn't handle the more complex part of a side-stepping system; which is basically creating 2 additional partial hit-boxes on either side of the collision point and then testing movement on them to see whether you should bother side-stepping at all.
In response to Falacy
Falacy wrote:
Such a proc wouldn't be all that difficult to create ourselves

Just like a sliding feature, right? With the PixelBump() proc I just suggested it would be a simple matter of measuring the surface area to check whether or not the atom should slide. You'd be able to do what you wanted to with the added benefit of having a proc which could be used for other things rather than just sliding.
In response to SuperAntx
SuperAntx wrote:
Just like a sliding feature, right? With the PixelBump() proc I just suggested it would be a simple matter of measuring the surface area to check whether or not the atom should slide. You'd be able to do what you wanted to with the added benefit of having a proc which could be used for other things rather than just sliding.

As I explained in the post that you were responding to, it is not easy, nor is your proposed example sufficient. Your example basically only handles the simple part that you are requesting. BYOND games are (most commonly) composed of 32x32 tiles, just because you are hanging 90% off the side of one wall tile doesn't mean that there isn't another wall tile directly next to it that would be negating your side-step.

EDIT: Also, regardless of how simple this may or may not be to implement ourselves, it is a feature that any competent game engine should offer by default.
Ah, I see what you mean. That's definitely more complicated.

If you were aware of everything you're bumping into it could work.
It's a behavior you might want, but probably not by default. For a sidescroller, you might want it for walking up steps:

+---+
|mob| ##
|-->| ####
+---+ ######
#############

Since the steps are small the player automatically walks up them, but this same behavior might not be desirable in all situations:

 +---+
| |
|-->|
+---+####
####
####
####
/\/\/\####
##########

If the player narrowly misses the jump, this sliding would automatically move them up onto the platform. Yet, at the same time, it could turn close landings into misses:

  +---+
| | |
| V |
+---+
####
####
####
####
/\/\/\####
##########

The player would be nudged to the left since they are only hitting a few pixels of the platform.

The way to go about this is to add whatever helper procs you'd need and implement this in the Bump() proc yourself. With the right helper procs it should be just a few lines of code.

EDIT: Also, regardless of how simple this may or may not be to implement ourselves, it is a feature that any competent game engine should offer by default.

An engine doesn't have to handle everything for you (ex: some engines just handle graphics). BYOND handles graphics, networking, and user input, but there's no reason it has to handle all aspects of a game. You can use BYOND to create a game engine and it might make sense for that engine to include these types of features, but for BYOND itself it's best to provide the necessary helper procs so people can easily create whatever they need for their game.
In response to Forum_account
Forum_account wrote:
It's a behavior you might want, but probably not by default. For a sidescroller, you might want it for walking up steps:
Since the steps are small the player automatically walks up them, but this same behavior might not be desirable in all situations:
If the player narrowly misses the jump, this sliding would automatically move them up onto the platform. Yet, at the same time, it could turn close landings into misses:
The player would be nudged to the left since they are only hitting a few pixels of the platform.
This should be easily customizable with something like side_step_width/height. Since the mobs bound_width and bound_height are commonly different, you would also want 2 setting for the minimum overhang distance. You would simply set their side_step_height=1 and side_step_width=0. You could even easily dynamically change these while the player was jumping to change the effect of their movement against edges.

The way to go about this is to add whatever helper procs you'd need and implement this in the Bump() proc yourself. With the right helper procs it should be just a few lines of code.
The problem is, there aren't helper procs that are going to be helpful here. What Antx is suggesting would only handle half the problem, and not the important/difficult part. Unless they were going to add some "helper" proc that tested movement on temporary hitboxes... And even then, you would have to do quite a bit of work using those "helper" procs to get such a simple system setup, a system that should be built in now that pixel movement is.

An engine doesn't have to handle everything for you (ex: some engines just handle graphics).
It doesn't have to, but the more it handles the better.
Falacy wrote:
The problem is, there aren't helper procs that are going to be helpful here. What Antx is suggesting would only handle half the problem, and not the important/difficult part.

What's the difficult part? It should be easy to write a proc that uses obounds() to find all obstacles on one side of the player and see how much of that side the obstacles overlap.
In response to Forum_account
Forum_account wrote:
What's the difficult part? It should be easy to write a proc that uses obounds() to find all obstacles on one side of the player and see how much of that side the obstacles overlap.

Bump()
//Calculate overhanging bounds
//Create, size, and position temporary objects to represent the overhanging bounds
//Test movement on those temporary objects (which may not even be accurate if there is special movement)
//Step player in largest overhanging area that had a successful test move
+---+o
| |o
|-->|o
+---+#

+---+#
| |o
|-->|o
+---+o

When a mob bumps something, use obounds() to check each space occupied by the Os. If the space to the left of the obstacle is open, shift the mob left. If the space to the right of the obstacle is open, shift the mob right. You just need a proc to check if there's an obstacle in each space and a proc to shift the mob left/right (based on the direction they're facing - shifting left while facing SOUTH means you move to the right).

With the right helper procs (which are easy to create using obounds) this ends up being four lines of code in the Bump() proc:

mob
step_size = 4
var
slide_dist = 4

Bump(atom/a)
if(front_left(slide_dist))
shift_left(slide_dist)
else if(front_right(slide_dist))
shift_right(slide_dist)
mob
proc
shift_left(d)
if(dir == EAST)
step_y += d
else if(dir == WEST)
step_y -= d
else if(dir == NORTH)
step_x -= d
else if(dir == SOUTH)
step_x += d

shift_right(d)
if(dir == EAST)
step_y -= d
else if(dir == WEST)
step_y += d
else if(dir == NORTH)
step_x += d
else if(dir == SOUTH)
step_x -= d

front_left(d)

var/list/atoms

if(dir == EAST)
atoms = obounds(src, bound_width, d, -bound_width + 1, -d)
else if(dir == WEST)
atoms = obounds(src, -1, 0, -bound_width + 1, -d)
else if(dir == NORTH)
atoms = obounds(src, 0, bound_height, -d, -bound_height + 1)
else if(dir == SOUTH)
atoms = obounds(src, d, -1, -d, -bound_height + 1)

for(var/atom/a in atoms)
if(a.density)
return 0

return 1

front_right(d)

var/list/atoms

if(dir == EAST)
atoms = obounds(src, bound_width, 0, -bound_width + 1, -d)
else if(dir == WEST)
atoms = obounds(src, -1, d, -bound_width + 1, -d)
else if(dir == NORTH)
atoms = obounds(src, d, bound_height, -d, -bound_height + 1)
else if(dir == SOUTH)
atoms = obounds(src, 0, -1, -d, -bound_height + 1)

for(var/atom/a in atoms)
if(a.density)
return 0

return 1
That seems to have a lot of problems.
  • You only seem to be checking a single pixel worth of atom collision at a time. You would want to make sure there was at least enough open area for the player to effectively move into.
  • As mentioned above, you would want to check a min distance to make sure the player can fit through the hole, but you would also want to move them this min distance, so they don't get stuck on the other side of it.
  • You are using the slide_dist var for pretty much everything. This should be cut into two vars for vertical and horizontal overhang requirements, and another (maybe 2) for how far the side-step would move you.
  • You aren't actually "moving" the player or checking if they can move in the side direction, you are just bumping their step_x/y
  • I'm not sure how much I would consider those "helper" procs, as they are doing practically everything. If BYOND was going to go through the trouble of implementing every calculation except the side-step itself, they might as well include that too.
I've never heard of a game engine altering basic rectangular collisions in this way. I think it might even look strange, if the mob suddenly moves an extra 'x' pixels faster one frame to get around an edge (especially if they weren't already moving along that axis).

I think typically this is handled with a circular collision shape, which gives a pretty smooth transition. You could do a simple "lame" collision response that just pushes the object out in the direction of the <player.pos - wall.pos> vector. Or you could calculate the collision normal and break the overlapping movement into tangential and normal components (you would throw out the normal component, but move them along the tangential). Then you would have to run through the collisions again in case it moved into a different dense object.

Anyways, I don't think adding more variables to the /atom/movable type is the way to go.
In response to Falacy
Falacy wrote:
You only seem to be checking a single pixel worth of atom collision at a time. You would want to make sure there was at least enough open area for the player to effectively move into.

There's no way to quantify what is "at least enough open area".

The other way to approach this is to have each turf determine if bumping into it should cause players to slide around it. For example, when you place /turf/doorway on a tile, it'll tell it's neighbors to make mobs slide around them to make it easier for players to fit through the doorway.

As mentioned above, you would want to check a min distance to make sure the player can fit through the hole, but you would also want to move them this min distance, so they don't get stuck on the other side of it.

You can make the front_left/right procs return the number of pixels you need to move and pass that to shift_left/right. It won't change the Bump() proc much at all.

You are using the slide_dist var for pretty much everything. This should be cut into two vars for vertical and horizontal overhang requirements, and another (maybe 2) for how far the side-step would move you

This is another change you can easily make that doesn't change the complexity of the Bump() proc.

You aren't actually "moving" the player or checking if they can move in the side direction, you are just bumping their step_x/y

You can call Move() to make them shift left or right if you'd like.

I'm not sure how much I would consider those "helper" procs, as they are doing practically everything.

That's because this is a very simple task and there's not much to do. The helper procs don't even do that much, obounds() does.
In response to Forum_account
Forum_account wrote:
There's no way to quantify what is "at least enough open area".
The players bound_width/height would determine how much open room is necessary.

You can make the front_left/right procs return the number of pixels you need to move and pass that to shift_left/right. It won't change the Bump() proc much at all.
This is another change you can easily make that doesn't change the complexity of the Bump() proc.
So if all this was built in and working properly, I wouldn't have to change my involvement? That's kind of the point, isn't it?

You can call Move() to make them shift left or right if you'd like.
You should pretty much never be directly changing step_x/y, unless you're trying to do something like a pixel locate(), it ignores all hit detection and if used for "normal" movement is likely to cause problems.

That's because this is a very simple task and there's not much to do.
If its so simple, how did you manage to get every part of it wrong?
In response to DarkCampainger
DarkCampainger wrote:
I've never heard of a game engine altering basic rectangular collisions in this way.
We discussed this a bit in that other topic. 3D engines sort of handle this on almost any collision, because of basic physics and the collision not usually being at an exact angle. Even if most 2D engines don't (not sure if they do or not) practically all 2D games offer a similar feature.

I think typically this is handled with a circular collision shape, which gives a pretty smooth transition.
Well, BYOND doesn't have circular hit-boxes, and it would be a generally similar concept either way.

Anyways, I don't think adding more variables to the /atom/movable type is the way to go.
Its not like you would have to use them.
In response to Falacy
Falacy wrote:
We discussed this a bit in that other topic. 3D engines sort of handle this on almost any collision, because of basic physics and the collision not usually being at an exact angle. Even if most 2D engines don't (not sure if they do or not) practically all 2D games offer a similar feature.

They're using circles / cylinders, not nudging axis-aligned rectangles. Older, semi-tile-based games like zelda might have used a method like the one you're suggesting, but I think it'll become unwieldy very quickly as you try to make exceptions for edge cases (a few of which you were able to quickly identify in FA's example)

Well, BYOND doesn't have circular hit-boxes, and it would be a generally similar concept either way.

Well, if you can get a soft-coded version of it working, I think you'll find nudging them into the open space looks pretty bad. Then again, maybe it won't be noticeable.

Its not like you would have to use them.

It just seems like a lot of clutter for such a specific feature. On its own, two more variables doesn't seem like much, but if you keep designing new features to just build on the base types, they'll become unmanageable. If BYOND starts implementing some more physics options, it would probably be worth making a /shape datum to track stuff like this.
I plugged FA's code into Simple Move and tweaked a few things. It works pretty well.

The only tweaks I had to do was change the bumping distance to the player's movement speed, it looks more natural that way. I also prevented the sliding from activating while moving diagonally.
Page: 1 2