diff --git a/DungeonShooting_Godot/prefab/role/Enemy0001.tscn b/DungeonShooting_Godot/prefab/role/Enemy0001.tscn index 866f829..7326d5a 100644 --- a/DungeonShooting_Godot/prefab/role/Enemy0001.tscn +++ b/DungeonShooting_Godot/prefab/role/Enemy0001.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=7 format=3 uid="uid://dbrig6dq441wo"] +[gd_scene load_steps=7 format=3 uid="uid://b8s1dgu63fddf"] -[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/RoleTemplate.tscn" id="1_5po38"] -[ext_resource type="Script" path="res://src/game/activity/role/enemy/Enemy.cs" id="2_1plrq"] +[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="Shader" path="res://resource/material/Blend.gdshader" id="3_x8agd"] -[ext_resource type="SpriteFrames" uid="uid://cnctpyrn02rhd" path="res://resource/spriteFrames/role/Role1001.tres" id="4_qv8w5"] +[ext_resource type="SpriteFrames" uid="uid://cnctpyrn02rhd" path="res://resource/spriteFrames/role/Enemy0001.tres" id="4_qv8w5"] -[sub_resource type="ShaderMaterial" id="ShaderMaterial_8vxx6"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_f7y56"] resource_local_to_scene = true shader = ExtResource("3_x8agd") shader_parameter/blend = Color(0, 0, 0, 0.470588) @@ -16,7 +16,7 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[sub_resource type="ShaderMaterial" id="ShaderMaterial_k8mt5"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_2kup1"] resource_local_to_scene = true shader = ExtResource("3_x8agd") shader_parameter/blend = Color(1, 1, 1, 1) @@ -27,38 +27,26 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[node name="Enemy0001" node_paths=PackedStringArray("HurtArea", "HurtCollision", "MountPoint", "BackMountPoint", "InteractiveArea", "InteractiveCollision", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_5po38")] -collision_layer = 16 -collision_mask = 25 -script = ExtResource("2_1plrq") -HurtArea = NodePath("HurtArea") -HurtCollision = NodePath("HurtArea/HurtCollision") +[node name="Enemy0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_2vqwe")] +script = ExtResource("2_thbey") +ViewRay = NodePath("ViewRay") +NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D") +NavigationPoint = NodePath("NavigationPoint") MountPoint = NodePath("MountPoint") BackMountPoint = NodePath("BackMountPoint") -InteractiveArea = NodePath("InteractiveArea") -InteractiveCollision = NodePath("InteractiveArea/InteractiveCollision") MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") +HurtArea = NodePath("HurtArea") +HurtCollision = NodePath("HurtArea/HurtCollision") +InteractiveArea = NodePath("InteractiveArea") +InteractiveCollision = NodePath("InteractiveArea/InteractiveCollision") ShadowSprite = NodePath("ShadowSprite") AnimatedSprite = NodePath("AnimatedSprite") Collision = NodePath("Collision") [node name="ShadowSprite" parent="." index="0"] -material = SubResource("ShaderMaterial_8vxx6") +material = SubResource("ShaderMaterial_f7y56") [node name="AnimatedSprite" parent="." index="2"] -material = SubResource("ShaderMaterial_k8mt5") +material = SubResource("ShaderMaterial_2kup1") sprite_frames = ExtResource("4_qv8w5") -animation = &"run" - -[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/Enemy0002.tscn b/DungeonShooting_Godot/prefab/role/Enemy0002.tscn index fd86415..c9fd918 100644 --- a/DungeonShooting_Godot/prefab/role/Enemy0002.tscn +++ b/DungeonShooting_Godot/prefab/role/Enemy0002.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=7 format=3 uid="uid://iweob17o1hn5"] +[gd_scene load_steps=7 format=3 uid="uid://b5r3hd8kv2wmd"] -[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/RoleTemplate.tscn" id="1_rncjb"] +[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_8vxx6"] +[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) @@ -16,7 +16,7 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[sub_resource type="ShaderMaterial" id="ShaderMaterial_k8mt5"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_ntjmx"] resource_local_to_scene = true shader = ExtResource("3_gr4gs") shader_parameter/blend = Color(1, 1, 1, 1) @@ -27,14 +27,11 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[node name="Enemy0001" node_paths=PackedStringArray("MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_rncjb")] -collision_layer = 16 -collision_mask = 25 +[node name="Enemy0002" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_rikvp")] script = ExtResource("2_wjtfl") -MountPoint = NodePath("MountPoint") -BackMountPoint = NodePath("BackMountPoint") -MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") -MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") +ViewRay = NodePath("ViewRay") +NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D") +NavigationPoint = NodePath("NavigationPoint") HurtArea = NodePath("HurtArea") HurtCollision = NodePath("HurtArea/HurtCollision") InteractiveArea = NodePath("InteractiveArea") @@ -44,23 +41,9 @@ Collision = NodePath("Collision") [node name="ShadowSprite" parent="." index="0"] -material = SubResource("ShaderMaterial_8vxx6") +material = SubResource("ShaderMaterial_7theg") -[node name="AnimatedSprite" parent="." index="2"] -material = SubResource("ShaderMaterial_k8mt5") +[node name="AnimatedSprite" parent="." index="1"] +material = SubResource("ShaderMaterial_ntjmx") position = Vector2(0, -10) sprite_frames = ExtResource("4_ehtyi") -animation = &"run" -frame_progress = 0.685531 - -[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/Role0001.tscn b/DungeonShooting_Godot/prefab/role/Role0001.tscn index fd8b964..97d017d 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/RoleTemplate.tscn" id="1_10c2n"] +[ext_resource type="PackedScene" uid="uid://cyrcv2jdgr8cf" path="res://prefab/role/template/AdvancedRoleTemplate.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,17 +27,17 @@ shader_parameter/outline_rainbow = false shader_parameter/outline_use_blend = true -[node name="Role0001" node_paths=PackedStringArray("HurtArea", "HurtCollision", "MountPoint", "BackMountPoint", "InteractiveArea", "InteractiveCollision", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_10c2n")] +[node name="Role0001" node_paths=PackedStringArray("MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_10c2n")] collision_layer = 8 script = ExtResource("2_6xwnt") -HurtArea = NodePath("HurtArea") -HurtCollision = NodePath("HurtArea/HurtCollision") MountPoint = NodePath("MountPoint") BackMountPoint = NodePath("BackMountPoint") -InteractiveArea = NodePath("InteractiveArea") -InteractiveCollision = NodePath("InteractiveArea/InteractiveCollision") MeleeAttackArea = NodePath("MountPoint/MeleeAttackArea") MeleeAttackCollision = NodePath("MountPoint/MeleeAttackArea/MeleeAttackCollision") +HurtArea = NodePath("HurtArea") +HurtCollision = NodePath("HurtArea/HurtCollision") +InteractiveArea = NodePath("InteractiveArea") +InteractiveCollision = NodePath("InteractiveArea/InteractiveCollision") ShadowSprite = NodePath("ShadowSprite") AnimatedSprite = NodePath("AnimatedSprite") Collision = NodePath("Collision") @@ -48,3 +48,4 @@ [node name="AnimatedSprite" parent="." index="2"] material = SubResource("ShaderMaterial_8hgu2") sprite_frames = ExtResource("4_galcc") +frame_progress = 0.658799 diff --git a/DungeonShooting_Godot/prefab/role/RoleTemplate.tscn b/DungeonShooting_Godot/prefab/role/RoleTemplate.tscn deleted file mode 100644 index 584b37c..0000000 --- a/DungeonShooting_Godot/prefab/role/RoleTemplate.tscn +++ /dev/null @@ -1,82 +0,0 @@ -[gd_scene load_steps=8 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"] - -[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="RoleTemplate" type="CharacterBody2D"] - -[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") -position = 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"] diff --git a/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn new file mode 100644 index 0000000..b02e44a --- /dev/null +++ b/DungeonShooting_Godot/prefab/role/template/AdvancedEnemyTemplate.tscn @@ -0,0 +1,48 @@ +[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 new file mode 100644 index 0000000..2394511 --- /dev/null +++ b/DungeonShooting_Godot/prefab/role/template/AdvancedRoleTemplate.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=8 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"] + +[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") +position = 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"] diff --git a/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn new file mode 100644 index 0000000..8d83646 --- /dev/null +++ b/DungeonShooting_Godot/prefab/role/template/EnemyTemplate.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=5 format=3 uid="uid://dxeqcssparqoo"] + +[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"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_fhls5"] +resource_local_to_scene = true +shader = ExtResource("2_tedjs") +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_lnent"] +resource_local_to_scene = true +shader = ExtResource("2_tedjs") +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="EnemyTemplate" instance=ExtResource("1_u04qy")] +collision_layer = 16 +collision_mask = 25 + +[node name="ShadowSprite" parent="." index="0"] +material = SubResource("ShaderMaterial_fhls5") + +[node name="AnimatedSprite" parent="." index="1"] +material = SubResource("ShaderMaterial_lnent") + +[node name="ViewRay" type="RayCast2D" parent="." index="5"] +position = Vector2(0, -8) +enabled = false + +[node name="NavigationPoint" type="Marker2D" parent="." index="6"] +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/RoleTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn new file mode 100644 index 0000000..ad986de --- /dev/null +++ b/DungeonShooting_Godot/prefab/role/template/RoleTemplate.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=7 format=3 uid="uid://0uc4naitjprl"] + +[ext_resource type="Shader" path="res://resource/material/Blend.gdshader" id="1_xk5yk"] + +[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="RoleTemplate" type="CharacterBody2D"] +collision_layer = 0 + +[node name="ShadowSprite" type="Sprite2D" parent="."] +z_index = -1 +material = SubResource("ShaderMaterial_v2kfw") + +[node name="AnimatedSprite" type="AnimatedSprite2D" parent="."] +material = SubResource("ShaderMaterial_yif6x") +position = 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") diff --git a/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs b/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs index f08bc15..ff5d0c9 100644 --- a/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs +++ b/DungeonShooting_Godot/src/framework/map/preinstall/RoomPreinstall.cs @@ -430,8 +430,8 @@ } else if (activityMark.ActivityType == ActivityType.Enemy) //敌人类型 { - var enemy = (Enemy)activityObject; - if (activityMark.Attr.TryGetValue("Weapon", out var weaponId)) //使用的武器 + var role = (Role)activityObject; + if (role is AdvancedEnemy enemy && activityMark.Attr.TryGetValue("Weapon", out var weaponId)) //使用的武器 { if (!string.IsNullOrEmpty(weaponId)) { @@ -451,7 +451,7 @@ if (activityMark.DerivedAttr.TryGetValue("Face", out var face)) //脸朝向, 应该只有 -1 和 1 { var faceDir = int.Parse(face); - enemy.Face = (FaceDirection)faceDir; + role.Face = (FaceDirection)faceDir; } } } diff --git a/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs b/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs index 5dc5429..6bbec1c 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs @@ -136,7 +136,7 @@ if (len <= _hitRadius) //在伤害半径内 { - if (o is AdvancedRole role) //是角色 + if (o is Role role) //是角色 { role.CallDeferred(nameof(role.Hurt), _harm, angle); } diff --git a/DungeonShooting_Godot/src/game/activity/bullet/laser/Laser.cs b/DungeonShooting_Godot/src/game/activity/bullet/laser/Laser.cs index d3216f9..5a62788 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/laser/Laser.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/laser/Laser.cs @@ -136,7 +136,7 @@ private void OnArea2dEntered(Area2D other) { - var role = other.AsActivityObject(); + var role = other.AsActivityObject(); if (role != null) { //击退 @@ -145,7 +145,7 @@ role.MoveController.AddForce(Vector2.FromAngle(Rotation) * BulletData.Repel); } //造成伤害 - role.CallDeferred(nameof(AdvancedRole.Hurt), BulletData.Harm, Rotation); + role.CallDeferred(nameof(Role.Hurt), BulletData.Harm, Rotation); } } diff --git a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs index 40642b3..2e7af1e 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs @@ -131,7 +131,7 @@ /// public virtual void OnCollisionTarget(ActivityObject o) { - if (o is AdvancedRole role) + if (o is Role role) { PlayDisappearEffect(); diff --git a/DungeonShooting_Godot/src/game/activity/role/Role.cs b/DungeonShooting_Godot/src/game/activity/role/Role.cs index 2abb192..0fda986 100644 --- a/DungeonShooting_Godot/src/game/activity/role/Role.cs +++ b/DungeonShooting_Godot/src/game/activity/role/Role.cs @@ -364,8 +364,8 @@ } protected override void Process(float delta) - { - //检查可互动的物体 + { + //检查可互动的物体 bool findFlag = false; for (int i = 0; i < InteractiveItemList.Count; i++) { 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..be5d336 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/AIStateEnum.cs @@ -0,0 +1,32 @@ + +public enum AiStateEnum +{ + /// + /// Ai 状态, 正常, 未发现目标 + /// + AiNormal, + // /// + // /// 发现目标, 但不知道在哪 + // /// + // AiProbe, + /// + /// 收到其他敌人通知, 前往发现目标的位置 + /// + AiLeaveFor, + /// + /// 发现目标, 目标不在视野内, 但是知道位置 + /// + AiTailAfter, + /// + /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 + /// + AiFollowUp, + /// + /// 距离足够近, 在目标附近随机移动 + /// + AiSurround, + /// + /// Ai 寻找弹药 + /// + AiFindAmmo, +} \ 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 new file mode 100644 index 0000000..f80fe8d --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/AdvancedEnemy.cs @@ -0,0 +1,512 @@ +#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; } + + /// + /// Ai攻击状态, 调用 EnemyAttack() 函数后会刷新 + /// + public AiAttackState AttackState { get; private set; } + + //锁定目标时间 + private float _lockTargetTime = 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.ChangeStateInstant(AiStateEnum.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; + } + //目标在视野内的时间 + var currState = StateController.CurrState; + if (currState == AiStateEnum.AiSurround || currState == AiStateEnum.AiFollowUp) + { + var weapon = WeaponPack.ActiveItem; + if (weapon != null) + { + if (weapon.GetBeLoadedStateState() >= 2 && !weapon.IsAttackIntervalTime()) //必须在可以开火时记录时间 + { + _lockTargetTime += delta; + } + else + { + _lockTargetTime = 0; + } + + if (AttackState == AiAttackState.LockingTime) //锁定玩家状态 + { + var aiLockRemainderTime = weapon.GetAiLockRemainderTime(); + MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime; + //更新瞄准辅助线 + if (weapon.Attribute.AiAttackAttr.ShowSubline) + { + if (SubLine == null) + { + InitSubLine(); + } + else + { + SubLine.Enable = true; + } + + //播放警告删掉动画 + if (!SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f) + { + SubLine.PlayWarnAnimation(0.5f); + } + } + } + else + { + //关闭辅助线 + if (SubLine != null) + { + SubLine.Enable = false; + } + + if (AttackState == AiAttackState.Attack || AttackState == AiAttackState.AttackInterval) + { + if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度 + { + //连发状态锁定角度 + MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0); + } + else + { + MountLookTarget = true; + } + } + else + { + MountLookTarget = true; + } + } + } + else + { + MountLookTarget = true; + _lockTargetTime = 0; + } + } + else + { + MountLookTarget = true; + _lockTargetTime = 0; + } + + //拾起武器操作 + EnemyPickUpWeapon(); + } + + protected override void OnHit(int damage, bool realHarm) + { + //受到伤害 + var state = StateController.CurrState; + if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiLeaveFor) //|| state == AiStateEnum.AiProbe + { + 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; + } + + /// + /// 检查是否能切换到 AiStateEnum.AiLeaveFor 状态 + /// + public bool CanChangeLeaveFor() + { + if (!World.Enemy_IsFindTarget) + { + return false; + } + + var currState = StateController.CurrState; + if (currState == AiStateEnum.AiNormal)// || currState == AiStateEnum.AiProbe) + { + //判断是否在同一个房间内 + return World.Enemy_FindTargetAffiliationSet.Contains(AffiliationArea); + } + + return false; + } + + /// + /// Ai触发的攻击 + /// + public void EnemyAttack() + { + var weapon = WeaponPack.ActiveItem; + if (weapon != null) + { + AttackState = weapon.AiTriggerAttackState(); + } + else //没有武器 + { + AttackState = AiAttackState.NoWeapon; + } + } + + /// + /// 获取武器攻击范围 (最大距离值与最小距离的中间值) + /// + /// 从最小到最大距离的过渡量, 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; + } + + /// + /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null + /// + 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 == 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.ItemConfig.Id == weapon.ItemConfig.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 GetLockTime() + { + return _lockTargetTime; + } + + /// + /// 强制设置锁定目标时间 + /// + public void SetLockTargetTime(float time) + { + _lockTargetTime = time; + } +} diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs index 6fecea5..f1f7d3b 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs @@ -1,24 +1,12 @@ -#region 基础敌人设计思路 -/* -敌人有三种状态: -状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1 -状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3 -状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火, - 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2 - -敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现 -*/ -#endregion - - -using System; + using Godot; +using NnormalState; /// /// 基础敌人 /// [Tool] -public partial class Enemy : AdvancedRole +public partial class Enemy : Role { /// /// 目标是否在视野内 @@ -48,23 +36,21 @@ /// /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 /// + [Export, ExportFillNode] public RayCast2D ViewRay { get; private set; } /// /// 导航代理 /// + [Export, ExportFillNode] public NavigationAgent2D NavigationAgent2D { get; private set; } /// /// 导航代理中点 /// + [Export, ExportFillNode] public Marker2D NavigationPoint { get; private set; } - /// - /// Ai攻击状态, 调用 EnemyAttack() 函数后会刷新 - /// - public AiAttackState AttackState { get; private set; } - //锁定目标时间 private float _lockTargetTime = 0; @@ -82,25 +68,9 @@ MaxHp = 20; Hp = 20; - - //视野射线 - ViewRay = GetNode("ViewRay"); - NavigationPoint = GetNode("NavigationPoint"); - NavigationAgent2D = NavigationPoint.GetNode("NavigationAgent2D"); - - //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.ChangeStateInstant(AiStateEnum.AiNormal); + StateController.Register(new AiNormalState()); + StateController.ChangeState(AiStateEnum.AiNormal); } public override void EnterTree() @@ -115,400 +85,4 @@ { 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; - } - //目标在视野内的时间 - var currState = StateController.CurrState; - if (currState == AiStateEnum.AiSurround || currState == AiStateEnum.AiFollowUp) - { - var weapon = WeaponPack.ActiveItem; - if (weapon != null) - { - if (weapon.GetBeLoadedStateState() >= 2 && !weapon.IsAttackIntervalTime()) //必须在可以开火时记录时间 - { - _lockTargetTime += delta; - } - else - { - _lockTargetTime = 0; - } - - if (AttackState == AiAttackState.LockingTime) //锁定玩家状态 - { - var aiLockRemainderTime = weapon.GetAiLockRemainderTime(); - MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime; - //更新瞄准辅助线 - if (weapon.Attribute.AiAttackAttr.ShowSubline) - { - if (SubLine == null) - { - InitSubLine(); - } - else - { - SubLine.Enable = true; - } - - //播放警告删掉动画 - if (!SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f) - { - SubLine.PlayWarnAnimation(0.5f); - } - } - } - else - { - //关闭辅助线 - if (SubLine != null) - { - SubLine.Enable = false; - } - - if (AttackState == AiAttackState.Attack || AttackState == AiAttackState.AttackInterval) - { - if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度 - { - //连发状态锁定角度 - MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0); - } - else - { - MountLookTarget = true; - } - } - else - { - MountLookTarget = true; - } - } - } - else - { - MountLookTarget = true; - _lockTargetTime = 0; - } - } - else - { - MountLookTarget = true; - _lockTargetTime = 0; - } - - //拾起武器操作 - EnemyPickUpWeapon(); - } - - protected override void OnHit(int damage, bool realHarm) - { - //受到伤害 - var state = StateController.CurrState; - if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiProbe || state == AiStateEnum.AiLeaveFor) - { - 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; - } - - /// - /// 检查是否能切换到 AiStateEnum.AiLeaveFor 状态 - /// - /// - public bool CanChangeLeaveFor() - { - if (!World.Enemy_IsFindTarget) - { - return false; - } - - var currState = StateController.CurrState; - if (currState == AiStateEnum.AiNormal || currState == AiStateEnum.AiProbe) - { - //判断是否在同一个房间内 - return World.Enemy_FindTargetAffiliationSet.Contains(AffiliationArea); - } - - return false; - } - - /// - /// Ai触发的攻击 - /// - public void EnemyAttack() - { - var weapon = WeaponPack.ActiveItem; - if (weapon != null) - { - AttackState = weapon.AiTriggerAttackState(); - } - else //没有武器 - { - AttackState = AiAttackState.NoWeapon; - } - } - - /// - /// 获取武器攻击范围 (最大距离值与最小距离的中间值) - /// - /// 从最小到最大距离的过渡量, 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; - } - - /// - /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null - /// - 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 == 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.ItemConfig.Id == weapon.ItemConfig.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 GetLockTime() - { - return _lockTargetTime; - } - - /// - /// 强制设置锁定目标时间 - /// - public void SetLockTargetTime(float time) - { - _lockTargetTime = time; - } -} +} \ 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 new file mode 100644 index 0000000..ab9ef33 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFindAmmoState.cs @@ -0,0 +1,154 @@ + +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(AiStateEnum.AiFindAmmo) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + if (args.Length == 0) + { + Debug.LogError("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来"); + ChangeState(prev); + return; + } + + 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; + } + + var playerPos = Player.Current.GetCenterPosition(); + //枪口指向玩家 + Master.LookTargetPosition(playerPos); + + 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(playerPos); + if (_isInTailAfterRange) + { + _tailAfterTimer = 0; + } + else + { + _tailAfterTimer += delta; + } + + //向武器移动 + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = + (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else + { + Master.BasisVelocity = Vector2.Zero; + } + } + } + + 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(Player.Current.GetCenterPosition()), Colors.Orange); + } + else if (_tailAfterTimer <= 10) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Player.Current.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 new file mode 100644 index 0000000..f635a9d --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiFollowUpState.cs @@ -0,0 +1,132 @@ + +using Godot; + +namespace AdvancedState; + +/// +/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 +/// +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) + { + _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 = Player.Current.GetCenterPosition(); + + //更新玩家位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + Master.NavigationAgent2D.TargetPosition = playerPos; + } + else + { + _navigationUpdateTimer -= delta; + } + + var masterPosition = Master.GlobalPosition; + + //是否在攻击范围内 + var inAttackRange = false; + + var weapon = Master.WeaponPack.ActiveItem; + if (weapon != null) + { + inAttackRange = masterPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2); + } + + //枪口指向玩家 + Master.LookTargetPosition(playerPos); + + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || + (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - masterPosition - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + Master.BasisVelocity = Vector2.Zero; + } + } + else + { + Master.BasisVelocity = Vector2.Zero; + } + + //检测玩家是否在视野内 + if (Master.IsInTailAfterViewRange(playerPos)) + { + Master.TargetInView = !Master.TestViewRayCast(playerPos); + //关闭射线检测 + Master.TestViewRayCastOver(); + } + else + { + Master.TargetInView = false; + } + + //在视野中, 或者锁敌状态下, 或者攻击状态下, 继续保持原本逻辑 + if (Master.TargetInView || Master.AttackState == AiAttackState.LockingTime || Master.AttackState == AiAttackState.Attack) + { + if (inAttackRange) //在攻击范围内 + { + //发起攻击 + Master.EnemyAttack(); + + //距离够近, 可以切换到环绕模式 + if (Master.GlobalPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange), 2) * 0.7f) + { + ChangeState(AiStateEnum.AiSurround); + } + } + } + else //不在视野中 + { + ChangeState(AiStateEnum.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/advancedState/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiLeaveForState.cs new file mode 100644 index 0000000..73e339b --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiLeaveForState.cs @@ -0,0 +1,103 @@ + +using Godot; + +namespace AdvancedState; + +/// +/// 收到其他敌人通知, 前往发现目标的位置 +/// +public class AiLeaveForState : StateBase +{ + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 0.3f; + + public AiLeaveForState() : base(AiStateEnum.AiLeaveFor) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + if (Master.World.Enemy_IsFindTarget) + { + Master.NavigationAgent2D.TargetPosition = Master.World.Enemy_FindTargetPosition; + } + else + { + ChangeState(prev); + return; + } + + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + var targetWeapon = Master.FindTargetWeapon(); + if (targetWeapon != null) + { + ChangeState(AiStateEnum.AiFindAmmo, targetWeapon); + } + } + } + + 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()) + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.LookTargetPosition(Master.World.Enemy_FindTargetPosition); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else + { + Master.BasisVelocity = Vector2.Zero; + } + + var playerPos = Player.Current.GetCenterPosition(); + //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 + if (Master.IsInTailAfterViewRange(playerPos)) + { + if (!Master.TestViewRayCast(playerPos)) //看到玩家 + { + //关闭射线检测 + Master.TestViewRayCastOver(); + //切换成发现目标状态 + 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/advancedState/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNormalState.cs new file mode 100644 index 0000000..77cf398 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiNormalState.cs @@ -0,0 +1,183 @@ + +using Godot; + +namespace AdvancedState; + +/// +/// 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(AiStateEnum.AiNormal) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + _isFindPlayer = false; + _isMoveOver = true; + _againstWall = false; + _againstWallNormalAngle = 0; + _pauseTimer = 0; + _moveFlag = false; + } + + public override void Process(float delta) + { + //其他敌人发现玩家 + if (Master.CanChangeLeaveFor()) + { + ChangeState(AiStateEnum.AiLeaveFor); + return; + } + + if (_isFindPlayer) //已经找到玩家了 + { + //现临时处理, 直接切换状态 + ChangeState(AiStateEnum.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.BasisVelocity = Vector2.Zero; + } + else if (!_moveFlag) + { + _moveFlag = true; + var pos = Master.GlobalPosition; + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + _prevPos = pos; + } + else + { + var pos = Master.GlobalPosition; + var lastSlideCollision = Master.GetLastSlideCollision(); + if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f); + _isMoveOver = true; + _moveFlag = false; + Master.BasisVelocity = Vector2.Zero; + } + else + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + + 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/AiProbeState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiProbeState.cs new file mode 100644 index 0000000..3f8a865 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiProbeState.cs @@ -0,0 +1,20 @@ +// +// /// +// /// Ai 不确定玩家位置 +// /// +// public class AiProbeState : StateBase +// { +// public AiProbeState() : base(AiStateEnum.AiProbe) +// { +// } +// +// public override void Process(float delta) +// { +// //其他敌人发现玩家 +// if (Master.CanChangeLeaveFor()) +// { +// ChangeState(AiStateEnum.AiLeaveFor); +// return; +// } +// } +// } \ 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 new file mode 100644 index 0000000..a7b2fe4 --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiSurroundState.cs @@ -0,0 +1,187 @@ + +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(AiStateEnum.AiSurround) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + 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 = Player.Current.GetCenterPosition(); + var weapon = Master.WeaponPack.ActiveItem; + + //枪口指向玩家 + Master.LookTargetPosition(playerPos); + + //检测玩家是否在视野内 + if (Master.IsInTailAfterViewRange(playerPos)) + { + Master.TargetInView = !Master.TestViewRayCast(playerPos); + //关闭射线检测 + Master.TestViewRayCastOver(); + } + else + { + Master.TargetInView = false; + } + + //在视野中, 或者锁敌状态下, 或者攻击状态下, 继续保持原本逻辑 + if (Master.TargetInView || + (weapon != null && weapon.Attribute.AiAttackAttr.FiringStand && + (Master.AttackState == AiAttackState.LockingTime || Master.AttackState == AiAttackState.Attack) + )) + { + if (_pauseTimer >= 0) + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + _pauseTimer -= delta; + } + else if (_isMoveOver) //移动已经完成 + { + RunOver(playerPos); + _isMoveOver = false; + } + else + { + 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.BasisVelocity = Vector2.Zero; + } + else if (!_moveFlag) + { + _moveFlag = true; + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = + (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else + { + var pos = Master.GlobalPosition; + var lastSlideCollision = Master.GetLastSlideCollision(); + if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 + { + _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); + _isMoveOver = true; + _moveFlag = false; + Master.BasisVelocity = Vector2.Zero; + } + else + { + //判断开火状态, 进行移动 + if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || + (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) + { //正常移动 + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else //站立不动 + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + Master.BasisVelocity = Vector2.Zero; + } + } + + if (_prevPos.DistanceSquaredTo(pos) <= 0.01f) + { + _lockTimer += delta; + } + else + { + _prevPos = pos; + } + } + + if (weapon != null) + { + var position = Master.GlobalPosition; + if (position.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围 + { + ChangeState(AiStateEnum.AiFollowUp); + } + else + { + //发起攻击 + Master.EnemyAttack(); + } + } + } + } + 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/advancedState/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiTailAfterState.cs new file mode 100644 index 0000000..569690e --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/advancedState/AiTailAfterState.cs @@ -0,0 +1,137 @@ + +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(AiStateEnum.AiTailAfter) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + _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 = Player.Current.GetCenterPosition(); + + //更新玩家位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + Master.NavigationAgent2D.TargetPosition = playerPos; + } + else + { + _navigationUpdateTimer -= delta; + } + + //枪口指向玩家 + Master.LookTargetPosition(playerPos); + + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + var weapon = Master.WeaponPack.ActiveItem; + if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || + (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); + Master.AnimatedSprite.Play(AnimatorNames.Run); + Master.BasisVelocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.RoleState.MoveSpeed; + } + else + { + Master.AnimatedSprite.Play(AnimatorNames.Idle); + Master.BasisVelocity = Vector2.Zero; + } + } + else + { + Master.BasisVelocity = Vector2.Zero; + } + //检测玩家是否在视野内, 如果在, 则切换到 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 = 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/normalState/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNormalState.cs new file mode 100644 index 0000000..0cdef1d --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/normalState/AiNormalState.cs @@ -0,0 +1,17 @@ +namespace NnormalState; + +/// +/// AI 正常状态 +/// +public class AiNormalState : StateBase +{ + public AiNormalState() : base(AiStateEnum.AiNormal) + { + + } + + public override void Process(float delta) + { + //Master.BasisVelocity = (Player.Current.Position - Master.Position).LimitLength(10); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AIStateEnum.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AIStateEnum.cs deleted file mode 100644 index 6bb8db4..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AIStateEnum.cs +++ /dev/null @@ -1,32 +0,0 @@ - -public enum AiStateEnum -{ - /// - /// Ai 状态, 正常, 未发现目标 - /// - AiNormal, - /// - /// 发现目标, 但不知道在哪 - /// - AiProbe, - /// - /// 收到其他敌人通知, 前往发现目标的位置 - /// - AiLeaveFor, - /// - /// 发现目标, 目标不在视野内, 但是知道位置 - /// - AiTailAfter, - /// - /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 - /// - AiFollowUp, - /// - /// 距离足够近, 在目标附近随机移动 - /// - AiSurround, - /// - /// Ai 寻找弹药 - /// - AiFindAmmo, -} \ 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 deleted file mode 100644 index 79cb04e..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs +++ /dev/null @@ -1,152 +0,0 @@ - -using Godot; - -/// -/// 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 (args.Length == 0) - { - Debug.LogError("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来"); - ChangeState(prev); - return; - } - - 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; - } - - var playerPos = Player.Current.GetCenterPosition(); - //枪口指向玩家 - Master.LookTargetPosition(playerPos); - - 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(playerPos); - if (_isInTailAfterRange) - { - _tailAfterTimer = 0; - } - else - { - _tailAfterTimer += delta; - } - - //向武器移动 - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = - (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else - { - Master.BasisVelocity = Vector2.Zero; - } - } - } - - 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(Player.Current.GetCenterPosition()), Colors.Orange); - } - else if (_tailAfterTimer <= 10) - { - Master.DrawLine(Vector2.Zero, Master.ToLocal(Player.Current.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 deleted file mode 100644 index 02d1de1..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs +++ /dev/null @@ -1,130 +0,0 @@ - -using Godot; - -/// -/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 -/// -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) - { - _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 = Player.Current.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - var masterPosition = Master.GlobalPosition; - - //是否在攻击范围内 - var inAttackRange = false; - - var weapon = Master.WeaponPack.ActiveItem; - if (weapon != null) - { - inAttackRange = masterPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2); - } - - //枪口指向玩家 - Master.LookTargetPosition(playerPos); - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || - (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - masterPosition - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - Master.BasisVelocity = Vector2.Zero; - } - } - else - { - Master.BasisVelocity = Vector2.Zero; - } - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中, 或者锁敌状态下, 或者攻击状态下, 继续保持原本逻辑 - if (Master.TargetInView || Master.AttackState == AiAttackState.LockingTime || Master.AttackState == AiAttackState.Attack) - { - if (inAttackRange) //在攻击范围内 - { - //发起攻击 - Master.EnemyAttack(); - - //距离够近, 可以切换到环绕模式 - if (Master.GlobalPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange), 2) * 0.7f) - { - ChangeState(AiStateEnum.AiSurround); - } - } - } - else //不在视野中 - { - ChangeState(AiStateEnum.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/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs deleted file mode 100644 index bb1c761..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs +++ /dev/null @@ -1,101 +0,0 @@ - -using Godot; - -/// -/// 收到其他敌人通知, 前往发现目标的位置 -/// -public class AiLeaveForState : StateBase -{ - //导航目标点刷新计时器 - private float _navigationUpdateTimer = 0; - private float _navigationInterval = 0.3f; - - public AiLeaveForState() : base(AiStateEnum.AiLeaveFor) - { - } - - public override void Enter(AiStateEnum prev, params object[] args) - { - if (Master.World.Enemy_IsFindTarget) - { - Master.NavigationAgent2D.TargetPosition = Master.World.Enemy_FindTargetPosition; - } - else - { - ChangeState(prev); - return; - } - - //先检查弹药是否打光 - if (Master.IsAllWeaponTotalAmmoEmpty()) - { - //再寻找是否有可用的武器 - var targetWeapon = Master.FindTargetWeapon(); - if (targetWeapon != null) - { - ChangeState(AiStateEnum.AiFindAmmo, targetWeapon); - } - } - } - - 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()) - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.LookTargetPosition(Master.World.Enemy_FindTargetPosition); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else - { - Master.BasisVelocity = Vector2.Zero; - } - - var playerPos = Player.Current.GetCenterPosition(); - //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态 - if (Master.IsInTailAfterViewRange(playerPos)) - { - if (!Master.TestViewRayCast(playerPos)) //看到玩家 - { - //关闭射线检测 - Master.TestViewRayCastOver(); - //切换成发现目标状态 - 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 deleted file mode 100644 index a18851f..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs +++ /dev/null @@ -1,181 +0,0 @@ - -using Godot; - -/// -/// 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(AiStateEnum.AiNormal) - { - } - - public override void Enter(AiStateEnum prev, params object[] args) - { - _isFindPlayer = false; - _isMoveOver = true; - _againstWall = false; - _againstWallNormalAngle = 0; - _pauseTimer = 0; - _moveFlag = false; - } - - public override void Process(float delta) - { - //其他敌人发现玩家 - if (Master.CanChangeLeaveFor()) - { - ChangeState(AiStateEnum.AiLeaveFor); - return; - } - - if (_isFindPlayer) //已经找到玩家了 - { - //现临时处理, 直接切换状态 - ChangeState(AiStateEnum.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.BasisVelocity = Vector2.Zero; - } - else if (!_moveFlag) - { - _moveFlag = true; - var pos = Master.GlobalPosition; - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - _prevPos = pos; - } - else - { - var pos = Master.GlobalPosition; - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f); - _isMoveOver = true; - _moveFlag = false; - Master.BasisVelocity = Vector2.Zero; - } - else - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - - 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/AiProbeState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiProbeState.cs deleted file mode 100644 index 1015095..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiProbeState.cs +++ /dev/null @@ -1,20 +0,0 @@ - -/// -/// Ai 不确定玩家位置 -/// -public class AiProbeState : StateBase -{ - public AiProbeState() : base(AiStateEnum.AiProbe) - { - } - - public override void Process(float delta) - { - //其他敌人发现玩家 - if (Master.CanChangeLeaveFor()) - { - ChangeState(AiStateEnum.AiLeaveFor); - return; - } - } -} \ 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 deleted file mode 100644 index 38a6284..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs +++ /dev/null @@ -1,185 +0,0 @@ - -using Godot; - -/// -/// 距离目标足够近, 在目标附近随机移动, 并开火 -/// -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) - { - 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 = Player.Current.GetCenterPosition(); - var weapon = Master.WeaponPack.ActiveItem; - - //枪口指向玩家 - Master.LookTargetPosition(playerPos); - - //检测玩家是否在视野内 - if (Master.IsInTailAfterViewRange(playerPos)) - { - Master.TargetInView = !Master.TestViewRayCast(playerPos); - //关闭射线检测 - Master.TestViewRayCastOver(); - } - else - { - Master.TargetInView = false; - } - - //在视野中, 或者锁敌状态下, 或者攻击状态下, 继续保持原本逻辑 - if (Master.TargetInView || - (weapon != null && weapon.Attribute.AiAttackAttr.FiringStand && - (Master.AttackState == AiAttackState.LockingTime || Master.AttackState == AiAttackState.Attack) - )) - { - if (_pauseTimer >= 0) - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - _pauseTimer -= delta; - } - else if (_isMoveOver) //移动已经完成 - { - RunOver(playerPos); - _isMoveOver = false; - } - else - { - 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.BasisVelocity = Vector2.Zero; - } - else if (!_moveFlag) - { - _moveFlag = true; - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = - (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else - { - var pos = Master.GlobalPosition; - var lastSlideCollision = Master.GetLastSlideCollision(); - if (lastSlideCollision != null && lastSlideCollision.GetCollider() is AdvancedRole) //碰到其他角色 - { - _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f); - _isMoveOver = true; - _moveFlag = false; - Master.BasisVelocity = Vector2.Zero; - } - else - { - //判断开火状态, 进行移动 - if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || - (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) - { //正常移动 - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - pos - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else //站立不动 - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - Master.BasisVelocity = Vector2.Zero; - } - } - - if (_prevPos.DistanceSquaredTo(pos) <= 0.01f) - { - _lockTimer += delta; - } - else - { - _prevPos = pos; - } - } - - if (weapon != null) - { - var position = Master.GlobalPosition; - if (position.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围 - { - ChangeState(AiStateEnum.AiFollowUp); - } - else - { - //发起攻击 - Master.EnemyAttack(); - } - } - } - } - 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 deleted file mode 100644 index 87b21aa..0000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs +++ /dev/null @@ -1,135 +0,0 @@ - -using Godot; - -/// -/// 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) - { - _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 = Player.Current.GetCenterPosition(); - - //更新玩家位置 - if (_navigationUpdateTimer <= 0) - { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - Master.NavigationAgent2D.TargetPosition = playerPos; - } - else - { - _navigationUpdateTimer -= delta; - } - - //枪口指向玩家 - Master.LookTargetPosition(playerPos); - - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - var weapon = Master.WeaponPack.ActiveItem; - if (weapon == null || !weapon.Attribute.AiAttackAttr.FiringStand || - (Master.AttackState != AiAttackState.LockingTime && Master.AttackState != AiAttackState.Attack)) - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextPathPosition(); - Master.AnimatedSprite.Play(AnimatorNames.Run); - Master.BasisVelocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * - Master.RoleState.MoveSpeed; - } - else - { - Master.AnimatedSprite.Play(AnimatorNames.Idle); - Master.BasisVelocity = Vector2.Zero; - } - } - else - { - Master.BasisVelocity = Vector2.Zero; - } - //检测玩家是否在视野内, 如果在, 则切换到 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 = 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/player/Player.cs b/DungeonShooting_Godot/src/game/activity/role/player/Player.cs index 3b7fc1d..21e4ab7 100644 --- a/DungeonShooting_Godot/src/game/activity/role/player/Player.cs +++ b/DungeonShooting_Godot/src/game/activity/role/player/Player.cs @@ -83,6 +83,12 @@ { return; } + + if (_rollCoolingTimer > 0) + { + _rollCoolingTimer -= delta; + } + //脸的朝向 if (LookTarget == null) { @@ -187,7 +193,7 @@ var enemyList = AffiliationArea.FindIncludeItems(o => o.CollisionWithMask(PhysicsLayer.Enemy)); foreach (var enemy in enemyList) { - ((Enemy)enemy).Hurt(1000, 0); + ((AdvancedEnemy)enemy).Hurt(1000, 0); } } // //测试用 diff --git a/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs index 2e2400b..43295bc 100644 --- a/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs +++ b/DungeonShooting_Godot/src/game/activity/weapon/Weapon.cs @@ -1948,7 +1948,7 @@ } else { - var enemy = (Enemy)Master; + var enemy = (AdvancedEnemy)Master; if (enemy.GetLockTime() >= Attribute.AiAttackAttr.LockingTime) //正常射击 { if (GetDelayedAttackTime() > 0) @@ -2027,7 +2027,7 @@ } else { - var enemy = (Enemy)Master; + var enemy = (AdvancedEnemy)Master; if (enemy.GetLockTime() >= Attribute.AiAttackAttr.LockingTime) //正常射击 { if (GetDelayedAttackTime() > 0) @@ -2073,10 +2073,14 @@ /// /// 获取Ai锁定目标的剩余时间 /// - /// public float GetAiLockRemainderTime() { - return Attribute.AiAttackAttr.LockingTime - ((Enemy)Master).GetLockTime(); + if (Master is AdvancedEnemy enemy) + { + return Attribute.AiAttackAttr.LockingTime - enemy.GetLockTime(); + } + + return 0; } // /// diff --git a/DungeonShooting_Godot/src/game/event/EventEnum.cs b/DungeonShooting_Godot/src/game/event/EventEnum.cs index 632b0cb..6a64560 100644 --- a/DungeonShooting_Godot/src/game/event/EventEnum.cs +++ b/DungeonShooting_Godot/src/game/event/EventEnum.cs @@ -7,7 +7,7 @@ public enum EventEnum { /// - /// 敌人死亡, 参数为死亡的敌人的实例, 参数类型为 + /// 敌人死亡, 参数为死亡的敌人的实例, 参数类型为 /// OnEnemyDie, /// diff --git a/DungeonShooting_Godot/src/game/room/DungeonManager.cs b/DungeonShooting_Godot/src/game/room/DungeonManager.cs index fae30b5..09a0576 100644 --- a/DungeonShooting_Godot/src/game/room/DungeonManager.cs +++ b/DungeonShooting_Godot/src/game/room/DungeonManager.cs @@ -567,9 +567,23 @@ //不与玩家处于同一个房间 if (enemy.AffiliationArea != playerAffiliationArea) { - if (enemy.StateController.CurrState != AiStateEnum.AiNormal) + if (enemy is Enemy e) { - enemy.StateController.ChangeState(AiStateEnum.AiNormal); + if (e.StateController.CurrState != AiStateEnum.AiNormal) + { + e.StateController.ChangeState(AiStateEnum.AiNormal); + } + } + else if (enemy is AdvancedEnemy ae) + { + if (ae.StateController.CurrState != AiStateEnum.AiNormal) + { + ae.StateController.ChangeState(AiStateEnum.AiNormal); + } + } + else + { + throw new Exception("World.Enemy_InstanceList 混入了非 Enemy 和 AdvancedEnemy 类型的对象!"); } } } @@ -630,7 +644,19 @@ for (var i = 0; i < World.Enemy_InstanceList.Count; i++) { var enemy = World.Enemy_InstanceList[i]; - var state = enemy.StateController.CurrState; + AiStateEnum state; + if (enemy is Enemy e) + { + state = e.StateController.CurrState; + } + else if (enemy is AdvancedEnemy ae) + { + state = ae.StateController.CurrState; + } + else + { + throw new Exception("World.Enemy_InstanceList 混入了非 Enemy 和 AdvancedEnemy 类型的对象!"); + } if (state == AiStateEnum.AiFollowUp || state == AiStateEnum.AiSurround) //目标在视野内 { if (!World.Enemy_IsFindTarget) diff --git a/DungeonShooting_Godot/src/game/room/World.cs b/DungeonShooting_Godot/src/game/room/World.cs index 35af42b..30a6676 100644 --- a/DungeonShooting_Godot/src/game/room/World.cs +++ b/DungeonShooting_Godot/src/game/room/World.cs @@ -62,7 +62,7 @@ /// /// 记录所有存活的敌人 /// - public List Enemy_InstanceList { get; } = new List(); + public List Enemy_InstanceList { get; } = new List(); /// /// 公共属性, 敌人是否找到目标, 如果找到目标, 则与目标同房间的所有敌人都会知道目标的位置