diff --git a/DungeonShooting_Godot/prefab/role/enemy/Enemy0001.tscn b/DungeonShooting_Godot/prefab/role/enemy/Enemy0001.tscn
index 3e85288..b616e02 100644
--- a/DungeonShooting_Godot/prefab/role/enemy/Enemy0001.tscn
+++ b/DungeonShooting_Godot/prefab/role/enemy/Enemy0001.tscn
@@ -44,12 +44,14 @@
"query": ExtResource("7_e37p2")
}
-[node name="Enemy0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_2vqwe")]
+[node name="Enemy0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "ViewArea", "ViewAreaCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_2vqwe")]
script = ExtResource("2_0pcq3")
ViewRay = NodePath("ViewRay")
NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D")
NavigationPoint = NodePath("NavigationPoint")
FirePoint = NodePath("FirePoint")
+ViewArea = NodePath("MountPoint/ViewArea")
+ViewAreaCollision = NodePath("MountPoint/ViewArea/ViewAreaCollision")
HurtArea = NodePath("HurtArea")
HurtCollision = NodePath("HurtArea/HurtCollision")
InteractiveArea = NodePath("InteractiveArea")
diff --git a/DungeonShooting_Godot/prefab/role/enemy/Enemy0002.tscn b/DungeonShooting_Godot/prefab/role/enemy/Enemy0002.tscn
index e983127..cfc530c 100644
--- a/DungeonShooting_Godot/prefab/role/enemy/Enemy0002.tscn
+++ b/DungeonShooting_Godot/prefab/role/enemy/Enemy0002.tscn
@@ -84,12 +84,14 @@
"query": ExtResource("7_h4cls")
}
-[node name="Enemy0002" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_fanet")]
+[node name="Enemy0002" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "ViewArea", "ViewAreaCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_fanet")]
script = ExtResource("2_3an4s")
ViewRay = NodePath("ViewRay")
NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D")
NavigationPoint = NodePath("NavigationPoint")
FirePoint = NodePath("FirePoint")
+ViewArea = NodePath("MountPoint/ViewArea")
+ViewAreaCollision = NodePath("MountPoint/ViewArea/ViewAreaCollision")
HurtArea = NodePath("HurtArea")
HurtCollision = NodePath("HurtArea/HurtCollision")
InteractiveArea = NodePath("InteractiveArea")
diff --git a/DungeonShooting_Godot/prefab/role/shopBoss/ShopBoss0001.tscn b/DungeonShooting_Godot/prefab/role/shopBoss/ShopBoss0001.tscn
index d0f6ea5..3638fac 100644
--- a/DungeonShooting_Godot/prefab/role/shopBoss/ShopBoss0001.tscn
+++ b/DungeonShooting_Godot/prefab/role/shopBoss/ShopBoss0001.tscn
@@ -29,13 +29,15 @@
shader_parameter/outline_use_blend = true
shader_parameter/grey = 0.0
-[node name="ShopBoss0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_25fpq")]
+[node name="ShopBoss0001" node_paths=PackedStringArray("ViewRay", "NavigationAgent2D", "NavigationPoint", "FirePoint", "ViewArea", "ViewAreaCollision", "HurtArea", "HurtCollision", "InteractiveArea", "InteractiveCollision", "TipRoot", "TipSprite", "AnimationPlayer", "MountPoint", "BackMountPoint", "MeleeAttackArea", "MeleeAttackCollision", "ShadowSprite", "AnimatedSprite", "Collision") instance=ExtResource("1_25fpq")]
collision_layer = 1024
script = ExtResource("2_2ng7e")
ViewRay = NodePath("ViewRay")
NavigationAgent2D = NodePath("NavigationPoint/NavigationAgent2D")
NavigationPoint = NodePath("NavigationPoint")
FirePoint = NodePath("FirePoint")
+ViewArea = NodePath("MountPoint/ViewArea")
+ViewAreaCollision = NodePath("MountPoint/ViewArea/ViewAreaCollision")
HurtArea = NodePath("HurtArea")
HurtCollision = NodePath("HurtArea/HurtCollision")
InteractiveArea = NodePath("InteractiveArea")
diff --git a/DungeonShooting_Godot/prefab/role/template/AiTemplate.tscn b/DungeonShooting_Godot/prefab/role/template/AiTemplate.tscn
index 23667b3..3866ed8 100644
--- a/DungeonShooting_Godot/prefab/role/template/AiTemplate.tscn
+++ b/DungeonShooting_Godot/prefab/role/template/AiTemplate.tscn
@@ -33,6 +33,13 @@
position = Vector2(0, -8)
enabled = false
+[node name="ViewArea" type="Area2D" parent="MountPoint" index="1"]
+collision_layer = 0
+collision_mask = 16
+monitorable = false
+
+[node name="ViewAreaCollision" type="CollisionPolygon2D" parent="MountPoint/ViewArea" index="0"]
+
[node name="FirePoint" type="Marker2D" parent="." index="8"]
[node name="NavigationPoint" type="Marker2D" parent="." index="9"]
diff --git a/DungeonShooting_Godot/resource/map/tileMaps/Test1/inlet/Start2/Preinstall.json b/DungeonShooting_Godot/resource/map/tileMaps/Test1/inlet/Start2/Preinstall.json
index e0cc338..155c178 100644
--- a/DungeonShooting_Godot/resource/map/tileMaps/Test1/inlet/Start2/Preinstall.json
+++ b/DungeonShooting_Godot/resource/map/tileMaps/Test1/inlet/Start2/Preinstall.json
@@ -1 +1 @@
-[{"Name":"Preinstall1","Weight":100,"Remark":"","AutoFill":true,"WaveList":[[{"Position":{"X":-81,"Y":25},"Size":{"X":0,"Y":0},"SpecialMarkType":1,"DelayTime":0,"MarkList":[]},{"Position":{"X":53,"Y":31},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"enemy0001","Weight":100,"Attr":{"Face":"0","Weapon":"weapon0003","CurrAmmon":"12","ResidueAmmo":"12"},"Altitude":0,"VerticalSpeed":5.551115E-14}]},{"Position":{"X":84,"Y":8},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"enemy0001","Weight":100,"Attr":{"Face":"0","Weapon":"weapon0002","CurrAmmon":"7","ResidueAmmo":"7"},"Altitude":0,"VerticalSpeed":5.551115E-14}]},{"Position":{"X":-54,"Y":20},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"weapon0001","Weight":100,"Attr":{"CurrAmmon":"30","ResidueAmmo":"210"},"Altitude":8,"VerticalSpeed":5.551115E-14}]}]]}]
\ No newline at end of file
+[{"Name":"Preinstall1","Weight":100,"Remark":"","AutoFill":true,"WaveList":[[{"Position":{"X":-81,"Y":25},"Size":{"X":0,"Y":0},"SpecialMarkType":1,"DelayTime":0,"MarkList":[]},{"Position":{"X":21,"Y":31},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"enemy0001","Weight":100,"Attr":{"Face":"1","Weapon":"weapon0003","CurrAmmon":"12","ResidueAmmo":"12"},"Altitude":0,"VerticalSpeed":5.551115E-14}]},{"Position":{"X":110,"Y":37},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"enemy0001","Weight":100,"Attr":{"Face":"-1","Weapon":"weapon0002","CurrAmmon":"7","ResidueAmmo":"7"},"Altitude":0,"VerticalSpeed":5.551115E-14}]},{"Position":{"X":-54,"Y":20},"Size":{"X":16,"Y":16},"SpecialMarkType":0,"DelayTime":0,"MarkList":[{"Id":"weapon0001","Weight":100,"Attr":{"CurrAmmon":"30","ResidueAmmo":"210"},"Altitude":8,"VerticalSpeed":5.551115E-14}]}]]}]
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/framework/map/DungeonGenerator.cs b/DungeonShooting_Godot/src/framework/map/DungeonGenerator.cs
index 3f82e5d..b439592 100644
--- a/DungeonShooting_Godot/src/framework/map/DungeonGenerator.cs
+++ b/DungeonShooting_Godot/src/framework/map/DungeonGenerator.cs
@@ -207,14 +207,14 @@
if (errorCode == GenerateRoomErrorCode.OutArea)
{
_failCount++;
- Debug.Log("超出区域失败次数: " + _failCount);
+ //Debug.Log("超出区域失败次数: " + _failCount);
if (_failCount >= _maxFailCount)
{
//_enableLimitRange = false;
_failCount = 0;
_rangeX += 50;
_rangeY += 50;
- Debug.Log("生成房间失败次数过多, 增大区域");
+ //Debug.Log("生成房间失败次数过多, 增大区域");
}
}
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs
index de6b899..2935087 100644
--- a/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs
@@ -1,4 +1,5 @@
+using System.Collections.Generic;
using AiState;
using Godot;
@@ -42,6 +43,18 @@
public Marker2D FirePoint { get; set; }
///
+ /// 视野区域
+ ///
+ [Export, ExportFillNode]
+ public Area2D ViewArea { get; set; }
+
+ ///
+ /// 视野区域碰撞器形状
+ ///
+ [Export, ExportFillNode]
+ public CollisionPolygon2D ViewAreaCollision { get; set; }
+
+ ///
/// 当前敌人所看向的对象, 也就是枪口指向的对象
///
public ActivityObject LookTarget { get; set; }
@@ -55,11 +68,27 @@
/// 锁定目标已经走过的时间
///
public float LockTargetTime { get; set; } = 0;
-
+
///
/// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
///
- public float ViewRange { get; set; } = 250;
+ public float ViewRange
+ {
+ get => _viewRange;
+ set
+ {
+ if (_viewRange != value)
+ {
+ _viewRange = value;
+ if (ViewAreaCollision != null)
+ {
+ ViewAreaCollision.Polygon = Utils.CreateSectorPolygon(0, _viewRange, 120, 4);
+ }
+ }
+ }
+ }
+
+ private float _viewRange = -1;
///
/// 发现玩家后跟随玩家的视野半径
@@ -89,12 +118,16 @@
///
/// 临时存储攻击目标, 获取该值请调用 GetAttackTarget() 函数
///
- protected Role AttackTarget { get; set; } = null;
+ private Role AttackTarget { get; set; } = null;
+
+ private HashSet _viewTargets = new HashSet();
public override void OnInit()
{
base.OnInit();
IsAi = true;
+ ViewArea.BodyEntered += OnViewAreaBodyEntered;
+ ViewArea.BodyExited += OnViewAreaBodyExited;
StateController = AddComponent>();
@@ -118,20 +151,31 @@
///
/// 获取攻击的目标对象, 当 HasAttackDesire 为 true 时才会调用
///
- public virtual Role GetAttackTarget()
+ /// 上一次发现的角色在本次检测中是否开启视野透视
+ public Role GetAttackTarget(bool perspective = true)
{
+ //目标丢失
if (AttackTarget == null || AttackTarget.IsDestroyed || !IsEnemy(AttackTarget))
{
- AttackTarget = null;
- foreach (var role in World.Role_InstanceList)
+ AttackTarget = RefreshAttackTargets(AttackTarget);
+ return AttackTarget;
+ }
+
+ if (!perspective)
+ {
+ //被墙阻挡
+ if (TestViewRayCast(AttackTarget.GetCenterPosition()))
{
- if (role.AffiliationArea == AffiliationArea && IsEnemy(role))
- {
- AttackTarget = role;
- break;
- }
+ AttackTarget = RefreshAttackTargets(AttackTarget);
+ TestViewRayCastOver();
+ return AttackTarget;
+ }
+ else
+ {
+ TestViewRayCastOver();
}
}
+
return AttackTarget;
}
@@ -434,6 +478,44 @@
{
HasMoveDesire = v;
}
+
+ private void OnViewAreaBodyEntered(Node2D node)
+ {
+ if (node is Role role)
+ {
+ _viewTargets.Add(role);
+ }
+ }
+
+ private void OnViewAreaBodyExited(Node2D node)
+ {
+ if (node is Role role)
+ {
+ _viewTargets.Remove(role);
+ }
+ }
+
+ private Role RefreshAttackTargets(Role prevRole)
+ {
+ if (_viewTargets.Count == 0)
+ {
+ return null;
+ }
+ foreach (var attackTarget in _viewTargets)
+ {
+ if (prevRole != attackTarget && !attackTarget.IsDestroyed && IsEnemy(attackTarget))
+ {
+ if (!TestViewRayCast(attackTarget.GetCenterPosition()))
+ {
+ TestViewRayCastOver();
+ return attackTarget;
+ }
+ }
+ }
+
+ TestViewRayCastOver();
+ return null;
+ }
// private void OnVelocityComputed(Vector2 velocity)
// {
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs
index c776dc0..adc289d 100644
--- a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs
@@ -81,17 +81,11 @@
var attackTarget = Master.GetAttackTarget();
if (attackTarget != null)
{
- var targetPos = attackTarget.GetCenterPosition();
- if (Master.IsInViewRange(targetPos) && !Master.TestViewRayCast(targetPos)) //发现玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //发现玩家
- Master.LookTarget = attackTarget;
- //进入惊讶状态, 然后再进入通知状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, TargetWeapon);
- return;
- }
+ //发现玩家
+ Master.LookTarget = attackTarget;
+ //进入惊讶状态, 然后再进入通知状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, TargetWeapon);
+ return;
}
}
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs
index 4f118db..5bec8fc 100644
--- a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs
@@ -108,27 +108,11 @@
var attackTarget = Master.GetAttackTarget();
if (attackTarget != null)
{
- var targetPos = attackTarget.GetCenterPosition();
- //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
- if (Master.IsInTailAfterViewRange(targetPos))
- {
- if (!Master.TestViewRayCast(targetPos)) //看到玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //切换成发现目标状态
- Master.LookTarget = attackTarget;
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFollowUp);
- return;
- }
- else
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- }
+ //切换成发现目标状态
+ Master.LookTarget = attackTarget;
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFollowUp);
+ return;
}
-
//移动到目标掉了, 还没发现目标
if (Master.NavigationAgent2D.IsNavigationFinished())
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs
index dee42c0..7844ea7 100644
--- a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs
@@ -49,34 +49,27 @@
if (Master.HasAttackDesire) //有攻击欲望
{
//获取攻击目标
- var attackTarget = Master.GetAttackTarget();
+ var attackTarget = Master.GetAttackTarget(false);
if (attackTarget != null)
{
- //玩家中心点坐标
- var targetPos = attackTarget.GetCenterPosition();
-
- if (Master.IsInViewRange(targetPos) && !Master.TestViewRayCast(targetPos)) //发现目标
+ //发现玩家
+ Master.LookTarget = attackTarget;
+ //判断是否进入通知状态
+ if (Master.World.Role_InstanceList.FindIndex(role =>
+ role is AiRole enemy &&
+ enemy != Master && !enemy.IsDie && enemy.AffiliationArea == Master.AffiliationArea &&
+ enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
{
- //关闭射线检测
- Master.TestViewRayCastOver();
- //发现玩家
- Master.LookTarget = attackTarget;
- //判断是否进入通知状态
- if (Master.World.Role_InstanceList.FindIndex(role =>
- role is AiRole enemy &&
- enemy != Master && !enemy.IsDie && enemy.AffiliationArea == Master.AffiliationArea &&
- enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
- {
- //进入惊讶状态, 然后再进入通知状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
- }
- else
- {
- //进入惊讶状态, 然后再进入跟随状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
- }
- return;
+ //进入惊讶状态, 然后再进入通知状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
}
+ else
+ {
+ //进入惊讶状态, 然后再进入跟随状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
+ }
+
+ return;
}
}
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs
index b59e849..00706f3 100644
--- a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs
@@ -53,7 +53,12 @@
public override void Process(float delta)
{
//这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽
-
+
+ if (Master.LookTarget == null)
+ {
+ ChangeState(AIStateEnum.AiNormal);
+ return;
+ }
var playerPos = Master.LookTarget.GetCenterPosition();
//更新玩家位置