Skip to content

Commit 7d56d39

Browse files
committed
Created a camera shake class
Created a camera shake class. It isn't exactly like the shake provided before so people might what to have a review.
1 parent c946cc9 commit 7d56d39

7 files changed

+272
-8
lines changed

arcade/camera/camera_2d.py

-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class Camera2D:
5151
:param ProjectionData projection_data: A data class which holds all the data needed to define the projection of
5252
the camera.
5353
"""
54-
# TODO: ADD PARAMS TO DOC FOR __init__
5554

5655
def __init__(self, *,
5756
window: Optional["Window"] = None,
@@ -116,7 +115,6 @@ def data(self) -> CameraData:
116115
If you use any of the built-in arcade camera-controllers
117116
or make your own this is the property to access.
118117
"""
119-
# TODO: Do not add setter
120118
return self._data
121119

122120
@property
@@ -134,7 +132,6 @@ def projection_data(self) -> OrthographicProjectionData:
134132
most use cases will only change the projection
135133
on screen resize.
136134
"""
137-
# TODO: Do not add setter
138135
return self._projection
139136

140137
@property

arcade/camera/controllers/input_controllers.py renamed to arcade/camera/controllers/debug_controller.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# TODO: Are 2D and 3D versions of a very simple controller
1+
# TODO: Add 2D and 3D versions of a polled input controller
22
# intended to be used for debugging.
33
from typing import TYPE_CHECKING, Tuple, Optional
44
from copy import deepcopy

arcade/camera/controllers/isometric_controller.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(self, camera_data: CameraData,
2424
self._up: Tuple[float, float, float] = up
2525
self._right: Tuple[float, float, float] = right
2626

27-
def update_position(self):
27+
def update_camera(self):
2828
# Ref: https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
2929
_pos_rads = radians(26.565 if self._pixel_angle else 30.0)
3030
_c, _s = cos(_pos_rads), sin(_pos_rads)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
from typing import TYPE_CHECKING, Optional, Tuple
2+
from math import e, sin, cos, radians, pi, log
3+
from random import uniform
4+
5+
from arcade.camera.data import CameraData
6+
from arcade.window_commands import get_window
7+
8+
if TYPE_CHECKING:
9+
from arcade.application import Window
10+
11+
12+
class ScreenShaker2D:
13+
"""
14+
Uses the equation Ae^{-fx}sin(v 2 pi x) to create shake effect which falls off over time.
15+
16+
where:
17+
A is the amplitude (size)
18+
f is the falloff
19+
v is the speed
20+
x is the time since start
21+
e is euler's constant
22+
"""
23+
24+
stop_range: float = 0.1
25+
# TODO Doc Strings
26+
27+
def __init__(self, camera_data: CameraData, default_shake_size: float = 1.0, default_shake_falloff: float = 1.0, *,
28+
window: Optional["Window"] = None,
29+
default_shake_speed: float = 1.0,
30+
default_shake_jitter: float = 0.0,
31+
default_shake_direction: Tuple[float, float] = (-1.0, 0.0)):
32+
self._win: "Window" = window or get_window()
33+
self._data: CameraData = camera_data
34+
35+
self._d_dir = default_shake_direction
36+
self._d_speed = default_shake_speed
37+
self._d_amplitude = default_shake_size
38+
self._d_falloff = default_shake_falloff
39+
self._d_jitter = default_shake_jitter
40+
41+
self._t_dir = default_shake_direction
42+
self._t_speed = default_shake_speed
43+
self._t_amplitude = default_shake_size
44+
self._t_falloff = default_shake_falloff
45+
self._t_jitter = default_shake_jitter
46+
47+
self._shake: bool = False
48+
self._time: float = 0.0
49+
self._stop_time: float = -1.0
50+
self._t_pos: Tuple[float, float] = camera_data.position[:2]
51+
52+
def start(self, *,
53+
true_pos: Optional[Tuple[float, float]] = None,
54+
temp_size: Optional[float] = None,
55+
temp_falloff: Optional[float] = None,
56+
temp_speed: Optional[float] = None,
57+
temp_jitter: Optional[float] = None,
58+
temp_direction: Optional[Tuple[float, float]] = None):
59+
60+
self._t_dir = temp_direction if temp_direction is not None else self._d_dir
61+
self._t_jitter = temp_jitter if temp_jitter is not None else self._d_jitter
62+
self._t_speed = temp_speed if temp_speed is not None else self._d_speed
63+
self._t_falloff = temp_falloff if temp_falloff is not None else self._d_falloff
64+
self._t_amplitude = temp_size if temp_size is not None else self._d_amplitude
65+
66+
self._time = 0.0
67+
self._shake = True
68+
self._t_pos = true_pos if true_pos is not None else self._t_pos
69+
self._stop_time = self.estimated_length()
70+
71+
def _curve(self, _t: float) -> float:
72+
return self._t_amplitude * e**(-self._t_falloff*_t) * sin(self._t_speed * 2.0 * pi * self._time)
73+
74+
def estimated_length(self) -> float:
75+
_t = (log(self._t_amplitude) - log(self.stop_range)) / self._t_falloff
76+
_dt = _t % (0.5 / self._t_speed) # Find the distance from to the last x = 0.0
77+
return _t - _dt
78+
79+
def shaking(self) -> bool:
80+
return self._shake
81+
82+
def stop(self):
83+
self.stop_in(self._t_speed - (self._time % self._t_speed))
84+
85+
def stop_in(self, _time: float):
86+
# This derivation was a pain
87+
_dt = self._time % (1.0 / self._t_speed)
88+
89+
_f = log(self._t_amplitude) - log(0.1) - self._t_falloff * self._time
90+
_a = self._t_amplitude * e**(_f * _dt - self._t_falloff * self._time)
91+
92+
self._t_amplitude = _a
93+
self._t_falloff = _f
94+
self._time = _dt
95+
96+
_st = _time % (0.5 / self._t_speed)
97+
self._stop_time = _time - _st
98+
99+
def update(self, delta_time: float, true_pos: Optional[Tuple[float, float]] = None):
100+
if true_pos is not None:
101+
self._t_pos = true_pos
102+
103+
if 0.0 <= self._stop_time <= self._time + delta_time:
104+
self._time = 0.0
105+
self._shake = False
106+
return
107+
108+
if not self._shake:
109+
self._time = 0.0
110+
return
111+
112+
_m = 0.5 / self._t_speed
113+
if self._t_jitter != 0.0 and self._time % _m > (self._time + delta_time) % _m:
114+
radians_shift = radians(uniform(-self._t_jitter/2.0, self._t_jitter/2.0))
115+
_c, _s = cos(radians_shift), sin(radians_shift)
116+
dx = self._t_dir[0] * _c - self._t_dir[1] * _s
117+
dy = self._t_dir[0] * _s + self._t_dir[1] * _c
118+
self._t_dir = (dx, dy)
119+
120+
self._time += delta_time
121+
122+
def update_camera(self, true_pos: Optional[Tuple[float, float]] = None):
123+
if true_pos is not None:
124+
self._t_pos = true_pos
125+
126+
step = self._curve(self._time)
127+
pos = (
128+
self._t_pos[0] + step * self._t_dir[0],
129+
self._t_pos[1] + step * self._t_dir[1]
130+
)
131+
self._data.position = (pos[0], pos[1], self._data.position[2])
132+
133+
@property
134+
def direction(self) -> Tuple[float, float]:
135+
return self._t_dir
136+
137+
@direction.setter
138+
def direction(self, _dir: Tuple[float, float]):
139+
self._t_dir = _dir
140+
self._stop_time = self.estimated_length()
141+
142+
@property
143+
def jitter(self) -> float:
144+
return self._t_jitter
145+
146+
@jitter.setter
147+
def jitter(self, _jitter: float):
148+
self._t_jitter = _jitter
149+
self._stop_time = self.estimated_length()
150+
151+
@property
152+
def speed(self) -> float:
153+
return self._t_speed
154+
155+
@speed.setter
156+
def speed(self, _speed: float):
157+
self._t_speed = _speed
158+
self._stop_time = self.estimated_length()
159+
160+
@property
161+
def falloff(self) -> float:
162+
return self._t_falloff
163+
164+
@falloff.setter
165+
def falloff(self, _falloff: float):
166+
self._t_falloff = _falloff
167+
self._stop_time = self.estimated_length()
168+
169+
@property
170+
def amplitude(self) -> float:
171+
return self._t_amplitude
172+
173+
@amplitude.setter
174+
def amplitude(self, _amplitude: float):
175+
self._t_amplitude = _amplitude
176+
self._stop_time = self.estimated_length()
177+
178+
@property
179+
def default_direction(self) -> Tuple[float, float]:
180+
return self._d_dir
181+
182+
@default_direction.setter
183+
def default_direction(self, _dir: Tuple[float, float]):
184+
self._d_dir = _dir
185+
self._t_dir = _dir
186+
self._stop_time = self.estimated_length()
187+
188+
@property
189+
def default_jitter(self) -> float:
190+
return self._d_jitter
191+
192+
@default_jitter.setter
193+
def default_jitter(self, _jitter: float):
194+
self._d_jitter = _jitter
195+
self._t_jitter = _jitter
196+
self._stop_time = self.estimated_length()
197+
198+
@property
199+
def default_speed(self) -> float:
200+
return self._d_speed
201+
202+
@default_speed.setter
203+
def default_speed(self, _speed: float):
204+
self._d_speed = _speed
205+
self._t_speed = _speed
206+
self._stop_time = self.estimated_length()
207+
208+
@property
209+
def default_falloff(self) -> float:
210+
return self._d_falloff
211+
212+
@default_falloff.setter
213+
def default_falloff(self, _falloff: float):
214+
self._d_falloff = _falloff
215+
self._t_falloff = _falloff
216+
self._stop_time = self.estimated_length()
217+
218+
@property
219+
def default_amplitude(self) -> float:
220+
return self._d_amplitude
221+
222+
@default_amplitude.setter
223+
def default_amplitude(self, _amplitude: float):
224+
self._d_amplitude = _amplitude
225+
self._t_amplitude = _amplitude
226+
self._stop_time = self.estimated_length()
227+
228+
229+
def _shakey():
230+
from arcade import Window, draw_point
231+
232+
from arcade.camera import Camera2D
233+
234+
win = Window()
235+
cam = Camera2D()
236+
shake = ScreenShaker2D(cam.data, 200, default_shake_speed=1.0, default_shake_jitter=20, default_shake_falloff=0.5)
237+
238+
def on_key_press(*args):
239+
if shake.shaking():
240+
shake.stop()
241+
else:
242+
shake.start()
243+
244+
win.on_key_press = on_key_press
245+
246+
def on_update(delta_time: float):
247+
shake.update(delta_time)
248+
249+
win.on_update = on_update
250+
251+
def on_draw():
252+
win.clear()
253+
shake.update_camera()
254+
cam.use()
255+
draw_point(100, 100, (255, 255, 255, 255), 10)
256+
257+
win.on_draw = on_draw
258+
259+
win.run()
260+
261+
262+
if __name__ == '__main__':
263+
_shakey()
264+

arcade/camera/controllers/simple_controller_functions.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ def rotate_around_up(data: CameraData, angle: float):
5050

5151

5252
def rotate_around_right(data: CameraData, angle: float):
53-
_crossed_vec = Vec3(*data.forward).cross(*data.up)
53+
_forward = Vec3(data.forward[0], data.forward[1], data.forward[2])
54+
_up = Vec3(data.up[0], data.up[1], data.up[2])
55+
_crossed_vec = _forward.cross(_up)
5456
_right: Tuple[float, float, float] = (_crossed_vec.x, _crossed_vec.y, _crossed_vec.z)
5557
data.forward = quaternion_rotation(_right, data.forward, angle)
5658
data.up = quaternion_rotation(_right, data.up, angle)
@@ -128,4 +130,3 @@ def simple_easing_2D(percent: float,
128130
"""
129131

130132
simple_easing(percent, start + (0,), target + (0,), data, func)
131-

arcade/camera/offscreen.py

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
class OffScreenSpace:
1313
_geometry: Optional[Geometry] = quad_2d_fs()
1414

15+
# TODO: Doc String
16+
1517
def __init__(self, *,
1618
window: Optional["Window"] = None,
1719
size: Optional[Tuple[int, int]] = None,

arcade/context.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def __init__(self, window: pyglet.window.Window, gc_mode: str = "context_gc", gl
137137
# renders a quad (without projection) with a single 4-component texture.
138138
self.utility_textured_quad_program: Program = self.load_program(
139139
vertex_shader=":system:shaders/util/textured_quad_vs.glsl",
140-
fragment_shader=":system:shaders/collision/textured_quad_fs.glsl",
140+
fragment_shader=":system:shaders/util/textured_quad_fs.glsl",
141141
)
142142

143143
# --- Pre-created geometry and buffers for unbuffered draw calls ----

0 commit comments

Comments
 (0)