From 727c40e1f0ab293f29ebe4f5bb2de4c1dbca8f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=A8=E8=90=BD=E5=9F=BA=E5=9B=B4=E8=99=BE?= <3161880837@qq.com> Date: Tue, 26 Aug 2025 12:21:09 +0800 Subject: [PATCH] feat: Add weapon system and attack functionality to Rooster character - Introduced a weapons node in EntityBase for weapon management. - Updated Rooster to include cooldown for attacks and weapon anchoring. - Implemented attack method in Rooster to generate bullets. - Enhanced BulletBase with life distance and lifetime properties. - Created Chick character with animations and integrated into the world. - Updated PurpleCrystal bullet behavior to move forward based on rotation. - Refactored EntityBase to use a fields dictionary for attributes. - Added FieldStore for managing entity-related constants. - Adjusted GameRule to include bullet speed multiplier. --- components/Abstracts/EntityBase.tscn | 3 ++ components/Bullets/PurpleCrystal.tscn | 8 +++- components/Characters/Chick.tscn | 36 +++++++++++++++++ components/Characters/Rooster.tscn | 4 ++ project.godot | 5 +++ resources/bullets/purple-crystal/frames/0.svg | 28 +------------ scripts/Contents/Bullets/PurpleCrystal.gd | 5 +++ scripts/Contents/Characters/Rooster.gd | 6 +++ scripts/Statemachine/BulletBase.gd | 22 ++++++++++ scripts/Statemachine/EntityBase.gd | 40 +++++++++++++++---- scripts/Tools/FieldStore.gd | 10 +++++ scripts/Tools/GameRule.gd | 3 +- scripts/Tools/WorldTool.gd | 4 +- world.tscn | 15 ++++++- 14 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 components/Characters/Chick.tscn create mode 100644 scripts/Contents/Bullets/PurpleCrystal.gd create mode 100644 scripts/Tools/FieldStore.gd diff --git a/components/Abstracts/EntityBase.tscn b/components/Abstracts/EntityBase.tscn index 88673cd..ff3ede3 100644 --- a/components/Abstracts/EntityBase.tscn +++ b/components/Abstracts/EntityBase.tscn @@ -114,6 +114,9 @@ tree_root = SubResource("AnimationNodeBlendSpace1D_51ube") anim_player = NodePath("..") parameters/blend_position = 1.0 +[node name="weapons" type="Node2D" parent="texture"] +unique_name_in_owner = true + [node name="movebox" type="CollisionShape2D" parent="."] shape = SubResource("RectangleShape2D_ce3to") diff --git a/components/Bullets/PurpleCrystal.tscn b/components/Bullets/PurpleCrystal.tscn index b992a5a..9664231 100644 --- a/components/Bullets/PurpleCrystal.tscn +++ b/components/Bullets/PurpleCrystal.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=5 format=3 uid="uid://uj0aqhm8wgy8"] +[gd_scene load_steps=6 format=3 uid="uid://uj0aqhm8wgy8"] [ext_resource type="PackedScene" uid="uid://crtdkysmnkith" path="res://components/Abstracts/BulletBase.tscn" id="1_45mh7"] +[ext_resource type="Script" path="res://scripts/Contents/Bullets/PurpleCrystal.gd" id="2_4lnlm"] [ext_resource type="Texture2D" uid="uid://c7hyatbuieaj" path="res://resources/bullets/purple-crystal/frames/0.svg" id="2_ca3pq"] [sub_resource type="SpriteFrames" id="SpriteFrames_r86b3"] @@ -17,10 +18,13 @@ animations = [{ [sub_resource type="CircleShape2D" id="CircleShape2D_ty1as"] [node name="PurpleCrystal" instance=ExtResource("1_45mh7")] +script = ExtResource("2_4lnlm") +speed = 5.0 +lifeDistance = 700.0 [node name="texture" parent="." index="0"] sprite_frames = SubResource("SpriteFrames_r86b3") [node name="hitbox" parent="." index="1"] -position = Vector2(0, -13) +position = Vector2(12, 0) shape = SubResource("CircleShape2D_ty1as") diff --git a/components/Characters/Chick.tscn b/components/Characters/Chick.tscn new file mode 100644 index 0000000..26db075 --- /dev/null +++ b/components/Characters/Chick.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=5 format=3 uid="uid://b0ncrvm8u4pox"] + +[ext_resource type="PackedScene" uid="uid://cvogxi7mktumf" path="res://components/Abstracts/EntityBase.tscn" id="1_goqmy"] +[ext_resource type="Texture2D" uid="uid://7pkplcqqxvnp" path="res://resources/characters/chick/chick-a.svg" id="2_syddq"] +[ext_resource type="Texture2D" uid="uid://dj5dvqb8gsedr" path="res://resources/characters/chick/chick-b.svg" id="3_064jv"] + +[sub_resource type="SpriteFrames" id="SpriteFrames_xji3d"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": ExtResource("2_syddq") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": ExtResource("2_syddq") +}, { +"duration": 1.0, +"texture": ExtResource("3_064jv") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] + +[node name="Chick" instance=ExtResource("1_goqmy")] + +[node name="texture" parent="." index="0"] +sprite_frames = SubResource("SpriteFrames_xji3d") +animation = &"walk" + +[node name="statebar" parent="." index="2" node_paths=PackedStringArray("entity")] +entity = NodePath("..") diff --git a/components/Characters/Rooster.tscn b/components/Characters/Rooster.tscn index 339209d..fc44457 100644 --- a/components/Characters/Rooster.tscn +++ b/components/Characters/Rooster.tscn @@ -32,6 +32,7 @@ radius = 41.0122 [node name="Rooster" instance=ExtResource("1_e5pl8")] script = ExtResource("2_oqdqd") +cooldownUnit = 200.0 [node name="texture" parent="." index="0"] position = Vector2(0, -70) @@ -42,6 +43,9 @@ animation = &"walk" position = Vector2(12, 16) shape = SubResource("CircleShape2D_h1v0q") +[node name="normal" type="Node2D" parent="texture/weapons" index="0"] +position = Vector2(54, -45) + [node name="statebar" parent="." index="2" node_paths=PackedStringArray("entity")] position = Vector2(0, -151) entity = NodePath("..") diff --git a/project.godot b/project.godot index 7d49dac..02b55fe 100644 --- a/project.godot +++ b/project.godot @@ -41,6 +41,11 @@ m_right={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) ] } +attack={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} [rendering] diff --git a/resources/bullets/purple-crystal/frames/0.svg b/resources/bullets/purple-crystal/frames/0.svg index 26ffcb7..b3c4e2a 100644 --- a/resources/bullets/purple-crystal/frames/0.svg +++ b/resources/bullets/purple-crystal/frames/0.svg @@ -1,27 +1 @@ - - - - - crystal-b - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/scripts/Contents/Bullets/PurpleCrystal.gd b/scripts/Contents/Bullets/PurpleCrystal.gd new file mode 100644 index 0000000..a38f4ea --- /dev/null +++ b/scripts/Contents/Bullets/PurpleCrystal.gd @@ -0,0 +1,5 @@ +extends BulletBase +class_name PurpleCrystal + +func ai(): + forward(Vector2.from_angle(rotation)) diff --git a/scripts/Contents/Characters/Rooster.gd b/scripts/Contents/Characters/Rooster.gd index 845628a..3a05283 100644 --- a/scripts/Contents/Characters/Rooster.gd +++ b/scripts/Contents/Characters/Rooster.gd @@ -10,3 +10,9 @@ func ai(): move(direction) if direction.length() == 0: texture.play("idle") + if Input.is_action_pressed("attack"): + tryAttack(0) +func attack(type): + if type == 0: + var weaponPos = findWeaponAnchor("normal") + BulletBase.generate(preload("res://components/Bullets/PurpleCrystal.tscn"), self, weaponPos, (get_global_mouse_position() - weaponPos).angle()) diff --git a/scripts/Statemachine/BulletBase.gd b/scripts/Statemachine/BulletBase.gd index ed6f36b..4b6e9ef 100644 --- a/scripts/Statemachine/BulletBase.gd +++ b/scripts/Statemachine/BulletBase.gd @@ -3,11 +3,26 @@ class_name BulletBase @export var speed: float = 1 @export var damage: float = 10 +@export var lifeDistance: float = -1 # -1表示无限距离 +@export var lifeTime: float = -1 # -1表示无限时间 var launcher: EntityBase = null +var spawnInWhen: float = 0 +var spawnInWhere: Vector2 = Vector2.ZERO func _ready(): area_entered.connect(hit) + spawnInWhen = Time.get_ticks_msec() + spawnInWhere = position +func _process(_delta: float) -> void: + if lifeTime > 0: + if Time.get_ticks_msec() - spawnInWhen >= lifeTime: + destroy() + if lifeDistance > 0: + if position.distance_to(spawnInWhere) >= lifeDistance: + destroy() +func _physics_process(_delta: float) -> void: + ai() func hit(target: Node): var entity: EntityBase = EntityTool.fromHurtbox(target) @@ -16,6 +31,13 @@ func hit(target: Node): if GameRule.allowFriendlyFire: if entity.isPlayer() == launcher.isPlayer(): return entity.takeDamage(self) +func forward(direction: Vector2): + position += direction.normalized() * speed * GameRule.bulletSpeedMultiplier + +func ai(): + pass +func destroy(): + queue_free() static func generate( bullet: PackedScene, diff --git a/scripts/Statemachine/EntityBase.gd b/scripts/Statemachine/EntityBase.gd index 4c68434..796e5a2 100644 --- a/scripts/Statemachine/EntityBase.gd +++ b/scripts/Statemachine/EntityBase.gd @@ -1,10 +1,16 @@ extends CharacterBody2D class_name EntityBase # 这是个抽象类 -@export var maxHealth: float = 100 -@export var movementSpeed: float = 1 +@export var fields: Dictionary = { + FieldStore.Entity.MAX_HEALTH: 100, + FieldStore.Entity.DAMAGE_MULTIPILER: 1, + FieldStore.Entity.MOVEMENT_SPEED: 1, + FieldStore.Entity.ATTACK_SPEED: 1, + FieldStore.Entity.CRIT_RATE: 0.05, # 0.05 = 5% + FieldStore.Entity.CRIT_DAMAGE: 2 # 2 = 200% +} # 存一下词条 @export var isBoss: bool = false -@export var weapons: Array[Node2D] = [] +@export var cooldownUnit: float = 100 # 100毫秒每次攻击 @onready var animatree: AnimationTree = $"%animatree" @onready var texture: AnimatedSprite2D = $"%texture" @@ -13,11 +19,12 @@ class_name EntityBase # 这是个抽象类 var health: float = 0 var lastDirection: int = 1 +var lastAttack: int = 0 func _ready(): - health = maxHealth + health = fields.get(FieldStore.Entity.MAX_HEALTH) func _process(_delta): - health = clamp(health, 0, maxHealth) + health = clamp(health, 0, fields.get(FieldStore.Entity.MAX_HEALTH)) animatree.set("parameters/blend_position", lerpf(animatree.get("parameters/blend_position"), lastDirection, 0.1)) func _physics_process(_delta: float) -> void: velocity = Vector2.ZERO @@ -26,7 +33,7 @@ func _physics_process(_delta: float) -> void: # 通用方法 func move(direction: Vector2): - velocity = direction.normalized() * movementSpeed * 150 * abs(animatree.get("parameters/blend_position")) + velocity = direction.normalized() * fields.get(FieldStore.Entity.MOVEMENT_SPEED) * 200 * abs(animatree.get("parameters/blend_position")) var currentDirection = sign(direction.x) if currentDirection != 0: lastDirection = currentDirection @@ -34,6 +41,22 @@ func takeDamage(bullet: BulletBase): health -= bullet.damage if health <= 0: die() +func isCooldowned(): + return Time.get_ticks_msec() - lastAttack >= cooldownUnit +func startCooldown(): + var state = isCooldowned() + if state: + lastAttack = Time.get_ticks_msec() + return state +func tryAttack(type: int): + if startCooldown(): + attack(type) +func findWeaponAnchor(weaponName: String): + var anchor = $"%weapons".get_node(weaponName) + if anchor is Node2D: + return (anchor.position + texture.position) * Vector2(animatree.get("parameters/blend_position"), 1) + position + else: + return Vector2.ZERO # 关于分组 func isPlayer(): @@ -51,11 +74,14 @@ static func generate( entity: PackedScene, spawnPosition: Vector2, spawnRotation: float, + isMob: bool = true, addtoWorld: bool = true ): - var instance = entity.instance() + var instance: EntityBase = entity.instance() instance.position = spawnPosition instance.rotation = spawnRotation + if isMob: + instance.add_to_group("mobs") if addtoWorld: WorldTool.rootNode.add_child(instance) return instance diff --git a/scripts/Tools/FieldStore.gd b/scripts/Tools/FieldStore.gd new file mode 100644 index 0000000..35d4015 --- /dev/null +++ b/scripts/Tools/FieldStore.gd @@ -0,0 +1,10 @@ +class_name FieldStore + +enum Entity { + MAX_HEALTH, + DAMAGE_MULTIPILER, + MOVEMENT_SPEED, + ATTACK_SPEED, + CRIT_RATE, + CRIT_DAMAGE, +} \ No newline at end of file diff --git a/scripts/Tools/GameRule.gd b/scripts/Tools/GameRule.gd index 36757f1..c4a082b 100644 --- a/scripts/Tools/GameRule.gd +++ b/scripts/Tools/GameRule.gd @@ -1,3 +1,4 @@ class_name GameRule -static var allowFriendlyFire: bool = false # 是否允许友军伤害 \ No newline at end of file +static var allowFriendlyFire: bool = false # 是否允许友军伤害 +static var bulletSpeedMultiplier: float = 1 # 子弹速度倍率 \ No newline at end of file diff --git a/scripts/Tools/WorldTool.gd b/scripts/Tools/WorldTool.gd index 2b507a5..ee69dc3 100644 --- a/scripts/Tools/WorldTool.gd +++ b/scripts/Tools/WorldTool.gd @@ -5,5 +5,5 @@ static var rootNode: Node2D static var tree: SceneTree func _ready(): - tree = get_tree() - rootNode = self + tree = get_tree() + rootNode = self diff --git a/world.tscn b/world.tscn index 2aac90e..0d5bc6e 100644 --- a/world.tscn +++ b/world.tscn @@ -1,10 +1,23 @@ -[gd_scene load_steps=3 format=3 uid="uid://dmxi1ikn6avig"] +[gd_scene load_steps=4 format=3 uid="uid://dmxi1ikn6avig"] [ext_resource type="PackedScene" uid="uid://bm7ymrri6pykb" path="res://components/Characters/Rooster.tscn" id="1_7jr0n"] [ext_resource type="Script" path="res://scripts/Tools/WorldTool.gd" id="1_ei0ch"] +[ext_resource type="PackedScene" uid="uid://b0ncrvm8u4pox" path="res://components/Characters/Chick.tscn" id="3_jho84"] [node name="world" type="Node2D"] script = ExtResource("1_ei0ch") [node name="rooster" parent="." groups=["players"] instance=ExtResource("1_7jr0n")] position = Vector2(394, 274) + +[node name="chick" parent="." groups=["mobs"] instance=ExtResource("3_jho84")] +position = Vector2(644, 391) + +[node name="Chick" parent="." instance=ExtResource("3_jho84")] +position = Vector2(366, 547) + +[node name="Chick2" parent="." instance=ExtResource("3_jho84")] +position = Vector2(686, 551) + +[node name="Chick3" parent="." instance=ExtResource("3_jho84")] +position = Vector2(760, 200)