Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify smooth camera movement in pixel perfect games #6389

Closed
Tracked by #86837
fractalcounty opened this issue Mar 2, 2023 · 152 comments
Closed
Tracked by #86837

Simplify smooth camera movement in pixel perfect games #6389

fractalcounty opened this issue Mar 2, 2023 · 152 comments

Comments

@fractalcounty
Copy link

fractalcounty commented Mar 2, 2023

Describe the project you are working on

A low-res 2D platformer with a high-res UI, smooth camera movement, and zoom.

Describe the problem or limitation you are having in your project

In 3.0x+, the common method for making pixel perfect games with a high resolution UI was fairly straightforward. You would toggle the 2D stretch mode, set your project resolution to something like 1920 x 1080, and throw your game inside a viewport node at a desired base resolution (such as 640 x 360). If you wanted take it a step further and implement a smooth camera, a common solution was to use a framebuffer shader with custom camera logic. Although a bit cumbersome, it did the trick well enough.

In 4.0, it's no longer this simple. Implementing a high-res UI with a low-res game is doable, but every known method for smooth camera movement I could find no longer works as effectively in 4.0. Additionally, 4.0 introduces even more factors that impact how pixels are displayed in 4.0, each creating dozens of different combinations that produce vastly different, loosely documented, and often undesirable effects. While not all specific to 4.0, here are some factors I can think of:

  • Viewport stretch settings in project settings, also may cause sprites to render incorrectly when combined with subviewports as seen with #61301
  • Viewport and window override resolution in project settings
  • "Resize" toggle in project settings (may contribute to pixel distortion as per #62869)
  • Size and Size 2D override in SubViewport inspector
  • "Snap 2D Transforms to Pixel" and "Snap 2D Vertices to Pixel" in project settings, which introduces jitter as per #71074 as well as #63185, blur as shown in #66527, as well as undesirable scaling and rotation behavior as seen with #60443
  • "Snap 2D Transforms to Pixel" and "Snap 2D Vertices to Pixel" in the SubViewport node inspector, which may induce blurring with movement #66527 and rotation #57221
  • Texture filtering options for sprites as well as subviewports
  • The “centered” toggle for objects, especially sprites with odd dimensions
  • Editor grid pixel snap (which still occasionally places objects at non-integer positions)
  • Camera node smoothing options (seems to always introduce jitter to some degree)
  • Camera and player movement (ensuring that position is rounded to an integer, or vice versa)
  • Calculating camera or player movement in _physics_process vs _process, see #71226
  • Collision shapes being offset at subpixel values at runtime
  • Physics interpolation (not implemented in 4.0)
  • The position of the game window (causes unpredictable sprite bleed as shown in #67164?)
  • Usage of integer scaling (not a feature in the engine, here's a community made option.)

To me, this is a painful amount of variables to deal with for such a common use case. I have tried every combination of these options alongside upscaling shaders, pixel buffers, custom camera smoothing, viewport textures, and more. The resulting process is a maddening game of cat-and-mouse in which you're constantly balancing jitter, blur, and sprite distortion while never quite eliminating one or the other.

This is the closest I was able to get before throwing in the towel. I achieved smooth camera movement on the sides of the screen, but it introduces pixel distortion. Enabling pixel/vertices snap gets rid of the pixel distortion, but introduces blur on everything. I'm not sure which is worse, so I just avoided using it.

2023-03-01.20-40-03.mp4

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The exact solution to this problem is complicated, as it is most likely a complex combination of intentional engine design, user error, and lack of documentation. However, I think a few things would help in this regard.

Before mentioning any in-engine solutions, it's worth mentioning that a detailed and precise collection in the docs outlining the best practices for pixel perfect games, especially in regards to smooth camera movement or zoom, would help a great deal without needing to touch the engine itself. It wouldn't fix the inherent issue of complexity here, but combined with some sort of "hybrid pixel perfect" starting template, it would be a decent remedy.

In terms of in-engine solutions, a few preexisting proposals such as integrated integer scaling would help alleviate this problem to some degree. However, I think a "PixelCamera2D" node or something similar designed specifically for this common use case would be wildly beneficial.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

An example would be a camera node called "PixelCamera2D" that would allow functionality for sub-pixel perfect smooth scrolling in 2D games. This concept isn't new or particularly complicated, and this write up by Daniel Ludwig is a perfect starting point for how it could be implemented. In essence, it would just be a standard camera with extended framebuffer capabilities that would be used for low-res subviewports. The issue with this approach is that it isn't perfect (especially with parallax) and doesn't sufficiently mitigate jitter or distortion in 4.0+ for some reason.

An alternate approach may be necessary that avoids using a pixel buffer entirely that utilizes the core engine to achieve smooth camera movement in some other way. This goes well beyond my scope, but definitely worth looking into.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I imagine it would be used very frequently, at least by indie developers who work with pixel art a lot. It could very easily optionally just not be used.

Is there a reason why this should be core and not an add-on in the asset library?

Normally just implementing that method on a per-project basis or as an addon would be more than sufficient, but as previously mentioned, things have changed quite a bit. Here's a few reasons why I believe this should be a core feature:

  • Pixel perfect games (and ones with smooth camera movement by extension) are an absurdly common use case and having that functionality available out of the box would be a game changer for quality of life
  • Godot 4.0 introduces a plethora of new changes and modifications to the render server that makes dealing with this issue unreasonably cumbersome as a user, let alone anybody working with the core engine.
  • This is a frustrating issue that I imagine many users will have to deal with, and I'm not sure it will ever be completely addressed through an addon any time soon (especially if it needs to be addressed through additions to the core engine)
@fractalcounty
Copy link
Author

fractalcounty commented Mar 2, 2023

Little update for anyone stumbling upon this via search engines: Reddit user /u/golddotasksquestions provided an excellent step by step write-up on how to combine low res UI with low-res games using viewports in 4.0.

As you can see, the process is pretty janky and unintuitive by Godot standards- especially for such a common practice in indie games. It's also worth mentioning that this probably won't help with smooth camera movement or alleviating jitter unless you're willing to implement a cocktail of features on top of it such as physics interpolation, custom camera logic, as well as a pixel buffer. I've tried everything I could find so far and haven't had much luck, but your mileage may vary.

My biggest concern about this whole process is the amount of new users that are going to inevitably google something as basic as "how to set up pixel perfect smooth camera in godot 4" and be faced with 3 or 4 results consisting of wildly different workarounds that don't even work anymore. For that, I think this is a pretty crucial usability issue that needs to be addressed in some shape or form.

@Calinou
Copy link
Member

Calinou commented Mar 2, 2023

Can you upload the project that was used to create the video somewhere? This could be useful to try to make it work correctly.

Also, was the video recorded with integer window scaling (or a sharp bilinear shader applied on a SubViewport)?

@Calinou Calinou changed the title A simplified process of achieving smooth camera movement in pixel perfect games Simplify smooth camera movement in pixel perfect games Mar 2, 2023
@fractalcounty
Copy link
Author

Also, was the video recorded with integer window scaling (or a sharp bilinear shader applied on a SubViewport)?

Working on cleaning up the project now. The video was recorded with manual integer window scaling; aka setting the project resolution to 1920x1080, setting the subviewport size to 640x360, and setting the subviewport scale to 3x. Bypassing the subviewport scale entirely and using the built in editor scaling in the settings achieves the same effect.

@bitbrain
Copy link

bitbrain commented Mar 4, 2023

The current workaround with sub viewport is not optimal, as the editor only displays content in the bounding box of the sub viewport and it is not possible to drag & drop nodes into the scene. Having a large scene (e.g. TileMap) is very difficult to edit/deal with on a pixel perfect setup. The workaround is to navigate to the scene inside the SubViewport directly and edit the level from there (needs to be its own scene file). Especially for larger games this process is painful.

PixelPerfectCamera2D hopefully addresses that issue, just like Camera2D from an editor perspective, so the scene itself is still editable/fully viewable.

@HybridEidolon
Copy link

This issue encompasses far more individual issues than what the title suggests.

The viewport-upscaled-to-screen technique still works just fine in Godot 4.0, for both 3D and 2D, so this convention is untouched.

For @bitbrain's problem, this is easily solved by a runtime Autoload script which arranges the SceneTree to handle your viewport setup, so that it's not part of your scenes at all. You'd want autoloads in a larger project that needs to manage game state in a consistent way anyway (think scene transitions, persistent state across scenes, networking, etc). I usually set up a global autoloaded state machine that bootstraps the selected play-in-editor scene into itself to manage the game while still being able to start from a specific scene, 2D or otherwise.

The "smooth 2D camera" technique is actually fairly specific to very small viewport resolutions and the specifics of how it's implemented could practically vary between games (custom camera solutions?). I'm not sure if I would want such an effect for something that is mimicking a real retro console. This specific method only applies to a particular subcategory of a category of 2D games and thus I don't think it makes sense to have it as a core feature.

For coordinate snapping, I use this script in addition to the vertex snapping option, and it mostly works fine. In combination with Nearest filtering, you will get about 90% of the way there. Counterintuitively, you do not want to snap the Camera2D's coordinates when using this approach, otherwise you will get the jittering seen in those issues because the rounding directions across different items will not always match.

extends Node2D

class_name PixelSnapped

# usage: make this node the parent of a CanvasItem you want snapped to worldspace integer boundaries
# only apply this to purely visual node hierarchies

func _process(delta: float) -> void:
    var parent = get_parent()
    if parent == null:
        return
    position.x = round(parent.global_position.x) - parent.global_position.x
    position.y = round(parent.global_position.y) - parent.global_position.y

The editor failing to snap to integer coordinates in some scenarios is definitely a bug. If you need to, you can write tests in your game's test framework to verify that scenes have no nodes on subpixel coordinates, which will catch any project member's mistakes too.

I can't imagine how having collision shapes snap to pixels would work. Not even retro console games did this; they often used fixed point arithmetic ("subpixels") when integrating physics, but didn't allow static objects to exist at subpixel coordinates. Changing this would require a completely different physics model. A given project might want that, but I don't think this makes sense as a core feature.

The problem with sprite texture filtering exists regardless of engine. In Godot 4 you can set the default filtering to Nearest No-Repeat to alleviate this, but you're still ultimately on the hook to ensure your materials have the proper filtering settings. Once again, since Godot is general purpose, there are things you are ultimately responsible for deciding how to accomplish.

Overall I feel the story here is that Godot is a flexible engine serving a lot of interests and there is no one-size-fits-all option when it comes to rendering in 2D (IMO there never has been, but Godot comes closer than any other option I've seen). This is a problem that existed before 4 and will continue to exist forever. Asking it to serve a specific niche like this introduces a lot of unnecessary complexity to the core.

@bitbrain
Copy link

bitbrain commented Mar 5, 2023

@HybridEidolon a GDExtension providing a PixelPerfectCamera2D node can always be an alternative, in case it is a too specific use case for Godot itself. However, I am not sure if the Godot C++ API allows for that (yet).

Asking it to serve a specific niche like this

I would not call this problem niche, considering how many pixel perfect games are out there. I have no quantifiable data to back this up, though.

EDIT @HybridEidolon a bit offtopic but could you share an example of how you implemented this?

I usually set up a global autoloaded state machine that bootstraps the selected play-in-editor scene into itself to manage the game while still being able to start from a specific scene, 2D or otherwise.

@fractalcounty
Copy link
Author

fractalcounty commented Mar 5, 2023

@HybridEidolon I would like to clarify that the list I included in the post isn't a collection "changes the core engine needs" or "things that need to be fixed", it was just a list of factors that the end user has to battle with when approaching a common use case like this. Some of them may or may not be impacted by open issues, which is why I linked them as they only add to the complexity. This list has only grown since 3.x, which complicates things considerably.

The "smooth 2D camera" technique is actually fairly specific to very small viewport resolutions and the specifics of how it's implemented could practically vary between games

I mentioned this specific implementation because it covered a wide variety of use cases in 3.x, but is no longer as effective under identical conditions in 4.0. Like I said, it isn't perfect and is in no way a comprehensive solution. However, I did find success in tweaking it for a wide variety of different resolutions for what it's worth.

This specific method only applies to a particular subcategory of a category of 2D games and thus I don't think it makes sense to have it as a core feature.

I'm not suggesting that it specifically should be implemented into the engine as a core feature, it's just an example of one of many common techniques that previously worked. Whether or not it's a common use case ultimately comes down to opinion, but that still doesn't impact the issue of complexity that many will have to deal with.

In terms of the other suggestions you made such as using an autoloaded state machine to dynamically arrange and handle viewports or using a script to handle vertex snapping- I would personally argue that these are cumbersome workarounds that shouldn't be required in the first place. These aren't just practices for very specific and niche scenarios, these are practices that apply to pixel perfect games as a whole- which highlights the whole usability problem even more.

This is a problem that existed before 4 and will continue to exist forever. Asking it to serve a specific niche like this introduces a lot of unnecessary complexity to the core.

This isn't a matter of asking "I want Godot to implement a core engine feature that that fits the exact needs of my game". It's a specific example of usability that wasn't great to begin with that was worsened in 4.0. Adding complexity to the core is the opposite of what I'm suggesting- this is more about improving usability in general.

Additionally, it's not just the camera smoothing, it's that the Camera2D and viewport nodes are just flat out frustrating to work with in pixel perfect games. There's too many points of failures and long-standing "unsolvable" issues that have only worsened in 4.0. If these usability issues are inherent to the engine and will continue to exist forever, then a dedicated solution makes sense in my opinion. That goes beyond just having a smooth camera.

At the end of the day, the workflow for working with pixel perfect games is significantly more complicated in 4.0, and borderline unfeasible for more specific use cases like the one I initially described. The line between user error and unintended behavior is far too blurry at the moment, as issues with jitter seem to affect low-res games across the board to some degree.

I now realize that this problem likely goes beyond what I initially created this issue for. If I could guess, it's probably a cacophony of bugs, user error, and lack of documentation that all contribute to the same exceedingly complicated problem. However, I still think it's a crucial issue that needs to be addressed in some way.

@HybridEidolon
Copy link

I would not call this problem niche, considering how many pixel perfect games are out there.

Niche in the sense that it has very specific rendering requirements that don't gel well with any other "style", not necessarily in popularity. This has always been a difficult problem with modern rendering and will continue to be. Even moreso for super low-res art.

a bit offtopic but could you share an example of how you implemented this?

I don't have an example on hand, but the pieces you need are described in the documentation. The SceneTree's root node has 1 child node which is the "current scene" and then all of your configured Autoload child nodes when the game starts. You can then move the nodes around however you need them arranged at runtime and implement your own "change scene" functions to accommodate. In Godot 4, the SceneTree root node is a Window. It may help to open the remote scene tree debugger while the game is running to visualize how the root hierarchy is arranged.

In terms of the other suggestions you made such as using an autoloaded state machine to dynamically arrange and handle viewports or using a script to handle vertex snapping- I would personally argue that these are cumbersome workarounds that shouldn't be required in the first place.

The former is what makes Godot uniquely powerful compared to contemporaries and I disagree that it is a "workaround". No other engine I've seen gives you as much control over the runtime scene tree as Godot. Learning how these tools work makes Godot significantly more powerful than it initially appears. The current documentation is a little sparse, but it does point out this flexibility.

The latter is just one method of implementing what you need; you could perform the same logic by putting all your snapped visual nodes into a SceneTree group and iterating over them in an Autoload so you don't have to litter your scene configurations with these nodes. I would favor solutions that don't require specialized node setups if I was starting a project today, and Godot absolutely grants you the power to do that.

Additionally, it's not just the camera smoothing, it's that the Camera2D and viewport nodes are just flat out frustrating to work with in pixel perfect games.

While these tools require a deep understanding of Godot's scene model to work well for 2D pixel art, it is also a massive boon to the engine that they are individually simple and flexible and have relatively unsurprising behavior.

If these usability issues are inherent to the engine and will continue to exist forever, then a dedicated solution makes sense in my opinion.

They're inherent to any engine that uses real numbers, triangle rasterization and a scene graph to represent the rendered world. The linear algebra becomes less intuitive when you start needing to round numbers at specific points. There is inherent complexity introduced by being able to "attach" sprites to other sprites and apply linear transformations to compose the scene. You would experience these same sorts of issues in a hand-rolled engine if you were using hardware rendering too. In some ways 2D pixel art games are easier done with simple masked blitting against a framebuffer and a flat array of "objects" with update and draw callbacks (which I'll add, you absolutely can do in GDScript if you really wanted to).

I think the problems outlined here are ultimately solved by better learning material and a template to demonstrate how to do it correctly. It is absolutely possible to get Godot 4 to do 2D pixel art well without significant hurdles; I have not really experienced regressions relative to Godot 3 in this respect, even porting Godot 3 projects into 4. Maybe I could do a write-up on what is specifically needed and why certain issues occur when implementing it.

@fractalcounty
Copy link
Author

I think the problems outlined here are ultimately solved by better learning material and a template to demonstrate how to do it correctly.

While I do completely agree with this, I still think the elephant in the room is the lost functionality from 4.0. It's not that making pixel perfect games are too hard, it's downright infeasible after a certain point as far as I can tell. There's just too many new issues that go far beyond this feature suggestion unfortunately.

@HybridEidolon
Copy link

Here is a project to serve as a test case: PixelPerfect.zip

  1. A SubViewport is created at runtime and arranged inside a SubViewportContainer and AspectContainer. The current scene is added to the SubViewport. The pixel-perfect configuration is set up on the SubViewport instead of the project, which consists of a forced resolution and both snapping settings enabled. This subviewport is at 320x180 (matching 16:9 320-width). This is all handled by an Autoload.
  2. None of the snapping settings are changed on the project itself.
  3. level.tscn contains a simple 2d platformer with a parallax background. No special snapping scripts are attached.
  4. A Label is added next to the AspectContainer to demonstrate full-viewport resolution rendering alongside the low-res sub viewport.
  5. This project is running in GL Compatibility renderer but is identical between all 3 renderers.

Notably, no scripts are needed to snap transforms on pixel alignment. This is a departure from approaches I've used for Godot 3. Everything became a lot simpler when I stopped trying to hack in my own solution!

For the most part, the behavior of the snapping is exactly as desired. The only thing that isn't correct is when the player begins moving for the first time, there are noticeable rounding issues between the camera and the player sprite, but as soon as the player's Y changes, this disappears entirely. I think that is probably a rounding issue in the canvas renderer. As far as I'm aware, this is basically identical to Godot 3.5, except that Godot 4 also grants us the ability to control the snapping settings on the SubViewport node rather than the entire project, which is a pretty significant improvement.

Hopefully this can serve as a useful basis to implement the smooth scrolling behavior described above; the actual Camera transforms aren't affected by scripts, so their global positions can be used in a shader on top of the viewport texture.

@bitbrain
Copy link

@HybridEidolon great solution right there! However, I was wondering how we could achieve "smooth" camera movement with this (e.g. camera following the player smoothly, with everything still pixel-perfect but the camera itself is not stuttering). A great example is Celeste: https://youtu.be/qyOapJgLcEI?t=997 they have pixel-perfect viewport (all the pixels always align on the screen) but the camera is smooth.

I tried to do it with the example you attached but the camera stutters once I enable position smoothing.

@HybridEidolon
Copy link

You need to make sure your camera is updating at the same frequency as the physics (i.e. switch the camera process callback to Physics)

@malaVydra
Copy link

malaVydra commented Mar 11, 2023

Thank you for the suggestion. However, this doesn't really achieve the desired result.
The camera still doesn't move smoothly like it does in the Celeste video shown above.
In order for camera to be smooth it would need to move by sub-pixel amount units.
This was easier to bypass by creating a Viewport with a shader that moves it on sub-pixel level while the camera loads normally.

However, although not the perfect solution, this was a lot easier to achieve in the earlier version, and I agree with everyone else here that this is something that would be really useful if made easier and more straight forward, as most of 2D indie games are pixel-art games.

@HybridEidolon
Copy link

The camera still doesn't move smoothly like it does in the Celeste video shown above.
In order for camera to be smooth it would need to move by sub-pixel amount units.

The camera is not on subpixel alignment in Celeste. The camera appears smooth because the resolution is relatively high and the camera is much more complex than the built-in smoothing option and doesn't directly lag behind the player in most situations.

This was easier to bypass by creating a Viewport with a shader that moves it on sub-pixel level while the camera loads normally. ... this was a lot easier to achieve in the earlier version

This is still possible and there are no regressions preventing this. All you need to do is apply a shader to a quad using the SubViewportTexture and displace it by its UVs, with the SubViewport being a few pixels larger than the target resolution. Same as Godot 3.

@lorenzo-arena
Copy link

All you need to do is apply a shader to a quad using the SubViewportTexture and displace it by its UVs, with the SubViewport being a few pixels larger than the target resolution. Same as Godot 3.

Did anyone manage to get this working?

@fractalcounty
Copy link
Author

fractalcounty commented Mar 23, 2023

Did anyone manage to get this working?

No, the subpixel method produces jittering and blur that isn't otherwise present in identical 3.x setups. I spent weeks testing and looking for a solution, but I haven't seen a single working implementation of it that plays nicely with viewports, character movement, and cameras in Godot 4.0.

On a slightly unrelated note, after a few more weeks of testing I've found that the issue here is that you can absolutely set up a blank project with a subviewport set to optimal settings and it will work as expected for a pixel perfect setup... up until you add any amount of complexity to your project. After awhile, the blur and jitter becomes so unmanageable that it often isn't worth dealing with anymore. There are just too many factors that induce it- whether that be user error, obscure bugs, or just the nature of your project. I heavily recommend avoiding the pixel perfect setup entirely if that's not something you want to deal with.

That's the big reason why this is a suggestion and not a bug report- there isn't a specific bug or regression responsible for this and it's not reproducible whatsoever due to sheer complexity. Otherwise, it would likely have been fixed years ago. Godot just kind of stinks when it comes to pixel perfect games at the moment and the workflow could really use some improvement.

@Proggle
Copy link

Proggle commented Mar 26, 2023

Godot just kind of stinks when it comes to pixel perfect games at the moment and the workflow could really use some improvement.

Yeah, the main blocker I have on adopting godot 4 is that it has become very difficult to make my game look the way I want it to, (and despite 2d sprite games in general being a very common use case, it doesn't feel like there are many people on the dev team who are focused on them.)

@afk-mario
Copy link

Great rundown on this subject if it's useful for someone to understand what's happening.

https://www.youtube.com/watch?v=Yu8k7a1hQuU

@MitrB
Copy link

MitrB commented Apr 23, 2023

On the topic of smooth camera: it can be achieved by having 2 cameras. One subviewport camera that is snapped to pixel coords. (Child of Subviewport) And a camera that will capture the full resolution. By giving the subviewport a border of 1 pixel, you can offset the full res camera by the decimal value of the subviewport camera on each update. Something like this setup:

- SubViewportContainer
- - SubViewport
- - - Scene
- - - - Player
- - - - - ViewportCamera
- FullResCamera

The Viewport Camera can be set to have position smoothing. You won't have any incremental movement now.

Script for camera offset:

	var viewport_camera = $"../SubViewportContainer/SubViewport/Player/ViewportCamera"
	var offset_x = fmod(viewport_camera.get_screen_center_position().x, 1.0)
	var offset_y = fmod(viewport_camera.get_screen_center_position().y, 1.0)

	self.offset = Vector2(offset_x, offset_y)

@MitrB
Copy link

MitrB commented Apr 23, 2023

I quickly threw together a prototype for what I explained in my comment above. I added camera panning too. One problem that I have is that for speeds that are not a multiple of 60, you can notice jitter on the character movement. I guess this could be fixed with some interpolation?

Also there is a staircasing effect when moving diagonally. This could be fixed by offsetting the sprite depending on velocity and previous offset.

GodotPixelPerfectSetup

@Calinou
Copy link
Member

Calinou commented Apr 23, 2023

One problem that I have is that for speeds that are not a multiple of 60, you can notice jitter on the character movement. I guess this could be fixed with some interpolation?

Indeed, you need to use physics interpolation with https://github.com/lawnjelly/smoothing-addon/tree/4.x.

@MitrB
Copy link

MitrB commented Apr 24, 2023

@Calinou

Indeed, you need to use physics interpolation with https://github.com/lawnjelly/smoothing-addon/tree/4.x.

I think it doesn't work that well with a pixel perfect setup. As you can see in the recording, the red sprite is being smoothed. But it jitters around.

2023-04-24_11-12-46.mp4

@MitrB
Copy link

MitrB commented Apr 25, 2023

I have shitty hardware, so the recording is choppy. Either way, this is how to achieve smooth camera movement. Feel free to check out the setup yourself: Github Repository

Jittery movement still needs to be fixed. I noticed that speeds that are a multiple of 60 produce smooth vertical and horizontal movement. Diagonal movement is jittery.

2023-04-25_18-45-26.mp4

@bitbrain
Copy link

Reading through the comments I believe there are two different use cases here that we need to be mindful of not to mix up:

  1. some people want to build pixelart games where sprites move smoothly between pixels (subpixel blending). For this usecase, a shader like this can be helpful
  2. some people want to build pixelart games that are 'pixel perfect', meaning that all pixels on the screen have the exact same size and are aligned perfectly in a raster. This can be achieved in Godot by using a SubViewport

My understanding is that this proposal is supposed to solve 2. via a new node "out of the box" while it does not try to also solve 1.

@Exerionius
Copy link

I'm pretty sure people just want this:

How to make a silky smooth camera for pixelart games in Godot

Pixel-perfect game + high-res UI + smooth camera. So it's all at once.
This video perfectly explains how to achieve this in 3.5

@starry-abyss
Copy link

@chutneyio

Only works with integer scaling. So it doesn't adapt well with many screen sizes, like we need to add black bars when going to fullscreen.

Yes, I think this is annoying. Given monitors are usually 16:9, the original small resolution is usually chosen in a way to cover most modern resolutions while staying with integer scaling. For example, 320 x 180.
For other resolutions, besides black bars, one can adaptively extend UI and borders, or use non-pixel-perfect scaling (there is a trick called hybrid scaling mode at Godot github, aka Shovel Knight way at many other internet sites).

Image edge is filled with staircase when rotation. It clearly changes the shape in an unexpected way. If it is a style choice, why just don't draw it beforehand.

Totally agree. To make rotations beautiful, draw them by hand.

To make them quick (sacrificing the beauty), use higher resolution for them. Also for text rendering, games often violate rules of pixel art gurus and just use higher resolution.

But seriously, how can i make my character move diagonally with the same speed as when he is moving horizontal and vertical?

Sprites can have any coordinate systems for position. Floating point (or fixed point) numbers are OK. Actually they are used pretty often. Divide speed by square root of 2, and you get projected speed for X and Y axes for moving diagonally at 45 degrees.

After physics are calculated, all sprites' positions are rounded before rendering to the screen. But the original floating point values are still used next time in physics calculations, not the rounded ones.

But if Godot's aim is to make the engine beginner friendly as most as possible then this is really not a way to do it.

Fun fact. Pixel art was a relatively rare style for modern games before about year 2015 (and Godot became open-source in 2014). Only after that it became very common. Cyangmou is a pixel artist well known in pixel art circles long before this happened. I remember backing his game on Kickstarter just because it was pixel art (not kidding!). Godot developers somehow missed that pixel art became mainstream, and a lot of times questions about pixel art support in Godot were answered like "pixel art is too niche to be officially supported by a general purpose engine". :D

But things are changing in recent years in Godot, and now, for example, we have integer scaling mode in options. With more and more people experienced in various fields contributing to the project, things are getting resolved gradually.

@Gnumaru
Copy link

Gnumaru commented Jan 6, 2024

(there is a trick called hybrid scaling mode at Godot github, aka Shovel Knight way at many other internet sites).

You mean this?

godotengine/godot#41814

and this?

https://godotshaders.com/shader/sub-pixel-accurate-pixel-sprite-filtering/

@starry-abyss
Copy link

@Gnumaru No, I'm referring to whole screen upscaling. The way it works is first upscales to resolution way higher than needed, and then downscales back to screen resolution. So instead of having lines of pixels on the screen with thickness different from neighbor lines, all will have the same thickness (differences in thickness evenly distributed).

@c0d3r9

This comment was marked as off-topic.

@Renari

This comment was marked as off-topic.

@c0d3r9

This comment was marked as off-topic.

@zhagsenkk
Copy link

If you can see the jittering, it seems to happen regardles of whether rendering/2d/snap/snap_2d_vertices_to_pixel or rendering/2d/snap/snap_2d_transforms_to_pixel are enabled or not.

It will be ok if I disable the rendering/2d/snap/snap_2d_transforms_to_pixel.

Add I made a demo. I cannt see any jitter.
#6389 (comment)

@Rubonnek Could you confirm?

@markdibarry
Copy link

markdibarry commented Jan 6, 2024

I just read over this thread a bit and I think it might suffer from trying to tackle many different issues in one go. A lot of people commenting seem to conflate pixel-perfect as an aesthetic, and pixel-perfect as an accurate integer-based representation. Some are asking about issues that are more general towards pixel-snap than camera scrolling, so it's hard to say what the topic even is. It might be good to make a new proposal that doesn't try to tackle everything at once.

In the case of smooth scrolling: most of Godot's issues with pixel-perfect rendering stems from trying to approximate integer-based game rendering (and movement) in a float-based engine. Keep in mind that the youtube video linked a few comments up shows you how to make an integer-based pixel-perfect viewport and display it with a non-pixel-perfect float-based camera scroll. It's up to you on how faithfully you want to mimic the aesthetic of older games, but it's worth mentioning that this type of sub-pixel scrolling camera movement would not be possible on the systems pixel-perfect games try to emulate. It's a great technique, but can not only be tricky but even undesirable for some to add an extra step where we take float-based rendering, snap them to integers, then move them around again in float-based space.

As Calinou pointed out, using the smooth scroll setting currently on the Camera2D node uses interpolation that slows down as it gets toward the current position, so when positions are rounded, the jumps between pixels get more noticeable (looking jittery), which is even more noticeable the lower resolution you use for your viewport. Even more so when you use parallax layers that move at different speeds. Similar to gliding your hand down all eighty-eight keys of a piano in three seconds makes for a continuous sound, but if you slowed down near the end it would sound more staggered and abrupt.

The PixelCamera2D in this original proposal wouldn't be sufficient for the non-pixel-perfect float-based camera scrolling effect desired by the OP, since it would require more than extra logic for the camera. If you're interested in a less-jittery pixel-perfect experience, you could write logic to make the camera movement delay linear-based rather than current implementation, so every scroll would be consistent. It would be pretty easy to add that as a Camera2D option tbh if the core team finds it desirable. However, if you want super smooth float-based scrolling like the linked example videos, you'd need to forego rendering things pixel-perfect. It's still very possible to achieve in Godot 4, and if there's a good proposal for how this system could be architected as a stand-alone node, I'd be interested to help.

@cixil
Copy link

cixil commented Jan 7, 2024

However, if you want super smooth float-based scrolling like the linked example videos, you'd need to forego rendering things pixel-perfect. It's still very possible to achieve in Godot 4,

@markdibarry The video from the first link on the first post shows how to render a pixel-perfect low-res game with a high-res camera to reduce jitter while maintaining the low-res aesthetic. I believe the main point of this proposal is that that method is no longer possible in Godot 4, while it was in Godot 3, and now we can't use Godot to make low-res games with a smooth high-res camera.

@markdibarry
Copy link

I believe the main point of this proposal is that that method is no longer possible in Godot 4, while it was in Godot 3, and now we can't use Godot to make low-res games with a smooth high-res camera.

@cixil The method in the video for non-pixel-perfect smooth camera scrolling with a pixel-perfect viewport is still possible, it's just as cumbersome to set up as it was in 3.x. Here's the same setup in 4.2:

2024-01-07.13-23-46.mp4

If this proposal's main point is that an existing functionality is broken, then that should be an issue, not a proposal. There are a lot of other pixel-perfect rendering issues mentioned in this proposal and in the comments, but those are outside the scope of the proposal and already have separate tickets. Trying to consolidate them all into one ticket almost guarantees a muddied conversation. Since this proposal was first made, some of these issues have already been fixed, and others have existing proposals and PRs. 🎉 Some may make what the OP wants to achieve easier, but they can't be solved here.

Aside from the existing issues linked, this proposal does suggest a PixelCamera2D as an all-in-one solution for this specific effect from the youtube link. This unfortunately wouldn't work on its own, since it needs a lot more than just the camera logic to function. If you did include all the pieces of the puzzle: a custom Camera2D, custom SubViewport, custom SubViewportContainer, and global singleton, all in one node, isn't possible... or at least wouldn't be very usable. Even then, due to its lack of flexibility, that sounds more like a plugin rather than a core node, but... that wouldn't be difficult to make and provide to the community immediately (!!).

@cixil
Copy link

cixil commented Jan 7, 2024

@markdibarry thanks! Is the project for that video you posted online somewhere?

@markdibarry
Copy link

markdibarry commented Jan 8, 2024

@cixil No. I just followed the video exactly. The only change I had to make was to put the Camera2D code in _physics_process() since there were changes to that between 3.x and 4.x. Hope that helps if you were having issues. 🙂

@adamscott
Copy link
Member

adamscott commented Jan 8, 2024

but if the above is reproducible for someone else as well, looking into why Camera2D::get_camera_transform() has a side effect might yield insight into a proper jitter fix.

@Rubonnek I tested your finding and, yes, it fixes the jitter, but it's only by chance. get_camera_transform was already called on each frame (with a side-effect), but calling it twice like you did would "fix" (by chance) the floating point imprecision. If you were to call it again, later, the issue would be brought back.

But godotengine/godot#84380 seems to be able to fix your issue.

@jordanlis
Copy link

jordanlis commented Jan 8, 2024

I believe the main point of this proposal is that that method is no longer possible in Godot 4, while it was in Godot 3, and now we can't use Godot to make low-res games with a smooth high-res camera.

@cixil The method in the video for non-pixel-perfect smooth camera scrolling with a pixel-perfect viewport is still possible, it's just as cumbersome to set up as it was in 3.x. Here's the same setup in 4.2:
2024-01-07.13-23-46.mp4

If this proposal's main point is that an existing functionality is broken, then that should be an issue, not a proposal. There are a lot of other pixel-perfect rendering issues mentioned in this proposal and in the comments, but those are outside the scope of the proposal and already have separate tickets. Trying to consolidate them all into one ticket almost guarantees a muddied conversation. Since this proposal was first made, some of these issues have already been fixed, and others have existing proposals and PRs. 🎉 Some may make what the OP wants to achieve easier, but they can't be solved here.

Aside from the existing issues linked, this proposal does suggest a PixelCamera2D as an all-in-one solution for this specific effect from the youtube link. This unfortunately wouldn't work on its own, since it needs a lot more than just the camera logic to function. If you did include all the pieces of the puzzle: a custom Camera2D, custom SubViewport, custom SubViewportContainer, and global singleton, all in one node, isn't possible... or at least wouldn't be very usable. Even then, due to its lack of flexibility, that sounds more like a plugin rather than a core node, but... that wouldn't be difficult to make and provide to the community immediately (!!).

This is 4.3. I tested it with a 4.2 or a previous version, and it didn't worked. This is a good news anyway

@cixil
Copy link

cixil commented Jan 8, 2024

Yes, great news! Will have to try it out again soon!

@chutneyio
Copy link

I just want to clarify that using Canvas Item scaling mode is enough for smooth camera movement in non-pixel perfect game. My previous posts mentioning this require SubViewport is completely wrong.

If you have already known please ignore this. I just don't want who new to Godot stumped upon this thread is headed to wrong direction because of my post.

Thanks @golddotasksquestions again for the explanation!

@Cyangmou
Copy link

Cyangmou commented Jan 9, 2024

I think I never talked about my main problems, only the symptoms arising from it and the issues i found along the way.
The main problem I faced with Godot and where it started to break down/ be unusable for me is: "the use for modern pixelart games."
This means using pixelart aesthetics, but also a high resolution UI. Most of modern pixelart games work this way.
I found a ton of deeper lying issues, but just regarding camera related issues:

In depth, this means you make your game a resolution of let's say 640x360, but your UI fonts are meant to scale to the overall screen resolution (to be always buttery smooth and pixels don't retract from font readability). However when you do this, you still want your camera (at least when it stops) to lock on to pixels, while in motion it could be theorethically buttery smooth, in some cases you would like to have stuff placed on the pixel grid.
So this means the camera movement would ideally snap at the end of a movement exactly to the pixel grid, while in motion it doesn't matter so much where the camera is at any given point, because in motion you don't see the pixel alignment.
Sometimes it'd be useful if the camera would snap to "pixel perfect resolution" exactly, which is most likely a fraction of the screen, especially if you'd go for a 100% purist pixel perfect way.
But even with the options in the camera, can you be sure if the camera is using the screen resolution, the viewport resolution or the picture is rendered, the camera positioned and a sa post processing effect gets scaled on the screen? You have no control here.

Godot is very basic with giving you resolution options. You can either have a window with an exact size or some kind of stretching / fullscreen mode. The fullscreen mode render I think takes the output of the window size and just stretches it across the screen, which already leads to some stretching issues. Making any options menu with resolutions (like you expect from professional games) to mitigate stretching issues didn't really work out, due to how much resolution is simplified in Godot. But this stretching already leads to some unclean movements, because let's say your game is made to scale cleanly to 1920x1080 but you then render it on a laptop with 1600x900px
This issue then becomes painfully visible in the UI, if you set it up at your games native resolution as for a pixelart font now each pixel will look like it's stretched differently. I mean yes... this is bound to cause problems, but that's exactly why you use a high resolution UI with pixelart in any more professional pixelart production and keep the pixel fonts in the options.

But now if you want to use a high resolution UI you need to set Godot to some high resolution, let's say 1920x1080. Ideally you would like to have the game viewport at the native pixel resolution of 640x360. But now the camera will move with the 1920x1080 set up size.
You run into a completely different issue here, that it will be rather impossible to get a visual interpretation of the high resolution ui on the pixel viewport, but this is a totally unrelated issue.
And you got a bunch of options you could handle this scenario:
1.) camera just moves on the 1920x1080
2.) camera moves on the 640x360
3.) camera moves with 1920x1080, but when it stops it locks clearly on every 3th pixel - it stops clean at any integer of the 640x360 resolution (which basically you calculate somehting like pixelstepHorizontal = screen width /3, pixelstepVertical = screenheight /3 )
3. is a huge point of pain, because of the way how the core resolution is set up in Godot. if your screen is 1920x1080 great because this matches the resolution you built your game in. But now if your screen is 1600x900, your game was built in 1920x1080 and you made some personal calculations with your camera you round your visual output twice. The first time from the screen translation, the second time from the camera movement. Ideally your screen output resolution would be the only rounding. But you physically can't do that because the resolutions options in Godot are too simplified and streamlined to prevent this double rounding, which leads to problems. And you lack options to tell your camera what to do.

Point 1 leads to pain when you use a differently sized screen from the resolution you built your game in, this is mostly expected.

If you want to realize point 2 you can do this, but it comes with a whole lot of design related restrictions. You a limited to use a low-resolution ui, but with the resolution setup this turned out to lead to the best results, although it is extremely limiting and not pretty and feels technically outdated. But at least there is no double camera rounding ongoing.

And all these problems come down to the way how godot handles resolutions on a design layer. And if you want to do anything a bit more sophisticated you run into bugs with the stretchmodes or limits of features of camera or the editor as not all cases are handled ideally.

Let's say you use viewport, which seems like the most sensible way to do in pixelart. And you set the viewport to 640x360. And the camera settings can't be specified enough, because which resolution the camera will be following? If the viewport is stretched it's not possible to specify if the camera is stretched to or rather follows the base resolution of the screen. It's just limiting. And the editor output with viewports and subviewports is visually heavily bugged. I don#t know if what I see is correct or an editor hallucination and I can't develop a game with these shortcomings.

I learned about renderer related issues later, so I am not sure at this point if some of the hacky and choppy movement was related to the forward plus renderer which you said you don't want to have discussed anymore, despite godot being literally the only tool i ever encountered that kind of frameswitches but ok.
But even if not I still would lack the whole amount of options to tell the camera exactly which resolution it should focus on and how it should behave.
And now you ask me are you sure the motion problems weren't caused by yourself? Well maybe they were, but only because the options I'd need to set things up perfectly are missing. Or are buggy. Or not fully thought through.

There are very real reasons why in most engines UI and Game resolution are seperated from each other. And the streamlined simplification of godot to take away resolution related options for the developer might also lead to a ton of the issues with the camera, because when rounding is happening in the rendering process can't be specified fully by the developer.
I don't think much of this matters to indies making their first game and setting up whatever resolution and stuff "just works"
But for more professional productions where you ideally want to reach a high grade of polish and take care of cases which you know will happen, godot 4s options aren't up for the stress real world production would require from the engine.

@Cyangmou
Copy link

Cyangmou commented Jan 9, 2024

As Calinou pointed out, using the smooth scroll setting currently on the Camera2D node uses interpolation that slows down as it gets toward the current position, so when positions are rounded, the jumps between pixels get more noticeable (looking jittery), which is even more noticeable the lower resolution you use for your viewport. Even more so when you use parallax layers that move at different speeds. Similar to gliding your hand down all eighty-eight keys of a piano in three seconds makes for a continuous sound, but if you slowed down near the end it would sound more staggered and abrupt.

This setting basically is dangerous for any game made with godot as far as i understood it.
Let's go again back to the issue that you set your game up at 1920x1080.
Now you play it at a 1600x900 screen.

If you have this setting on a 1920x1080 screen it's ok, sliding down the piano will be a consistent curve or a slowed down curve with jitter as you describe,
But if you have a different screen and it scales already 1600x1900 and you proportionally put the 1920 on the 1600 you have floats. But you also have floats in the camera movement coordinates. But these coordinates get calculated to the resolution you set the game up with (1920x1080)
And then rounded again once more to the output of 1600x900.
Because numbers are rounded twice in this setup you run into four types of numbers: some are twice rounded up, some are twice rounded down and some are first rounded up and then rounded down. Some are first rounded down and then rounded up
And now if you would again slide over the piano will destroy your tonal progression and more importantly the jitters in the progression will appear in various different spots causing more and more jitters. Because the ones which got rounded up and rounded down are wrong. These are the true offenders.

It however could be fine, given you would round all numbers once - simply at the end.
But the issue here is that a rounding issue is introduced and then numbers with the rounding fault are taken as a credible number which leads to "jitter" because the information on them was lost.

If the rounding would be handled sensible, the low resolution wouldn't change the setup. But the rounding is handled poorly and performed too often. To have the least amount of problems you only must round a single time (at the end).

@markdibarry
Copy link

markdibarry commented Jan 9, 2024

I think things are getting a bit off topic again. I think as far as this proposal is concerned we have a few options:

  1. The proposal suggested a new PixelCamera2D to recreate the mixed resolution effect in the linked video all in one node. Unfortunately, the proposal doesn't provide enough details to achieve this, and it's probably only achievable via a custom system of a group of nodes, not a custom stand-alone camera node, so I don't believe this proposal is possible or viable as a core node. For that reason, I believe it should be closed. Though, I would like to see someone make a plugin for this setup for the community to help streamline the process for a lot of beginners.
  2. Despite being a long and confusing thread, there are a lot of good ideas, questions, and issues that (although unrelated) I'd like to see addressed. If it were only one or two and the discussion was focused, I'd recommend converting this proposal to a discussion, but, because there are so many, I'd like to see some new discussions and issues be opened by anyone who wants to champion any of the topics. Some of the topics mentioned aren't limited to:
    • Pixel-perfect snapping issues and jitter for low resolution games (being discussed and possibly solved in Replace floor() with round() in pixel snap to transform godot#84380)
    • Inability to recreate the 3.x effect in the youtube link in the proposal (fix for 4.x explained above)
    • Guidance on camera and viewport setup to recreate specific games like Celeste (should be a separate discussion or forum question, but would awesome if someone compiled a list of different setups for different games. I'd be happy to contribute!)
    • Guidance for setup of mixed resolution games (should be separate discussion or forum question)
    • Guidance on understanding render filtering of odd-shaped, offset, rotated, and scaled sprites (should be a separate discussion or forum question. This misunderstanding comes up often so it'd be great to have a central learning place to point users towards!)
    • Camera smoothing ideas for integer and float-based setups (should be a separate discussion or proposal)
    • Issues with discrepancies between different renderers and lost frames on Vulkan (Being discussed in Forward Plus Renderer causes frameskips/jitter/judder/stutter, GPU frames aren't sorted correctly on Windows godot#84137)

@c0d3r9

This comment was marked as off-topic.

@c0d3r9

This comment was marked as off-topic.

@c0d3r9

This comment was marked as off-topic.

@adamscott
Copy link
Member

@c0d3r9 Your comments are marked as off-topic because they don't respect the Godot Code of Conduct.

To cite the code of conduct:

Feedback is always welcome but keep your criticism constructive. We encourage you to open discussions, proposals, issues, and bug reports. Use the community platforms to discuss improvements, not to vent out frustration. Similarly, when other users offer you feedback please accept it gracefully.

@gerblesh
Copy link

gerblesh commented Jan 25, 2024

Hi all! I know I may be late to the party on this but I came up with a simple integer lerp solution for jitter-free position smoothing in Godot 4. This isn't perfect but it works well enough (needs to be called from _physics_process). This still keeps the camera snapped to the pixel grid, but helps alleviate/remove jitter

static func lerpi(origin: float, target: float, weight: float) -> float:
    target = floorf(target)
    origin = floorf(origin)
    var distance: float = ceilf(absf(target - origin) * weight)
    return move_toward(origin, target, distance)

@adamscott
Copy link
Member

After much thought about this proposal, the team is closing this proposal as "not planned".

This proposal began with the simple idea of "[simplifying] smooth camera movement in pixel perfect games", but this quickly turned into a repository of issues with pixel perfect games with Godot.

Simplifying smooth camera movement

About simplifying smooth camera movement, pixel perfect games are known to be quite difficult to "smooth" camera movement, and an engine alone cannot guarantee smooth camera movement. The code of the game itself have an influence on this.

So, the team decided that if such camera logic should exist, it should be in a form of a plugin/addon. Here's why:

  1. It's pretty easy to extend the Camera2D class to implement custom logic.

  2. Even in cases where the base class Camera2D would be at fault, somehow, it's possible to create custom cameras without using Camera2D at all, because the camera system in 2D is a fiction. It's just a wrapper for Viewport.canvas_transform. As the documentation states:

    This node is intended to be a simple helper to get things going quickly, but more functionality may be desired to change how the camera works. To make your own custom camera node, inherit it from Node2D and change the transform of the canvas by setting Viewport.canvas_transform in Viewport (you can obtain the current Viewport by using Node.get_viewport).

Issue tracking

For tracking purposes, a tracker does a better job with this. You can follow the pixel perfect tracker here: godotengine/godot#86837.

Don't hesitate to create new issues if you encounter bugs or new proposals here. And make sure to comment on the tracker with the link of your created thread if you feel that your issue belongs there.

The team's commitment to pixel perfect games

Fortunately, this doesn't mean that the team doesn't care about the engine's capabilities to do pixel perfect games.

This proposal was studied thoroughly and it led in fact to the creation and the merge of godotengine/godot#87297 (special thanks to @KeyboardDanni), which greatly help stabilizing and fixing issues. I highly encourage everybody to try the latest 4.3.dev releases to test the new Transform2D snapping logic.

Conclusion

Finally, I greatly thank everybody that participated in this thread for your constructive and instructive comments. Thank you very much.

Have a nice day!

@adamscott adamscott closed this as not planned Won't fix, can't repro, duplicate, stale Mar 6, 2024
@godotengine godotengine locked and limited conversation to collaborators Mar 7, 2024
@adamscott adamscott converted this issue into discussion #9256 Mar 7, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests