Skip to content

Commit

Permalink
feat: 添加动态改变颜色的功能 (#84)
Browse files Browse the repository at this point in the history
* feat(utils.gd): 初始化 Utils.gd

可将一些常用的函数(如 max_by 等)写入 Utils,方便复用。

* feat(expr_validator): 添加笑脸列表函数及其余修改

- 添加 get_smile_inds() 函数,用于获取表达式中笑脸位置
- 将 check_valid() 改为 check_invalid(),并返回所有非法下标列表,
  而不是仅返回部分
- 将 check() 改为 @deprecated

* feat(shake_component): 添加 shake_component

ShakeComponent 可对 ShakeComponent.attach_node: Node 的 position
应用抖动效果。

默认 attach_node = null,在 _ready 时设置 attach_node 为父节点。

ShakeComponent.shake(amount: float, duration: float) 可
令其抖动 duration 秒,幅度为 amount。

可通过 ShakeComponent.shake_type 设置抖动类型。

* docs(shake_componen.gd): 添加文档

* refactor: 将 card/block 的变色 / 震动逻辑重构

- 将震动逻辑独立到 ShakeComponent 中,交由其处理
- 当设置非法符号 / 金色符号时,可使用 set_is_invalid / set_is_golden
  方法

* feat: 添加动态改变颜色的功能

现在当卡牌放置时,若卡牌非法,就会自动让卡牌变红,而不是等到全部放完
才变红。

* style: 小格式修正
  • Loading branch information
cutekibry authored Sep 23, 2024
1 parent fad89d3 commit beceb8e
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 128 deletions.
38 changes: 38 additions & 0 deletions components/shake_component/shake_component.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## ShakeComponent 可对 ShakeComponent.attach_node: Node 的 position 应用抖动效果。
## 默认 attach_node = null,在 _ready 时设置 attach_node 为父节点。
## ShakeComponent.shake(amount: float, duration: float) 可令其抖动 duration 秒,幅度为 amount。
## 可通过 ShakeComponent.shake_type 设置抖动类型。

class_name ShakeComponent extends Node

@export var shake_type := ShakeType.H_SIN
@export var attached_node: Node = null

var progress_ratio := 1.0
var shake_amount := 0.0
var duration := 0.001

enum ShakeType {
H_SIN, ## 水平方向正弦抖动,[code]pos_delta = Vector2(sin(progress_ratio * 2.0 * PI) * shake_amount, 0)[/code]。
}

## 返回抖动的位置偏移。
func get_position_delta():
match self.shake_type:
ShakeType.H_SIN:
return Vector2(sin(progress_ratio * 2.0 * PI) * shake_amount, 0)

## 令其抖动 duration 秒,幅度为 amount。
func shake(amount: float, duration_time: float = 0.001) -> void:
self.progress_ratio = 0.0
self.shake_amount = amount
self.duration = duration_time

func _ready():
if self.attached_node == null:
self.attached_node = self.get_parent()

func _process(delta: float):
if self.progress_ratio < 1.0:
self.progress_ratio = min(1.0, self.progress_ratio + delta / self.duration)
self.attached_node.position = self.get_position_delta()
6 changes: 6 additions & 0 deletions components/shake_component/shake_component.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://oxlryy260uym"]

[ext_resource type="Script" path="res://components/shake_component/shake_component.gd" id="1_3k4mj"]

[node name="ShakeComponent" type="Node"]
script = ExtResource("1_3k4mj")
97 changes: 61 additions & 36 deletions levels/base_level/base_level.gd
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func init(_chap_id: int, _lvl_id: int) -> void:

var new_block: Block = BlockScn.instantiate()

new_block.quest_pos = i
new_block.ind_in_question = i
if ch != "." and ch != "_":
new_block.set_is_fixed(true)
new_block.set_word(ch)
Expand Down Expand Up @@ -128,46 +128,71 @@ func stage_clear() -> void:


func _on_block_occupied_card_changed(_node) -> void:
var block_array = []
for block: Block in $Blocks.get_children():
var block_array : Array[Block]
block_array.assign($Blocks.get_children())

var sfx_wa_flag := false

# 获取 expr
for block in block_array:
if block.is_empty():
#print(block.quest_pos, " is not occupied")
return
expr[block.quest_pos] = block.get_word()
block_array.append(block)
expr[block.ind_in_question] = "_"
else:
expr[block.ind_in_question] = block.get_word()

# prints("# expr: ", expr)
prints()
prints("# expr: ", expr)

# 检查表达式是否合法,并设置 invalid 标志
var invalid_inds := ExprValidator.check_invalid(expr)
for block in block_array:
if block.ind_in_question in invalid_inds:
if not block.get_is_invalid():
sfx_wa_flag = true
block.set_is_invalid(true)
else:
block.set_is_invalid(false)

if expr.count("_") == 0:
var info = ExprValidator.check(expr, req_pos)
# prints("expr:", expr)
# prints("info:", info)

if info[0] != "OK":
SoundManager.play_sfx(sfx_wrong_answer, sfx_wrong_answer_db)
print("# invalid_inds: ", invalid_inds)

# 检查表达式笑脸列表,并设置 is_golden 标志
var smile_inds := ExprValidator.get_smile_inds(expr)
for block in block_array:
block.set_is_golden(block.ind_in_question in smile_inds)

if info[0] == "INVALID":
for block: Block in $Blocks.get_children():
if block.quest_pos in info[1]:
block.shake(false)
elif info[0] == "SMILE_UNSATISFIED":
for block: Block in $Blocks.get_children():
if block.quest_pos in info[1]:
block.shake(true)
elif info[0] == "NOT_ALWAYS_TRUE":
for block: Block in $Blocks.get_children():
block.shake(false)
print("# smile_inds: ", smile_inds)

# 若表达式合法,且已填满,则考虑检查是否满足笑脸要求
if invalid_inds.size() == 0 and expr.find("_") == -1:
var smile_unsatisfied_inds: Array[int]
smile_unsatisfied_inds.assign(req_pos.filter(func(ind: int): return ind not in smile_inds))

# 若不满足,则设置不满足笑脸要求的块为 invalid
if smile_unsatisfied_inds.size() > 0:
for block in block_array:
if block.ind_in_question in smile_unsatisfied_inds:
if not block.get_is_invalid():
sfx_wa_flag = true
block.set_is_invalid(true)

# 若满足笑脸要求,则检测是否恒等
else:
# victory
get_tree().current_scene.set_victory(true)
stage_clear()
# print(block_array)
for i in range(len(block_array)):
if i != 0:
if ExprValidator.is_smile(expr[i - 1] + expr[i]):
# print(expr[i-1]+expr[i])
block_array[i - 1].set_color(ImageLib.get_palette_color_by_name("golden"))
block_array[i].set_color(ImageLib.get_palette_color_by_name("golden"))

print("# check: ", ExprValidator.check_always_true(expr))
if not ExprValidator.check_always_true(expr).is_empty():
# 若不恒等,设置所有块为 invalid
for block in block_array:
if not block.get_is_invalid():
sfx_wa_flag = true
block.set_is_invalid(true)

else:
get_tree().current_scene.set_victory(true)
stage_clear()


if sfx_wa_flag:
SoundManager.play_sfx(sfx_wrong_answer, sfx_wrong_answer_db)

func _input(event: InputEvent):
if event is InputEventKey:
Expand Down
74 changes: 37 additions & 37 deletions objects/block/block.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ class_name Block extends Area2D
## 当被占用的 [Card] 发生改变后发出。
## [br][br]
## [param block] 为发出该信号的 [Block] 对象。
signal occupied_card_changed(block: Block)
signal occupied_card_changed(block: Block)



const SHAKE_AMOUNT := 3.0 ## 振动时的振幅(单位为像素)。
const SHAKE_DURATION := 0.2 ## 振动时的持续时间(单位为秒)。


var is_fixed : bool ## 是否为固定的(即在表达式中已初始化了的)。
var is_golden_frame : bool ## 是否为金色框。
var is_shaking := false ## 是否正在振动。

var is_invalid := false ## 是否为非法位置。
var is_golden := false ## 是否为金色字符。

var occupied_card: Card = null ## 占用的 Card 对象。

var quest_pos := -1 ## 在表达式中对应字符位置的下标。
var ind_in_question := -1 ## 在表达式中对应字符位置的下标。



Expand Down Expand Up @@ -63,6 +67,37 @@ func get_word() -> String:
else:
return self.occupied_card.get_word()

func get_is_invalid() -> bool:
return self.is_invalid

func set_is_invalid(value: bool) -> void:
if get_is_invalid() == value:
return

self.is_invalid = value
if self.occupied_card != null:
self.occupied_card.set_is_invalid(value)

if self.is_invalid:
$Word.set_color(ImageLib.get_palette_color_by_name("red"))

$Word/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION)
$GoalFrameSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION)
$PitSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION)

else:
$Word.set_color(ImageLib.get_palette_color_by_name("black"))

func set_is_golden(value: bool) -> void:
if self.is_golden == value:
return

self.is_golden = value
if self.occupied_card != null:
self.occupied_card.set_is_golden(value)

if not self.is_invalid:
$Word.set_color(ImageLib.get_palette_color_by_name("golden" if self.is_golden else "black"))

## 设置 [member is_fixed] 为 [param value]。
func set_is_fixed(value: bool) -> void:
Expand Down Expand Up @@ -90,38 +125,3 @@ func set_color(value: Color) -> void:
else:
if self.occupied_card != null:
self.occupied_card.set_color(value)



## 开启震动。若 [param is_frame_red] 为 [code]true[/code],则将框改为红色。
func shake(is_frame_red: bool) -> void:
# 开启震动
$ShakeTimer.start()
self.is_shaking = true

# 使附着的卡牌也震动
if self.occupied_card != null:
self.occupied_card.shake(not is_frame_red, SHAKE_AMOUNT, $ShakeTimer.wait_time) # 若框不红,则里面的字要红
elif not is_frame_red:
$Word.set_color(ImageLib.get_palette_color_by_name("red"))

if is_frame_red:
$GoalFrameSprite.animation = "red"


func _process(_delta):
var offset := 0
if is_shaking:
var progress = (1 - $ShakeTimer.time_left / $ShakeTimer.wait_time) * 2 * PI
offset = int(sin(progress) * SHAKE_AMOUNT)
$GoalFrameSprite.position.x = offset
$PitSprite.position.x = offset
$Word.position.x = offset



func _on_shake_timer_timeout() -> void:
self.is_shaking = false
$GoalFrameSprite.animation = "default"
$Word.set_color(ImageLib.get_palette_color_by_name("black"))

16 changes: 15 additions & 1 deletion objects/block/block.tscn
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
[gd_scene load_steps=8 format=3 uid="uid://ckjmjwywh78fe"]
[gd_scene load_steps=11 format=3 uid="uid://ckjmjwywh78fe"]

[ext_resource type="Script" path="res://objects/block/block.gd" id="1_11h57"]
[ext_resource type="PackedScene" uid="uid://cvx7wowcbfo0r" path="res://objects/word/word.tscn" id="2_pjs40"]
[ext_resource type="Shader" path="res://objects/word/word.gdshader" id="3_gn6mn"]
[ext_resource type="Texture2D" uid="uid://dvj7bjepxeks0" path="res://objects/block/pit.png" id="3_ye3hk"]
[ext_resource type="PackedScene" uid="uid://oxlryy260uym" path="res://components/shake_component/shake_component.tscn" id="4_vdwdr"]
[ext_resource type="Texture2D" uid="uid://cb71r50jw2a3i" path="res://objects/block/goal_frame.png" id="4_yvtjk"]
[ext_resource type="Texture2D" uid="uid://cvv532ui6p5gg" path="res://objects/block/goal_frame_red.png" id="5_et5vw"]

[sub_resource type="ShaderMaterial" id="ShaderMaterial_55t0h"]
resource_local_to_scene = true
shader = ExtResource("3_gn6mn")
shader_parameter/color = null

[sub_resource type="RectangleShape2D" id="RectangleShape2D_3if4e"]
size = Vector2(22, 22)

Expand All @@ -32,6 +39,9 @@ animations = [{
script = ExtResource("1_11h57")

[node name="Word" parent="." instance=ExtResource("2_pjs40")]
material = SubResource("ShaderMaterial_55t0h")

[node name="ShakeComponent" parent="Word" instance=ExtResource("4_vdwdr")]

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
z_index = 50
Expand All @@ -42,12 +52,16 @@ debug_color = Color(0.968627, 0, 0.470588, 0.419608)
z_index = -1
texture = ExtResource("3_ye3hk")

[node name="ShakeComponent" parent="PitSprite" instance=ExtResource("4_vdwdr")]

[node name="ShakeTimer" type="Timer" parent="."]
wait_time = 0.2

[node name="GoalFrameSprite" type="AnimatedSprite2D" parent="."]
sprite_frames = SubResource("SpriteFrames_3dme2")

[node name="ShakeComponent" parent="GoalFrameSprite" instance=ExtResource("4_vdwdr")]

[connection signal="area_entered" from="." to="." method="_on_area_entered"]
[connection signal="area_exited" from="." to="." method="_on_area_exited"]
[connection signal="timeout" from="ShakeTimer" to="." method="_on_shake_timer_timeout"]
Loading

0 comments on commit beceb8e

Please sign in to comment.