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(); //更新玩家位置