Skip to content

Implemetation for a Simple 2D Camera System in pygame (1991) #1062

Open
@GalacticEmperor1

Description

@GalacticEmperor1

Issue №1991 opened by Mega-JC at 2020-06-29 10:10:53

Hi,

Since I started using pygame, I've always wondered how one could implement nice camera movements for game projects. Then I later discovered that many popular 2d games have many different approaches to implementing smooth and dynamic camera movement,
from which some have been analyzed in this great Gamasutra article: https://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php

To approach this kind of task, one can emulate camera movement by simply subtracting the camera's x and y coordinate from all visible game objects, but this becomes rather cumbersome when trying to implement dynamic effects like parallax and smooth camera follow.

So I implemented two user-friendly classes for this, which make it far easier to get a 2D camera system working.

The first class is Camera2, which is essentially a 2D camera with rect-like behaviour. To setup something like player tracking, one can simply do:

Cam2 = Camera2(area=(0, 0, WIDTH, HEIGHT), view_distance=1000)
Cam2.center = player.rect.center

For even more precise control, one can do:

Cam2.anchor = (Cam2.w*0.3, Cam2.h*0.7)    #  some coordinate on the screen
Cam2.goto(player.rect.center)         #  moves the camera's world coordinate to the player's center

The second class is CameraView, which is a subclass of pygame.sprite.LayeredUpdates .

This is where all the scrolling is implemented, without ever messing with the actual sprite coordinates.

For something to be visible in the camera's view, one can simply add those sprites into the camera's CameraView group:

Cam2.view.add(Background1, Background2, player, monster1, monster2, ...)

Note however, that each sprite needs to have a .distance attribute that is set to a positive real number. This is required for parallax effects to work.

Each Camera2 object has a .view_distance property, which also is set to a positive real number.

To finally draw sprites with the right displacement, one writes:

Cam2.draw(surface, parallax=1, return_dirty_rects=1)

This subsequently calls the draw() method of the CameraView object:

with parallax set to 1:
All sprites are displaced, with the strength based on their distance relative to the camera's view distance: clamp( (spr.distance/Cam2.view_distance) , 0.0, 1.0)

with parallax set to 0:
All sprites are displaced, at maximum strength.

if a sprite's rect (after displacement) is still visible in the camera's area on the screen (using Rect.colliderect()), its image will be blitted unto the camera's view unto the destination surface, otherwise it won't.

Multi Camera system:

This system also supports multiple cameras on the screen, as long as their areas do not overlap:

Cam = Camera2(area=(0, 0, WIDTH//2, HEIGHT), view_distance=1000)
Cam2 = Camera2(area=(WIDTH//2, 0, WIDTH//2, HEIGHT), view=Cam.view, view_distance=1000)  #  CameraView objects can be shared

Cam.view.add(Background1, Background2, player, monster1, monster2, ...)

Cam.draw(surface, parallax=1, return_dirty_rects=1)
Cam2.draw(surface, parallax=1, return_dirty_rects=1)

Here's the code and a simple test:
https://gist.github.com/Just-JC/6b67083d0f94d4bfc4a83f063d8724f5

I'm also working on a version of this using Cython, to make it easier to use it with the cythonized sprite module.


Comments

# # MyreMylar commented at 2020-06-29 12:07:59

Hi,

Is this intended as a tutorial for adding cameras in pygame, something you wanted to add to pygame as a new feature, or just something you were excited to tell people about?


# # Mega-JC commented at 2020-06-29 12:17:22

It could be added as a feature for pygame.sprite to make it easier to create 2D camera animations for beginners, and more skilled pygame users.


# # robertpfeiffer commented at 2020-09-03 15:19:53

Adding a draw position to group drawing seems doable with the current system.

@dr0id: How should clearing sprites behave? Clear at the drawn (screenspace) position? Should clearing need a camera pos?


# # robertpfeiffer commented at 2020-10-11 09:01:25

@just-jc
I like that talk by Itay Keren, but I wouldn't implement any tracking/lookahead/lerp smoothing camera motion code in PyGame itself.

On the other hand, I think adding a camera position argument to spritegroup drawing might be a good idea. This camera could be either be a rect that is used as a "world space" position, or a camera object with a rect attribute and an update method. I wouldn't want any anchor, goto or whatever else in PyGame, but you could implement your own camera class, as long as you pass an object with a rect or position attribute to draw().

It would probably be best not to have the drawing code call an update function on the camera, in case the same camera is used to draw multiple groups in the same frame.


# # Mega-JC commented at 2020-10-11 12:47:22

True, adding position arguments to the draw() method for sprite groups might be a good start. This might also allow users to make their own camera class to wrap a sprite group.


# # joereynolds commented at 2021-03-01 12:56:36

Sorry to bump out of nowhere but I think this would be a great addition to Pygame. What's needed to get it PR ready?


# # robertpfeiffer commented at 2021-03-06 09:09:18

@joereynolds I do not think there is a chance the code from that gist will be added/merged into PyGame. On the other hand, somebody could make a PR for a camera position. It's not difficult to add an optional parameter for draw position to draw(), but it might be tricky to get erase and DirtySprite right. Mostly it's straightforward but tedious. If you were to implement a PR that adds a camera position to the sprite system, I would look at it and help you get it into shape to get it merged.

I don't think any of the contributors apart from maybe our maintainer are enthusiastic about adding more features to the old sprite system right now, because we may or may make a new GPU-based sprite system for pygame._sdl2 anyway. (That new system would be 95% compatible with the current one, but have some key differences because it would use Textures instead of Surfaces.)

But if you want to add a camera position parameter to the Sprite system, go for it, and we'll help you get it merged.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions