429 lines
11 KiB
C#
429 lines
11 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using BehaviorDesigner.Runtime.Tasks;
|
||
using DragonLi.Behaviour;
|
||
using UnityEngine;
|
||
using Action = BehaviorDesigner.Runtime.Tasks.Action;
|
||
|
||
using System.Collections;
|
||
using BehaviorDesigner.Runtime.Tasks;
|
||
using DG.Tweening;
|
||
using Pathfinding;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 吐舌攻击
|
||
/// </summary>
|
||
public class FrogBleepAttack : Action
|
||
{
|
||
private FrogBoss boss;
|
||
private bool isFinished;
|
||
|
||
public float tongueSpeed = 25f;
|
||
public float damage = 30f;
|
||
|
||
private float rotateSpeed = 360f; // 每秒旋转角度
|
||
|
||
public override void OnStart()
|
||
{
|
||
boss = GetComponent<FrogBoss>();
|
||
isFinished = false;
|
||
|
||
boss.ResetSkill1();
|
||
boss.StartCoroutine(SkillRoutine());
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
return isFinished ? TaskStatus.Success : TaskStatus.Running;
|
||
}
|
||
|
||
IEnumerator SkillRoutine()
|
||
{
|
||
// 1️⃣ 不在攻击范围 → 先移动
|
||
if (!boss.IsPlayerInTongueRange())
|
||
{
|
||
yield return boss.StartCoroutine(
|
||
boss.MoveToTongueRange()
|
||
);
|
||
}
|
||
|
||
// 2️⃣ 锁定玩家位置(关键)
|
||
Vector3 lockPos = GameManager.Ins.player.transform.position;
|
||
|
||
// 面向玩家
|
||
yield return RotateToPlayer();
|
||
|
||
// 3️⃣ 吐舌头
|
||
boss.TongueAttack();
|
||
yield return new WaitForSeconds(0.4f);
|
||
|
||
float dis = Vector3.Distance(boss.mouthPoint.position, lockPos);
|
||
float time = dis / tongueSpeed;
|
||
|
||
yield return new WaitForSeconds(time);
|
||
|
||
boss.Idle();
|
||
isFinished = true;
|
||
}
|
||
|
||
IEnumerator RotateToPlayer()
|
||
{
|
||
Transform player = GameManager.Ins.player.transform;
|
||
|
||
Vector3 dir = player.position - transform.position;
|
||
dir.y = 0;
|
||
|
||
if (dir.sqrMagnitude < 0.01f)
|
||
yield break;
|
||
|
||
Quaternion targetRot = Quaternion.LookRotation(dir);
|
||
|
||
while (Quaternion.Angle(transform.rotation, targetRot) > 1f)
|
||
{
|
||
transform.rotation = Quaternion.RotateTowards(
|
||
transform.rotation,
|
||
targetRot,
|
||
rotateSpeed * Time.deltaTime
|
||
);
|
||
yield return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 毒液弹攻击
|
||
/// </summary>
|
||
public class FrogPoisonAttack : Action
|
||
{
|
||
private FrogBoss boss;
|
||
private bool isFinished;
|
||
private float rotateSpeed = 360f; // 每秒旋转角度
|
||
|
||
public override void OnStart()
|
||
{
|
||
boss = GetComponent<FrogBoss>();
|
||
isFinished = false;
|
||
|
||
boss.ResetSkill2();
|
||
boss.StartCoroutine(Attack());
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
return isFinished ? TaskStatus.Success : TaskStatus.Running;
|
||
}
|
||
|
||
IEnumerator Attack()
|
||
{
|
||
yield return RotateToPlayer();
|
||
boss.Spit();
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
Vector2 rand = Random.insideUnitCircle * 3f;
|
||
Vector3 target = GameManager.Ins.player.transform.position
|
||
+ new Vector3(rand.x, 0, rand.y);
|
||
|
||
GameObject ball = Object.Instantiate(
|
||
boss.poisonBallPrefab,
|
||
boss.mouthPoint.position,
|
||
Quaternion.identity
|
||
);
|
||
|
||
ball.GetComponent<PoisonBall>().Launch(
|
||
boss.mouthPoint.position,
|
||
target,
|
||
boss.Data.Atk_2dot,
|
||
boss.Data.Atk_2Time
|
||
);
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
}
|
||
|
||
boss.Idle();
|
||
isFinished = true;
|
||
}
|
||
|
||
IEnumerator RotateToPlayer()
|
||
{
|
||
Transform player = GameManager.Ins.player.transform;
|
||
|
||
Vector3 dir = player.position - transform.position;
|
||
dir.y = 0;
|
||
|
||
if (dir.sqrMagnitude < 0.01f)
|
||
yield break;
|
||
|
||
Quaternion targetRot = Quaternion.LookRotation(dir);
|
||
|
||
while (Quaternion.Angle(transform.rotation, targetRot) > 1f)
|
||
{
|
||
transform.rotation = Quaternion.RotateTowards(
|
||
transform.rotation,
|
||
targetRot,
|
||
rotateSpeed * Time.deltaTime
|
||
);
|
||
yield return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 吐卵 召唤小虫怪
|
||
/// </summary>
|
||
public class FrogEggAttack : Action
|
||
{
|
||
private FrogBoss boss;
|
||
private bool isFinished;
|
||
|
||
[Header("Egg Config")]
|
||
public int eggCount = 12;
|
||
public float shootInterval = 0.15f;
|
||
public float fanAngle = 60f;
|
||
public float launchHeight = 3f;
|
||
public float flightTime = 1.1f;
|
||
|
||
public override void OnStart()
|
||
{
|
||
boss = GetComponent<FrogBoss>();
|
||
isFinished = false;
|
||
boss.StartCoroutine(Attack());
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
return isFinished ? TaskStatus.Success : TaskStatus.Running;
|
||
}
|
||
|
||
IEnumerator Attack()
|
||
{
|
||
boss.EggAttack();
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
boss.aiPath.isStopped = true;
|
||
Transform firePoint = boss.eggFirePoint; // 嘴巴
|
||
|
||
float step = fanAngle / (eggCount - 1);
|
||
float startAngle = -fanAngle * 0.5f;
|
||
|
||
int eggBugCount = boss.GetAliveTentacles().Count;
|
||
int insCount;
|
||
if (eggBugCount >= 12)
|
||
{
|
||
boss.Idle();
|
||
isFinished = true;
|
||
yield break;
|
||
}
|
||
insCount=12-eggBugCount;
|
||
for (int i = 0; i < insCount; i++)
|
||
{
|
||
float angle = startAngle + step * i;
|
||
|
||
// 🔥 轻微左右转头
|
||
boss.transform
|
||
.DORotate(
|
||
boss.transform.eulerAngles + new Vector3(0, angle * 0.15f, 0),
|
||
0.1f
|
||
);
|
||
|
||
SpawnEgg(firePoint, angle);
|
||
|
||
yield return new WaitForSeconds(shootInterval);
|
||
}
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
|
||
// 吐完跑向左右
|
||
Vector3 side = Random.value > 0.5f ? -boss.transform.right : boss.transform.right;
|
||
boss.transform.DOMove(boss.transform.position + side * 5f, 1.5f);
|
||
|
||
boss.Idle();
|
||
isFinished = true;
|
||
}
|
||
|
||
void SpawnEgg(Transform firePoint, float angle)
|
||
{
|
||
GameObject egg = Object.Instantiate(
|
||
boss.eggPrefab,
|
||
firePoint.position,
|
||
Quaternion.identity
|
||
);
|
||
|
||
FrogBugEnemy bug = egg.GetComponent<FrogBugEnemy>();
|
||
bug.SetAtk(boss.Data.Atk_2,boss.Data.Hp_CS2);
|
||
|
||
// 扇形方向
|
||
Vector3 dir = Quaternion.Euler(0, angle, 0) * boss.transform.forward;
|
||
|
||
// 落点
|
||
Vector3 targetPos =
|
||
firePoint.position +
|
||
dir * Random.Range(3.5f, 5.5f);
|
||
|
||
bug.LaunchEgg(
|
||
firePoint.position,
|
||
targetPos,
|
||
launchHeight,
|
||
flightTime,
|
||
boss.Data.Atk_3,
|
||
boss.Data.Hp_CS2
|
||
);
|
||
boss.frogBugs.Add(bug);
|
||
}
|
||
}
|
||
|
||
public class FrogIdleMove : Action
|
||
{
|
||
[Header("Move Area (Fan Shape)")]
|
||
private float moveRadius = 40f; // 可移动半径
|
||
private float moveAngle = 90f; // 扇形角度(玩家正前方)
|
||
|
||
[Header("Stay")]
|
||
public float stayTime = 1.5f; // 到点后停留时间
|
||
|
||
[Header("Rotate")]
|
||
public float rotateSpeed = 360f;
|
||
|
||
[Header("Distance To Player")]
|
||
public float minPlayerDistance = 6f; // 离玩家最近距离
|
||
public float maxPlayerDistance = 15f; // 离玩家最远距离
|
||
|
||
[Header("Patrol")]
|
||
public float minMoveStep = 5f; // 每次移动的最小距离(关键)
|
||
|
||
|
||
private FrogBoss boss;
|
||
private Transform player;
|
||
|
||
private Vector3 curTarget;
|
||
private float stayTimer;
|
||
private bool isStaying;
|
||
|
||
public override void OnStart()
|
||
{
|
||
boss = GetComponent<FrogBoss>();
|
||
player = GameManager.Ins.player.transform;
|
||
|
||
isStaying = false;
|
||
stayTimer = 0f;
|
||
|
||
PickNewTarget();
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (player == null)
|
||
return TaskStatus.Running;
|
||
|
||
// 🔥 有技能可放 → 退出待机行为
|
||
if (boss.HasAnySkillReady())
|
||
{
|
||
boss.aiPath.isStopped = true;
|
||
boss.Idle();
|
||
return TaskStatus.Failure;
|
||
}
|
||
|
||
FaceToPlayer();
|
||
HandleMove();
|
||
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
#region Core Logic
|
||
|
||
void FaceToPlayer()
|
||
{
|
||
Vector3 dir = player.position - transform.position;
|
||
dir.y = 0;
|
||
if (dir.sqrMagnitude < 0.01f)
|
||
return;
|
||
|
||
Quaternion targetRot = Quaternion.LookRotation(dir);
|
||
transform.rotation = Quaternion.RotateTowards(
|
||
transform.rotation,
|
||
targetRot,
|
||
rotateSpeed * Time.deltaTime
|
||
);
|
||
}
|
||
|
||
void HandleMove()
|
||
{
|
||
float distToTarget = Vector3.Distance(transform.position, curTarget);
|
||
|
||
// ===== 已到达目标点 =====
|
||
if (distToTarget < 0.4f)
|
||
{
|
||
boss.aiPath.isStopped = true;
|
||
boss.Idle();
|
||
|
||
isStaying = true;
|
||
stayTimer += Time.deltaTime;
|
||
|
||
if (stayTimer >= stayTime)
|
||
{
|
||
PickNewTarget();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// ===== 正在移动 =====
|
||
isStaying = false;
|
||
stayTimer = 0f;
|
||
|
||
boss.Run();
|
||
boss.aiPath.isStopped = false;
|
||
boss.aiPath.destination = curTarget;
|
||
}
|
||
|
||
void PickNewTarget()
|
||
{
|
||
stayTimer = 0f;
|
||
isStaying = false;
|
||
|
||
Vector3 bossPos = transform.position;
|
||
Vector3 playerPos = player.position;
|
||
|
||
const int MAX_TRY = 20;
|
||
|
||
for (int i = 0; i < MAX_TRY; i++)
|
||
{
|
||
// 🎯 以玩家为中心选点(而不是 boss)
|
||
float angle = Random.Range(0f, 360f);
|
||
|
||
float radius = Random.Range(minPlayerDistance, maxPlayerDistance);
|
||
|
||
Vector3 offset = Quaternion.Euler(0, angle, 0) * Vector3.forward * radius;
|
||
Vector3 candidate = playerPos + offset;
|
||
candidate.y = bossPos.y;
|
||
|
||
// 👉 保证和当前点“足够远”
|
||
if (Vector3.Distance(candidate, bossPos) < minMoveStep)
|
||
continue;
|
||
|
||
// 👉 A* 校正
|
||
NNInfo nn = AstarPath.active.GetNearest(candidate);
|
||
if (nn.node != null && nn.node.Walkable)
|
||
{
|
||
curTarget = nn.position;
|
||
boss.aiPath.isStopped = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 🛑 兜底:往侧前方大步走
|
||
Vector3 fallbackDir = Vector3.Cross(Vector3.up, (playerPos - bossPos)).normalized;
|
||
curTarget = bossPos + fallbackDir * minMoveStep;
|
||
boss.aiPath.isStopped = false;
|
||
}
|
||
|
||
|
||
#endregion
|
||
}
|
||
|
||
|
||
|
||
|