272 lines
5.5 KiB
C#
272 lines
5.5 KiB
C#
using System.Collections;
|
|
using DG.Tweening;
|
|
using Pathfinding;
|
|
using UnityEngine;
|
|
|
|
public class FrogBugEnemy : Enemy
|
|
{
|
|
public GameObject model;
|
|
public GameObject egg;
|
|
[Header("Egg")]
|
|
public float eggShakeTime = 2f;
|
|
public float eggShakeStrength = 0.15f;
|
|
public GameObject eggBreakFx;
|
|
|
|
[Header("AI")]
|
|
public float attackRange = 1.2f;
|
|
public float attackCooldown = 1.5f;
|
|
|
|
[Header("Jump Back")]
|
|
public float jumpBackDistance = 1.5f;
|
|
public float jumpBackTime = 0.35f;
|
|
|
|
[Header("Animation")]
|
|
public Animator anim;
|
|
|
|
private Transform player;
|
|
private bool isAttacking;
|
|
private float attackTimer;
|
|
private float atk;
|
|
|
|
#region Unity
|
|
|
|
|
|
protected void Start()
|
|
{
|
|
player = GameManager.Ins.player.transform;
|
|
model.SetActive(false);
|
|
}
|
|
|
|
public override void Show()
|
|
{
|
|
base.Show();
|
|
//蛋到达落点后 抖动两下 之后播放蛋破碎动画 播放后model打开 蛋消失 怪物切换成EnemyState.NormalAttack
|
|
}
|
|
|
|
public void SetAtk(float curAtk,float maxHp)
|
|
{
|
|
atk = curAtk;
|
|
maxHealth = maxHp;
|
|
health = maxHp;
|
|
}
|
|
|
|
|
|
public override void Update()
|
|
{
|
|
base.Update();
|
|
if (enemyState != EnemyState.NormalAttack || player == null)
|
|
return;
|
|
|
|
attackTimer += Time.deltaTime;
|
|
|
|
HandleMoveAndAttack();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Core Logic
|
|
|
|
public void LaunchEgg(
|
|
Vector3 start,
|
|
Vector3 target,
|
|
float height,
|
|
float time,
|
|
float curAtk,
|
|
float hp
|
|
)
|
|
{
|
|
atk = curAtk;
|
|
maxHealth = hp;
|
|
transform.position = start;
|
|
model.SetActive(false);
|
|
egg.SetActive(true);
|
|
StartCoroutine(EggFlyRoutine(start, target, height, time));
|
|
}
|
|
|
|
IEnumerator EggFlyRoutine(
|
|
Vector3 start,
|
|
Vector3 target,
|
|
float height,
|
|
float time
|
|
)
|
|
{
|
|
float timer = 0f;
|
|
|
|
while (timer < time)
|
|
{
|
|
timer += Time.deltaTime;
|
|
float t = timer / time;
|
|
|
|
Vector3 pos = Vector3.Lerp(start, target, t);
|
|
pos.y += Mathf.Sin(t * Mathf.PI) * height;
|
|
|
|
transform.position = pos;
|
|
yield return null;
|
|
}
|
|
|
|
transform.position = target;
|
|
StartCoroutine(EggHatch());
|
|
}
|
|
|
|
IEnumerator EggHatch()
|
|
{
|
|
enemyState = EnemyState.Show;
|
|
|
|
// 抖动
|
|
egg.transform.DOShakePosition(
|
|
eggShakeTime,
|
|
eggShakeStrength,
|
|
vibrato: 10
|
|
);
|
|
|
|
yield return new WaitForSeconds(eggShakeTime);
|
|
if (egg.GetComponent<Animator>())
|
|
{
|
|
egg.GetComponent<Animator>().SetBool("isDie",true);
|
|
}
|
|
|
|
model.SetActive(true);
|
|
yield return new WaitForSeconds(2);
|
|
egg.SetActive(false);
|
|
|
|
enemyState = EnemyState.NormalAttack;
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandleMoveAndAttack()
|
|
{
|
|
float dist = Vector3.Distance(transform.position, player.position);
|
|
|
|
if (dist <= attackRange)
|
|
{
|
|
TryAttack();
|
|
}
|
|
else
|
|
{
|
|
ChasePlayer();
|
|
}
|
|
}
|
|
|
|
void ChasePlayer()
|
|
{
|
|
if (isAttacking)
|
|
return;
|
|
|
|
ResumeAI();
|
|
aiPath.destination = player.position;
|
|
anim.SetInteger("State", 1); // Run
|
|
}
|
|
|
|
void TryAttack()
|
|
{
|
|
if (isAttacking)
|
|
return;
|
|
|
|
if (attackTimer < attackCooldown)
|
|
return;
|
|
|
|
StartCoroutine(AttackRoutine());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Attack
|
|
|
|
IEnumerator AttackRoutine()
|
|
{
|
|
isAttacking = true;
|
|
attackTimer = 0f;
|
|
|
|
StopAI();
|
|
|
|
// 朝向玩家
|
|
Vector3 dir = player.position - transform.position;
|
|
dir.y = 0;
|
|
if (dir.sqrMagnitude > 0.01f)
|
|
transform.rotation = Quaternion.LookRotation(dir);
|
|
|
|
anim.SetInteger("State", 2); // Attack
|
|
|
|
yield return new WaitForSeconds(0.4f);
|
|
|
|
DoDamageCheck();
|
|
|
|
yield return new WaitForSeconds(0.3f);
|
|
|
|
yield return JumpBack();
|
|
|
|
anim.SetInteger("State", 1); // Run
|
|
ResumeAI();
|
|
|
|
isAttacking = false;
|
|
}
|
|
|
|
void DoDamageCheck()
|
|
{
|
|
float dist = Vector3.Distance(transform.position, player.position);
|
|
if (dist <= attackRange + 0.3f)
|
|
{
|
|
player.GetComponent<EnemyIDamagable>()
|
|
?.ApplyDamage(atk, null, transform);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Jump Back
|
|
|
|
IEnumerator JumpBack()
|
|
{
|
|
StopAI();
|
|
|
|
Vector3 backDir = -transform.forward;
|
|
Vector3 targetPos = transform.position + backDir * jumpBackDistance;
|
|
|
|
if (Physics.Raycast(transform.position, backDir, out RaycastHit hit, jumpBackDistance))
|
|
{
|
|
targetPos = hit.point + hit.normal * 0.2f;
|
|
}
|
|
anim.SetInteger("State", 1); // Run
|
|
transform.DOJump(
|
|
targetPos,
|
|
0.6f,
|
|
1,
|
|
jumpBackTime
|
|
);
|
|
|
|
yield return new WaitForSeconds(jumpBackTime);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AI Control (A* 新版标准)
|
|
|
|
void StopAI()
|
|
{
|
|
aiPath.isStopped = true;
|
|
aiPath.destination = transform.position;
|
|
}
|
|
|
|
void ResumeAI()
|
|
{
|
|
aiPath.isStopped = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Death
|
|
|
|
public override void Dead()
|
|
{
|
|
base.Dead();
|
|
|
|
StopAllCoroutines();
|
|
StopAI();
|
|
anim.SetBool("IsDie",true);
|
|
}
|
|
|
|
#endregion
|
|
}
|