logo of the comfy engine Comfy

Camera

Almost every game needs a camera. Comfy recognizes this and provides a reasonable default main camera implementation.

TL;DR: [0, 0] is in the center of the screen, y goes up, world units independent of pixels, default zoom level is 30.

This means that if your game draws a rectangular tile with size 1, you should fit roughly 30 tiles on the screen horizontally. While this is opinionated, we found it to be a very useful default across our games.

If you're making a pixel art game with 16x16 sprites, you would use draw_sprite and specify size of 1. This means your base sprite size is "one world unit". A small side-benefit is that if your art style changes and you now have 32x32 pixel sprites, all of your code still works. A sprite with size = 1 will render with the same world size independent of your texture's dimensions.

This becomes especially useful for games with multiple resolutions, e.g. non-pixel art games that utilize a lot of asset packs with inconsistent resolution, where one does not have to worry about resizing things just to try out new textures/sprites.

The camera is controlled by a MainCamera, and as a pragmatic limitation comfy has one global main camera which can be accessed via the main_camera() function (or main_camera_mut()), which can be used to change where the camera points to, or a zoom level, simply as follows:

let mut camera = main_camera_mut();

camera.position = vec2(5.0, 0.0);
camera.zoom = 50.0;

Allowing for a multi-camera setup is something that is on comfy's roadmap, but again it's not something we needed for our own games, and as such it hasn't been a priority.

If you want to move the camera to a new location and then move it back, you can use .push_center and .pop_center which will store the current values on a stack and later restore them. Since this uses a stack you can push as many values you want.

Converting between world and screen coordinates🔗

Comfy also provides simple utility functions for converting between world and screen space, namely world_to_screen and screen_to_world. These functions are useful for things like mouse picking, or drawing UI elements without egui.

As our camera is global, these functions are also global and can be called simply as

let mouse_world = screen_to_world(mouse_position());
let player_screen = world_to_screen(...);

Note that here we use another global function, mouse_position, which returns the screen-space mouse position. We also have a helper mouse_world() which already returns the world-space mouse position as would be converted by the above code.

To get the current screen size in pixels or world units:

let screen_width_pixels = screen_width(); // for example: 1800
let screen_height_pixels = screen_height(); // for example: 920
let visible_world_size = main_camera().world_viewport(); // for example: 30.0 x 16.8