diff --git a/DungeonShooting_Godot/prefab/role/Enemy0001.tscn b/DungeonShooting_Godot/prefab/role/Enemy0001.tscn index 815c72f..923ba15 100644 --- a/DungeonShooting_Godot/prefab/role/Enemy0001.tscn +++ b/DungeonShooting_Godot/prefab/role/Enemy0001.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=12 format=3 uid="uid://b8s1dgu63fddf"] -[ext_resource type="PackedScene" uid="uid://dbrig6dq441wo" path="res://prefab/role/template/AdvancedEnemyTemplate.tscn" id="1_2vqwe"] -[ext_resource type="Script" path="res://src/game/activity/role/enemy/AdvancedEnemy.cs" id="2_thbey"] +[ext_resource type="PackedScene" uid="uid://dbrig6dq441wo" path="res://prefab/role/template/EnemyTemplate.tscn" id="1_2vqwe"] +[ext_resource type="Script" path="res://src/game/activity/role/enemy/Enemy.cs" id="2_0pcq3"] [ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="3_x8agd"] [ext_resource type="SpriteFrames" uid="uid://cnctpyrn02rhd" path="res://resource/spriteFrames/role/Enemy0001.tres" id="4_qv8w5"] @@ -269,15 +269,11 @@ "query": SubResource("Animation_usfrh") } -[node name="Enemy0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_2vqwe")] -script = ExtResource("2_thbey") +[node name="Enemy0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_2vqwe")] +script = ExtResource("2_0pcq3") ViewRay = NodePath("ViewRay") NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D") NavigationPoint = NodePath("NavigationPoint") -MountPoint = NodePath("MountPoint") -BackMountPoint = NodePath("BackMountPoint") -MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") -MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") HurtArea = NodePath("HurtArea") HurtCollision = NodePath("HurtArea/HurtCollision") InteractiveArea = NodePath("InteractiveArea") @@ -285,6 +281,10 @@ TipRoot = NodePath("TipRoot") TipSprite = NodePath("TipRoot/TipSprite") AnimationPlayer = NodePath("AnimationPlayer") +MountPoint = NodePath("MountPoint") +BackMountPoint = NodePath("BackMountPoint") +MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") +MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") ShadowSprite = NodePath("ShadowSprite") AnimatedSprite = NodePath("AnimatedSprite") Collision = NodePath("Collision") diff --git a/DungeonShooting_Godot/prefab/role/Enemy0002.tscn b/DungeonShooting_Godot/prefab/role/Enemy0002.tscn deleted file mode 100644 index b7b5558..0000000 --- a/DungeonShooting_Godot/prefab/role/Enemy0002.tscn +++ /dev/null @@ -1,57 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://b5r3hd8kv2wmd"] - -[ext_resource type="PackedScene" uid="uid://dxeqcssparqoo" path="res://prefab/role/template/EnemyTemplate.tscn" id="1_rikvp"] -[ext_resource type="Script" path="res://src/game/activity/role/enemy/Enemy.cs" id="2_wjtfl"] -[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="3_gr4gs"] -[ext_resource type="SpriteFrames" uid="uid://ctpkpxgcwb583" path="res://resource/spriteFrames/role/Enemy0002.tres" id="4_ehtyi"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_7theg"] -resource_local_to_scene = true -shader = ExtResource("3_gr4gs") -shader_parameter/blend = Color(0, 0, 0, 0.470588) -shader_parameter/schedule = 1.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_ntjmx"] -resource_local_to_scene = true -shader = ExtResource("3_gr4gs") -shader_parameter/blend = Color(1, 1, 1, 1) -shader_parameter/schedule = 0.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[node name="Enemy0002" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_rikvp")] -script = ExtResource("2_wjtfl") -ViewRay = NodePath("ViewRay") -NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D") -NavigationPoint = NodePath("NavigationPoint") -FirePoint = NodePath("AnimatedSprite/FirePoint") -HurtArea = NodePath("HurtArea") -HurtCollision = NodePath("HurtArea/HurtCollision") -InteractiveArea = NodePath("InteractiveArea") -InteractiveCollision = NodePath("InteractiveArea/InteractiveCollision") -TipRoot = NodePath("TipRoot") -TipSprite = NodePath("TipRoot/TipSprite") -AnimationPlayer = NodePath("AnimationPlayer") -ShadowSprite = NodePath("ShadowSprite") -AnimatedSprite = NodePath("AnimatedSprite") -Collision = NodePath("Collision") - -[node name="ShadowSprite" parent="." index="0"] -material = SubResource("ShaderMaterial_7theg") - -[node name="AnimatedSprite" parent="." index="1"] -material = SubResource("ShaderMaterial_ntjmx") -position = Vector2(0, -10) -sprite_frames = ExtResource("4_ehtyi") -animation = &"attack" - -[node name="FirePoint" type="Marker2D" parent="AnimatedSprite" index="0"] -position = Vector2(9, 4) diff --git a/DungeonShooting_Godot/prefab/role/Role0001.tscn b/DungeonShooting_Godot/prefab/role/Role0001.tscn index 24c63b1..43ea2ef 100644 --- a/DungeonShooting_Godot/prefab/role/Role0001.tscn +++ b/DungeonShooting_Godot/prefab/role/Role0001.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=7 format=3 uid="uid://cxhrcytrx0kcf"] -[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/template/AdvancedRoleTemplate.tscn" id="1_10c2n"] +[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/template/RoleTemplate.tscn" id="1_10c2n"] [ext_resource type="Script" path="res://src/game/activity/role/player/Player.cs" id="2_6xwnt"] [ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="3_rk4gg"] [ext_resource type="SpriteFrames" uid="uid://n11thtali6es" path="res://resource/spriteFrames/role/Role0001.tres" id="4_galcc"] @@ -27,13 +27,9 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[node name="Role0001" node_paths=PackedStringArray("MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_10c2n")] +[node name="Role0001" node_paths=PackedStringArray("HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_10c2n")] collision_layer = 8 script = ExtResource("2_6xwnt") -MountPoint = NodePath("MountPoint") -BackMountPoint = NodePath("BackMountPoint") -MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") -MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") HurtArea = NodePath("HurtArea") HurtCollision = NodePath("HurtArea/HurtCollision") InteractiveArea = NodePath("InteractiveArea") @@ -41,6 +37,10 @@ TipRoot = NodePath("TipRoot") TipSprite = NodePath("TipRoot/TipSprite") AnimationPlayer = NodePath("AnimationPlayer") +MountPoint = NodePath("MountPoint") +BackMountPoint = NodePath("BackMountPoint") +MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") +MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") ShadowSprite = NodePath("ShadowSprite") AnimatedSprite = NodePath("AnimatedSprite") Collision = NodePath("Collision") diff --git a/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn deleted file mode 100644 index b02e44a..0000000 --- a/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn +++ /dev/null @@ -1,48 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://dbrig6dq441wo"] - -[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/template/AdvancedRoleTemplate.tscn" id="1_5po38"] -[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="3_x8agd"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_8vxx6"] -resource_local_to_scene = true -shader = ExtResource("3_x8agd") -shader_parameter/blend = Color(0, 0, 0, 0.470588) -shader_parameter/schedule = 1.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_k8mt5"] -resource_local_to_scene = true -shader = ExtResource("3_x8agd") -shader_parameter/blend = Color(1, 1, 1, 1) -shader_parameter/schedule = 0.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[node name="AdvancedEnemyTemplate" instance=ExtResource("1_5po38")] -collision_layer = 16 -collision_mask = 25 - -[node name="ShadowSprite" parent="." index="0"] -material = SubResource("ShaderMaterial_8vxx6") - -[node name="AnimatedSprite" parent="." index="2"] -material = SubResource("ShaderMaterial_k8mt5") - -[node name="ViewRay" type="RayCast2D" parent="." index="6"] -position = Vector2(0, -8) -enabled = false - -[node name="NavigationPoint" type="Marker2D" parent="." index="8"] -position = Vector2(0, -5) - -[node name="NavigationAgent2D" type="NavigationAgent2D" parent="NavigationPoint" index="0"] -path_desired_distance = 3.0 -target_desired_distance = 3.0 -radius = 20.0 diff --git a/DungeonShooting_Godot/prefab/role/template/AdvancedRoleTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/AdvancedRoleTemplate.tscn deleted file mode 100644 index 7b79ea0..0000000 --- a/DungeonShooting_Godot/prefab/role/template/AdvancedRoleTemplate.tscn +++ /dev/null @@ -1,93 +0,0 @@ -[gd_scene load_steps=9 format=3 uid="uid://cyrcv2jdgr8cf"] - -[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="1_xk5yk"] -[ext_resource type="Script" path="res://src/game/activity/role/MountRotation.cs" id="2_5ddpw"] -[ext_resource type="SpriteFrames" uid="uid://c8h5svp76h3kw" path="res://resource/spriteFrames/role/Role_tip.tres" id="3_bo78w"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_v2kfw"] -resource_local_to_scene = true -shader = ExtResource("1_xk5yk") -shader_parameter/blend = Color(0, 0, 0, 0.470588) -shader_parameter/schedule = 0.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_yif6x"] -resource_local_to_scene = true -shader = ExtResource("1_xk5yk") -shader_parameter/blend = Color(1, 1, 1, 1) -shader_parameter/schedule = 0.0 -shader_parameter/modulate = Color(1, 1, 1, 1) -shader_parameter/show_outline = true -shader_parameter/outline_color = Color(0, 0, 0, 1) -shader_parameter/outline_rainbow = false -shader_parameter/outline_use_blend = true - -[sub_resource type="CircleShape2D" id="CircleShape2D_5pj80"] -radius = 4.0 - -[sub_resource type="RectangleShape2D" id="RectangleShape2D_1eja2"] -size = Vector2(12, 18) - -[sub_resource type="RectangleShape2D" id="RectangleShape2D_n68nu"] -size = Vector2(10, 16.5) - -[node name="AdvancedRoleTemplate" type="CharacterBody2D"] -collision_layer = 0 - -[node name="ShadowSprite" type="Sprite2D" parent="."] -z_index = -1 -material = SubResource("ShaderMaterial_v2kfw") - -[node name="BackMountPoint" type="Marker2D" parent="."] -position = Vector2(0, -12) - -[node name="AnimatedSprite" type="AnimatedSprite2D" parent="."] -material = SubResource("ShaderMaterial_yif6x") -offset = Vector2(0, -12) - -[node name="Collision" type="CollisionShape2D" parent="."] -position = Vector2(0, -4) -shape = SubResource("CircleShape2D_5pj80") - -[node name="HurtArea" type="Area2D" parent="."] -collision_layer = 0 -collision_mask = 0 -monitoring = false - -[node name="HurtCollision" type="CollisionShape2D" parent="HurtArea"] -position = Vector2(0, -9) -shape = SubResource("RectangleShape2D_1eja2") - -[node name="InteractiveArea" type="Area2D" parent="."] -visible = false -collision_layer = 0 -collision_mask = 4 -monitorable = false - -[node name="InteractiveCollision" type="CollisionShape2D" parent="InteractiveArea"] -position = Vector2(0, -5) -shape = SubResource("RectangleShape2D_n68nu") - -[node name="MountPoint" type="Marker2D" parent="."] -position = Vector2(2, -8) -script = ExtResource("2_5ddpw") - -[node name="MeleeAttackArea" type="Area2D" parent="MountPoint"] -collision_layer = 0 -collision_mask = 0 -monitorable = false - -[node name="MeleeAttackCollision" type="CollisionPolygon2D" parent="MountPoint/MeleeAttackArea"] - -[node name="TipRoot" type="Node2D" parent="."] - -[node name="TipSprite" type="AnimatedSprite2D" parent="TipRoot"] -visible = false -position = Vector2(0, -22) -sprite_frames = ExtResource("3_bo78w") - -[node name="AnimationPlayer" type="AnimationPlayer" parent="."] diff --git a/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn index 8d83646..272b97a 100644 --- a/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn +++ b/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=5 format=3 uid="uid://dxeqcssparqoo"] +[gd_scene load_steps=5 format=3 uid="uid://dbrig6dq441wo"] -[ext_resource type="PackedScene" uid="uid://0uc4naitjprl" path="res://prefab/role/template/RoleTemplate.tscn" id="1_u04qy"] -[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="2_tedjs"] +[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/template/RoleTemplate.tscn" id="1_5po38"] +[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="3_x8agd"] -[sub_resource type="ShaderMaterial" id="ShaderMaterial_fhls5"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_8vxx6"] resource_local_to_scene = true -shader = ExtResource("2_tedjs") +shader = ExtResource("3_x8agd") shader_parameter/blend = Color(0, 0, 0, 0.470588) shader_parameter/schedule = 1.0 shader_parameter/modulate = Color(1, 1, 1, 1) @@ -14,9 +14,9 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[sub_resource type="ShaderMaterial" id="ShaderMaterial_lnent"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_k8mt5"] resource_local_to_scene = true -shader = ExtResource("2_tedjs") +shader = ExtResource("3_x8agd") shader_parameter/blend = Color(1, 1, 1, 1) shader_parameter/schedule = 0.0 shader_parameter/modulate = Color(1, 1, 1, 1) @@ -25,21 +25,21 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[node name="EnemyTemplate" instance=ExtResource("1_u04qy")] +[node name="AdvancedEnemyTemplate" instance=ExtResource("1_5po38")] collision_layer = 16 collision_mask = 25 [node name="ShadowSprite" parent="." index="0"] -material = SubResource("ShaderMaterial_fhls5") +material = SubResource("ShaderMaterial_8vxx6") -[node name="AnimatedSprite" parent="." index="1"] -material = SubResource("ShaderMaterial_lnent") +[node name="AnimatedSprite" parent="." index="2"] +material = SubResource("ShaderMaterial_k8mt5") -[node name="ViewRay" type="RayCast2D" parent="." index="5"] +[node name="ViewRay" type="RayCast2D" parent="." index="6"] position = Vector2(0, -8) enabled = false -[node name="NavigationPoint" type="Marker2D" parent="." index="6"] +[node name="NavigationPoint" type="Marker2D" parent="." index="8"] position = Vector2(0, -5) [node name="NavigationAgent2D" type="NavigationAgent2D" parent="NavigationPoint" index="0"] diff --git a/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn index b8cb5a0..7b79ea0 100644 --- a/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn +++ b/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=8 format=3 uid="uid://0uc4naitjprl"] +[gd_scene load_steps=9 format=3 uid="uid://cyrcv2jdgr8cf"] [ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="1_xk5yk"] -[ext_resource type="SpriteFrames" uid="uid://c8h5svp76h3kw" path="res://resource/spriteFrames/role/Role_tip.tres" id="2_1udon"] +[ext_resource type="Script" path="res://src/game/activity/role/MountRotation.cs" id="2_5ddpw"] +[ext_resource type="SpriteFrames" uid="uid://c8h5svp76h3kw" path="res://resource/spriteFrames/role/Role_tip.tres" id="3_bo78w"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_v2kfw"] resource_local_to_scene = true @@ -34,13 +35,16 @@ [sub_resource type="RectangleShape2D" id="RectangleShape2D_n68nu"] size = Vector2(10, 16.5) -[node name="RoleTemplate" type="CharacterBody2D"] +[node name="AdvancedRoleTemplate" type="CharacterBody2D"] collision_layer = 0 [node name="ShadowSprite" type="Sprite2D" parent="."] z_index = -1 material = SubResource("ShaderMaterial_v2kfw") +[node name="BackMountPoint" type="Marker2D" parent="."] +position = Vector2(0, -12) + [node name="AnimatedSprite" type="AnimatedSprite2D" parent="."] material = SubResource("ShaderMaterial_yif6x") offset = Vector2(0, -12) @@ -68,9 +72,22 @@ position = Vector2(0, -5) shape = SubResource("RectangleShape2D_n68nu") -[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +[node name="MountPoint" type="Marker2D" parent="."] +position = Vector2(2, -8) +script = ExtResource("2_5ddpw") + +[node name="MeleeAttackArea" type="Area2D" parent="MountPoint"] +collision_layer = 0 +collision_mask = 0 +monitorable = false + +[node name="MeleeAttackCollision" type="CollisionPolygon2D" parent="MountPoint/MeleeAttackArea"] [node name="TipRoot" type="Node2D" parent="."] [node name="TipSprite" type="AnimatedSprite2D" parent="TipRoot"] -sprite_frames = ExtResource("2_1udon") +visible = false +position = Vector2(0, -22) +sprite_frames = ExtResource("3_bo78w") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] diff --git a/DungeonShooting_Godot/resource/sprite/role/common/Role_astonished.png b/DungeonShooting_Godot/resource/sprite/role/common/Role_astonished.png index 6ba090b..8d232dd 100644 --- a/DungeonShooting_Godot/resource/sprite/role/common/Role_astonished.png +++ b/DungeonShooting_Godot/resource/sprite/role/common/Role_astonished.png Binary files differ diff --git a/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs b/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs index ff5d0c9..00e5e81 100644 --- a/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs +++ b/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs @@ -431,7 +431,7 @@ else if (activityMark.ActivityType == ActivityType.Enemy) //敌人类型 { var role = (Role)activityObject; - if (role is AdvancedEnemy enemy && activityMark.Attr.TryGetValue("Weapon", out var weaponId)) //使用的武器 + if (role is Enemy enemy && activityMark.Attr.TryGetValue("Weapon", out var weaponId)) //使用的武器 { if (!string.IsNullOrEmpty(weaponId)) { diff --git a/DungeonShooting_Godot/src/game/Cursor.cs b/DungeonShooting_Godot/src/game/Cursor.cs index 7f8e528..aca4d11 100644 --- a/DungeonShooting_Godot/src/game/Cursor.cs +++ b/DungeonShooting_Godot/src/game/Cursor.cs @@ -13,7 +13,7 @@ /// /// 非GUI模式下鼠标指针所挂载的角色 /// - private AdvancedRole _mountRole; + private Role _mountRole; private Sprite2D center; private Sprite2D lt; @@ -88,7 +88,7 @@ /// /// 设置非GUI模式下鼠标指针所挂载的角色 /// - public void SetMountRole(AdvancedRole role) + public void SetMountRole(Role role) { _mountRole = role; } @@ -96,7 +96,7 @@ /// /// 获取非GUI模式下鼠标指针所挂载的角色 /// - public AdvancedRole GetMountRole() + public Role GetMountRole() { return _mountRole; } diff --git a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs index 5491250..a516c2f 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs @@ -146,7 +146,7 @@ } //造成伤害 - role.CallDeferred(nameof(AdvancedRole.Hurt), BulletData.TriggerRole.IsDestroyed ? null : BulletData.TriggerRole, BulletData.Harm, Rotation); + role.CallDeferred(nameof(Role.Hurt), BulletData.TriggerRole.IsDestroyed ? null : BulletData.TriggerRole, BulletData.Harm, Rotation); //穿透次数 CurrentPenetration++; diff --git a/DungeonShooting_Godot/src/game/activity/prop/Prop.cs b/DungeonShooting_Godot/src/game/activity/prop/Prop.cs index 09ddfca..c79dfc0 100644 --- a/DungeonShooting_Godot/src/game/activity/prop/Prop.cs +++ b/DungeonShooting_Godot/src/game/activity/prop/Prop.cs @@ -32,7 +32,7 @@ /// 触发扔掉道具效果, 并不会管道具是否在道具背包中 /// /// 触发扔掉该道具的的角色 - public void ThrowProp(AdvancedRole master) + public void ThrowProp(Role master) { ThrowProp(master, master.GlobalPosition); } diff --git a/DungeonShooting_Godot/src/game/activity/prop/active/ActiveProp5001.cs b/DungeonShooting_Godot/src/game/activity/prop/active/ActiveProp5001.cs index eb4662c..2bac5e6 100644 --- a/DungeonShooting_Godot/src/game/activity/prop/active/ActiveProp5001.cs +++ b/DungeonShooting_Godot/src/game/activity/prop/active/ActiveProp5001.cs @@ -16,24 +16,16 @@ public override bool OnCheckUse() { - if (Master is AdvancedRole advancedRole) - { - return advancedRole.WeaponPack.ActiveItem != null && !advancedRole.WeaponPack.ActiveItem.IsAmmoFull(); - } - - return false; + return Master.WeaponPack.ActiveItem != null && !Master.WeaponPack.ActiveItem.IsAmmoFull(); } protected override int OnUse() { - if (Master is AdvancedRole advancedRole) + var weapon = Master.WeaponPack.ActiveItem; + if (weapon != null) { - var weapon = advancedRole.WeaponPack.ActiveItem; - if (weapon != null) - { - weapon.SetTotalAmmo(weapon.Attribute.MaxAmmoCapacity); - return 1; - } + weapon.SetTotalAmmo(weapon.Attribute.MaxAmmoCapacity); + return 1; } return 0; diff --git a/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0010.cs b/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0010.cs index 6e92d10..6254213 100644 --- a/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0010.cs +++ b/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0010.cs @@ -52,9 +52,8 @@ private void CalcBulletRepelEvent(float originRepel, RefValue repel) { - if (repel.Value < 0 || (Master is AdvancedRole advancedRole && - advancedRole.WeaponPack.ActiveItem != null && - advancedRole.WeaponPack.ActiveItem.Attribute.IsMelee)) + if (repel.Value < 0 || (Master.WeaponPack.ActiveItem != null && + Master.WeaponPack.ActiveItem.Attribute.IsMelee)) { return; } diff --git a/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0013.cs b/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0013.cs index 05e0202..e43270a 100644 --- a/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0013.cs +++ b/DungeonShooting_Godot/src/game/activity/prop/buff/BuffProp0013.cs @@ -8,17 +8,11 @@ { public override void OnPickUpItem() { - if (Master is AdvancedRole advancedRole) - { - advancedRole.WeaponPack.SetCapacity(advancedRole.WeaponPack.Capacity + 1); - } + Master.WeaponPack.SetCapacity(Master.WeaponPack.Capacity + 1); } public override void OnRemoveItem() { - if (Master is AdvancedRole advancedRole) - { - advancedRole.WeaponPack.SetCapacity(advancedRole.WeaponPack.Capacity - 1); - } + Master.WeaponPack.SetCapacity(Master.WeaponPack.Capacity - 1); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/AdvancedRole.cs b/DungeonShooting_Godot/src/game/activity/role/AdvancedRole.cs deleted file mode 100644 index c1350c1..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/AdvancedRole.cs +++ /dev/null @@ -1,407 +0,0 @@ -using Godot; - -/// -/// 高级角色,可以拾起和使用武器 -/// -public abstract partial class AdvancedRole : Role -{ - /// - /// 角色携带的武器背包 - /// - public Package WeaponPack { get; private set; } - - /// - /// 武器挂载点 - /// - [Export, ExportFillNode] - public MountRotation MountPoint { get; set; } - /// - /// 背后武器的挂载点 - /// - [Export, ExportFillNode] - public Marker2D BackMountPoint { get; set; } - - /// - /// 近战碰撞检测区域 - /// - [Export, ExportFillNode] - public Area2D MeleeAttackArea { get; set; } - - /// - /// 近战碰撞检测区域的碰撞器 - /// - [Export, ExportFillNode] - public CollisionPolygon2D MeleeAttackCollision { get; set; } - - /// - /// 近战攻击时挥动武器的角度 - /// - [Export] - public float MeleeAttackAngle { get; set; } = 120; - - /// - /// 武器挂载点是否始终指向目标 - /// - public bool MountLookTarget { get; set; } = true; - - /// - /// 是否处于近战攻击中 - /// - public bool IsMeleeAttack { get; private set; } - - //近战计时器 - private float _meleeAttackTimer = 0; - - /// - /// 当拾起某个武器时调用 - /// - protected virtual void OnPickUpWeapon(Weapon weapon) - { - } - - /// - /// 当扔掉某个武器时调用 - /// - protected virtual void OnThrowWeapon(Weapon weapon) - { - } - - /// - /// 当切换到某个武器时调用 - /// - protected virtual void OnExchangeWeapon(Weapon weapon) - { - } - - public override void OnInit() - { - base.OnInit(); - WeaponPack = AddComponent>(); - WeaponPack.SetCapacity(4); - - MountPoint.Master = this; - - MeleeAttackCollision.Disabled = true; - //切换武器回调 - WeaponPack.ChangeActiveItemEvent += OnChangeActiveItem; - //近战区域进入物体 - MeleeAttackArea.BodyEntered += OnMeleeAttackBodyEntered; - } - - protected override void Process(float delta) - { - if (IsDie) - { - return; - } - - if (_meleeAttackTimer > 0) - { - _meleeAttackTimer -= delta; - } - - base.Process(delta); - } - - /// - /// 当武器放到后背时调用, 用于设置武器位置和角度 - /// - /// 武器实例 - /// 放入武器背包的位置 - public virtual void OnPutBackMount(Weapon weapon, int index) - { - if (index < 8) - { - if (index % 2 == 0) - { - weapon.Position = new Vector2(-4, 3); - weapon.RotationDegrees = 90 - (index / 2f) * 20; - weapon.Scale = new Vector2(-1, 1); - } - else - { - weapon.Position = new Vector2(4, 3); - weapon.RotationDegrees = 270 + (index - 1) / 2f * 20; - weapon.Scale = new Vector2(1, 1); - } - } - else - { - weapon.Visible = false; - } - } - - protected override void OnAffiliationChange(AffiliationArea prevArea) - { - //身上的武器的所属区域也得跟着变 - WeaponPack.ForEach((weapon, i) => - { - if (AffiliationArea != null) - { - AffiliationArea.InsertItem(weapon); - } - else if (weapon.AffiliationArea != null) - { - weapon.AffiliationArea.RemoveItem(weapon); - } - }); - } - - public override void LookTargetPosition(Vector2 pos) - { - LookPosition = pos; - if (MountLookTarget) - { - //脸的朝向 - var gPos = Position; - if (pos.X > gPos.X && Face == FaceDirection.Left) - { - Face = FaceDirection.Right; - } - else if (pos.X < gPos.X && Face == FaceDirection.Right) - { - Face = FaceDirection.Left; - } - //枪口跟随目标 - MountPoint.SetLookAt(pos); - } - } - - /// - /// 返回所有武器是否弹药都打光了 - /// - public bool IsAllWeaponTotalAmmoEmpty() - { - foreach (var weapon in WeaponPack.ItemSlot) - { - if (weapon != null && !weapon.IsTotalAmmoEmpty()) - { - return false; - } - } - - return true; - } - - //------------------------------------------------------------------------------------- - - /// - /// 拾起一个武器, 返回是否成功拾起, 如果不想立刻切换到该武器, exchange 请传 false - /// - /// 武器对象 - /// 是否立即切换到该武器, 默认 true - public bool PickUpWeapon(Weapon weapon, bool exchange = true) - { - if (WeaponPack.PickupItem(weapon, exchange) != -1) - { - //从可互动队列中移除 - InteractiveItemList.Remove(weapon); - OnPickUpWeapon(weapon); - return true; - } - - return false; - } - - /// - /// 切换到下一个武器 - /// - public void ExchangeNextWeapon() - { - var weapon = WeaponPack.ActiveItem; - WeaponPack.ExchangeNext(); - if (WeaponPack.ActiveItem != weapon) - { - OnExchangeWeapon(WeaponPack.ActiveItem); - } - } - - /// - /// 切换到上一个武器 - /// - public void ExchangePrevWeapon() - { - var weapon = WeaponPack.ActiveItem; - WeaponPack.ExchangePrev(); - if (WeaponPack.ActiveItem != weapon) - { - OnExchangeWeapon(WeaponPack.ActiveItem); - } - } - - /// - /// 扔掉当前使用的武器, 切换到上一个武器 - /// - public void ThrowWeapon() - { - ThrowWeapon(WeaponPack.ActiveIndex); - } - - /// - /// 扔掉指定位置的武器 - /// - /// 武器在武器背包中的位置 - public void ThrowWeapon(int index) - { - var weapon = WeaponPack.GetItem(index); - if (weapon == null) - { - return; - } - - var temp = weapon.AnimatedSprite.Position; - if (Face == FaceDirection.Left) - { - temp.Y = -temp.Y; - } - //var pos = GlobalPosition + temp.Rotated(weapon.GlobalRotation); - WeaponPack.RemoveItem(index); - //播放抛出效果 - weapon.ThrowWeapon(this, GlobalPosition); - } - - /// - /// 切换到下一个武器 - /// - public void ExchangeNextActiveProp() - { - var prop = ActivePropsPack.ActiveItem; - ActivePropsPack.ExchangeNext(); - if (prop != ActivePropsPack.ActiveItem) - { - OnExchangeActiveProp(ActivePropsPack.ActiveItem); - } - } - - /// - /// 切换到上一个武器 - /// - public void ExchangePrevActiveProp() - { - var prop = ActivePropsPack.ActiveItem; - ActivePropsPack.ExchangePrev(); - if (prop != ActivePropsPack.ActiveItem) - { - OnExchangeActiveProp(ActivePropsPack.ActiveItem); - } - } - - //------------------------------------------------------------------------------------- - - - /// - /// 触发换弹 - /// - public virtual void Reload() - { - if (WeaponPack.ActiveItem != null) - { - WeaponPack.ActiveItem.Reload(); - } - } - - public override void Attack() - { - if (!IsMeleeAttack && WeaponPack.ActiveItem != null) - { - WeaponPack.ActiveItem.Trigger(this); - } - } - - /// - /// 触发近战攻击 - /// - public virtual void MeleeAttack() - { - if (IsMeleeAttack || _meleeAttackTimer > 0) - { - return; - } - - if (WeaponPack.ActiveItem != null && WeaponPack.ActiveItem.Attribute.CanMeleeAttack) - { - IsMeleeAttack = true; - _meleeAttackTimer = RoleState.MeleeAttackTime; - MountLookTarget = false; - - //WeaponPack.ActiveItem.TriggerMeleeAttack(this); - //播放近战动画 - PlayAnimation_MeleeAttack(() => - { - MountLookTarget = true; - IsMeleeAttack = false; - }); - } - } - - /// - /// 切换当前使用的武器的回调 - /// - private void OnChangeActiveItem(Weapon weapon) - { - //这里处理近战区域 - if (weapon != null) - { - MeleeAttackCollision.Polygon = Utils.CreateSectorPolygon( - Utils.ConvertAngle(-MeleeAttackAngle / 2f), - (weapon.GetLocalFirePosition() - weapon.GripPoint.Position).Length() * 1.2f, - MeleeAttackAngle, - 6 - ); - MeleeAttackArea.CollisionMask = AttackLayer | PhysicsLayer.Bullet; - } - } - - /// - /// 近战区域碰到敌人 - /// - private void OnMeleeAttackBodyEntered(Node2D body) - { - var activeWeapon = WeaponPack.ActiveItem; - if (activeWeapon == null) - { - return; - } - var activityObject = body.AsActivityObject(); - if (activityObject != null) - { - if (activityObject is AdvancedRole role) //攻击角色 - { - var damage = Utils.Random.RandomConfigRange(activeWeapon.Attribute.MeleeAttackHarmRange); - damage = RoleState.CalcDamage(damage); - - //击退 - if (role is not Player) //目标不是玩家才会触发击退 - { - var attr = IsAi ? activeWeapon.AiUseAttribute : activeWeapon.PlayerUseAttribute; - var repel = Utils.Random.RandomConfigRange(attr.MeleeAttackRepelRange); - var position = role.GlobalPosition - MountPoint.GlobalPosition; - var v2 = position.Normalized() * repel; - role.MoveController.AddForce(v2); - } - - role.CallDeferred(nameof(Hurt), this, damage, (role.GetCenterPosition() - GlobalPosition).Angle()); - } - else if (activityObject is Bullet bullet) //攻击子弹 - { - var attackLayer = bullet.AttackLayer; - if (CollisionWithMask(attackLayer)) //是攻击玩家的子弹 - { - bullet.PlayDisappearEffect(); - bullet.Destroy(); - } - } - } - } - - public override float GetFirePointAltitude() - { - return -MountPoint.Position.Y; - } - - protected override void OnDestroy() - { - base.OnDestroy(); - WeaponPack.Destroy(); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/AdvancedRole_Animation.cs b/DungeonShooting_Godot/src/game/activity/role/AdvancedRole_Animation.cs deleted file mode 100644 index e829a1f..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/AdvancedRole_Animation.cs +++ /dev/null @@ -1,78 +0,0 @@ - -using System; -using Godot; -using Vector2 = Godot.Vector2; - -public partial class AdvancedRole -{ - /// - /// 播放近战攻击动画 - /// - public virtual void PlayAnimation_MeleeAttack(Action finish) - { - var r = MountPoint.RotationDegrees; - //var gp = MountPoint.GlobalPosition; - var p1 = MountPoint.Position; - var p2 = p1 + new Vector2(6, 0).Rotated(Mathf.DegToRad(r - MeleeAttackAngle / 2f)); - var p3 = p1 + new Vector2(6, 0).Rotated(Mathf.DegToRad(r + MeleeAttackAngle / 2f)); - - var tween = CreateTween(); - tween.SetParallel(); - - tween.TweenProperty(MountPoint, "rotation_degrees", r - MeleeAttackAngle / 2f, 0.1); - tween.TweenProperty(MountPoint, "position", p2, 0.1); - tween.TweenProperty(MountPoint, "position", p2, 0.1); - tween.Chain(); - - tween.TweenCallback(Callable.From(() => - { - MountPoint.RotationDegrees = r + MeleeAttackAngle / 2f; - MountPoint.Position = p3; - //重新计算武器阴影位置 - var activeItem = WeaponPack.ActiveItem; - activeItem.CalcShadowTransform(); - //创建屏幕抖动 - if (Face == FaceDirection.Right) - { - //GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(r - 90)) * 5); - GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(r - 180)) * 6); - } - else - { - //GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(270 - r)) * 5); - GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(-r)) * 6); - } - //播放特效 - var effect = ObjectManager.GetPoolItem(ResourcePath.prefab_effect_weapon_MeleeAttack1_tscn); - var sprite = (Node2D)effect; - var localFirePosition = activeItem.GetLocalFirePosition() - activeItem.GripPoint.Position; - localFirePosition *= 0.9f; - sprite.Position = p1 + localFirePosition.Rotated(Mathf.DegToRad(r)); - sprite.RotationDegrees = r; - AddChild(sprite); - effect.PlayEffect(); - - //启用近战碰撞区域 - MeleeAttackCollision.Disabled = false; - })); - tween.Chain(); - - tween.TweenInterval(0.1f); - tween.Chain(); - - tween.TweenCallback(Callable.From(() => - { - //关闭近战碰撞区域 - MeleeAttackCollision.Disabled = true; - })); - tween.TweenProperty(MountPoint, "rotation_degrees", r, 0.2); - tween.TweenProperty(MountPoint, "position", p1, 0.2); - tween.Chain(); - - tween.TweenCallback(Callable.From(() => - { - finish(); - })); - tween.Play(); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/MountRotation.cs b/DungeonShooting_Godot/src/game/activity/role/MountRotation.cs index eca09e9..e265b10 100644 --- a/DungeonShooting_Godot/src/game/activity/role/MountRotation.cs +++ b/DungeonShooting_Godot/src/game/activity/role/MountRotation.cs @@ -15,7 +15,7 @@ /// /// 所在的角色 /// - public AdvancedRole Master { get; set; } + public Role Master { get; set; } /// /// 当前节点真实的旋转角度, 角度制 diff --git a/DungeonShooting_Godot/src/game/activity/role/Role.cs b/DungeonShooting_Godot/src/game/activity/role/Role.cs index fcea8b0..0a22232 100644 --- a/DungeonShooting_Godot/src/game/activity/role/Role.cs +++ b/DungeonShooting_Godot/src/game/activity/role/Role.cs @@ -95,6 +95,54 @@ /// public FaceDirection Face { get => _face; set => SetFace(value); } private FaceDirection _face; + + /// + /// 角色携带的武器背包 + /// + public Package WeaponPack { get; private set; } + + /// + /// 武器挂载点 + /// + [Export, ExportFillNode] + public MountRotation MountPoint { get; set; } + + /// + /// 背后武器的挂载点 + /// + [Export, ExportFillNode] + public Marker2D BackMountPoint { get; set; } + + /// + /// 近战碰撞检测区域 + /// + [Export, ExportFillNode] + public Area2D MeleeAttackArea { get; set; } + + /// + /// 近战碰撞检测区域的碰撞器 + /// + [Export, ExportFillNode] + public CollisionPolygon2D MeleeAttackCollision { get; set; } + + /// + /// 近战攻击时挥动武器的角度 + /// + [Export] + public float MeleeAttackAngle { get; set; } = 120; + + /// + /// 武器挂载点是否始终指向目标 + /// + public bool MountLookTarget { get; set; } = true; + + /// + /// 是否处于近战攻击中 + /// + public bool IsMeleeAttack { get; private set; } + + //近战计时器 + private float _meleeAttackTimer = 0; /// /// 是否死亡 @@ -372,14 +420,6 @@ { } - - /// - /// 触发攻击 - /// - public virtual void Attack() - { - } - public override void OnInit() { ActivePropsPack = AddComponent>(); @@ -396,10 +436,33 @@ //连接互动物体信号 InteractiveArea.BodyEntered += _OnPropsEnter; InteractiveArea.BodyExited += _OnPropsExit; + + //------------------------ + + WeaponPack = AddComponent>(); + WeaponPack.SetCapacity(4); + + MountPoint.Master = this; + + MeleeAttackCollision.Disabled = true; + //切换武器回调 + WeaponPack.ChangeActiveItemEvent += OnChangeActiveItem; + //近战区域进入物体 + MeleeAttackArea.BodyEntered += OnMeleeAttackBodyEntered; } protected override void Process(float delta) - { + { + if (IsDie) + { + return; + } + + if (_meleeAttackTimer > 0) + { + _meleeAttackTimer -= delta; + } + //检查可互动的物体 bool findFlag = false; for (int i = 0; i < InteractiveItemList.Count; i++) @@ -541,24 +604,6 @@ } /// - /// 使角色看向指定的坐标的方向 - /// - public virtual void LookTargetPosition(Vector2 pos) - { - LookPosition = pos; - //脸的朝向 - var gPos = Position; - if (pos.X > gPos.X && Face == FaceDirection.Left) - { - Face = FaceDirection.Right; - } - else if (pos.X < gPos.X && Face == FaceDirection.Right) - { - Face = FaceDirection.Left; - } - } - - /// /// 判断指定坐标是否在角色视野方向 /// public bool IsPositionInForward(Vector2 pos) @@ -884,7 +929,322 @@ /// public virtual float GetFirePointAltitude() { - return -AnimatedSprite.Position.Y; + return -MountPoint.Position.Y; + } + + /// + /// 当拾起某个武器时调用 + /// + protected virtual void OnPickUpWeapon(Weapon weapon) + { + } + + /// + /// 当扔掉某个武器时调用 + /// + protected virtual void OnThrowWeapon(Weapon weapon) + { + } + + /// + /// 当切换到某个武器时调用 + /// + protected virtual void OnExchangeWeapon(Weapon weapon) + { + } + + /// + /// 当武器放到后背时调用, 用于设置武器位置和角度 + /// + /// 武器实例 + /// 放入武器背包的位置 + public virtual void OnPutBackMount(Weapon weapon, int index) + { + if (index < 8) + { + if (index % 2 == 0) + { + weapon.Position = new Vector2(-4, 3); + weapon.RotationDegrees = 90 - (index / 2f) * 20; + weapon.Scale = new Vector2(-1, 1); + } + else + { + weapon.Position = new Vector2(4, 3); + weapon.RotationDegrees = 270 + (index - 1) / 2f * 20; + weapon.Scale = new Vector2(1, 1); + } + } + else + { + weapon.Visible = false; + } + } + + protected override void OnAffiliationChange(AffiliationArea prevArea) + { + //身上的武器的所属区域也得跟着变 + WeaponPack.ForEach((weapon, i) => + { + if (AffiliationArea != null) + { + AffiliationArea.InsertItem(weapon); + } + else if (weapon.AffiliationArea != null) + { + weapon.AffiliationArea.RemoveItem(weapon); + } + }); + } + + public virtual void LookTargetPosition(Vector2 pos) + { + LookPosition = pos; + if (MountLookTarget) + { + //脸的朝向 + var gPos = Position; + if (pos.X > gPos.X && Face == FaceDirection.Left) + { + Face = FaceDirection.Right; + } + else if (pos.X < gPos.X && Face == FaceDirection.Right) + { + Face = FaceDirection.Left; + } + //枪口跟随目标 + MountPoint.SetLookAt(pos); + } + } + + /// + /// 返回所有武器是否弹药都打光了 + /// + public bool IsAllWeaponTotalAmmoEmpty() + { + foreach (var weapon in WeaponPack.ItemSlot) + { + if (weapon != null && !weapon.IsTotalAmmoEmpty()) + { + return false; + } + } + + return true; + } + + //------------------------------------------------------------------------------------- + + /// + /// 拾起一个武器, 返回是否成功拾起, 如果不想立刻切换到该武器, exchange 请传 false + /// + /// 武器对象 + /// 是否立即切换到该武器, 默认 true + public bool PickUpWeapon(Weapon weapon, bool exchange = true) + { + if (WeaponPack.PickupItem(weapon, exchange) != -1) + { + //从可互动队列中移除 + InteractiveItemList.Remove(weapon); + OnPickUpWeapon(weapon); + return true; + } + + return false; + } + + /// + /// 切换到下一个武器 + /// + public void ExchangeNextWeapon() + { + var weapon = WeaponPack.ActiveItem; + WeaponPack.ExchangeNext(); + if (WeaponPack.ActiveItem != weapon) + { + OnExchangeWeapon(WeaponPack.ActiveItem); + } + } + + /// + /// 切换到上一个武器 + /// + public void ExchangePrevWeapon() + { + var weapon = WeaponPack.ActiveItem; + WeaponPack.ExchangePrev(); + if (WeaponPack.ActiveItem != weapon) + { + OnExchangeWeapon(WeaponPack.ActiveItem); + } + } + + /// + /// 扔掉当前使用的武器, 切换到上一个武器 + /// + public void ThrowWeapon() + { + ThrowWeapon(WeaponPack.ActiveIndex); + } + + /// + /// 扔掉指定位置的武器 + /// + /// 武器在武器背包中的位置 + public void ThrowWeapon(int index) + { + var weapon = WeaponPack.GetItem(index); + if (weapon == null) + { + return; + } + + var temp = weapon.AnimatedSprite.Position; + if (Face == FaceDirection.Left) + { + temp.Y = -temp.Y; + } + //var pos = GlobalPosition + temp.Rotated(weapon.GlobalRotation); + WeaponPack.RemoveItem(index); + //播放抛出效果 + weapon.ThrowWeapon(this, GlobalPosition); + } + + /// + /// 切换到下一个武器 + /// + public void ExchangeNextActiveProp() + { + var prop = ActivePropsPack.ActiveItem; + ActivePropsPack.ExchangeNext(); + if (prop != ActivePropsPack.ActiveItem) + { + OnExchangeActiveProp(ActivePropsPack.ActiveItem); + } + } + + /// + /// 切换到上一个武器 + /// + public void ExchangePrevActiveProp() + { + var prop = ActivePropsPack.ActiveItem; + ActivePropsPack.ExchangePrev(); + if (prop != ActivePropsPack.ActiveItem) + { + OnExchangeActiveProp(ActivePropsPack.ActiveItem); + } + } + + //------------------------------------------------------------------------------------- + + + /// + /// 触发换弹 + /// + public virtual void Reload() + { + if (WeaponPack.ActiveItem != null) + { + WeaponPack.ActiveItem.Reload(); + } + } + + /// + /// 攻击函数 + /// + public virtual void Attack() + { + if (!IsMeleeAttack && WeaponPack.ActiveItem != null) + { + WeaponPack.ActiveItem.Trigger(this); + } + } + + /// + /// 触发近战攻击 + /// + public virtual void MeleeAttack() + { + if (IsMeleeAttack || _meleeAttackTimer > 0) + { + return; + } + + if (WeaponPack.ActiveItem != null && WeaponPack.ActiveItem.Attribute.CanMeleeAttack) + { + IsMeleeAttack = true; + _meleeAttackTimer = RoleState.MeleeAttackTime; + MountLookTarget = false; + + //WeaponPack.ActiveItem.TriggerMeleeAttack(this); + //播放近战动画 + PlayAnimation_MeleeAttack(() => + { + MountLookTarget = true; + IsMeleeAttack = false; + }); + } + } + + /// + /// 切换当前使用的武器的回调 + /// + private void OnChangeActiveItem(Weapon weapon) + { + //这里处理近战区域 + if (weapon != null) + { + MeleeAttackCollision.Polygon = Utils.CreateSectorPolygon( + Utils.ConvertAngle(-MeleeAttackAngle / 2f), + (weapon.GetLocalFirePosition() - weapon.GripPoint.Position).Length() * 1.2f, + MeleeAttackAngle, + 6 + ); + MeleeAttackArea.CollisionMask = AttackLayer | PhysicsLayer.Bullet; + } + } + + /// + /// 近战区域碰到敌人 + /// + private void OnMeleeAttackBodyEntered(Node2D body) + { + var activeWeapon = WeaponPack.ActiveItem; + if (activeWeapon == null) + { + return; + } + var activityObject = body.AsActivityObject(); + if (activityObject != null) + { + if (activityObject is Role role) //攻击角色 + { + var damage = Utils.Random.RandomConfigRange(activeWeapon.Attribute.MeleeAttackHarmRange); + damage = RoleState.CalcDamage(damage); + + //击退 + if (role is not Player) //目标不是玩家才会触发击退 + { + var attr = IsAi ? activeWeapon.AiUseAttribute : activeWeapon.PlayerUseAttribute; + var repel = Utils.Random.RandomConfigRange(attr.MeleeAttackRepelRange); + var position = role.GlobalPosition - MountPoint.GlobalPosition; + var v2 = position.Normalized() * repel; + role.MoveController.AddForce(v2); + } + + role.CallDeferred(nameof(Hurt), this, damage, (role.GetCenterPosition() - GlobalPosition).Angle()); + } + else if (activityObject is Bullet bullet) //攻击子弹 + { + var attackLayer = bullet.AttackLayer; + if (CollisionWithMask(attackLayer)) //是攻击玩家的子弹 + { + bullet.PlayDisappearEffect(); + bullet.Destroy(); + } + } + } } protected override void OnDestroy() @@ -896,5 +1256,7 @@ } BuffPropPack.Clear(); ActivePropsPack.Destroy(); + + WeaponPack.Destroy(); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/Role_Animation.cs b/DungeonShooting_Godot/src/game/activity/role/Role_Animation.cs new file mode 100644 index 0000000..1dfcc68 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/Role_Animation.cs @@ -0,0 +1,78 @@ + +using System; +using Godot; +using Vector2 = Godot.Vector2; + +public partial class Role +{ + /// + /// 播放近战攻击动画 + /// + public virtual void PlayAnimation_MeleeAttack(Action finish) + { + var r = MountPoint.RotationDegrees; + //var gp = MountPoint.GlobalPosition; + var p1 = MountPoint.Position; + var p2 = p1 + new Vector2(6, 0).Rotated(Mathf.DegToRad(r - MeleeAttackAngle / 2f)); + var p3 = p1 + new Vector2(6, 0).Rotated(Mathf.DegToRad(r + MeleeAttackAngle / 2f)); + + var tween = CreateTween(); + tween.SetParallel(); + + tween.TweenProperty(MountPoint, "rotation_degrees", r - MeleeAttackAngle / 2f, 0.1); + tween.TweenProperty(MountPoint, "position", p2, 0.1); + tween.TweenProperty(MountPoint, "position", p2, 0.1); + tween.Chain(); + + tween.TweenCallback(Callable.From(() => + { + MountPoint.RotationDegrees = r + MeleeAttackAngle / 2f; + MountPoint.Position = p3; + //重新计算武器阴影位置 + var activeItem = WeaponPack.ActiveItem; + activeItem.CalcShadowTransform(); + //创建屏幕抖动 + if (Face == FaceDirection.Right) + { + //GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(r - 90)) * 5); + GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(r - 180)) * 6); + } + else + { + //GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(270 - r)) * 5); + GameCamera.Main.DirectionalShake(Vector2.FromAngle(Mathf.DegToRad(-r)) * 6); + } + //播放特效 + var effect = ObjectManager.GetPoolItem(ResourcePath.prefab_effect_weapon_MeleeAttack1_tscn); + var sprite = (Node2D)effect; + var localFirePosition = activeItem.GetLocalFirePosition() - activeItem.GripPoint.Position; + localFirePosition *= 0.9f; + sprite.Position = p1 + localFirePosition.Rotated(Mathf.DegToRad(r)); + sprite.RotationDegrees = r; + AddChild(sprite); + effect.PlayEffect(); + + //启用近战碰撞区域 + MeleeAttackCollision.Disabled = false; + })); + tween.Chain(); + + tween.TweenInterval(0.1f); + tween.Chain(); + + tween.TweenCallback(Callable.From(() => + { + //关闭近战碰撞区域 + MeleeAttackCollision.Disabled = true; + })); + tween.TweenProperty(MountPoint, "rotation_degrees", r, 0.2); + tween.TweenProperty(MountPoint, "position", p1, 0.2); + tween.Chain(); + + tween.TweenCallback(Callable.From(() => + { + finish(); + })); + tween.Play(); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/SubLine.cs b/DungeonShooting_Godot/src/game/activity/role/SubLine.cs index 03039ac..5f9dd84 100644 --- a/DungeonShooting_Godot/src/game/activity/role/SubLine.cs +++ b/DungeonShooting_Godot/src/game/activity/role/SubLine.cs @@ -5,7 +5,7 @@ /// /// 瞄准辅助线 /// -public class SubLine : Component +public class SubLine : Component { /// /// 是否正在播放警告闪烁动画 diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/AIStateEnum.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/AIStateEnum.cs new file mode 100644 index 0000000..9b923d5 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/AIStateEnum.cs @@ -0,0 +1,40 @@ + +public enum AIStateEnum +{ + /// + /// Ai 状态, 正常, 未发现目标 + /// + AiNormal, + /// + /// 找到玩家,准备通知其他敌人 + /// + AiNotify, + /// + /// 惊讶状态 + /// + AiAstonished, + /// + /// 收到其他敌人通知, 前往发现目标的位置 + /// + AiLeaveFor, + /// + /// 发现目标, 目标不在视野内, 但是知道位置 + /// + AiTailAfter, + /// + /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 + /// + AiFollowUp, + /// + /// 距离足够近, 在目标附近随机移动 + /// + AiSurround, + /// + /// Ai 寻找弹药 + /// + AiFindAmmo, + /// + /// Ai攻击 + /// + AiAttack, +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/AdvancedEnemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/AdvancedEnemy.cs deleted file mode 100644 index b2d184d..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/AdvancedEnemy.cs +++ /dev/null @@ -1,467 +0,0 @@ -#region 基础敌人设计思路 -/* -敌人有三种状态: -状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1 -状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3 -状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火, - 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2 - -敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现 -*/ -#endregion - - -using System; -using AdvancedState; -using Godot; - -/// -/// 高级敌人,可以携带武器 -/// -[Tool] -public partial class AdvancedEnemy : AdvancedRole -{ - /// - /// 目标是否在视野内 - /// - public bool TargetInView { get; set; } = true; - - /// - /// 敌人身上的状态机控制器 - /// - public StateController StateController { get; private set; } - - /// - /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 - /// - public float ViewRange { get; set; } = 250; - - /// - /// 发现玩家后的视野半径 - /// - public float TailAfterViewRange { get; set; } = 400; - - /// - /// 背后的视野半径, 单位像素 - /// - public float BackViewRange { get; set; } = 50; - - /// - /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 - /// - [Export, ExportFillNode] - public RayCast2D ViewRay { get; private set; } - - /// - /// 导航代理 - /// - [Export, ExportFillNode] - public NavigationAgent2D NavigationAgent2D { get; private set; } - - /// - /// 导航代理中点 - /// - [Export, ExportFillNode] - public Marker2D NavigationPoint { get; private set; } - - /// - /// 当前敌人所看向的对象, 也就是枪口指向的对象 - /// - public ActivityObject LookTarget { get; set; } - - /// - /// 锁定目标已经走过的时间 - /// - public float LockTargetTime { get; set; } = 0; - - public override void OnInit() - { - base.OnInit(); - IsAi = true; - StateController = AddComponent>(); - - AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Player; - EnemyLayer = PhysicsLayer.Player; - Camp = CampEnum.Camp2; - - RoleState.MoveSpeed = 20; - - MaxHp = 20; - Hp = 20; - - //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Node3D.Player); - - //注册Ai状态机 - StateController.Register(new AiNormalState()); - //StateController.Register(new AiProbeState()); - StateController.Register(new AiTailAfterState()); - StateController.Register(new AiFollowUpState()); - StateController.Register(new AiLeaveForState()); - StateController.Register(new AiSurroundState()); - StateController.Register(new AiFindAmmoState()); - StateController.Register(new AiAttackState()); - StateController.Register(new AiAstonishedState()); - StateController.Register(new AiNotifyState()); - - //默认状态 - StateController.ChangeStateInstant(AIAdvancedStateEnum.AiNormal); - } - - public override void EnterTree() - { - if (!World.Enemy_InstanceList.Contains(this)) - { - World.Enemy_InstanceList.Add(this); - } - } - - public override void ExitTree() - { - World.Enemy_InstanceList.Remove(this); - } - - protected override void OnDie() - { - //扔掉所有武器 - var weapons = WeaponPack.GetAndClearItem(); - for (var i = 0; i < weapons.Length; i++) - { - weapons[i].ThrowWeapon(this); - } - - var effPos = Position + new Vector2(0, -Altitude); - //血液特效 - var blood = ObjectManager.GetPoolItem(ResourcePath.prefab_effect_enemy_EnemyBloodEffect_tscn); - blood.Position = effPos - new Vector2(0, 12); - blood.AddToActivityRoot(RoomLayerEnum.NormalLayer); - blood.PlayEffect(); - - //创建敌人碎片 - var count = Utils.Random.RandomRangeInt(3, 6); - for (var i = 0; i < count; i++) - { - var debris = Create(Ids.Id_effect0001); - debris.PutDown(effPos, RoomLayerEnum.NormalLayer); - debris.InheritVelocity(this); - } - - //派发敌人死亡信号 - EventManager.EmitEvent(EventEnum.OnEnemyDie, this); - Destroy(); - } - - protected override void Process(float delta) - { - base.Process(delta); - if (IsDie) - { - return; - } - - //看向目标 - if (LookTarget != null && MountLookTarget) - { - var pos = LookTarget.Position; - LookPosition = pos; - //脸的朝向 - var gPos = Position; - if (pos.X > gPos.X && Face == FaceDirection.Left) - { - Face = FaceDirection.Right; - } - else if (pos.X < gPos.X && Face == FaceDirection.Right) - { - Face = FaceDirection.Left; - } - //枪口跟随目标 - MountPoint.SetLookAt(pos); - } - - //拾起武器操作 - EnemyPickUpWeapon(); - } - - protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm) - { - //受到伤害 - var state = StateController.CurrState; - if (state == AIAdvancedStateEnum.AiNormal || state == AIAdvancedStateEnum.AiLeaveFor) //|| state == AiStateEnum.AiProbe - { - LookTarget = target; - StateController.ChangeState(AIAdvancedStateEnum.AiTailAfter); - } - } - - /// - /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器 - /// - public bool CheckUsableWeaponInUnclaimed() - { - foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons) - { - //判断是否能拾起武器, 条件: 相同的房间 - if (unclaimedWeapon.AffiliationArea == AffiliationArea) - { - if (!unclaimedWeapon.IsTotalAmmoEmpty()) - { - if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign)) - { - return true; - } - else - { - //判断是否可以移除该标记 - var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign); - if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 - { - unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); - return true; - } - else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 - { - unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); - return true; - } - } - } - } - } - - return false; - } - - /// - /// 寻找可用的武器 - /// - public Weapon FindTargetWeapon() - { - Weapon target = null; - var position = Position; - foreach (var weapon in World.Weapon_UnclaimedWeapons) - { - //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间 - if (weapon.AffiliationArea == AffiliationArea) - { - //还有弹药 - if (!weapon.IsTotalAmmoEmpty()) - { - //查询是否有其他敌人标记要拾起该武器 - if (weapon.HasSign(SignNames.AiFindWeaponSign)) - { - var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); - if (enemy == this) //就是自己标记的 - { - - } - else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 - { - weapon.RemoveSign(SignNames.AiFindWeaponSign); - } - else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 - { - weapon.RemoveSign(SignNames.AiFindWeaponSign); - } - else //放弃这把武器 - { - continue; - } - } - - if (target == null) //第一把武器 - { - target = weapon; - } - else if (target.Position.DistanceSquaredTo(position) > - weapon.Position.DistanceSquaredTo(position)) //距离更近 - { - target = weapon; - } - } - } - } - - return target; - } - - /// - /// 获取武器攻击范围 (最大距离值与最小距离的中间值) - /// - /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5 - public float GetWeaponRange(float weight = 0.5f) - { - if (WeaponPack.ActiveItem != null) - { - var attribute = WeaponPack.ActiveItem.Attribute; - return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight); - } - - return 0; - } - - /// - /// 返回目标点是否在视野范围内 - /// - public bool IsInViewRange(Vector2 target) - { - var isForward = IsPositionInForward(target); - if (isForward) - { - if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径 - { - return true; - } - } - - return false; - } - - /// - /// 返回目标点是否在跟随状态下的视野半径内 - /// - public bool IsInTailAfterViewRange(Vector2 target) - { - var isForward = IsPositionInForward(target); - if (isForward) - { - if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径 - { - return true; - } - } - - return false; - } - - /// - /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true - /// - public bool TestViewRayCast(Vector2 target) - { - ViewRay.Enabled = true; - ViewRay.TargetPosition = ViewRay.ToLocal(target); - ViewRay.ForceRaycastUpdate(); - return ViewRay.IsColliding(); - } - - /// - /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线 - /// - public void TestViewRayCastOver() - { - ViewRay.Enabled = false; - } - - /// - /// AI 拾起武器操作 - /// - private void EnemyPickUpWeapon() - { - //这几个状态不需要主动拾起武器操作 - var state = StateController.CurrState; - if (state == AIAdvancedStateEnum.AiNormal) - { - return; - } - - //拾起地上的武器 - if (InteractiveItem is Weapon weapon) - { - if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起 - { - TriggerInteractive(); - return; - } - - //没弹药了 - if (weapon.IsTotalAmmoEmpty()) - { - return; - } - - var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id); - if (index != -1) //与武器背包中武器类型相同, 补充子弹 - { - if (!WeaponPack.GetItem(index).IsAmmoFull()) - { - TriggerInteractive(); - } - - return; - } - - // var index2 = Holster.FindWeapon((we, i) => - // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); - var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty()); - if (index2 != -1) //扔掉没子弹的武器 - { - ThrowWeapon(index2); - TriggerInteractive(); - return; - } - - // if (Holster.HasVacancy()) //有空位, 拾起武器 - // { - // TriggerInteractive(); - // return; - // } - } - } - - /// - /// 获取锁定目标的剩余时间 - /// - public float GetLockRemainderTime() - { - var weapon = WeaponPack.ActiveItem; - if (weapon == null) - { - return 0; - } - return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime; - } - - /// - /// 强制设置锁定目标时间 - /// - public void SetLockTargetTime(float time) - { - LockTargetTime = time; - } - - public override void LookTargetPosition(Vector2 pos) - { - LookTarget = null; - base.LookTargetPosition(pos); - } - - /// - /// 执行移动操作 - /// - public void DoMove() - { - AnimatedSprite.Play(AnimatorNames.Run); - //计算移动 - var nextPos = NavigationAgent2D.GetNextPathPosition(); - BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed; - } - - /// - /// 执行站立操作 - /// - public void DoIdle() - { - AnimatedSprite.Play(AnimatorNames.Idle); - BasisVelocity = Vector2.Zero; - } - - /// - /// 更新房间中标记的目标位置 - /// - public void UpdateMarkTargetPosition() - { - if (LookTarget != null) - { - AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position; - } - } -} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs index 1513d7c..3bb3691 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs @@ -1,11 +1,21 @@ - -using System; -using Config; +#region 基础敌人设计思路 +/* +敌人有三种状态: +状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1 +状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3 +状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火, + 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2 + +敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现 +*/ +#endregion + + +using EnemyState; using Godot; -using NnormalState; /// -/// 基础敌人 +/// 高级敌人,可以携带武器 /// [Tool] public partial class Enemy : Role @@ -18,7 +28,7 @@ /// /// 敌人身上的状态机控制器 /// - public StateController StateController { get; private set; } + public StateController StateController { get; private set; } /// /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 @@ -36,11 +46,6 @@ public float BackViewRange { get; set; } = 50; /// - /// 攻击范围 - /// - public float AttackRange { get; set; } = 200; - - /// /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 /// [Export, ExportFillNode] @@ -59,37 +64,20 @@ public Marker2D NavigationPoint { get; private set; } /// - /// 开火位置 - /// - [Export, ExportFillNode] - public Marker2D FirePoint { get; private set; } - - /// - /// 攻击时间间隔 - /// - public float AttackInterval { get; set; } = 5; - - /// /// 当前敌人所看向的对象, 也就是枪口指向的对象 /// public ActivityObject LookTarget { get; set; } /// - /// Ai 攻击属性 + /// 锁定目标已经走过的时间 /// - public ExcelConfig.AiAttackAttr AttackAttribute { get; private set; } - - //锁定目标时间 - private float _lockTargetTime = 0; - //攻击冷却计时器 - private float _attackTimer = 0; + public float LockTargetTime { get; set; } = 0; public override void OnInit() { base.OnInit(); - AttackAttribute = ExcelConfig.AiAttackAttr_Map["0001"]; IsAi = true; - StateController = AddComponent>(); + StateController = AddComponent>(); AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Player; EnemyLayer = PhysicsLayer.Player; @@ -99,14 +87,23 @@ MaxHp = 20; Hp = 20; - + + //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Node3D.Player); + + //注册Ai状态机 StateController.Register(new AiNormalState()); + //StateController.Register(new AiProbeState()); StateController.Register(new AiTailAfterState()); StateController.Register(new AiFollowUpState()); - StateController.Register(new AiSurroundState()); StateController.Register(new AiLeaveForState()); + StateController.Register(new AiSurroundState()); + StateController.Register(new AiFindAmmoState()); StateController.Register(new AiAttackState()); - StateController.ChangeState(AINormalStateEnum.AiNormal); + StateController.Register(new AiAstonishedState()); + StateController.Register(new AiNotifyState()); + + //默认状态 + StateController.ChangeStateInstant(AIStateEnum.AiNormal); } public override void EnterTree() @@ -122,79 +119,15 @@ World.Enemy_InstanceList.Remove(this); } - public override void Attack() - { - _attackTimer = AttackInterval; - OnAttack(); - } - - /// - /// 敌人发动攻击 - /// - protected virtual void OnAttack() - { - Debug.Log("触发攻击"); - var bulletData = FireManager.GetBulletData(this, ConvertRotation(Position.AngleTo(LookPosition)), ExcelConfig.BulletBase_Map["0006"]); - FireManager.ShootBullet(bulletData, AttackLayer); - } - - protected override void Process(float delta) - { - base.Process(delta); - if (IsDie) - { - return; - } - - //看向目标 - if (LookTarget != null) - { - var pos = LookTarget.Position; - LookPosition = pos; - //脸的朝向 - var gPos = Position; - if (pos.X > gPos.X && Face == FaceDirection.Left) - { - Face = FaceDirection.Right; - } - else if (pos.X < gPos.X && Face == FaceDirection.Right) - { - Face = FaceDirection.Left; - } - } - - if (_attackTimer > 0) - { - _attackTimer -= delta; - } - //目标在视野内的时间 - var currState = StateController.CurrState; - if (currState == AINormalStateEnum.AiAttack && _attackTimer <= 0) //必须在可以开火时记录时间 - { - _lockTargetTime += delta; - } - else - { - _lockTargetTime = 0; - } - } - - protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm) - { - //受到伤害 - var state = StateController.CurrState; - // if (state == AINormalStateEnum.AiNormal) - // { - // StateController.ChangeState(AINormalStateEnum.AiLeaveFor, target); - // } - // else if (state == AINormalStateEnum.AiLeaveFor) - // { - // - // } - } - protected override void OnDie() { + //扔掉所有武器 + var weapons = WeaponPack.GetAndClearItem(); + for (var i = 0; i < weapons.Length; i++) + { + weapons[i].ThrowWeapon(this); + } + var effPos = Position + new Vector2(0, -Altitude); //血液特效 var blood = ObjectManager.GetPoolItem(ResourcePath.prefab_effect_enemy_EnemyBloodEffect_tscn); @@ -215,8 +148,156 @@ EventManager.EmitEvent(EventEnum.OnEnemyDie, this); Destroy(); } + + protected override void Process(float delta) + { + base.Process(delta); + if (IsDie) + { + return; + } + + //看向目标 + if (LookTarget != null && MountLookTarget) + { + var pos = LookTarget.Position; + LookPosition = pos; + //脸的朝向 + var gPos = Position; + if (pos.X > gPos.X && Face == FaceDirection.Left) + { + Face = FaceDirection.Right; + } + else if (pos.X < gPos.X && Face == FaceDirection.Right) + { + Face = FaceDirection.Left; + } + //枪口跟随目标 + MountPoint.SetLookAt(pos); + } + + //拾起武器操作 + EnemyPickUpWeapon(); + } + + protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm) + { + //受到伤害 + var state = StateController.CurrState; + if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiLeaveFor) //|| state == AiStateEnum.AiProbe + { + LookTarget = target; + StateController.ChangeState(AIStateEnum.AiTailAfter); + } + } + + /// + /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器 + /// + public bool CheckUsableWeaponInUnclaimed() + { + foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons) + { + //判断是否能拾起武器, 条件: 相同的房间 + if (unclaimedWeapon.AffiliationArea == AffiliationArea) + { + if (!unclaimedWeapon.IsTotalAmmoEmpty()) + { + if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign)) + { + return true; + } + else + { + //判断是否可以移除该标记 + var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign); + if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 + { + unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); + return true; + } + else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 + { + unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); + return true; + } + } + } + } + } + + return false; + } /// + /// 寻找可用的武器 + /// + public Weapon FindTargetWeapon() + { + Weapon target = null; + var position = Position; + foreach (var weapon in World.Weapon_UnclaimedWeapons) + { + //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间 + if (weapon.AffiliationArea == AffiliationArea) + { + //还有弹药 + if (!weapon.IsTotalAmmoEmpty()) + { + //查询是否有其他敌人标记要拾起该武器 + if (weapon.HasSign(SignNames.AiFindWeaponSign)) + { + var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); + if (enemy == this) //就是自己标记的 + { + + } + else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 + { + weapon.RemoveSign(SignNames.AiFindWeaponSign); + } + else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 + { + weapon.RemoveSign(SignNames.AiFindWeaponSign); + } + else //放弃这把武器 + { + continue; + } + } + + if (target == null) //第一把武器 + { + target = weapon; + } + else if (target.Position.DistanceSquaredTo(position) > + weapon.Position.DistanceSquaredTo(position)) //距离更近 + { + target = weapon; + } + } + } + } + + return target; + } + + /// + /// 获取武器攻击范围 (最大距离值与最小距离的中间值) + /// + /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5 + public float GetWeaponRange(float weight = 0.5f) + { + if (WeaponPack.ActiveItem != null) + { + var attribute = WeaponPack.ActiveItem.Attribute; + return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight); + } + + return 0; + } + + /// /// 返回目标点是否在视野范围内 /// public bool IsInViewRange(Vector2 target) @@ -251,7 +332,7 @@ } /// - /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null + /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true /// public bool TestViewRayCast(Vector2 target) { @@ -268,42 +349,82 @@ { ViewRay.Enabled = false; } - - /// - /// 获取锁定目标的时间 - /// - public float GetLockTime() - { - return _lockTargetTime; - } /// + /// AI 拾起武器操作 + /// + private void EnemyPickUpWeapon() + { + //这几个状态不需要主动拾起武器操作 + var state = StateController.CurrState; + if (state == AIStateEnum.AiNormal) + { + return; + } + + //拾起地上的武器 + if (InteractiveItem is Weapon weapon) + { + if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起 + { + TriggerInteractive(); + return; + } + + //没弹药了 + if (weapon.IsTotalAmmoEmpty()) + { + return; + } + + var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id); + if (index != -1) //与武器背包中武器类型相同, 补充子弹 + { + if (!WeaponPack.GetItem(index).IsAmmoFull()) + { + TriggerInteractive(); + } + + return; + } + + // var index2 = Holster.FindWeapon((we, i) => + // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); + var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty()); + if (index2 != -1) //扔掉没子弹的武器 + { + ThrowWeapon(index2); + TriggerInteractive(); + return; + } + + // if (Holster.HasVacancy()) //有空位, 拾起武器 + // { + // TriggerInteractive(); + // return; + // } + } + } + + /// /// 获取锁定目标的剩余时间 /// public float GetLockRemainderTime() { - return AttackAttribute.LockingTime - _lockTargetTime; + var weapon = WeaponPack.ActiveItem; + if (weapon == null) + { + return 0; + } + return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime; } - + /// /// 强制设置锁定目标时间 /// public void SetLockTargetTime(float time) { - _lockTargetTime = time; - } - - /// - /// 获取攻击范围 - /// - public float GetAttackRange() - { - return AttackRange; - } - - public override float GetFirePointAltitude() - { - return -AnimatedSprite.Position.Y - FirePoint.Position.Y; + LockTargetTime = time; } public override void LookTargetPosition(Vector2 pos) @@ -313,14 +434,6 @@ } /// - /// 获取攻击冷却计时时间, 小于等于 0 表示冷却完成 - /// - public float GetAttackTimer() - { - return _attackTimer; - } - - /// /// 执行移动操作 /// public void DoMove() @@ -339,4 +452,15 @@ AnimatedSprite.Play(AnimatorNames.Idle); BasisVelocity = Vector2.Zero; } -} \ No newline at end of file + + /// + /// 更新房间中标记的目标位置 + /// + public void UpdateMarkTargetPosition() + { + if (LookTarget != null) + { + AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position; + } + } +} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs new file mode 100644 index 0000000..b2da111 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs @@ -0,0 +1,11 @@ + +using Godot; + +/// +/// 没有武器的敌人 +/// +[Tool] +public partial class NoWeaponEnemy : Enemy +{ + +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AIAdvancedStateEnum.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AIAdvancedStateEnum.cs deleted file mode 100644 index c02b0d2..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AIAdvancedStateEnum.cs +++ /dev/null @@ -1,40 +0,0 @@ - -public enum AIAdvancedStateEnum -{ - /// - /// Ai 状态, 正常, 未发现目标 - /// - AiNormal, - /// - /// 找到玩家,准备通知其他敌人 - /// - AiNotify, - /// - /// 惊讶状态 - /// - AiAstonished, - /// - /// 收到其他敌人通知, 前往发现目标的位置 - /// - AiLeaveFor, - /// - /// 发现目标, 目标不在视野内, 但是知道位置 - /// - AiTailAfter, - /// - /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 - /// - AiFollowUp, - /// - /// 距离足够近, 在目标附近随机移动 - /// - AiSurround, - /// - /// Ai 寻找弹药 - /// - AiFindAmmo, - /// - /// Ai攻击 - /// - AiAttack, -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAstonishedState.cs deleted file mode 100644 index dae6a74..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAstonishedState.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Godot; - -namespace AdvancedState; - -/// -/// 发现目标时的惊讶状态 -/// -public class AiAstonishedState : StateBase -{ - /// - /// 下一个状态 - /// - public AIAdvancedStateEnum NextState; - - private object[] _args; - private float _timer; - - public AiAstonishedState() : base(AIAdvancedStateEnum.AiAstonished) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (args.Length == 0) - { - Debug.Log("进入 AINormalStateEnum.AiAstonished 状态必传入下一个状态做完参数!"); - ChangeState(prev); - return; - } - - NextState = (AIAdvancedStateEnum)args[0]; - _args = args; - _timer = 0.6f; - - if (NextState == AIAdvancedStateEnum.AiLeaveFor) - { - var target = (ActivityObject)args[1]; - Master.LookTargetPosition(target.GetCenterPosition()); - } - - //播放惊讶表情 - Master.AnimationPlayer.Play(AnimatorNames.Astonished); - } - - public override void Process(float delta) - { - Master.DoIdle(); - _timer -= delta; - if (_timer <= 0) - { - if (_args.Length == 1) - { - ChangeState(NextState); - } - else if (_args.Length == 2) - { - ChangeState(NextState, _args[1]); - } - else if (_args.Length == 3) - { - ChangeState(NextState, _args[1], _args[2]); - } - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAttackState.cs deleted file mode 100644 index f49c137..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiAttackState.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using Godot; - -namespace AdvancedState; - -/// -/// ai 攻击状态 -/// -public class AiAttackState : StateBase -{ - /// - /// 上一个状态 - /// - public AIAdvancedStateEnum PrevState; - - /// - /// 攻击状态 - /// - public AiAttackEnum AttackState; - - //是否移动结束 - private bool _isMoveOver; - - //移动停顿计时器 - private float _pauseTimer; - private bool _moveFlag; - - //下一个移动点 - private Vector2 _nextPosition; - - //上一帧位置 - private Vector2 _prevPos; - //卡在一个位置的时间 - private float _lockTimer; - - public AiAttackState() : base(AIAdvancedStateEnum.AiAttack) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有攻击目标!"); - } - - var weapon = Master.WeaponPack.ActiveItem; - if (weapon == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有武器!"); - - } - - if (!weapon.TriggerIsReady()) - { - throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色武器还玩法触动扳机!"); - } - - Master.BasisVelocity = Vector2.Zero; - AttackState = AiAttackEnum.None; - Master.LockTargetTime = 0; - PrevState = prev; - - _isMoveOver = true; - _pauseTimer = 0; - _moveFlag = false; - } - - public override void Exit(AIAdvancedStateEnum next) - { - Master.MountLookTarget = true; - Master.LockTargetTime = 0; - } - - public override void Process(float delta) - { - //更新标记位置 - Master.UpdateMarkTargetPosition(); - - var weapon = Master.WeaponPack.ActiveItem; - if (weapon == null) - { - //攻击结束 - ChangeState(PrevState); - } - else if (AttackState == AiAttackEnum.AttackInterval) //攻击完成 - { - if (weapon.GetAttackTimer() <= 0) //攻击冷却完成 - { - Master.MountLookTarget = true; - //这里要做换弹判断, 还有上膛判断 - if (weapon.CurrAmmo <= 0) //换弹判断 - { - if (!weapon.Reloading) - { - weapon.Reload(); - } - } - else if (weapon.GetBeLoadedStateState() != 2) //上膛 - { - if (weapon.GetBeLoadedStateState() == 0) - { - weapon.BeLoaded(); - } - } - else - { - //攻击结束 - ChangeState(PrevState); - } - } - MoveHandler(delta); - } - else //攻击状态 - { - //触发扳机 - AttackState = Master.WeaponPack.ActiveItem.AiTriggerAttackState(); - - if (AttackState == AiAttackEnum.LockingTime) //锁定玩家状态 - { - Master.LockTargetTime += delta; - - var aiLockRemainderTime = Master.GetLockRemainderTime(); - Master.MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime; - //更新瞄准辅助线 - if (weapon.Attribute.AiAttackAttr.ShowSubline) - { - if (Master.SubLine == null) - { - Master.InitSubLine(); - } - else - { - Master.SubLine.Enable = true; - } - - //播放警告删掉动画 - if (!Master.SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f) - { - Master.SubLine.PlayWarnAnimation(0.5f); - } - } - - if (weapon.Attribute.AiAttackAttr.LockingStand) //锁定目标时站立不动 - { - Master.DoIdle(); - } - else //正常移动 - { - MoveHandler(delta); - } - } - else - { - Master.LockTargetTime = 0; - //关闭辅助线 - if (Master.SubLine != null) - { - Master.SubLine.Enable = false; - } - - if (AttackState == AiAttackEnum.Attack || AttackState == AiAttackEnum.AttackInterval) - { - if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度 - { - //连发状态锁定角度 - Master.MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0); - } - else - { - Master.MountLookTarget = true; - } - } - else - { - Master.MountLookTarget = true; - } - - if (AttackState == AiAttackEnum.Attack && weapon.Attribute.AiAttackAttr.FiringStand) //开火时站立不动 - { - Master.DoIdle(); - } - else //正常移动 - { - MoveHandler(delta); - } - } - } - } - - private void MoveHandler(float delta) - { - - if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //移动已经完成 - { - RunOver(Master.LookTarget.Position); - _isMoveOver = false; - } - else - { - var masterPosition = Master.Position; - if (_lockTimer >= 1) //卡在一个点超过一秒 - { - RunOver(Master.LookTarget.Position); - _isMoveOver = false; - _lockTimer = 0; - } - else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else if (!_moveFlag) - { - _moveFlag = true; - //移动 - Master.DoMove(); - } - else - { - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else - { - //移动 - Master.DoMove(); - } - - if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta) - { - _lockTimer += delta; - } - else - { - _lockTimer = 0; - _prevPos = masterPosition; - } - } - } - } - - private void RunOver(Vector2 targetPos) - { - var weapon = Master.WeaponPack.ActiveItem; - var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f)); - _nextPosition = new Vector2( - targetPos.X + Utils.Random.RandomRangeInt(-distance, distance), - targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance) - ); - Master.NavigationAgent2D.TargetPosition = _nextPosition; - } - -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFindAmmoState.cs deleted file mode 100644 index 3857cea..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFindAmmoState.cs +++ /dev/null @@ -1,151 +0,0 @@ - -using System; -using Godot; - -namespace AdvancedState; - -/// -/// Ai 寻找弹药, 进入该状态需要在参数中传入目标武器对象 -/// -public class AiFindAmmoState : StateBase -{ - - private Weapon _target; - - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 1f; - - private bool _isInTailAfterRange = false; - private float _tailAfterTimer = 0; - - public AiFindAmmoState() : base(AIAdvancedStateEnum.AiFindAmmo) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiFindAmmo 状态时角色没有攻击目标!"); - } - - if (args.Length == 0) - { - throw new Exception("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来"); - } - - SetTargetWeapon((Weapon)args[0]); - _navigationUpdateTimer = 0; - _isInTailAfterRange = false; - _tailAfterTimer = 0; - - //标记武器 - _target.SetSign(SignNames.AiFindWeaponSign, Master); - } - - public override void Process(float delta) - { - if (!Master.IsAllWeaponTotalAmmoEmpty()) //已经有弹药了 - { - ChangeState(GetNextState()); - return; - } - - //更新目标位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - var position = _target.GlobalPosition; - Master.NavigationAgent2D.TargetPosition = position; - } - else - { - _navigationUpdateTimer -= delta; - } - - if (_target.IsDestroyed || _target.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走 - { - //再去寻找其他武器 - SetTargetWeapon(Master.FindTargetWeapon()); - - if (_target == null) //也没有其他可用的武器了 - { - ChangeState(GetNextState()); - } - } - else if (_target.Master == Master) //已经被自己拾起 - { - ChangeState(GetNextState()); - } - else if (_target.Master != null) //武器已经被其他角色拾起! - { - //再去寻找其他武器 - SetTargetWeapon(Master.FindTargetWeapon()); - - if (_target == null) //也没有其他可用的武器了 - { - ChangeState(GetNextState()); - } - } - else - { - //检测目标没有超出跟随视野距离 - _isInTailAfterRange = Master.IsInTailAfterViewRange(Master.LookTarget.GetCenterPosition()); - if (_isInTailAfterRange) - { - _tailAfterTimer = 0; - } - else - { - _tailAfterTimer += delta; - } - - //向武器移动 - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - } - } - - private AIAdvancedStateEnum GetNextState() - { - return _tailAfterTimer > 10 ? AIAdvancedStateEnum.AiNormal : AIAdvancedStateEnum.AiTailAfter; - } - - private void SetTargetWeapon(Weapon weapon) - { - _target = weapon; - //设置目标点 - if (_target != null) - { - Master.NavigationAgent2D.TargetPosition = _target.GlobalPosition; - } - } - - public override void DebugDraw() - { - if (_target != null) - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(_target.GlobalPosition), Colors.Purple); - - if (_tailAfterTimer <= 0) - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Orange); - } - else if (_tailAfterTimer <= 10) - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Blue); - } - - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFollowUpState.cs deleted file mode 100644 index 4a602a8..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFollowUpState.cs +++ /dev/null @@ -1,127 +0,0 @@ - -using System; -using Godot; - -namespace AdvancedState; - -/// -/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 -/// -public class AiFollowUpState : StateBase -{ - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - public AiFollowUpState() : base(AIAdvancedStateEnum.AiFollowUp) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiFollowUp 状态时角色没有攻击目标!"); - } - - _navigationUpdateTimer = 0; - Master.TargetInView = true; - } - - public override void Process(float delta) - { - //先检查弹药是否打光 - if (Master.IsAllWeaponTotalAmmoEmpty()) - { - //再寻找是否有可用的武器 - var targetWeapon = Master.FindTargetWeapon(); - if (targetWeapon != null) - { - ChangeState(AIAdvancedStateEnum.AiFindAmmo, targetWeapon); - return; - } - else - { - //切换到随机移动状态 - ChangeState(AIAdvancedStateEnum.AiSurround); - } - } - - var playerPos = Master.LookTarget.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - //是否在攻击范围内 - var inAttackRange = false; - - var weapon = Master.WeaponPack.ActiveItem; - var distanceSquared = Master.Position.DistanceSquaredTo(playerPos); - if (weapon != null) - { - inAttackRange = distanceSquared <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2); - } - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中 - if (Master.TargetInView) - { - //更新标记位置 - Master.UpdateMarkTargetPosition(); - if (inAttackRange) //在攻击范围内 - { - //距离够近, 可以切换到环绕模式 - if (distanceSquared <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange), 2) * 0.7f) - { - ChangeState(AIAdvancedStateEnum.AiSurround); - } - else if (weapon.TriggerIsReady()) //可以攻击 - { - //攻击状态 - ChangeState(AIAdvancedStateEnum.AiAttack); - } - } - } - else //不在视野中 - { - ChangeState(AIAdvancedStateEnum.AiTailAfter); - } - } - - public override void DebugDraw() - { - var playerPos = Master.LookTarget.GetCenterPosition(); - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiLeaveForState.cs deleted file mode 100644 index 030736f..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiLeaveForState.cs +++ /dev/null @@ -1,128 +0,0 @@ - -using System; -using Godot; - -namespace AdvancedState; - -/// -/// 收到其他敌人通知, 前往发现目标的位置 -/// -public class AiLeaveForState : StateBase -{ - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - private float _idleTimer = 0; - - //目标 - private ActivityObject _target; - //目标点 - private Vector2 _targetPosition; - - public AiLeaveForState() : base(AIAdvancedStateEnum.AiLeaveFor) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (args.Length == 0) - { - throw new Exception("进入 AINormalStateEnum.AiLeaveFor 状态必须带上目标对象"); - } - - _target = (ActivityObject)args[0]; - - //先检查弹药是否打光 - if (Master.IsAllWeaponTotalAmmoEmpty()) - { - //再寻找是否有可用的武器 - var targetWeapon = Master.FindTargetWeapon(); - if (targetWeapon != null) - { - Master.LookTarget = _target; - ChangeState(AIAdvancedStateEnum.AiFindAmmo, targetWeapon); - return; - } - } - - _idleTimer = 1; - _targetPosition = _target.GetCenterPosition(); - Master.LookTargetPosition(_targetPosition); - Master.AnimationPlayer.Play(AnimatorNames.Query); - } - - public override void Exit(AIAdvancedStateEnum next) - { - Master.AnimationPlayer.Play(AnimatorNames.Reset); - } - - public override void Process(float delta) - { - if (_idleTimer > 0) - { - _idleTimer -= delta; - return; - } - //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - if (Master.AffiliationArea.RoomInfo.MarkTargetPosition.TryGetValue(_target.Id, out var pos)) - { - _targetPosition = pos; - } - Master.NavigationAgent2D.TargetPosition = _targetPosition; - } - else - { - _navigationUpdateTimer -= delta; - } - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - Master.LookTargetPosition(_targetPosition); - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - - var playerPos = Player.Current.GetCenterPosition(); - //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 - if (Master.IsInTailAfterViewRange(playerPos)) - { - if (!Master.TestViewRayCast(playerPos)) //看到玩家 - { - //关闭射线检测 - Master.TestViewRayCastOver(); - //切换成发现目标状态 - Master.LookTarget = Player.Current; - ChangeState(AIAdvancedStateEnum.AiFollowUp); - return; - } - else - { - //关闭射线检测 - Master.TestViewRayCastOver(); - } - } - - //移动到目标掉了, 还没发现目标 - if (Master.NavigationAgent2D.IsNavigationFinished()) - { - ChangeState(AIAdvancedStateEnum.AiNormal); - } - } - - public override void DebugDraw() - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.NavigationAgent2D.TargetPosition), Colors.Yellow); - } -} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNormalState.cs deleted file mode 100644 index 8a4858b..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNormalState.cs +++ /dev/null @@ -1,164 +0,0 @@ - -using Godot; - -namespace AdvancedState; - -/// -/// AI 正常状态 -/// -public class AiNormalState : StateBase -{ - //下一个运动的坐标 - private Vector2 _nextPos; - - //是否移动结束 - private bool _isMoveOver; - - //上一次移动是否撞墙 - private bool _againstWall; - - //撞墙法线角度 - private float _againstWallNormalAngle; - - //移动停顿计时器 - private float _pauseTimer; - private bool _moveFlag; - - //上一帧位置 - private Vector2 _prevPos; - //卡在一个位置的时间 - private float _lockTimer; - - public AiNormalState() : base(AIAdvancedStateEnum.AiNormal) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - _isMoveOver = true; - _againstWall = false; - _againstWallNormalAngle = 0; - _pauseTimer = 0; - _moveFlag = false; - Master.LookTarget = null; - } - - public override void Process(float delta) - { - //检测玩家 - var player = Player.Current; - //玩家中心点坐标 - var playerPos = player.GetCenterPosition(); - - if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家 - { - //发现玩家 - Master.LookTarget = player; - //进入惊讶状态, 然后再进入通知状态 - ChangeState(AIAdvancedStateEnum.AiAstonished, AIAdvancedStateEnum.AiNotify); - return; - } - else if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //没发现玩家, 且已经移动完成 - { - RunOver(); - _isMoveOver = false; - } - else //移动中 - { - if (_lockTimer >= 1) //卡在一个点超过一秒 - { - RunOver(); - _isMoveOver = false; - _lockTimer = 0; - } - else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0.3f, 2f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else if (!_moveFlag) - { - _moveFlag = true; - var pos = Master.Position; - //移动 - Master.DoMove(); - _prevPos = pos; - } - else - { - var pos = Master.Position; - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else - { - //移动 - Master.DoMove(); - } - - if (_prevPos.DistanceSquaredTo(pos) <= 0.01f) - { - _lockTimer += delta; - } - else - { - _prevPos = pos; - } - } - } - - //关闭射线检测 - Master.TestViewRayCastOver(); - } - - //移动结束 - private void RunOver() - { - float angle; - if (_againstWall) - { - angle = Utils.Random.RandomRangeFloat(_againstWallNormalAngle - Mathf.Pi * 0.5f, - _againstWallNormalAngle + Mathf.Pi * 0.5f); - } - else - { - angle = Utils.Random.RandomRangeFloat(0, Mathf.Pi * 2f); - } - - var len = Utils.Random.RandomRangeInt(30, 200); - _nextPos = new Vector2(len, 0).Rotated(angle) + Master.GlobalPosition; - //获取射线碰到的坐标 - if (Master.TestViewRayCast(_nextPos)) //碰到墙壁 - { - _nextPos = Master.ViewRay.GetCollisionPoint(); - _againstWall = true; - _againstWallNormalAngle = Master.ViewRay.GetCollisionNormal().Angle(); - } - else - { - _againstWall = false; - } - - Master.NavigationAgent2D.TargetPosition = _nextPos; - Master.LookTargetPosition(_nextPos); - } - - public override void DebugDraw() - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPos), Colors.Green); - } -} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNotifyState.cs deleted file mode 100644 index f1f0c10..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNotifyState.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace AdvancedState; - -/// -/// 发现目标, 通知其它敌人 -/// -public class AiNotifyState : StateBase -{ - private float _timer; - - public AiNotifyState() : base(AIAdvancedStateEnum.AiNotify) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiNotify 没有攻击目标!"); - } - _timer = 0.6f; - //通知其它角色 - Master.World.NotifyEnemyTarget(Master, Master.LookTarget); - Master.AnimationPlayer.Play(AnimatorNames.Notify); - } - - public override void Process(float delta) - { - Master.DoIdle(); - _timer -= delta; - if (_timer <= 0) - { - ChangeState(AIAdvancedStateEnum.AiTailAfter); - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiSurroundState.cs deleted file mode 100644 index 875d365..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiSurroundState.cs +++ /dev/null @@ -1,175 +0,0 @@ - -using System; -using Godot; - -namespace AdvancedState; - -/// -/// 距离目标足够近, 在目标附近随机移动, 并开火 -/// -public class AiSurroundState : StateBase -{ - //是否移动结束 - private bool _isMoveOver; - - //移动停顿计时器 - private float _pauseTimer; - private bool _moveFlag; - - //下一个移动点 - private Vector2 _nextPosition; - - //上一帧位置 - private Vector2 _prevPos; - //卡在一个位置的时间 - private float _lockTimer; - - public AiSurroundState() : base(AIAdvancedStateEnum.AiSurround) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiSurround 状态时角色没有攻击目标!"); - } - - Master.TargetInView = true; - _isMoveOver = true; - _pauseTimer = 0; - _moveFlag = false; - } - - public override void Process(float delta) - { - //先检查弹药是否打光 - if (Master.IsAllWeaponTotalAmmoEmpty()) - { - //再寻找是否有可用的武器 - var targetWeapon = Master.FindTargetWeapon(); - if (targetWeapon != null) - { - ChangeState(AIAdvancedStateEnum.AiFindAmmo, targetWeapon); - return; - } - } - - var playerPos = Master.LookTarget.GetCenterPosition(); - var weapon = Master.WeaponPack.ActiveItem; - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中 - if (Master.TargetInView) - { - //更新标记位置 - Master.UpdateMarkTargetPosition(); - - if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //移动已经完成 - { - RunOver(playerPos); - _isMoveOver = false; - } - else - { - var masterPosition = Master.Position; - if (_lockTimer >= 1) //卡在一个点超过一秒 - { - RunOver(playerPos); - _isMoveOver = false; - _lockTimer = 0; - } - else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else if (!_moveFlag) - { - _moveFlag = true; - //移动 - Master.DoMove(); - } - else - { - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); - _isMoveOver = true; - _moveFlag = false; - //站立 - Master.DoIdle(); - } - else - { - //移动 - Master.DoMove(); - } - - if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta) - { - _lockTimer += delta; - } - else - { - _lockTimer = 0; - _prevPos = masterPosition; - } - } - - if (weapon != null) - { - if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围 - { - ChangeState(AIAdvancedStateEnum.AiFollowUp); - } - else if (weapon.TriggerIsReady()) //可以攻击 - { - //发起攻击 - ChangeState(AIAdvancedStateEnum.AiAttack); - } - } - } - } - else //目标离开视野 - { - ChangeState(AIAdvancedStateEnum.AiTailAfter); - } - } - - private void RunOver(Vector2 targetPos) - { - var weapon = Master.WeaponPack.ActiveItem; - var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f)); - _nextPosition = new Vector2( - targetPos.X + Utils.Random.RandomRangeInt(-distance, distance), - targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance) - ); - Master.NavigationAgent2D.TargetPosition = _nextPosition; - } - - public override void DebugDraw() - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiTailAfterState.cs deleted file mode 100644 index a4bd0f6..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiTailAfterState.cs +++ /dev/null @@ -1,128 +0,0 @@ - -using System; -using Godot; - -namespace AdvancedState; - -/// -/// AI 发现玩家, 跟随玩家, 但是不在视野范围内 -/// -public class AiTailAfterState : StateBase -{ - /// - /// 目标是否在视野半径内 - /// - private bool _isInViewRange; - - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - //目标从视野消失时已经过去的时间 - private float _viewTimer; - - public AiTailAfterState() : base(AIAdvancedStateEnum.AiTailAfter) - { - } - - public override void Enter(AIAdvancedStateEnum prev, params object[] args) - { - if (Master.LookTarget == null) - { - throw new Exception("进入 AIAdvancedStateEnum.AiTailAfter 状态时角色没有攻击目标!"); - } - - _isInViewRange = true; - _navigationUpdateTimer = 0; - _viewTimer = 0; - - //先检查弹药是否打光 - if (Master.IsAllWeaponTotalAmmoEmpty()) - { - //再寻找是否有可用的武器 - var targetWeapon = Master.FindTargetWeapon(); - if (targetWeapon != null) - { - ChangeState(AIAdvancedStateEnum.AiFindAmmo, targetWeapon); - } - } - } - - public override void Process(float delta) - { - //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 - - var playerPos = Master.LookTarget.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 - if (Master.IsInTailAfterViewRange(playerPos)) - { - if (!Master.TestViewRayCast(playerPos)) //看到玩家 - { - //关闭射线检测 - Master.TestViewRayCastOver(); - //切换成发现目标状态 - ChangeState(AIAdvancedStateEnum.AiFollowUp); - return; - } - else - { - //关闭射线检测 - Master.TestViewRayCastOver(); - } - } - - //检测玩家是否在穿墙视野范围内, 直接检测距离即可 - _isInViewRange = Master.IsInViewRange(playerPos); - if (_isInViewRange) - { - _viewTimer = 0; - } - else //超出视野 - { - if (_viewTimer > 10) //10秒 - { - ChangeState(AIAdvancedStateEnum.AiNormal); - } - else - { - _viewTimer += delta; - } - } - } - - public override void DebugDraw() - { - var playerPos = Master.LookTarget.GetCenterPosition(); - if (_isInViewRange) - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange); - } - else - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Blue); - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AINormalStateEnum.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AINormalStateEnum.cs deleted file mode 100644 index 35e56dd..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AINormalStateEnum.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NnormalState; - -public enum AINormalStateEnum -{ - /// - /// Ai 状态, 正常, 未发现目标 - /// - AiNormal, - /// - /// 找到玩家,准备通知其他敌人 - /// - AiNotify, - /// - /// 发现目标, 惊讶状态 - /// - AiAstonished, - /// - /// 收到其他敌人通知, 前往发现目标的位置 - /// - AiLeaveFor, - /// - /// 发现目标, 目标不在视野内, 但是知道位置 - /// - AiTailAfter, - /// - /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 - /// - AiFollowUp, - /// - /// 距离足够近, 在目标附近随机移动 - /// - AiSurround, - /// - /// 攻击状态 - /// - AiAttack, -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAstonishedState.cs deleted file mode 100644 index 74fd553..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAstonishedState.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NnormalState; - -/// -/// 发现目标时的惊讶状态 -/// -public class AiAstonishedState : StateBase -{ - /// - /// 下一个状态 - /// - public AINormalStateEnum NextState; - - private float _timer; - - public AiAstonishedState() : base(AINormalStateEnum.AiAstonished) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - if (args.Length == 0) - { - Debug.Log("进入 AINormalStateEnum.AiAstonished 状态必传入下一个状态做完参数!"); - ChangeState(prev); - return; - } - - NextState = (AINormalStateEnum)args[0]; - _timer = 1.5f; - } - - public override void Process(float delta) - { - Master.DoIdle(); - _timer -= delta; - if (_timer <= 0) - { - ChangeState(NextState); - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAttackState.cs deleted file mode 100644 index 4fc5e3e..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiAttackState.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Godot; - -namespace NnormalState; - -public class AiAttackState : StateBase -{ - //上一个状态 - public AINormalStateEnum PrevState; - //攻击步骤, 0.锁定目标, 1.执行攻击, 2.攻击完成 - private byte _attackStep = 0; - - public AiAttackState() : base(AINormalStateEnum.AiAttack) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - if (Master.GetAttackTimer() > 0) - { - Debug.LogError("攻击冷却还未完成就进入了 AINormalStateEnum.AiAttack 状态!"); - ChangeState(prev); - return; - } - _attackStep = 0; - PrevState = prev; - Master.AnimationPlayer.AnimationFinished += OnAnimationFinished; - } - - public override void Exit(AINormalStateEnum next) - { - Master.AnimationPlayer.AnimationFinished -= OnAnimationFinished; - } - - public override void Process(float delta) - { - if (_attackStep == 0) //锁定目标步骤 - { - if (Master.GetLockRemainderTime() > 0) //锁定目标时间 - { - if (!Master.AttackAttribute.FiringStand && !Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - } - else - { - _attackStep = 1; - } - } - else if (_attackStep == 1) //攻击步骤 - { - Master.BasisVelocity = Vector2.Zero; - Master.AnimationPlayer.Play(AnimatorNames.Attack); - _attackStep = 2; - } - - } - - public void OnAnimationFinished(StringName name) - { - ChangeState(PrevState); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiFollowUpState.cs deleted file mode 100644 index 9ce25d8..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiFollowUpState.cs +++ /dev/null @@ -1,103 +0,0 @@ - -using Godot; - -namespace NnormalState; - -/// -/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 -/// -public class AiFollowUpState : StateBase -{ - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - public AiFollowUpState() : base(AINormalStateEnum.AiFollowUp) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - _navigationUpdateTimer = 0; - Master.TargetInView = true; - } - - public override void Exit(AINormalStateEnum next) - { - Master.LookTarget = null; - } - - public override void Process(float delta) - { - var playerPos = Player.Current.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - var distanceSquared = Master.GlobalPosition.DistanceSquaredTo(playerPos); - //是否在攻击范围内 - var inAttackRange = distanceSquared <= Mathf.Pow(Master.GetAttackRange(), 2); - - //枪口指向玩家 - Master.LookTarget = Player.Current; - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - //站立 - Master.DoIdle(); - } - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中 - if (Master.TargetInView) - { - if (inAttackRange) //在攻击范围内 - { - //距离够近, 可以切换到环绕模式 - if (distanceSquared <= Mathf.Pow(Master.GetAttackRange() * 0.7f, 2) * 0.7f) - { - ChangeState(AINormalStateEnum.AiSurround); - } - else if (Master.GetAttackTimer() <= 0) //攻击 - { - ChangeState(AINormalStateEnum.AiAttack); - } - } - } - else //不在视野中 - { - ChangeState(AINormalStateEnum.AiTailAfter); - } - } - - public override void DebugDraw() - { - var playerPos = Player.Current.GetCenterPosition(); - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiLeaveForState.cs deleted file mode 100644 index 98163be..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiLeaveForState.cs +++ /dev/null @@ -1,89 +0,0 @@ - -using Godot; - -namespace NnormalState; - -/// -/// 收到其他敌人通知, 前往发现目标的位置 -/// -public class AiLeaveForState : StateBase -{ - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - public AiLeaveForState() : base(AINormalStateEnum.AiLeaveFor) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - // if (Master.World.Enemy_IsFindTarget) - // { - // Master.NavigationAgent2D.TargetPosition = Master.World.Enemy_FindTargetPosition; - // } - // else - // { - // ChangeState(prev); - // } - } - - public override void Process(float delta) - { - // //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 - // - // //更新玩家位置 - // if (_navigationUpdateTimer <= 0) - // { - // //每隔一段时间秒更改目标位置 - // _navigationUpdateTimer = _navigationInterval; - // Master.NavigationAgent2D.TargetPosition = Master.World.Enemy_FindTargetPosition; - // } - // else - // { - // _navigationUpdateTimer -= delta; - // } - // - // if (!Master.NavigationAgent2D.IsNavigationFinished()) - // { - // Master.LookTargetPosition(Master.World.Enemy_FindTargetPosition); - // //移动 - // Master.DoMove(); - // } - // else - // { - // //站立 - // Master.DoIdle(); - // } - // - // var playerPos = Player.Current.GetCenterPosition(); - // //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 - // if (Master.IsInTailAfterViewRange(playerPos)) - // { - // if (!Master.TestViewRayCast(playerPos)) //看到玩家 - // { - // //关闭射线检测 - // Master.TestViewRayCastOver(); - // //切换成发现目标状态 - // ChangeState(AINormalStateEnum.AiFollowUp); - // return; - // } - // else - // { - // //关闭射线检测 - // Master.TestViewRayCastOver(); - // } - // } - // - // //移动到目标掉了, 还没发现目标 - // if (Master.NavigationAgent2D.IsNavigationFinished()) - // { - // ChangeState(AINormalStateEnum.AiNormal); - // } - } - - public override void DebugDraw() - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.NavigationAgent2D.TargetPosition), Colors.Yellow); - } -} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNormalState.cs deleted file mode 100644 index 61469b8..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNormalState.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Godot; - -namespace NnormalState; - -/// -/// AI 正常状态 -/// -public class AiNormalState : StateBase -{ - //是否发现玩家 - private bool _isFindPlayer; - - //下一个运动的坐标 - private Vector2 _nextPos; - - //是否移动结束 - private bool _isMoveOver; - - //上一次移动是否撞墙 - private bool _againstWall; - - //撞墙法线角度 - private float _againstWallNormalAngle; - - //移动停顿计时器 - private float _pauseTimer; - private bool _moveFlag; - - //上一帧位置 - private Vector2 _prevPos; - //卡在一个位置的时间 - private float _lockTimer; - - public AiNormalState() : base(AINormalStateEnum.AiNormal) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - _isFindPlayer = false; - _isMoveOver = true; - _againstWall = false; - _againstWallNormalAngle = 0; - _pauseTimer = 0; - _moveFlag = false; - } - - public override void Process(float delta) - { - if (_isFindPlayer) //已经找到玩家了 - { - //现临时处理, 直接切换状态 - ChangeState(AINormalStateEnum.AiTailAfter); - } - else //没有找到玩家 - { - //检测玩家 - var player = Player.Current; - //玩家中心点坐标 - var playerPos = player.GetCenterPosition(); - - if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家 - { - //发现玩家 - _isFindPlayer = true; - } - else if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //没发现玩家, 且已经移动完成 - { - RunOver(); - _isMoveOver = false; - } - else //移动中 - { - if (_lockTimer >= 1) //卡在一个点超过一秒 - { - RunOver(); - _isMoveOver = false; - _lockTimer = 0; - } - else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0.3f, 2f); - _isMoveOver = true; - _moveFlag = false; - Master.DoIdle(); - } - else if (!_moveFlag) - { - _moveFlag = true; - var pos = Master.Position; - //移动 - Master.DoMove(); - _prevPos = pos; - } - else - { - var pos = Master.Position; - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - Master.DoIdle(); - } - else - { - //计算移动 - Master.DoMove(); - } - - if (_prevPos.DistanceSquaredTo(pos) <= 0.01f) - { - _lockTimer += delta; - } - else - { - _prevPos = pos; - } - } - } - - //关闭射线检测 - Master.TestViewRayCastOver(); - } - } - - //移动结束 - private void RunOver() - { - float angle; - if (_againstWall) - { - angle = Utils.Random.RandomRangeFloat(_againstWallNormalAngle - Mathf.Pi * 0.5f, - _againstWallNormalAngle + Mathf.Pi * 0.5f); - } - else - { - angle = Utils.Random.RandomRangeFloat(0, Mathf.Pi * 2f); - } - - var len = Utils.Random.RandomRangeInt(30, 200); - _nextPos = new Vector2(len, 0).Rotated(angle) + Master.GlobalPosition; - //获取射线碰到的坐标 - if (Master.TestViewRayCast(_nextPos)) //碰到墙壁 - { - _nextPos = Master.ViewRay.GetCollisionPoint(); - _againstWall = true; - _againstWallNormalAngle = Master.ViewRay.GetCollisionNormal().Angle(); - } - else - { - _againstWall = false; - } - - Master.NavigationAgent2D.TargetPosition = _nextPos; - Master.LookTargetPosition(_nextPos); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNotifyState.cs deleted file mode 100644 index 76e46c4..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNotifyState.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NnormalState; - -/// -/// 发现目标, 通知其它敌人 -/// -public class AiNotifyState : StateBase -{ - - private float _timer; - - public AiNotifyState() : base(AINormalStateEnum.AiNotify) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - _timer = 1.5f; - //通知其它角色 - Master.World.NotifyEnemyTarget(Master, Player.Current); - } - - public override void Process(float delta) - { - _timer -= delta; - if (_timer <= 0) - { - ChangeState(AINormalStateEnum.AiTailAfter); - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiSurroundState.cs deleted file mode 100644 index 9e145c5..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiSurroundState.cs +++ /dev/null @@ -1,154 +0,0 @@ - -using Godot; - -namespace NnormalState; - -/// -/// 距离目标足够近, 在目标附近随机移动, 并开火 -/// -public class AiSurroundState : StateBase -{ - //是否移动结束 - private bool _isMoveOver; - - //移动停顿计时器 - private float _pauseTimer; - private bool _moveFlag; - - //下一个移动点 - private Vector2 _nextPosition; - - //上一帧位置 - private Vector2 _prevPos; - //卡在一个位置的时间 - private float _lockTimer; - - public AiSurroundState() : base(AINormalStateEnum.AiSurround) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - Master.TargetInView = true; - _isMoveOver = true; - _pauseTimer = 0; - _moveFlag = false; - } - - public override void Exit(AINormalStateEnum next) - { - Master.LookTarget = null; - } - - public override void Process(float delta) - { - var playerPos = Player.Current.GetCenterPosition(); - - //枪口指向玩家 - Master.LookTarget = Player.Current; - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中 - if (Master.TargetInView) - { - if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //移动已经完成 - { - RunOver(playerPos); - _isMoveOver = false; - } - else - { - var masterPosition = Master.Position; - if (_lockTimer >= 1) //卡在一个点超过一秒 - { - RunOver(playerPos); - _isMoveOver = false; - _lockTimer = 0; - } - else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - Master.DoIdle(); - } - else if (!_moveFlag) - { - _moveFlag = true; - //计算移动 - Master.DoMove(); - } - else - { - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); - _isMoveOver = true; - _moveFlag = false; - Master.DoIdle(); - } - else - { - //计算移动 - Master.DoMove(); - } - - if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta) - { - _lockTimer += delta; - } - else - { - _lockTimer = 0; - _prevPos = masterPosition; - } - } - - if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetAttackRange() * 0.7f, 2)) //玩家离开正常射击范围 - { - ChangeState(AINormalStateEnum.AiFollowUp); - } - else if (Master.GetAttackTimer() <= 0) //发起攻击 - { - ChangeState(AINormalStateEnum.AiAttack); - } - } - } - else //目标离开视野 - { - ChangeState(AINormalStateEnum.AiTailAfter); - } - } - - private void RunOver(Vector2 targetPos) - { - var distance = (int)(Master.GetAttackRange() * 0.7f); - _nextPosition = new Vector2( - targetPos.X + Utils.Random.RandomRangeInt(-distance, distance), - targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance) - ); - Master.NavigationAgent2D.TargetPosition = _nextPosition; - } - - public override void DebugDraw() - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiTailAfterState.cs deleted file mode 100644 index 864d094..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiTailAfterState.cs +++ /dev/null @@ -1,118 +0,0 @@ - -using Godot; - -namespace NnormalState; - -/// -/// AI 发现玩家, 跟随玩家 -/// -public class AiTailAfterState : StateBase -{ - /// - /// 目标是否在视野半径内 - /// - private bool _isInViewRange; - - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - //目标从视野消失时已经过去的时间 - private float _viewTimer; - - public AiTailAfterState() : base(AINormalStateEnum.AiTailAfter) - { - } - - public override void Enter(AINormalStateEnum prev, params object[] args) - { - _isInViewRange = true; - _navigationUpdateTimer = 0; - _viewTimer = 0; - } - - public override void Exit(AINormalStateEnum next) - { - Master.LookTarget = null; - } - - public override void Process(float delta) - { - //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 - - var playerPos = Player.Current.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - //枪口指向玩家 - Master.LookTarget = Player.Current; - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //移动 - Master.DoMove(); - } - else - { - Master.DoIdle(); - } - //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 - if (Master.IsInTailAfterViewRange(playerPos)) - { - if (!Master.TestViewRayCast(playerPos)) //看到玩家 - { - //关闭射线检测 - Master.TestViewRayCastOver(); - //切换成发现目标状态 - ChangeState(AINormalStateEnum.AiFollowUp); - return; - } - else - { - //关闭射线检测 - Master.TestViewRayCastOver(); - } - } - - //检测玩家是否在穿墙视野范围内, 直接检测距离即可 - _isInViewRange = Master.IsInViewRange(playerPos); - if (_isInViewRange) - { - _viewTimer = 0; - } - else //超出视野 - { - if (_viewTimer > 10) //10秒 - { - ChangeState(AINormalStateEnum.AiNormal); - } - else - { - _viewTimer += delta; - } - } - } - - public override void DebugDraw() - { - var playerPos = Player.Current.GetCenterPosition(); - if (_isInViewRange) - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange); - } - else - { - Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Blue); - } - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs new file mode 100644 index 0000000..081f791 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs @@ -0,0 +1,65 @@ +using Godot; + +namespace EnemyState; + +/// +/// 发现目标时的惊讶状态 +/// +public class AiAstonishedState : StateBase +{ + /// + /// 下一个状态 + /// + public AIStateEnum NextState; + + private object[] _args; + private float _timer; + + public AiAstonishedState() : base(AIStateEnum.AiAstonished) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (args.Length == 0) + { + Debug.Log("进入 AINormalStateEnum.AiAstonished 状态必传入下一个状态做完参数!"); + ChangeState(prev); + return; + } + + NextState = (AIStateEnum)args[0]; + _args = args; + _timer = 0.6f; + + if (NextState == AIStateEnum.AiLeaveFor) + { + var target = (ActivityObject)args[1]; + Master.LookTargetPosition(target.GetCenterPosition()); + } + + //播放惊讶表情 + Master.AnimationPlayer.Play(AnimatorNames.Astonished); + } + + public override void Process(float delta) + { + Master.DoIdle(); + _timer -= delta; + if (_timer <= 0) + { + if (_args.Length == 1) + { + ChangeState(NextState); + } + else if (_args.Length == 2) + { + ChangeState(NextState, _args[1]); + } + else if (_args.Length == 3) + { + ChangeState(NextState, _args[1], _args[2]); + } + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs new file mode 100644 index 0000000..2f0b18e --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs @@ -0,0 +1,268 @@ +using System; +using Godot; + +namespace EnemyState; + +/// +/// ai 攻击状态 +/// +public class AiAttackState : StateBase +{ + /// + /// 上一个状态 + /// + public AIStateEnum PrevState; + + /// + /// 攻击状态 + /// + public AiAttackEnum AttackState; + + //是否移动结束 + private bool _isMoveOver; + + //移动停顿计时器 + private float _pauseTimer; + private bool _moveFlag; + + //下一个移动点 + private Vector2 _nextPosition; + + //上一帧位置 + private Vector2 _prevPos; + //卡在一个位置的时间 + private float _lockTimer; + + public AiAttackState() : base(AIStateEnum.AiAttack) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有攻击目标!"); + } + + var weapon = Master.WeaponPack.ActiveItem; + if (weapon == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有武器!"); + + } + + if (!weapon.TriggerIsReady()) + { + throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色武器还玩法触动扳机!"); + } + + Master.BasisVelocity = Vector2.Zero; + AttackState = AiAttackEnum.None; + Master.LockTargetTime = 0; + PrevState = prev; + + _isMoveOver = true; + _pauseTimer = 0; + _moveFlag = false; + } + + public override void Exit(AIStateEnum next) + { + Master.MountLookTarget = true; + Master.LockTargetTime = 0; + } + + public override void Process(float delta) + { + //更新标记位置 + Master.UpdateMarkTargetPosition(); + + var weapon = Master.WeaponPack.ActiveItem; + if (weapon == null) + { + //攻击结束 + ChangeState(PrevState); + } + else if (AttackState == AiAttackEnum.AttackInterval) //攻击完成 + { + if (weapon.GetAttackTimer() <= 0) //攻击冷却完成 + { + Master.MountLookTarget = true; + //这里要做换弹判断, 还有上膛判断 + if (weapon.CurrAmmo <= 0) //换弹判断 + { + if (!weapon.Reloading) + { + weapon.Reload(); + } + } + else if (weapon.GetBeLoadedStateState() != 2) //上膛 + { + if (weapon.GetBeLoadedStateState() == 0) + { + weapon.BeLoaded(); + } + } + else + { + //攻击结束 + ChangeState(PrevState); + } + } + MoveHandler(delta); + } + else //攻击状态 + { + //触发扳机 + AttackState = Master.WeaponPack.ActiveItem.AiTriggerAttackState(); + + if (AttackState == AiAttackEnum.LockingTime) //锁定玩家状态 + { + Master.LockTargetTime += delta; + + var aiLockRemainderTime = Master.GetLockRemainderTime(); + Master.MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime; + //更新瞄准辅助线 + if (weapon.Attribute.AiAttackAttr.ShowSubline) + { + if (Master.SubLine == null) + { + Master.InitSubLine(); + } + else + { + Master.SubLine.Enable = true; + } + + //播放警告删掉动画 + if (!Master.SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f) + { + Master.SubLine.PlayWarnAnimation(0.5f); + } + } + + if (weapon.Attribute.AiAttackAttr.LockingStand) //锁定目标时站立不动 + { + Master.DoIdle(); + } + else //正常移动 + { + MoveHandler(delta); + } + } + else + { + Master.LockTargetTime = 0; + //关闭辅助线 + if (Master.SubLine != null) + { + Master.SubLine.Enable = false; + } + + if (AttackState == AiAttackEnum.Attack || AttackState == AiAttackEnum.AttackInterval) + { + if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度 + { + //连发状态锁定角度 + Master.MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0); + } + else + { + Master.MountLookTarget = true; + } + } + else + { + Master.MountLookTarget = true; + } + + if (AttackState == AiAttackEnum.Attack && weapon.Attribute.AiAttackAttr.FiringStand) //开火时站立不动 + { + Master.DoIdle(); + } + else //正常移动 + { + MoveHandler(delta); + } + } + } + } + + private void MoveHandler(float delta) + { + + if (_pauseTimer >= 0) + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + _pauseTimer -= delta; + } + else if (_isMoveOver) //移动已经完成 + { + RunOver(Master.LookTarget.Position); + _isMoveOver = false; + } + else + { + var masterPosition = Master.Position; + if (_lockTimer >= 1) //卡在一个点超过一秒 + { + RunOver(Master.LookTarget.Position); + _isMoveOver = false; + _lockTimer = 0; + } + else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else if (!_moveFlag) + { + _moveFlag = true; + //移动 + Master.DoMove(); + } + else + { + var lastSlideCollision = Master.GetLastSlideCollision(); + if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else + { + //移动 + Master.DoMove(); + } + + if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta) + { + _lockTimer += delta; + } + else + { + _lockTimer = 0; + _prevPos = masterPosition; + } + } + } + } + + private void RunOver(Vector2 targetPos) + { + var weapon = Master.WeaponPack.ActiveItem; + var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f)); + _nextPosition = new Vector2( + targetPos.X + Utils.Random.RandomRangeInt(-distance, distance), + targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance) + ); + Master.NavigationAgent2D.TargetPosition = _nextPosition; + } + +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs new file mode 100644 index 0000000..682aae1 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs @@ -0,0 +1,151 @@ + +using System; +using Godot; + +namespace EnemyState; + +/// +/// Ai 寻找弹药, 进入该状态需要在参数中传入目标武器对象 +/// +public class AiFindAmmoState : StateBase +{ + + private Weapon _target; + + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 1f; + + private bool _isInTailAfterRange = false; + private float _tailAfterTimer = 0; + + public AiFindAmmoState() : base(AIStateEnum.AiFindAmmo) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiFindAmmo 状态时角色没有攻击目标!"); + } + + if (args.Length == 0) + { + throw new Exception("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来"); + } + + SetTargetWeapon((Weapon)args[0]); + _navigationUpdateTimer = 0; + _isInTailAfterRange = false; + _tailAfterTimer = 0; + + //标记武器 + _target.SetSign(SignNames.AiFindWeaponSign, Master); + } + + public override void Process(float delta) + { + if (!Master.IsAllWeaponTotalAmmoEmpty()) //已经有弹药了 + { + ChangeState(GetNextState()); + return; + } + + //更新目标位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + var position = _target.GlobalPosition; + Master.NavigationAgent2D.TargetPosition = position; + } + else + { + _navigationUpdateTimer -= delta; + } + + if (_target.IsDestroyed || _target.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走 + { + //再去寻找其他武器 + SetTargetWeapon(Master.FindTargetWeapon()); + + if (_target == null) //也没有其他可用的武器了 + { + ChangeState(GetNextState()); + } + } + else if (_target.Master == Master) //已经被自己拾起 + { + ChangeState(GetNextState()); + } + else if (_target.Master != null) //武器已经被其他角色拾起! + { + //再去寻找其他武器 + SetTargetWeapon(Master.FindTargetWeapon()); + + if (_target == null) //也没有其他可用的武器了 + { + ChangeState(GetNextState()); + } + } + else + { + //检测目标没有超出跟随视野距离 + _isInTailAfterRange = Master.IsInTailAfterViewRange(Master.LookTarget.GetCenterPosition()); + if (_isInTailAfterRange) + { + _tailAfterTimer = 0; + } + else + { + _tailAfterTimer += delta; + } + + //向武器移动 + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + //移动 + Master.DoMove(); + } + else + { + //站立 + Master.DoIdle(); + } + } + } + + private AIStateEnum GetNextState() + { + return _tailAfterTimer > 10 ? AIStateEnum.AiNormal : AIStateEnum.AiTailAfter; + } + + private void SetTargetWeapon(Weapon weapon) + { + _target = weapon; + //设置目标点 + if (_target != null) + { + Master.NavigationAgent2D.TargetPosition = _target.GlobalPosition; + } + } + + public override void DebugDraw() + { + if (_target != null) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(_target.GlobalPosition), Colors.Purple); + + if (_tailAfterTimer <= 0) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Orange); + } + else if (_tailAfterTimer <= 10) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Blue); + } + + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs new file mode 100644 index 0000000..e107a5e --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs @@ -0,0 +1,127 @@ + +using System; +using Godot; + +namespace EnemyState; + +/// +/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 +/// +public class AiFollowUpState : StateBase +{ + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 0.3f; + + public AiFollowUpState() : base(AIStateEnum.AiFollowUp) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiFollowUp 状态时角色没有攻击目标!"); + } + + _navigationUpdateTimer = 0; + Master.TargetInView = true; + } + + public override void Process(float delta) + { + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + var targetWeapon = Master.FindTargetWeapon(); + if (targetWeapon != null) + { + ChangeState(AIStateEnum.AiFindAmmo, targetWeapon); + return; + } + else + { + //切换到随机移动状态 + ChangeState(AIStateEnum.AiSurround); + } + } + + var playerPos = Master.LookTarget.GetCenterPosition(); + + //更新玩家位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + Master.NavigationAgent2D.TargetPosition = playerPos; + } + else + { + _navigationUpdateTimer -= delta; + } + + //是否在攻击范围内 + var inAttackRange = false; + + var weapon = Master.WeaponPack.ActiveItem; + var distanceSquared = Master.Position.DistanceSquaredTo(playerPos); + if (weapon != null) + { + inAttackRange = distanceSquared <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2); + } + + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + //移动 + Master.DoMove(); + } + else + { + //站立 + Master.DoIdle(); + } + + //检测玩家是否在视野内 + if (Master.IsInTailAfterViewRange(playerPos)) + { + Master.TargetInView = !Master.TestViewRayCast(playerPos); + //关闭射线检测 + Master.TestViewRayCastOver(); + } + else + { + Master.TargetInView = false; + } + + //在视野中 + if (Master.TargetInView) + { + //更新标记位置 + Master.UpdateMarkTargetPosition(); + if (inAttackRange) //在攻击范围内 + { + //距离够近, 可以切换到环绕模式 + if (distanceSquared <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange), 2) * 0.7f) + { + ChangeState(AIStateEnum.AiSurround); + } + else if (weapon.TriggerIsReady()) //可以攻击 + { + //攻击状态 + ChangeState(AIStateEnum.AiAttack); + } + } + } + else //不在视野中 + { + ChangeState(AIStateEnum.AiTailAfter); + } + } + + public override void DebugDraw() + { + var playerPos = Master.LookTarget.GetCenterPosition(); + Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs new file mode 100644 index 0000000..39d62aa --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs @@ -0,0 +1,128 @@ + +using System; +using Godot; + +namespace EnemyState; + +/// +/// 收到其他敌人通知, 前往发现目标的位置 +/// +public class AiLeaveForState : StateBase +{ + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 0.3f; + + private float _idleTimer = 0; + + //目标 + private ActivityObject _target; + //目标点 + private Vector2 _targetPosition; + + public AiLeaveForState() : base(AIStateEnum.AiLeaveFor) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (args.Length == 0) + { + throw new Exception("进入 AINormalStateEnum.AiLeaveFor 状态必须带上目标对象"); + } + + _target = (ActivityObject)args[0]; + + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + var targetWeapon = Master.FindTargetWeapon(); + if (targetWeapon != null) + { + Master.LookTarget = _target; + ChangeState(AIStateEnum.AiFindAmmo, targetWeapon); + return; + } + } + + _idleTimer = 1; + _targetPosition = _target.GetCenterPosition(); + Master.LookTargetPosition(_targetPosition); + Master.AnimationPlayer.Play(AnimatorNames.Query); + } + + public override void Exit(AIStateEnum next) + { + Master.AnimationPlayer.Play(AnimatorNames.Reset); + } + + public override void Process(float delta) + { + if (_idleTimer > 0) + { + _idleTimer -= delta; + return; + } + //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 + + //更新玩家位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + if (Master.AffiliationArea.RoomInfo.MarkTargetPosition.TryGetValue(_target.Id, out var pos)) + { + _targetPosition = pos; + } + Master.NavigationAgent2D.TargetPosition = _targetPosition; + } + else + { + _navigationUpdateTimer -= delta; + } + + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + Master.LookTargetPosition(_targetPosition); + //移动 + Master.DoMove(); + } + else + { + //站立 + Master.DoIdle(); + } + + var playerPos = Player.Current.GetCenterPosition(); + //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 + if (Master.IsInTailAfterViewRange(playerPos)) + { + if (!Master.TestViewRayCast(playerPos)) //看到玩家 + { + //关闭射线检测 + Master.TestViewRayCastOver(); + //切换成发现目标状态 + Master.LookTarget = Player.Current; + ChangeState(AIStateEnum.AiFollowUp); + return; + } + else + { + //关闭射线检测 + Master.TestViewRayCastOver(); + } + } + + //移动到目标掉了, 还没发现目标 + if (Master.NavigationAgent2D.IsNavigationFinished()) + { + ChangeState(AIStateEnum.AiNormal); + } + } + + public override void DebugDraw() + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.NavigationAgent2D.TargetPosition), Colors.Yellow); + } +} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs new file mode 100644 index 0000000..f91594a --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs @@ -0,0 +1,164 @@ + +using Godot; + +namespace EnemyState; + +/// +/// AI 正常状态 +/// +public class AiNormalState : StateBase +{ + //下一个运动的坐标 + private Vector2 _nextPos; + + //是否移动结束 + private bool _isMoveOver; + + //上一次移动是否撞墙 + private bool _againstWall; + + //撞墙法线角度 + private float _againstWallNormalAngle; + + //移动停顿计时器 + private float _pauseTimer; + private bool _moveFlag; + + //上一帧位置 + private Vector2 _prevPos; + //卡在一个位置的时间 + private float _lockTimer; + + public AiNormalState() : base(AIStateEnum.AiNormal) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + _isMoveOver = true; + _againstWall = false; + _againstWallNormalAngle = 0; + _pauseTimer = 0; + _moveFlag = false; + Master.LookTarget = null; + } + + public override void Process(float delta) + { + //检测玩家 + var player = Player.Current; + //玩家中心点坐标 + var playerPos = player.GetCenterPosition(); + + if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家 + { + //发现玩家 + Master.LookTarget = player; + //进入惊讶状态, 然后再进入通知状态 + ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify); + return; + } + else if (_pauseTimer >= 0) + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + _pauseTimer -= delta; + } + else if (_isMoveOver) //没发现玩家, 且已经移动完成 + { + RunOver(); + _isMoveOver = false; + } + else //移动中 + { + if (_lockTimer >= 1) //卡在一个点超过一秒 + { + RunOver(); + _isMoveOver = false; + _lockTimer = 0; + } + else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0.3f, 2f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else if (!_moveFlag) + { + _moveFlag = true; + var pos = Master.Position; + //移动 + Master.DoMove(); + _prevPos = pos; + } + else + { + var pos = Master.Position; + var lastSlideCollision = Master.GetLastSlideCollision(); + if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else + { + //移动 + Master.DoMove(); + } + + if (_prevPos.DistanceSquaredTo(pos) <= 0.01f) + { + _lockTimer += delta; + } + else + { + _prevPos = pos; + } + } + } + + //关闭射线检测 + Master.TestViewRayCastOver(); + } + + //移动结束 + private void RunOver() + { + float angle; + if (_againstWall) + { + angle = Utils.Random.RandomRangeFloat(_againstWallNormalAngle - Mathf.Pi * 0.5f, + _againstWallNormalAngle + Mathf.Pi * 0.5f); + } + else + { + angle = Utils.Random.RandomRangeFloat(0, Mathf.Pi * 2f); + } + + var len = Utils.Random.RandomRangeInt(30, 200); + _nextPos = new Vector2(len, 0).Rotated(angle) + Master.GlobalPosition; + //获取射线碰到的坐标 + if (Master.TestViewRayCast(_nextPos)) //碰到墙壁 + { + _nextPos = Master.ViewRay.GetCollisionPoint(); + _againstWall = true; + _againstWallNormalAngle = Master.ViewRay.GetCollisionNormal().Angle(); + } + else + { + _againstWall = false; + } + + Master.NavigationAgent2D.TargetPosition = _nextPos; + Master.LookTargetPosition(_nextPos); + } + + public override void DebugDraw() + { + Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPos), Colors.Green); + } +} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs new file mode 100644 index 0000000..d7c199e --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs @@ -0,0 +1,37 @@ +using System; + +namespace EnemyState; + +/// +/// 发现目标, 通知其它敌人 +/// +public class AiNotifyState : StateBase +{ + private float _timer; + + public AiNotifyState() : base(AIStateEnum.AiNotify) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiNotify 没有攻击目标!"); + } + _timer = 0.6f; + //通知其它角色 + Master.World.NotifyEnemyTarget(Master, Master.LookTarget); + Master.AnimationPlayer.Play(AnimatorNames.Notify); + } + + public override void Process(float delta) + { + Master.DoIdle(); + _timer -= delta; + if (_timer <= 0) + { + ChangeState(AIStateEnum.AiTailAfter); + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs new file mode 100644 index 0000000..c9a8f6e --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs @@ -0,0 +1,175 @@ + +using System; +using Godot; + +namespace EnemyState; + +/// +/// 距离目标足够近, 在目标附近随机移动, 并开火 +/// +public class AiSurroundState : StateBase +{ + //是否移动结束 + private bool _isMoveOver; + + //移动停顿计时器 + private float _pauseTimer; + private bool _moveFlag; + + //下一个移动点 + private Vector2 _nextPosition; + + //上一帧位置 + private Vector2 _prevPos; + //卡在一个位置的时间 + private float _lockTimer; + + public AiSurroundState() : base(AIStateEnum.AiSurround) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiSurround 状态时角色没有攻击目标!"); + } + + Master.TargetInView = true; + _isMoveOver = true; + _pauseTimer = 0; + _moveFlag = false; + } + + public override void Process(float delta) + { + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + var targetWeapon = Master.FindTargetWeapon(); + if (targetWeapon != null) + { + ChangeState(AIStateEnum.AiFindAmmo, targetWeapon); + return; + } + } + + var playerPos = Master.LookTarget.GetCenterPosition(); + var weapon = Master.WeaponPack.ActiveItem; + + //检测玩家是否在视野内 + if (Master.IsInTailAfterViewRange(playerPos)) + { + Master.TargetInView = !Master.TestViewRayCast(playerPos); + //关闭射线检测 + Master.TestViewRayCastOver(); + } + else + { + Master.TargetInView = false; + } + + //在视野中 + if (Master.TargetInView) + { + //更新标记位置 + Master.UpdateMarkTargetPosition(); + + if (_pauseTimer >= 0) + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + _pauseTimer -= delta; + } + else if (_isMoveOver) //移动已经完成 + { + RunOver(playerPos); + _isMoveOver = false; + } + else + { + var masterPosition = Master.Position; + if (_lockTimer >= 1) //卡在一个点超过一秒 + { + RunOver(playerPos); + _isMoveOver = false; + _lockTimer = 0; + } + else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else if (!_moveFlag) + { + _moveFlag = true; + //移动 + Master.DoMove(); + } + else + { + var lastSlideCollision = Master.GetLastSlideCollision(); + if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); + _isMoveOver = true; + _moveFlag = false; + //站立 + Master.DoIdle(); + } + else + { + //移动 + Master.DoMove(); + } + + if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta) + { + _lockTimer += delta; + } + else + { + _lockTimer = 0; + _prevPos = masterPosition; + } + } + + if (weapon != null) + { + if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围 + { + ChangeState(AIStateEnum.AiFollowUp); + } + else if (weapon.TriggerIsReady()) //可以攻击 + { + //发起攻击 + ChangeState(AIStateEnum.AiAttack); + } + } + } + } + else //目标离开视野 + { + ChangeState(AIStateEnum.AiTailAfter); + } + } + + private void RunOver(Vector2 targetPos) + { + var weapon = Master.WeaponPack.ActiveItem; + var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f)); + _nextPosition = new Vector2( + targetPos.X + Utils.Random.RandomRangeInt(-distance, distance), + targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance) + ); + Master.NavigationAgent2D.TargetPosition = _nextPosition; + } + + public override void DebugDraw() + { + Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs new file mode 100644 index 0000000..4fa2cc5 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs @@ -0,0 +1,128 @@ + +using System; +using Godot; + +namespace EnemyState; + +/// +/// AI 发现玩家, 跟随玩家, 但是不在视野范围内 +/// +public class AiTailAfterState : StateBase +{ + /// + /// 目标是否在视野半径内 + /// + private bool _isInViewRange; + + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 0.3f; + + //目标从视野消失时已经过去的时间 + private float _viewTimer; + + public AiTailAfterState() : base(AIStateEnum.AiTailAfter) + { + } + + public override void Enter(AIStateEnum prev, params object[] args) + { + if (Master.LookTarget == null) + { + throw new Exception("进入 AIAdvancedStateEnum.AiTailAfter 状态时角色没有攻击目标!"); + } + + _isInViewRange = true; + _navigationUpdateTimer = 0; + _viewTimer = 0; + + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + var targetWeapon = Master.FindTargetWeapon(); + if (targetWeapon != null) + { + ChangeState(AIStateEnum.AiFindAmmo, targetWeapon); + } + } + } + + public override void Process(float delta) + { + //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 + + var playerPos = Master.LookTarget.GetCenterPosition(); + + //更新玩家位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + Master.NavigationAgent2D.TargetPosition = playerPos; + } + else + { + _navigationUpdateTimer -= delta; + } + + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + //移动 + Master.DoMove(); + } + else + { + //站立 + Master.DoIdle(); + } + //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 + if (Master.IsInTailAfterViewRange(playerPos)) + { + if (!Master.TestViewRayCast(playerPos)) //看到玩家 + { + //关闭射线检测 + Master.TestViewRayCastOver(); + //切换成发现目标状态 + ChangeState(AIStateEnum.AiFollowUp); + return; + } + else + { + //关闭射线检测 + Master.TestViewRayCastOver(); + } + } + + //检测玩家是否在穿墙视野范围内, 直接检测距离即可 + _isInViewRange = Master.IsInViewRange(playerPos); + if (_isInViewRange) + { + _viewTimer = 0; + } + else //超出视野 + { + if (_viewTimer > 10) //10秒 + { + ChangeState(AIStateEnum.AiNormal); + } + else + { + _viewTimer += delta; + } + } + } + + public override void DebugDraw() + { + var playerPos = Master.LookTarget.GetCenterPosition(); + if (_isInViewRange) + { + Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange); + } + else + { + Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Blue); + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/player/Player.cs b/DungeonShooting_Godot/src/game/activity/role/player/Player.cs index 9c1e3b1..87cf9c4 100644 --- a/DungeonShooting_Godot/src/game/activity/role/player/Player.cs +++ b/DungeonShooting_Godot/src/game/activity/role/player/Player.cs @@ -6,7 +6,7 @@ /// 玩家角色基类, 所有角色都必须继承该类 /// [Tool] -public partial class Player : AdvancedRole +public partial class Player : Role { /// /// 获取当前操作的角色 @@ -190,7 +190,7 @@ var enemyList = AffiliationArea.FindIncludeItems(o => o.CollisionWithMask(PhysicsLayer.Enemy)); foreach (var enemy in enemyList) { - ((AdvancedEnemy)enemy).Hurt(this, 1000, 0); + ((Enemy)enemy).Hurt(this, 1000, 0); } } // //测试用 diff --git a/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs index 9a2fd78..5c9ffe8 100644 --- a/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs +++ b/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs @@ -6,7 +6,7 @@ /// /// 武器的基类 /// -public abstract partial class Weapon : ActivityObject, IPackageItem +public abstract partial class Weapon : ActivityObject, IPackageItem { /// /// 武器使用的属性数据, 该属性会根据是否是玩家使用武器, 如果是Ai使用武器, 则会返回 AiUseAttribute 的属性对象 @@ -32,7 +32,7 @@ /// public CampEnum TargetCamp { get; set; } - public AdvancedRole Master { get; set; } + public Role Master { get; set; } public int PackageIndex { get; set; } = -1; @@ -148,7 +148,7 @@ /// /// 上一次触发改武器开火的角色, 可能为 null /// - public AdvancedRole TriggerRole { get; private set; } + public Role TriggerRole { get; private set; } /// /// 上一次触发改武器开火的触发开火攻击的层级, 数据源自于: @@ -397,7 +397,7 @@ /// 当武器被拾起时调用 /// /// 拾起该武器的角色 - protected virtual void OnPickUp(AdvancedRole master) + protected virtual void OnPickUp(Role master) { } @@ -405,7 +405,7 @@ /// 当武器从武器背包中移除时调用 /// /// 移除该武器的角色 - protected virtual void OnRemove(AdvancedRole master) + protected virtual void OnRemove(Role master) { } @@ -695,7 +695,7 @@ /// 扳机函数, 调用即视为按下扳机 /// /// 按下扳机的角色, 如果传 null, 则视为走火 - public void Trigger(AdvancedRole triggerRole) + public void Trigger(Role triggerRole) { //不能触发扳机 if (!NoMasterCanTrigger && Master == null) return; @@ -1105,7 +1105,7 @@ /// /// 根据触扳机的角色对象判断该角色使用的武器数据 /// - public ExcelConfig.WeaponBase GetUseAttribute(AdvancedRole triggerRole) + public ExcelConfig.WeaponBase GetUseAttribute(Role triggerRole) { if (triggerRole == null || !triggerRole.IsAi) { @@ -1125,7 +1125,7 @@ { return (uint)TriggerRoleAttackLayer; } - return Master != null ? Master.AttackLayer : AdvancedRole.DefaultAttackLayer; + return Master != null ? Master.AttackLayer : Role.DefaultAttackLayer; } /// @@ -1620,7 +1620,7 @@ { var result = new CheckInteractiveResult(this); - if (master is AdvancedRole roleMaster) //碰到角色 + if (master is Role roleMaster) //碰到角色 { if (Master == null) { @@ -1666,7 +1666,7 @@ public override void Interactive(ActivityObject master) { - if (master is AdvancedRole roleMaster) //与role互动 + if (master is Role roleMaster) //与role互动 { var holster = roleMaster.WeaponPack; //查找是否有同类型武器 @@ -1741,7 +1741,7 @@ /// 触发扔掉武器时抛出的效果, 并不会管武器是否在武器背包中 /// /// 触发扔掉该武器的的角色 - public void ThrowWeapon(AdvancedRole master) + public void ThrowWeapon(Role master) { ThrowWeapon(master, master.GlobalPosition); } @@ -1751,7 +1751,7 @@ /// /// 触发扔掉该武器的的角色 /// 投抛起始位置 - public void ThrowWeapon(AdvancedRole master, Vector2 startPosition) + public void ThrowWeapon(Role master, Vector2 startPosition) { //阴影偏移 ShadowOffset = new Vector2(0, 2); @@ -1968,7 +1968,7 @@ } else { - var enemy = (AdvancedEnemy)Master; + var enemy = (Enemy)Master; if (enemy.LockTargetTime >= Attribute.AiAttackAttr.LockingTime) //正常射击 { if (GetDelayedAttackTime() > 0) diff --git a/DungeonShooting_Godot/src/game/activity/weapon/knife/Knife.cs b/DungeonShooting_Godot/src/game/activity/weapon/knife/Knife.cs index b5db0d6..ca56de3 100644 --- a/DungeonShooting_Godot/src/game/activity/weapon/knife/Knife.cs +++ b/DungeonShooting_Godot/src/game/activity/weapon/knife/Knife.cs @@ -127,7 +127,7 @@ var activityObject = body.AsActivityObject(); if (activityObject != null) { - if (activityObject is AdvancedRole role) //碰到角色 + if (activityObject is Role role) //碰到角色 { var damage = Utils.Random.RandomConfigRange(Attribute.Bullet.HarmRange); //计算子弹造成的伤害 @@ -159,7 +159,7 @@ } //造成伤害 - role.CallDeferred(nameof(AdvancedRole.Hurt), TriggerRole, damage, (role.GetCenterPosition() - GlobalPosition).Angle()); + role.CallDeferred(nameof(Role.Hurt), TriggerRole, damage, (role.GetCenterPosition() - GlobalPosition).Angle()); } else if (activityObject is Bullet bullet) //攻击子弹 { diff --git a/DungeonShooting_Godot/src/game/camera/GameCamera.cs b/DungeonShooting_Godot/src/game/camera/GameCamera.cs index fd48f9b..b35d63a 100644 --- a/DungeonShooting_Godot/src/game/camera/GameCamera.cs +++ b/DungeonShooting_Godot/src/game/camera/GameCamera.cs @@ -49,7 +49,7 @@ /// /// 相机跟随目标 /// - private AdvancedRole _followTarget; + private Role _followTarget; /// /// SubViewportContainer 中的像素偏移, 因为游戏开启了完美像素, SubViewport 节点下的相机运动会造成非常大的抖动, @@ -119,7 +119,7 @@ /// /// 设置相机跟随目标 /// - public void SetFollowTarget(AdvancedRole target) + public void SetFollowTarget(Role target) { _followTarget = target; if (target != null) @@ -132,7 +132,7 @@ /// /// 获取相机跟随目标 /// - public AdvancedRole GetFollowTarget() + public Role GetFollowTarget() { return _followTarget; } diff --git a/DungeonShooting_Godot/src/game/manager/FireManager.cs b/DungeonShooting_Godot/src/game/manager/FireManager.cs index 727edf2..3f02409 100644 --- a/DungeonShooting_Godot/src/game/manager/FireManager.cs +++ b/DungeonShooting_Godot/src/game/manager/FireManager.cs @@ -218,18 +218,7 @@ Penetration = Utils.Random.RandomConfigRange(bullet.Penetration), }; - if (role is AdvancedRole advancedRole) - { - data.Position = advancedRole.MountPoint.GlobalPosition; - } - else if (role is Enemy enemy) - { - data.Position = enemy.FirePoint.GlobalPosition; - } - else - { - data.Position = role.AnimatedSprite.GlobalPosition; - } + data.Position = role.MountPoint.GlobalPosition; var deviationAngle = Utils.Random.RandomConfigRange(bullet.DeviationAngleRange); data.Altitude = role.GetFirePointAltitude(); diff --git a/DungeonShooting_Godot/src/game/manager/ResourcePath.cs b/DungeonShooting_Godot/src/game/manager/ResourcePath.cs index 6914429..cd7de40 100644 --- a/DungeonShooting_Godot/src/game/manager/ResourcePath.cs +++ b/DungeonShooting_Godot/src/game/manager/ResourcePath.cs @@ -48,10 +48,7 @@ public const string prefab_prop_buff_BuffProp0013_tscn = "res://prefab/prop/buff/BuffProp0013.tscn"; public const string prefab_prop_buff_BuffProp0014_tscn = "res://prefab/prop/buff/BuffProp0014.tscn"; public const string prefab_role_Enemy0001_tscn = "res://prefab/role/Enemy0001.tscn"; - public const string prefab_role_Enemy0002_tscn = "res://prefab/role/Enemy0002.tscn"; public const string prefab_role_Role0001_tscn = "res://prefab/role/Role0001.tscn"; - public const string prefab_role_template_AdvancedEnemyTemplate_tscn = "res://prefab/role/template/AdvancedEnemyTemplate.tscn"; - public const string prefab_role_template_AdvancedRoleTemplate_tscn = "res://prefab/role/template/AdvancedRoleTemplate.tscn"; public const string prefab_role_template_EnemyTemplate_tscn = "res://prefab/role/template/EnemyTemplate.tscn"; public const string prefab_role_template_RoleTemplate_tscn = "res://prefab/role/template/RoleTemplate.tscn"; public const string prefab_shell_Shell0001_tscn = "res://prefab/shell/Shell0001.tscn"; diff --git a/DungeonShooting_Godot/src/game/room/DungeonManager.cs b/DungeonShooting_Godot/src/game/room/DungeonManager.cs index 3cf058f..7e70905 100644 --- a/DungeonShooting_Godot/src/game/room/DungeonManager.cs +++ b/DungeonShooting_Godot/src/game/room/DungeonManager.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using Godot; -using NnormalState; /// /// 地牢管理器 @@ -566,23 +565,9 @@ //不与玩家处于同一个房间 if (!enemy.IsDestroyed && enemy.AffiliationArea != playerAffiliationArea) { - if (enemy is Enemy e) + if (enemy.StateController.CurrState != AIStateEnum.AiNormal) { - if (e.StateController.CurrState != AINormalStateEnum.AiNormal) - { - e.StateController.ChangeState(AINormalStateEnum.AiNormal); - } - } - else if (enemy is AdvancedEnemy ae) - { - if (ae.StateController.CurrState != AIAdvancedStateEnum.AiNormal) - { - ae.StateController.ChangeState(AIAdvancedStateEnum.AiNormal); - } - } - else - { - throw new Exception("World.Enemy_InstanceList 混入了非 Enemy 和 AdvancedEnemy 类型的对象!"); + enemy.StateController.ChangeState(AIStateEnum.AiNormal); } } } diff --git a/DungeonShooting_Godot/src/game/room/World.cs b/DungeonShooting_Godot/src/game/room/World.cs index 1ed2eab..e45156c 100644 --- a/DungeonShooting_Godot/src/game/room/World.cs +++ b/DungeonShooting_Godot/src/game/room/World.cs @@ -1,7 +1,6 @@ using System.Collections; using System.Collections.Generic; using Godot; -using NnormalState; /// /// 游戏世界 @@ -63,7 +62,7 @@ /// /// 记录所有存活的敌人 /// - public List Enemy_InstanceList { get; } = new List(); + public List Enemy_InstanceList { get; } = new List(); private bool _pause = false; private List _coroutineList; @@ -113,14 +112,11 @@ { if (role != self && !role.IsDestroyed && role.AffiliationArea == self.AffiliationArea) { - if (role is AdvancedEnemy advancedEnemy) + //将未发现目标的敌人状态置为惊讶状态 + var controller = role.StateController; + if (controller.CurrState == AIStateEnum.AiNormal) { - //将未发现目标的敌人状态置为惊讶状态 - var controller = advancedEnemy.StateController; - if (controller.CurrState == AIAdvancedStateEnum.AiNormal) - { - controller.ChangeState(AIAdvancedStateEnum.AiLeaveFor, target); - } + controller.ChangeState(AIStateEnum.AiLeaveFor, target); } } } diff --git a/DungeonShooting_Godot/src/game/ui/roomUI/RoomUIPanel.cs b/DungeonShooting_Godot/src/game/ui/roomUI/RoomUIPanel.cs index dbd01d3..9d8132b 100644 --- a/DungeonShooting_Godot/src/game/ui/roomUI/RoomUIPanel.cs +++ b/DungeonShooting_Godot/src/game/ui/roomUI/RoomUIPanel.cs @@ -71,10 +71,10 @@ { foreach (var role in World.Current.Enemy_InstanceList) { - if (!role.IsDestroyed && role is AdvancedEnemy advancedEnemy) + if (!role.IsDestroyed) { - var position = GameApplication.Instance.ViewToGlobalPosition(advancedEnemy.Position); - DrawString(ResourceManager.DefaultFont16Px, position, advancedEnemy.StateController.CurrState.ToString()); + var position = GameApplication.Instance.ViewToGlobalPosition(role.Position); + DrawString(ResourceManager.DefaultFont16Px, position, role.StateController.CurrState.ToString()); } } }