Retro Style Platformer Issues (Jitters And Shaking BG Sprite

Discussion and feedback on Construct 2

Post » Tue Sep 30, 2014 4:55 pm

How are you doing your camera positioning? I've set up a system that works pretty well for me. The camera, or CamFocus object, will scroll smoothly to a position ahead of the Player object (Character). When it's moved within a certain range of the destination (between 39 and 41) I just snap it into the integer position "round( Character.X + 40 )". The player sprite itself is separate from the Character object and is always drawn at round(Character.X/Y) positions.

Image

For a camera that's fixed to the player's position at all times, it should be enough to just set camera.x/y to round(player.x/y) and set sprite.x/y to round(player.x/y). If you need a catch-up camera like I do, there's an extra trick you can do that I'll describe below.

Couple of cave-ats with this tho'.

One:
I'm modifying C2's layout.js to make it render to a point-sampled texture. This way I get sharp pixels on integer positions, and interpolated pixels on float positions, even though I've got sampling set to linear in project settings. Only use if you don't mind modifying layout.js every time you update C2. As far as hacking into the official code goes, it's discouraged by Ashley for obvious reasons but this change is entirely cosmetic so I'm pretty much 100% sure it's not gonna break anything.

Two:
For a pixel-art game it's not perfect. Like I said, if you just fix the camera position to the player's movement you shouldn't even need to use it. But with a catch-up camera both the player sprite and other moving graphics will get blurred by interpolation until the camera has moved into position. I found that this doesn't look so bad; it's a temporary blur and everything's moving pretty fast so it's not all that noticable.

Three:
You need to use low-quality scaling with layout scale = 1 (default) for it to work. But since you're doing a pixel-art game that shouldn't be a problem.

If you want to try it out, back up layout.js in exporters/html5 and replace these lines:

Line 540 (inside Layout.prototype.draw = function (ctx)):
if (ctx_changed)
{
layout_ctx["webkitImageSmoothingEnabled"] = this.runtime.linearSampling;
layout_ctx["mozImageSmoothingEnabled"] = this.runtime.linearSampling;
layout_ctx["msImageSmoothingEnabled"] = this.runtime.linearSampling;
layout_ctx["imageSmoothingEnabled"] = this.runtime.linearSampling;
}

... with these:
if (ctx_changed)
{
// ErekT mod:
layout_ctx["webkitImageSmoothingEnabled"] = this.runtime.pointSampling;
layout_ctx["mozImageSmoothingEnabled"] = this.runtime.pointSampling;
layout_ctx["msImageSmoothingEnabled"] = this.runtime.pointSampling;
layout_ctx["imageSmoothingEnabled"] = this.runtime.pointSampling;
}


And these lines:

Line 584 (inside Layout.prototype.drawGL = function (glw))
if (render_to_texture)
{
// Need another canvas to render to. Ensure it is created.
if (!this.runtime.layout_tex)
{
this.runtime.layout_tex = glw.createEmptyTexture(this.runtime.draw_width, this.runtime.draw_height, this.runtime.linearSampling);
}

// Window size has changed (browser fullscreen mode)
if (this.runtime.layout_tex.c2width !== this.runtime.draw_width || this.runtime.layout_tex.c2height !== this.runtime.draw_height)
{
glw.deleteTexture(this.runtime.layout_tex);
this.runtime.layout_tex = glw.createEmptyTexture(this.runtime.draw_width, this.runtime.draw_height, this.runtime.linearSampling);
}

... with these:
if (render_to_texture)
{
// Need another canvas to render to. Ensure it is created.
if (!this.runtime.layout_tex)
{
// ErekT mod
this.runtime.layout_tex = glw.createEmptyTexture(this.runtime.draw_width, this.runtime.draw_height, this.runtime.pointSampling);
}

// Window size has changed (browser fullscreen mode)
if (this.runtime.layout_tex.c2width !== this.runtime.draw_width || this.runtime.layout_tex.c2height !== this.runtime.draw_height)
{
glw.deleteTexture(this.runtime.layout_tex);
// ErekT mod
this.runtime.layout_tex = glw.createEmptyTexture(this.runtime.draw_width, this.runtime.draw_height, this.runtime.pointSampling);
}


Then restart C2. Good to go.

A bonus you get with this is the ability to turn point-sampling on/off during runtime. Just increase layout scale or turn on high-quality scaling to enable linear sampling. It looks exactly the same as with vanilla C2 as far as I can tell.
B
40
S
16
G
6
Posts: 543
Reputation: 7,649

Post » Thu Oct 02, 2014 12:52 am

I've actually seen these problems in other creation software. It seems to be a common problem when making pixel-accurate games with engines that are designed with higher resolutions in mind. Erek's potential solution is sophisticated sure, but most people probably shouldn't need to go to those lengths to eliminate all of the movement stutters. Risk seams or other undesirable side-effects by leaving pixel rounding off, or set it to on and constantly wrestle with every movable object in your game to ensure that every number is being rounded to an integer.

@Ashley Is it possible for behaviors like MoveTo or Sine to account for whether pixel rounding is set to on or off? Are they supposed to already do that? On my end, they seem to operate in fractions regardless of what project properties are set. If we can force these behaviors to always use integers with round(), surely it's possible to implement some kind of option that will just make them default to that?
B
9
S
1
Posts: 8
Reputation: 635

Post » Thu Oct 02, 2014 11:36 am

I actually did not encounter those problems, but maybe that was because of the interger letterbox scaling combined with low quality and not using the pixel rounding nor the round() of any sort.

@Amanita : unless I am unaware, the moveto behavior is not maintained at all by scirra so this is not a question to ask to ashley, but I agree that the pixel rounding seems pretty much useless.
Game design is all about decomposing the core of your game so it becomes simple instructions.
B
54
S
22
G
18
Posts: 2,123
Reputation: 17,150

Post » Thu Oct 02, 2014 11:59 am

My mistake, I meant ScrollTo. MoveTo is one of Rex's add-ons.

I haven't been using pixel rounding since having these issues crop up. Despite that, aside from the overt smoothness of everything I haven't really seen any seams or related problems pop up. I guess it's a plausible solution to just ignore that option nowadays? I'd like to say that conclusively, but I'm not as versed in Construct as other similar programs.
B
9
S
1
Posts: 8
Reputation: 635

Post » Thu Oct 02, 2014 12:15 pm

@ErekT - your code modifications make no sense, referring to property names that don't exist. It looks like it will have identical effect to set 'Sampling' to 'Point' in project properties, which sets linearSampling to false and turns it off, as opposed to accessing the non-existent pointSampling property which returns undefined which is probably also interpreted as false.

I still have no idea what people are talking about when they say things are jittery, and still nobody has provided a .capx to demonstrate. Everything has always worked perfectly from C2's side as far as I've ever seen. My best theory is that everything is working correctly and the perceived "jitter' is simply the difference between point and linear sampling. Linear sampling can look very smooth and elegant, especially when scrolling slowly, because it can render smoothly along sub-pixel positions. When you switch to point sampling, what you are telling Construct 2 to do is to turn off that lovely smooth linear sampling and revert to a nearest-neighbour filter. This can sometimes look comparitively "jumpy", since it will not render any sub-pixel positions - it will pick a side, and go with that. So scrolling slowly goes from a lovely smooth sub-pixel transition to occasional pixel-size jumps. I would agree this does not look so good, but by choosing point sampling you are literally asking for that to happen.

So in other words this is the normal, expected result in computer graphics. It's more to do with how digital rendering works than Construct 2 itself. That would also explain why @Amanita has observed it in other tools.
Scirra Founder
B
403
S
238
G
89
Posts: 24,654
Reputation: 196,155

Post » Thu Oct 02, 2014 1:20 pm

@Ashley:
What I though I did, and what appears to happen when I run it, was to modify the final render texture to use point sampling instead of linear sampling. That way, when low-quality scaling is turned on and layout scale is 1.0, no linear sampling is applied to any of the displayed graphics as long as they're drawn at integer values. It's meant as a way to allow switching between linear and point sampling during runtime. And it works well for what I intended it to do.

The reason I posted it here was because it also allows for sub-pixel smoothing when graphics are drawn at non-integers, something I thought could help in the OP's situation; draw at float positions when moving, draw at integers when not. Smooth, but blurred graphics when scrolling/moving.

Not sure what you mean by referring to property names that don't exist. Which ones?
B
40
S
16
G
6
Posts: 543
Reputation: 7,649

Post » Thu Oct 02, 2014 2:33 pm

I think what people want is for the positions of sprites on screen to be moved consistently along pixels without it jittering back and forth, like in Mario, Megaman, and any other pixel-based game. There must be an option to achieve this basic functionality used by all pixel games we are overlooking
B
4
Posts: 1
Reputation: 252

Post » Thu Oct 02, 2014 3:28 pm

Ciel wrote:I think what people want is for the positions of sprites on screen to be moved consistently along pixels without it jittering back and forth, like in Mario, Megaman, and any other pixel-based game. There must be an option to achieve this basic functionality used by all pixel games we are overlooking


I think deactivating pixel rounding does that. Unless I am wrong, could you shiw an exemple if that is the case where this appears?
Game design is all about decomposing the core of your game so it becomes simple instructions.
B
54
S
22
G
18
Posts: 2,123
Reputation: 17,150

Post » Thu Oct 02, 2014 4:03 pm

All personal opinion, take with a grain of salt: :P

I think the need to use delta-time just isn't very well suited for pixel art games unfortunately. Constantly fluctuating framerates and delta-time to compensate is a bad fit for smooth movement on limited pixel grids. The reason you don't see herky-jerky sprite and background movements in 2D console and arcade games is probably due in part to approximately zero of them using delta-time. They don't need to: The hardware they're running on stays the same and has a fixed, non-fluctuating framerate. In cases where too much stuff is going on for the cpu and/or gpu to handle, the entire thing just slows down.

The other thing that makes them run so smooth, and I'm just guessing here, is because they make sure to move all graphics by increments that line up nicely with the screen update rate. So they might move things by 1.0, 0.5, 1.5 etc pixel increments but *never* something like 0.15, 1.7, or 0.4.

Imagine an object moving at an even 0.4 pixels per update. If it gets drawn at integers only It'll take 3 updates before it'll appear to have moved 1 pixel, while it's actually moved 1.2 pixels. Another 3 updates it'll appear to have moved 2 pixels while actual moved distance is 2.4. But for the 3rd pixel distance travelled the game will only need 2 more updates to get to 3.2. So the visible movement update rate will be 3-3-2, 3-3-2, instead of 3-3-3 which is required for it to look completely smooth. Of course this gets even worse when you factor in fluctuating fps and delta-time.

I think what people want is for the positions of sprites on screen to be moved consistently along pixels without it jittering back and forth, like in Mario, Megaman, and any other pixel-based game.

Every tick: Set Player_Sprite.X/Y to round(Player_Sprite.X/Y)
Set Camera.X/Y to round(Player_Sprite.X/Y)

This works for me.
B
40
S
16
G
6
Posts: 543
Reputation: 7,649

Post » Mon May 16, 2016 12:22 am

@ErikT - I round my camera to what ever it is following, but If I change objects, I make sure I change them back before altering there positions. This is where I wish the engine had an option for it because updating all dynamic objects at the end of all your events before a render and then undoing those changes is a huge performance drain. If you don't undo the change then you are literally moving those objects which can cause problems.

If you have a "real"velocity expressed as a float and then calculate a "facade" velocity that works nicely with everyone that is calculated every tick based on dt... The biggest thing Iv'e noticed is that you need to have the larger component of velocity (x/y) be evenly divisible by the other. I don't think moving 1,1,2,1,2,1,1,2,1,2... and so on is as bad as visually rounding just x this tick and then just y the next tick... (x,y,x/y,x,y,x,x/y)... as this gives a literal stair step movement look instead of a diagonal look... anyway...

I'm a bit surprised that Ashley doesn't understand this. He certainty is smart enough, maybe he isn't thinking about it from an asthetic standpoint and only programming?. He understands that it does what it does, and why it does it, but doesn't see the problem with that solution... after all, that IS what you are asking for with pixel rounding. I understand where he is coming from, I think . But if he would compare, as you mentioned, the arcades, and early consoles to C2, he would notice that x/y never NEVER stair step. You can run an emulator at 0.1 speed and see Mario update x/y without the smaller component ever being syncopated. You can achieve this on a modern system with variable frame rate... you just have to emulate it mathematically. The place for that is inside the engine and not the event editor.
Image
B
33
S
11
G
2
Posts: 564
Reputation: 5,173

PreviousNext

Return to Construct 2 General

Who is online

Users browsing this forum: No registered users and 9 guests