Skip to content

Commit 04d2b07

Browse files
committed
Add character randomizer script
A single button to randomize various aspects of a character. For now, the skin tone and the textures of a stack of AnimatedSprite2D that compose the character with parts like: legs, body, head, hair, head prop. Also provides a way to flip the character horizontally. And a way to start the current animation of each part at a random frame, but all using the same random frame. The caveat of having multiple parts is that existing logic like the RandomFrameSpriteBehavior don't work for an array of sprites, so some things have to be reimplemented.
1 parent 40e4bdd commit 04d2b07

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# SPDX-FileCopyrightText: The Threadbare Authors
2+
# SPDX-License-Identifier: MPL-2.0
3+
@tool
4+
extends Node2D
5+
## @experimental
6+
##
7+
## Provide a single button to randomize various aspects of a character.
8+
##
9+
## Using a seed to consistently apply the randomizations in game, to persist them without
10+
## the use of Editable Children, and to allow undo/redo.
11+
## [br][br]
12+
## [b]Note:[/b] Editable Children can still be used to customize a single aspect
13+
## of the randomization. For example if you are happy with the results of the "Randomize"
14+
## button, except for the skin color.
15+
## [br][br]
16+
## There is also logic to set the same random progress to all SpriteFrames animations.
17+
## Basically what [[RandomFrameSpriteBehavior]] does, but to an array.
18+
## [br][br]
19+
## Also can look at sides. Defaults to look at left, and scales everything by -1 to look
20+
## at right.
21+
22+
## The random seed of this character. Setting another character to the same seed
23+
## will make them identical. Setting it to zero will reset the skin color.
24+
@export var character_seed: int
25+
26+
## Where is the character facing. Relative to the character.
27+
@export var look_at_side: Enums.LookAtSide = Enums.LookAtSide.LEFT:
28+
set = _set_look_at_side
29+
30+
## Click this button to create a random character.
31+
@export_tool_button("Randomize") var randomize_character_button: Callable = randomize_character
32+
33+
## The inner AnimatedSprite2D nodes.
34+
var animated_sprites: Array[AnimatedSprite2D] = []
35+
36+
## The inner nodes that recolor the character skin.
37+
var skin_recolor_nodes: Array[CelShadingRecolor] = []
38+
39+
## The inner nodes that randomize sprites textures.
40+
var random_texture_nodes: Array[RandomTextureSpriteBehavior] = []
41+
42+
var _undoredo: Object # EditorUndoRedoManager
43+
44+
var _previous_look_at_side: Enums.LookAtSide = Enums.LookAtSide.UNSPECIFIED
45+
46+
47+
## Randomize the skin color and textures of the character.
48+
## [br][br]
49+
## Do it in a consistent way by first seeding the default random number generator
50+
## with the [member character_seed].
51+
func apply_character_randomizations() -> void:
52+
seed(character_seed)
53+
54+
if skin_recolor_nodes:
55+
var new_skin_medium_color: Color
56+
skin_recolor_nodes[-1].set_random_skin_color()
57+
new_skin_medium_color = skin_recolor_nodes[-1].medium_color
58+
for n in skin_recolor_nodes:
59+
n.automatic_shades = true
60+
n.medium_color = new_skin_medium_color
61+
62+
for n in random_texture_nodes:
63+
n.randomize_texture()
64+
65+
66+
## Set a random seed and randomize the character.
67+
## [br][br]
68+
## With undo/redo when running in the editor.
69+
func randomize_character() -> void:
70+
var new_character_seed := randi()
71+
if _undoredo:
72+
_undoredo.create_action("Randomize character")
73+
_undoredo.add_undo_property(self, "character_seed", character_seed)
74+
_undoredo.add_do_property(self, "character_seed", new_character_seed)
75+
_undoredo.add_undo_method(self, "apply_character_randomizations")
76+
_undoredo.add_do_method(self, "apply_character_randomizations")
77+
_undoredo.commit_action()
78+
else:
79+
character_seed = new_character_seed
80+
apply_character_randomizations()
81+
82+
83+
func _ready() -> void:
84+
_setup_nodes()
85+
86+
if character_seed:
87+
apply_character_randomizations()
88+
89+
if Engine.is_editor_hint():
90+
var plugin: Node = ClassDB.instantiate("EditorPlugin")
91+
_undoredo = plugin.get_undo_redo()
92+
plugin.queue_free()
93+
else:
94+
_randomize_all_sprites_progress()
95+
96+
97+
func _notification(what: int) -> void:
98+
match what:
99+
NOTIFICATION_EDITOR_PRE_SAVE:
100+
for node in animated_sprites:
101+
node.frame = 0
102+
103+
104+
func _traverse(node: Node) -> void:
105+
if node is AnimatedSprite2D:
106+
animated_sprites.append(node)
107+
for child in node.get_children():
108+
_traverse(child)
109+
elif node is CelShadingRecolor:
110+
skin_recolor_nodes.append(node)
111+
elif node is RandomTextureSpriteBehavior:
112+
random_texture_nodes.append(node)
113+
114+
115+
func _setup_nodes() -> void:
116+
animated_sprites = []
117+
skin_recolor_nodes = []
118+
random_texture_nodes = []
119+
for child in get_children():
120+
_traverse(child)
121+
122+
123+
func _randomize_all_sprites_progress() -> void:
124+
if not animated_sprites:
125+
return
126+
var default_animation := animated_sprites[-1].animation
127+
var frames_length := animated_sprites[-1].sprite_frames.get_frame_count(default_animation)
128+
var random_frame := randi_range(0, frames_length)
129+
var random_progress := randf()
130+
for sprite in animated_sprites:
131+
sprite.set_frame_and_progress(random_frame, random_progress)
132+
133+
134+
func _reset_sprite_frames_progress() -> void:
135+
for node in get_children():
136+
if node is AnimatedSprite2D:
137+
(node as AnimatedSprite2D).frame = 0
138+
139+
140+
func _set_look_at_side(new_look_at_side: Enums.LookAtSide) -> void:
141+
look_at_side = new_look_at_side
142+
scale.x = -1 if look_at_side == Enums.LookAtSide.RIGHT else 1
143+
144+
145+
func _on_interact_area_interaction_started(_player: Player, from_right: bool) -> void:
146+
_previous_look_at_side = look_at_side
147+
if look_at_side != Enums.LookAtSide.UNSPECIFIED:
148+
look_at_side = Enums.LookAtSide.LEFT if from_right else Enums.LookAtSide.RIGHT
149+
150+
151+
func _on_interact_area_interaction_ended() -> void:
152+
look_at_side = _previous_look_at_side
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dl6rgfwdn3qp4

0 commit comments

Comments
 (0)