[Solved] pixel-perfect platform engine using loops

Get help using Construct 2

Post » Wed Apr 10, 2013 10:30 pm

@ashley plus I was thinking: it's not that complicated a code either, and it also simultaneously takes care of direction of animation.

If you'd rather you found an official workaround, I'm fine with it as well. Are you averse to me posting this as a fully commented example on the forum?

On the off-chance that someone is looking for a loop-based engine, since old-school games are often about that. I will comply whatever your decision.
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Post » Wed Apr 10, 2013 10:36 pm

Hmm... I guess this is a trickier problem than I thought.

But how does your custom engine solve this? Even using events, Construct 2 will still register a collision at the entrance of a 25px tunnel and stop you moving down it, right?

Surely the easiest thing to do is just use a 1px smaller player object with a transparent background. Then the 1px thing is hardly noticable.
Scirra Founder
B
359
S
214
G
72
Posts: 22,949
Reputation: 178,544

Post » Wed Apr 10, 2013 11:57 pm

@ashley: If I'm picking nits, then your solution is not ideal. It is noticeable when you need the player to be in a vertical tunnel and unable to move even the slightest amount to the left and right.

My custom engine (heavily borrowed from Damian's MMF2 code, but also from old-school engines) solves the problem like this:


1. player.Xspeed is set to a positive or negative number (in my example 2 or -2) for as long as you're holding down the right or left cursor key respectively.

Xspeed is a variable that 'charges up' the number of times the movement loop is going to run each tick. The higher this number (absolute value), the more times the movement loop is going to run, and the faster the movement is going to look.

2.
-Every Tick
--abs(xSpeed) is added to xLoopIndex, which is the number of times the movement loop is going to run each tick. So xSpeed more times for each tick.

3.
-if LoopIndex >= 1
--perform the "Xmovement" loop "xLoopIndex" times
---each time you perform the loop, move 'player' by exactly one pixel, in the direction of the movement, unless it's overlapping an obstacle, in which case set 'player' back one pixel and then stand still (xLoopIndex set to 0, xSpeed set to zero, and the loop doesn't run any more)

4.
Quite an important step: each tick, we need to deduct the number of times the loop has run from xLoopIndex on the last tick. We need to start each tick with a clean slate, we don't want the code to recalculate movement it's already calculated before. One way to do this would be to set xLoopIndex to 0, but this produces erratic movement.

In hindsight, this happens because xLoopIndex is not necessarily an integer at all times. By setting it to 0, we are discarding the decimals, without giving them a chance to add up to integers between ticks.

Therefore a better way to do it is to deduct only the integer portion of this value, and keep the decimals-if any- for the next tick. This can be stated like so: xLoopIndex = xLoopIndex - int(xLoopIndex)



Therefore: each time the loop runs, and 'player' isn't overlapping an obstacle, the engine moves 'player' for an integer number of pixels. If it hits an obstacle, it is pushed back by exactly one pixel, and since this happens before the loop ends, you never even see the overlap drawn on the screen.

And since the loop runs only as many times as necessary (that is, as long as you've pressed a cursor key for), the xPosition of the object is always known and controlled, and quantized to a pixel.

Not so with the built-in Platform behavior, which uses the bounding box collision to stop the movement when and only when it has to deal with the problem. My custom movement only moves 'player' by one pixel when there's a demand to do so, and pushes 'player' out of obstacles exactly once per loop, then kills loop.


Note that C2 *does* still register a collision at the entrance of a 25px tunnel, and *does* stop me moving down. But I took this in stride, as a very sharp collision detection algorithm.

This presented itself as a perfect 1-pixel gap around 'player' at all times, because a collission was detected 1 pixel before it occurred visually.

But since this gap was consistently and at all times exactly 1-pixel wide, it was the practical thing to do to just make the bounding box exactly 1 pixel smaller in all directions. Oversensitive collision solved, and since pixel-quantized control of 'player' is *already* also solved thanks to loops, the engine allows for pixel-quantized movement every time.

I'm a fake programmer, so I hope this made some sense to you.
I'm not sure I need a different built-in platform behavior or collision detection. The one we have works great for what I need. And collisions are working more than great, and are extremely reliable. I've done extensive tests.

Also, this has nothing to do with deficiencies in your code. Old-school platformers used to use loops all the time, didn't they? Construct is still miles ahead, since it allows me to focus on the essentials of game creation, instead of having to worry about collision algorithms and performance hits and the like. It's still pretty much plug-and-play.

Did I help any?..christina2013-04-11 14:31:17
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Post » Sun Apr 14, 2013 2:27 am

Apologies for bumping this post. @ashley have you found a better solution to this? Are you averse to me posting it as an example and/or tutorial?
You've said you're trying to discourage custom engines, if the platform behavior is adequate.
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Post » Sun Apr 14, 2013 12:12 pm

I think just making the player object slightly smaller than the tile size is the easiest way to go. If it's that or re-invent an entire engine, I'd just go with the smaller player.

You could also try making the collision box just a little shorter (only drag down the top two collision points a little). That should let you run in to tunnels only 1 cell high, but won't let you jump down pits 1 cell wide.Ashley2013-04-14 12:13:06
Scirra Founder
B
359
S
214
G
72
Posts: 22,949
Reputation: 178,544

Post » Sun Apr 14, 2013 12:19 pm

@ashley. No this will not do. Ok, I will refrain from posting this anywhere else, thank you.
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Post » Sun Apr 14, 2013 12:40 pm

@christina

I don't think Ashley is saying don't post your workaround ( if he is, for shame ), but is suggesting an easier alternative for the majority of people.

Ashley has commented many times that the platform behavior is extremely complex, and so it's understandable he would not want to re-invent it.

But as you have found, the default behavior is not always good enough or appropriate for the task. This applies to not just the platform behavior.

Please post your alternative, as I think many people will be interested to see it, even if they have no intention of using it.
If your vision so exceeds your ability, then look to something closer.
Moderator
B
120
S
28
G
68
Posts: 4,842
Reputation: 48,285

Post » Sun Apr 14, 2013 2:06 pm

@zenox98 & @ashley No no, I don't think it's shameful to discourage convoluted custom engines. I understand it, why create confusion and potential buggy behavior?

But on the other hand, I have been testing and comparing the built-in engine to my own custom engine, and I can't get the former to do what I want it to do, so I don't see any compelling argument against using the latter. It's consistent, bug-free, doesn't require fiddly bounding boxes, the logic behind the code is sound and its been fairly rigorously tested. I won't hesitate in building my game with it.christina2013-04-15 03:37:36
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Post » Sat Apr 20, 2013 3:13 am

So if I understand right this means that the basic platforming mechanisms are not "pixel-perfect"?

Actually, I looked for such a topic because I am a real noob and started the example "Platform 1 (simple movement)" and noticed that sometimes the player block lands on the black blocks with an empty line in-between. But only sometimes, like 50% of the time (whatever), but it IS annoying since this such an elemental mechanic. (although I understand that it's implementation might be very difficult)

So no easy solution for this I see?
B
3
Posts: 1
Reputation: 187

Post » Sun Apr 21, 2013 12:39 am

@Ashley @Tarsul As I understand it, the collision algorithm is pixel-perfect (and even sub-pixel perfect as I understand) and absolutely brilliant, you can make a pixel-quantized and pixel-precise engine with it with relative ease (check out my .capx above, you can use it if you like).

The built in platform movement still uses the pixel-perfect collision algorithm for its collision detection, but handles the result of the collision in a non pixel-quantized, non-pixel-perfect way. The visual outcome is that very small pixelart appears to either float or sink.

As Ashley suggests, you *may* be content to just set the bounding box for your sprite half a pixel inward (check out previous photo on this thread).

Also, for larger sprites, the problem is not noticeable at all.

The built-in platform movement has this huge advantage that it is great for prototyping. If you need more control, you can just make your own engine.
B
19
S
7
G
2
Posts: 188
Reputation: 2,846

Previous

Return to How do I....?

Who is online

Users browsing this forum: No registered users and 17 guests