From 32b828d8f5685a422cb4f7713cf070fd60b9fd84 Mon Sep 17 00:00:00 2001 From: Emily Hunt Date: Wed, 22 Jan 2025 21:33:20 +0100 Subject: [PATCH] Add smooth camera --- README.md | 3 +- src/sk8plotlib/camera.py | 69 +++++++++++++++++++++++++++--------- src/sk8plotlib/sk8plotlib.py | 2 +- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9d1f1a2..43cc0b0 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Planned features before v0.1: - ~~Basic collision detection (skateboard only)~~ - ~~Skateboard rotation on surfaces~~ - ~~User control input~~ -- Smooth camera +- ~~Smooth camera~~ - Automatic map scaling - Auto-initialize in correct location @@ -59,6 +59,7 @@ Planned future features: - Improved player character - ~~Improved collisions~~ (had to be done sooner than planned...) +- Improve rotation snapping to be less aggressive - Improved graphics - Music - Support for log plots diff --git a/src/sk8plotlib/camera.py b/src/sk8plotlib/camera.py index 8cfe0e5..8baa8c2 100644 --- a/src/sk8plotlib/camera.py +++ b/src/sk8plotlib/camera.py @@ -1,4 +1,5 @@ import numpy as np +from .skater import Skater class Camera: @@ -6,32 +7,62 @@ class Camera: self, fig, ax, - skater, - starting_scale=100, - scale=10, - scale_response=0.1, - zoom_response=0.05, + skater: Skater, + starting_scale: int = 100, + scale: int = 10, + velocity_zoom_response: float = 0.1, + zoom_response: float = 0.025, + max_camera_distance: float = 25, + move_response: float = 1.5, ): self.fig, self.ax, self.skater = fig, ax, skater self.scale = starting_scale + self.position = skater.position self.desired_scale = scale - self.scale_response = scale_response + self.desired_position = skater.position + + self.scale_response = velocity_zoom_response self.minimum_scale = scale + self.maximum_scale = starting_scale self.zoom_response = zoom_response - def move_camera(self): - x, y, v_x, v_y = self.skater.x, self.skater.y, self.skater.v_x, self.skater.v_y - location, velocity = np.asarray([x, y]), np.asarray([v_x, v_y]) - - location += velocity * 0.05 + self.max_camera_distance = max_camera_distance + self.move_response = move_response + def move_camera(self): + self._update_scale() + self._update_position() self.ax.set( - xlim=(location[0] - self.scale, location[0] + self.scale), - ylim=(location[1] - self.scale, location[1] + self.scale), + xlim=(self.position[0] - self.scale, self.position[0] + self.scale), + ylim=(self.position[1] - self.scale, self.position[1] + self.scale), ) - self._update_scale(velocity) - def _update_scale(self, velocity): + def _update_position(self): + # We always aim to center on the skater, plus some velocity tolerance. We don't + # let this get too big to try and stop the skater going off screen + position_offset = self.skater.velocity * 0.3 + position_offset_magnitude = np.linalg.norm(position_offset) + if position_offset_magnitude > self.max_camera_distance: + position_offset *= self.max_camera_distance / position_offset_magnitude + self.desired_position = self.skater.position + position_offset + + # BUT, we lag behind it. + # We start by computing the difference between camera & skater + difference = self.desired_position - self.position + distance = np.linalg.norm(difference) + + # The amount that we move the camera scales on a few things + # If the distance is greater than our max distance, always move closer than that + amount_to_move = distance / self.max_camera_distance + + # Otherwise, we use a smooth scaling rule + # if distance < self.max_camera_distance: + amount_to_move = amount_to_move**self.move_response + + self.position += difference * amount_to_move + + def _update_scale(self): + velocity = self.skater.velocity excess_velocity = np.linalg.norm(velocity) - 1 if excess_velocity > 10: self.desired_scale = np.clip( @@ -43,8 +74,12 @@ class Camera: self.desired_scale = self.minimum_scale scale_change = np.clip( - (self.desired_scale - self.scale) * self.zoom_response, -np.inf, self.zoom_response + (self.desired_scale - self.scale) * self.zoom_response, + -np.inf, + self.zoom_response, + ) + self.scale = np.clip( + self.scale + scale_change, self.minimum_scale, self.maximum_scale ) - self.scale += scale_change self.skater.set_marker_scale(self.minimum_scale / self.scale) diff --git a/src/sk8plotlib/sk8plotlib.py b/src/sk8plotlib/sk8plotlib.py index e2d37c0..7712afb 100644 --- a/src/sk8plotlib/sk8plotlib.py +++ b/src/sk8plotlib/sk8plotlib.py @@ -14,7 +14,7 @@ matplotlib.use("QtAgg") def sk8plot(fig: Figure): animator = PlotAnimator(fig) - animation = FuncAnimation( + animation = FuncAnimation( # noqa: F841 fig, animator.update_animation, frames=range(100), interval=MIN_TIMESTEP ) plt.show() -- 2.51.2