Skip to content
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

[FR] Camera #1161

Closed
st-pasha opened this issue Dec 4, 2021 · 6 comments · Fixed by #1355
Closed

[FR] Camera #1161

st-pasha opened this issue Dec 4, 2021 · 6 comments · Fixed by #1355

Comments

@st-pasha
Copy link
Contributor

st-pasha commented Dec 4, 2021

This is a specific proposal for camera design, following the discussion in #931.

Overview

The following top-level classes to be added:

  • World: a simple container class which has most other game elements as children. Its main functionality is that it suppresses rendering of its children, however updates are working normally. The elements inside the World can only be viewed via a Camera.
  • Camera is a component that carries a reference to the world that it views, and a Viewport instance. Thus, a Camera works as a component-wrapper for a Viewport class. Camera's render method applies the viewport transform before rendering its children. Effects that are applied to the Camera should affect the viewport.
    • Viewport describes a "window" through which the game world is observed. This window has a specific size, shape, and a position on the canvas.
  • Viewfinder is a component that keeps track of the game world's current position, rotation angle, and scale (zoom). Thus, while Viewport represents a window that can be moved around the screen, the job of the Viewfinder is to move the world within that window. Viewfinder's render() method will apply the clip mask from the viewport, its own transform, and then render the game world. Effects that are applied to the Viewfinder will move the current point of view within the world.
  • CameraController is a component that controls the behavior of the camera (the viewfinder). It can do things like follow a component, restrict camera's movement to within a certain global boundary, etc. There could be multiple controllers attached to a camera, and they can be inter-mixed with effects.
┌────────────┐
│ FlameGame  │
└─┬──────────┘
  │ ┌───────────────┐
  ├─┤ World         │◄─ ─ ─ ─ ┐
  │ └─┬─────────────┘         |
  │   └── World elements...   |
  │                           |
  │ ┌────────────────┐        |
  └─┤ Camera         │        |
    │   .viewport    │        |
    │   .world       │─ ─ ─ ─ ┘
    └─┬──────────────┘
      │  ┌──────────────┐
      ├──┤ ViewFinder   │
      │  └─┬────────────┘
      │    │  ┌───────────────────┐
      │    └──┤ CameraController  │
      │       └───────────────────┘
      └── HUD elements...

Additional notes:

  • Elements that are attached to the Camera will be subject to the viewport transform, but not viewport clip mask. Such elements are what we commonly refer to .isHud (or .ignoreCamera) components.
  • Elements that are added to the root FlameGame will not be subject to either the viewport or viewfinder transforms. These components are equivalent to current .ignoreViewport components.
  • This design allows having multiple cameras, viewing either the same or different worlds.

Transition

The following is the outline for how to introduce these changes in a non-breaking way:

  1. Create class FlameGame2 which derives from FlameGame and overrides add*() methods so that the components would be added to an internal .world instance. Encourage users to try this new class and test how it works.
  2. Once we sufficiently sure that FlameGame2 is working, rename FlameGame into FlameGame1, and FlameGame2 into FlameGame.
  3. At a later release, mark add*() methods as @deprecated and instruct the users to add their components to game.world or game.camera directly. Components that need to be added outside of the game world will go through the addToRoot() method.
  4. At a later release, remove add*() method overrides. At this point whoever ignored warnings at stage 3 will have their game broken. Mark addToRoot method deprecated and instruct the users to use regular add() method.
  5. Remove the addToRoot method, and the original FlameGame1 class.

Special cases

  • Some components want to be aware of the current rendering context (clip rectangle and zoom level) for optimization purposes. In order to accommodate such needs, I suggest a simple static property Camera.currentContext which will be set whenever a camera begins its rendering pipeline, and reset when it finishes.
    • an alternative is to pass the context as render(canvas, context), but this is much more intrusive to current users;
    • an alternative where the camera is discovered by traversing up the tree is not feasible, since there could be more than one camera watching the same world (for example: split-screen view);
    • currentContext will also protect us against infinite rendering loops when a camera watches itself.
  • The ParallaxLayer component will be a regular component, belonging to the game world. When rendering, it will look at the camera's context and partially undo the translation that was applied by the camera. It will also need to modify the clip mask in the camera's context so that the children of the parallax layer can render correctly.
  • Touch events. In a sense, such events are the inverse of render, and have to be treated accordingly. This area needs more exploration, but touch events are not working properly even with the current implementation (in a sense that if a component modifies renderTree, the touch events do not respect this).
@st-pasha st-pasha changed the title Camera post-v1 [FR] Camera Dec 13, 2021
@spydon
Copy link
Member

spydon commented Dec 15, 2021

Very interesting thoughts! I think this removes a lot of the nice simplicity of FCS though and the only thing it really gives it the possibility for multiple cameras right?

Elements that are attached to the Camera will be subject to the viewport transform, but not viewport clip mask. Such elements are what we commonly refer to .isHud (or .ignoreCamera) components.

That part was very unintuitive I think - adding a component to a camera makes it ignore the camera.

@st-pasha
Copy link
Contributor Author

That part was very unintuitive I think - adding a component to a camera makes it ignore the camera.

Maybe the names of classes are not very well thought-out. In the diagram Camera is a component around the Viewport class. Thus, Camera = ViewportComponent. The ViewFinder is the component that contains "camera" transforms. So we could call it CameraComponent. With these names, we attach a component to ViewportComponent (outside of CameraComponent) to make those components ignore the camera; and we attach a component to FlameGame (outside of ViewportComponent) to make it ignore the viewport.

I think this removes a lot of the nice simplicity of FCS though

On the contrary! It encodes via the FCS the relationships that are currently stored in flags ignoreCamera and ignoreViewport.

and the only thing it really gives it the possibility for multiple cameras right?

There are several benefits of the new system:

  • Support for multiple cameras;
  • Support for multiple worlds (i.e. create several game scenes, then switch between them by pointing the camera at a specific scene -- useful for almost any game);
  • Existing effects can be applied directly to the camera;
  • Effects can also be applied to viewport;
  • Modularization of camera behaviors (Camera can be declared as Steerable, and behaviors from Artificial intelligence for game development #1164 reused);
  • Camera context allows rendering optimizations that are of utmost importance for games with large world sizes;
  • Flags ignoreCamera/ignoreViewport no longer needed.

@erickzanardo
Copy link
Member

I think this removes a lot of the nice simplicity of FCS

I agree with that, seems too complicated to have a world and a camera. Especially since FlameGame is a component now and we could have multiple games and alternate between them as needed.

@st-pasha
Copy link
Contributor Author

seems too complicated to have a world and a camera

We already have the notion of the world and the camera. Only they are merged together into a single entity, which limits extendability and the amount of reuse. If you look into the FlameGame class, here's what it says:

  @override
  void renderTree(Canvas canvas) {
    // Don't call super.renderTree, since the tree is rendered by the camera
    _cameraWrapper.render(canvas);
  }

So, FlameGame already does exactly what the World component is supposed to do. Of course, FlameGame also does a lot of other things (such as keeping track of system-level variables, implementing game loop, communicating with Flutter, etc), so it would be nice to separate them out. Especially since it's often desirable to have multiple worlds. Plus, the world class can be extended with additional functionality like time stop or slow down.

and we could have multiple games and alternate between them as needed.

I cannot say I understand well how game-in-game currently works (for example, what happens to all the machinery of game loops, render boxes, overlays, etc). However, conceptually world-in-game is much simpler than game-in-game.

@erickzanardo
Copy link
Member

This may be an interesting approach for people which wants to have multiple worlds in a game, but for people who don't need that (which by our experience on the community is the majority), we will just add complexity for the simple cases, which IMO is worse.

@st-pasha
Copy link
Contributor Author

I understand the unnecessary complexity angle.

But that doesn't seem like an insurmountable obstacle to me. We can have one generic class, and then another designed for ease of use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants