-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds initial camera shake implementation (#33)
- Loading branch information
Showing
12 changed files
with
268 additions
and
5 deletions.
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
addons/godot_gameplay_systems/camera_shake/nodes/camera_shake.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
class_name CameraShake extends Node | ||
|
||
|
||
## Emitted when the shake ends | ||
signal shake_ended() | ||
## Emitted when the shake starts | ||
signal shake_started() | ||
## Emitted when a shake occurs | ||
signal shaken(remaining_times: int) | ||
|
||
|
||
enum CameraContext { | ||
CAMERA_2D, | ||
CAMERA_3D, | ||
} | ||
|
||
|
||
@export_category("Camera shake") | ||
## The path to the camera to shake | ||
@export_node_path("Camera2D", "Camera3D") var camera_path: NodePath = NodePath() | ||
|
||
@export_group("Minimum values", "min_") | ||
## The minimum strength appliable to the shake | ||
@export var min_strength: float = 0.0 | ||
## How many seconds the camera will be shaked (min) | ||
@export var min_duration: float = 0.0 | ||
## How many times per second the camera will be shaked (min) | ||
@export var min_frequency: int = 0 | ||
|
||
@export_group("Maximum values", "max_") | ||
## The maximum strength appliable to the shake | ||
@export var max_strength: float = 50.0 | ||
## How many seconds the camera will be shaked (max) | ||
@export var max_duration: float = 5.0 | ||
## How many times per second the camera will be shaked (max) | ||
@export var max_frequency: int = 50 | ||
|
||
## The camera 2d/3d node | ||
var camera: Node | ||
## The camera context | ||
var camera_context: CameraContext | ||
## Current applied duration | ||
var duration: float = 0.0: | ||
get: | ||
return duration | ||
set(value): | ||
duration = clampf(value, min_duration, max_duration) | ||
## Current applied frequency | ||
var frequency: int = 0: | ||
get: | ||
return frequency | ||
set(value): | ||
frequency = clampf(value, min_frequency, max_frequency) | ||
## Previous used [Tween]. Is [code]null[/code] if the [member CameraShake.camera] never shook or the previous [Tween] finished it's own job. | ||
var previous_tween: Tween | ||
## Current applied strength | ||
var strength: float = 0.0: | ||
get: | ||
return strength | ||
set(value): | ||
strength = clampf(value, min_strength, max_strength) | ||
## Time remaining before the shake effect ends | ||
var time_remaining: float = 0.0: | ||
get: | ||
return duration / float(frequency) | ||
## Returns the number of tweens to operate | ||
var tweens_range: Array: | ||
get: | ||
return range(0, duration * frequency) | ||
|
||
## Applies the shake | ||
func _apply_shake() -> void: | ||
if camera_context == CameraContext.CAMERA_2D: | ||
_apply_shake_2d() | ||
elif camera_context == CameraContext.CAMERA_3D: | ||
_apply_shake_3d() | ||
|
||
|
||
## Applies the shake using 2D context | ||
func _apply_shake_2d() -> void: | ||
var _camera = camera as Camera2D | ||
|
||
assert(_camera != null, "Camera (2D) should not be null at this point, check your code before shaking the camera again") | ||
|
||
if previous_tween: | ||
previous_tween.stop() | ||
|
||
previous_tween = create_tween() | ||
previous_tween.tween_property(_camera, "offset", Vector2(0.0, 0.0), time_remaining) | ||
|
||
print("duration: ", duration) | ||
print("time_remaining: ", time_remaining) | ||
print("duration * frequency: ", duration * frequency) | ||
print("tweens_range: ", tweens_range) | ||
|
||
for _n in tweens_range: | ||
previous_tween.tween_property(_camera, "offset", Vector2(_get_shake_value(), _get_shake_value()), time_remaining) | ||
|
||
previous_tween.tween_property(_camera, "offset", Vector2(0.0, 0.0), time_remaining) | ||
previous_tween.play() | ||
|
||
previous_tween.step_finished.connect(func (_x): | ||
previous_tween = null | ||
) | ||
|
||
|
||
## Applies the shake using 3D context | ||
func _apply_shake_3d() -> void: | ||
var _camera = camera as Camera3D | ||
|
||
assert(_camera != null, "Camera (3D) should not be null at this point, check your code before shaking the camera again") | ||
|
||
if previous_tween != null: | ||
previous_tween.stop() | ||
|
||
previous_tween = create_tween() | ||
previous_tween.tween_property(_camera, "h_offset", 0.0, time_remaining) | ||
previous_tween.tween_property(_camera, "v_offset", 0.0, time_remaining) | ||
|
||
print("tweens_range: ", tweens_range) | ||
print("time_remaining: ", time_remaining) | ||
print("duration: ", duration) | ||
print("frequency: ", frequency) | ||
|
||
for _n in tweens_range: | ||
previous_tween.tween_property(_camera, "h_offset", _get_shake_value(), time_remaining) | ||
previous_tween.tween_property(_camera, "v_offset", _get_shake_value(), time_remaining) | ||
|
||
previous_tween.tween_property(_camera, "h_offset", 0.0, time_remaining) | ||
previous_tween.tween_property(_camera, "v_offset", 0.0, time_remaining) | ||
previous_tween.play() | ||
|
||
|
||
func _get_shake_value(rerolls_remaining: int = 5) -> float: | ||
var shake_value = randf_range(-1.0, 1.0) * strength | ||
|
||
# Avoiding stack overflows | ||
if max_strength == 0.0 and shake_value == 0.0: | ||
return 0.0 | ||
|
||
# Rerolls | ||
if shake_value == 0.0 and rerolls_remaining > 0: | ||
return _get_shake_value(rerolls_remaining - 1) | ||
|
||
return shake_value | ||
|
||
|
||
func _ready() -> void: | ||
if camera_path.is_empty(): | ||
if owner is Camera2D or owner is Camera3D: | ||
camera = owner | ||
else: | ||
camera = get_node(camera_path) | ||
|
||
assert(camera != null, "Camera is null. Set camera_path correctly.") | ||
|
||
## Try to guess if the camera is a Camera2D | ||
if camera is Camera2D: | ||
camera_context = CameraContext.CAMERA_2D | ||
## Try to guess if the camera is a Camera3D | ||
elif camera is Camera3D: | ||
camera_context = CameraContext.CAMERA_3D | ||
|
||
|
||
## Resets shake 2D | ||
func _reset_from_shake_2d() -> void: | ||
camera.offset = Vector2.ZERO | ||
|
||
|
||
## Resets shake 2D | ||
func _reset_from_shake_3d() -> void: | ||
camera.h_offset = 0.0 | ||
camera.v_offset = 0.0 | ||
|
||
|
||
## Shakes current camera by the given strength, duration, and frequency. | ||
## [br][code]strength[/code] defaults to [code]1.0[/code] | ||
## [br][code]duration[/code] defaults to [code]1.0[/code] | ||
## [br][code]frequency[/code] defaults to [code]5[/code] | ||
func shake(strength: float = 1.0, duration: float = 1.0, frequency: int = 5) -> void: | ||
self.strength = strength | ||
self.duration = duration | ||
self.frequency = frequency | ||
|
||
_apply_shake() | ||
|
||
|
||
## Resets from shake | ||
func reset_from_shake() -> void: | ||
if camera is Camera2D: | ||
_reset_from_shake_2d() | ||
elif camera is Camera3D: | ||
_reset_from_shake_3d() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@tool | ||
extends EditorPlugin | ||
|
||
|
||
func _enter_tree() -> void: | ||
add_custom_type("CameraShake", "Node", load("res://addons/godot_gameplay_systems/camera_shake/nodes/camera_shake.gd"), null) | ||
|
||
|
||
func _exit_tree() -> void: | ||
remove_custom_type("CameraShake") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Camera Shake | ||
============ | ||
|
||
Well, you all know what a camera shake is and why is it important (at least, for a majority of games). | ||
|
||
To implement this feature, add a `CameraShake` node into your character and point to your current character's camera. | ||
|
||
In the code, you can trigger the shake with | ||
|
||
```gdscript | ||
var strength = 1.0 # the maximum shake strenght. The higher, the messier | ||
var shake_time = 1.0 # how much it will last | ||
var shake_frequency = 250 # will apply 250 shakes per `shake_time` | ||
$CameraShake.shake(strength, shake_time, shake_frequency) | ||
``` | ||
|
||
It works on both `Camera2D` and `Camera3D` nodes. | ||
|
||
You can browse the examples in [simple_2d](https://github.com/OctoD/godot-gameplay-attributes/blob/b376e4bed656bea643ac04e2d9e0f4098febfed4/examples/simple_2d_platformer/player/player.gd) or [doom_like](https://github.com/OctoD/godot-gameplay-attributes/blob/a51cdb77d8f6f58f7239f3cf952dc170a367e136/examples/doom_like_fps/player/Player.gd) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.