flame-engine / flame

A Flutter based game engine.
https://flame-engine.org
MIT License
9.23k stars 902 forks source link

FixedResolutionViewport on the new CameraComponent API #2776

Closed Yayo-Arellano closed 1 year ago

Yayo-Arellano commented 1 year ago

Problem to solve

In the old camera API, we can use FixedResolutionViewport to set a virtual size.

camera.viewport = FixedResolutionViewport(Vector2(1280, 720));

In the new camera API we can use CameraComponent.withFixedResolution that internally looks like:

CameraComponent(
      world: world,
      viewport: FixedAspectRatioViewport(aspectRatio: width / height)
        ..addAll(hudComponents ?? []),
      viewfinder: Viewfinder()..visibleGameSize = Vector2(width, height),
    )

Using CameraComponent.withFixedResolution will not make the viewport to a fixed resolution. It partially works because the camera.viewfinder.visibleWorldRect will always be fixed to the given width and height but camera.viewport.size is not fixed and, also, zoom is not working at all.


When using Forge2D in the old API I can have a viewport with FixedResolutionViewport(Vector2(1280, 720)). I can add all the UI components to this viewport using PositionType.viewport.

For the Forge2D components, I will set a zoom of 100 to scale the world to a resolution of 12.8 * 7.2 to overcome Box2D speed and size limitations, and I can add all these components using the scaled values.

Here is a git repository with an example of how I use the old FixedResolutionViewPort https://github.com/Yayo-Arellano/flutter_games_compilation/blob/main/flutter_learn_flame/lib/my_game.dart#L31

ufrshubham commented 1 year ago

I'd say the behaviour you are seeing is the correct one. The old camera system didn't really made any distinction between Viewport and Viewfinder, which has given a lot of the devs wrong idea about Viewport.

CameraComponent.withFixedResolution does not promise that the Viewport will be of constant size. Instead, it just makes sure that only the visibleGameSize world is rendered through the viewport. And to achieve this, the Vewfinder has to zoom in on the game world. Hope that explains why manually changing the zoom does not seem to be working. By setting a visibleGameSize on Viewfinder, you are essentially giving up the control over zoom.

If you really want the Viewport to be constant, you should use the FixedSizeViewport.

Yayo-Arellano commented 1 year ago

The behavior of CameraComponent.withFixedResolution is correct and works as expected, but neither FixedSizeViewport nor CameraComponent.withFixedResolutionis are a good replacement for FixedResolutionViewport because if the screen is too small, there is a chance that the viewport is cut off, or I cannot use the zoom, etc.

The advantage of FixedResolutionViewport is it allows us to work with virtual screen size while keeping the aspect ratio. What I mean is that we can always assume the viewport size is virtualWidth x virtualHeight, and if the screen is too small it will add black bars to the top or bottom to keep the aspect ratio, and also I can apply zoom if needed.

Please look the following GIF. You can see that resizing the screen will keep the aspect ratio and will now cut off instead will add bars. From the GIF you ES: effectiveSize & GS: gameSize

ufrshubham commented 1 year ago

If I'm not wrong, that virtualWidth x virtualHeight will be visibleGameSize when using .withFixedResolution.

Is it possible for you to share a side by side comparison of the new and old system? I feel it should be possible to achieve the same behaviour with new APIs as well. But if it is not, we definitely need to fix something in Flame then.

Yayo-Arellano commented 1 year ago

I can share the side-by-side comparison later. To summarize the differences.

In the new API virtualWidth x virtualHeight will be visibleGameSize when using .withFixedResolution is correct. But only when adding components to the world. Like world. add(MyComponent()

But if I want to add UI elements to the viewport, for example, camera.viewport.add(FpsTextComponent(position: Vector2(0, 720))); then the component will not stay in the fixed position because the viewport does not have virtual size, instead, it will change size (while keeping the aspect ratio) and the components may get out of the screen.

The behavior of the old FixedResolutionViewport is exactly the same as the one from LibGDX FitViewport

ufrshubham commented 1 year ago

But if I want to add UI elements to the viewport, for example, camera.viewport.add(FpsTextComponent(position: Vector2(0, 720))); then the component will not stay in the fixed position because the viewport does not have virtual size, instead, it will change size (while keeping the aspect ratio) and the components may get out of the screen.

I think you are confusing Viewfinder and Viewport 😅. If your game has a FixedAspectRatioViewport and you resize the window, the size of viewport is expected to change. Remember, viewport is just the area of your application window where the game will get rendered. To get the behaviour that you want for the UI components, you need to add them to the Viewfinder, because that is the component that is ensuring the fixed resolution (the virtualWidth x virtualHeight).

Yayo-Arellano commented 1 year ago

I created two videos. Please take a look.

The first one is using the legacy camera with FixedAspectRatioViewport. All the UI HUD elements are of type PositionType.viewport. You can see the HUI items are fixed to the screen and do not change the size when I increase or decrease the zoom.

The second one is using CameraComponent.withFixedResolution all the UI HUD elements (except the black background) are added to the viewfinder like camera.viewfinder.add(fps) the game componens are added to the world like `world.add(gameComponent).

Some differences with the new camera:

Google Drive Folder with videos


Basically, what I was able to achieve with the legacy FixedResolutionViewport:

ufrshubham commented 1 year ago
  • The HUD items are not fixed to the screen when using camera.follow()

I wasn't aware of this. Its weird that the UI is not moving along with the camera in this case.

  • Zoom does not work. The workaround was to do camera.viewfinder.visibleGameSize = virtualSize * scale

Yup, that is the only way to get zoom effect when using visibleGameSize. In my case I try to apply a ScaleEffect to the viewfinder to make it zoom in/out.

  • "zooming in/out" will also scale the UI HUD components.

True, it does scale with the new system. Not much useful for hud component.