347 lines
11 KiB
C#
347 lines
11 KiB
C#
using UnityEngine;
|
|
using Watermelon.LevelSystem;
|
|
|
|
namespace Watermelon.SquadShooter
|
|
{
|
|
public class BossBomberBehaviour : BaseEnemyBehavior
|
|
{
|
|
protected static readonly int PARTICLE_BOSS_EAT_HASH = "Boss Eat".GetHashCode();
|
|
|
|
private readonly int PARTICLE_STEP_HASH = "Boss Step".GetHashCode();
|
|
private readonly int PARTICLE_DEATH_FALL_HASH = "Boss Death Fall".GetHashCode();
|
|
private readonly int PARTICLE_ENTER_FALL_HASH = "Boss Enter Fall".GetHashCode();
|
|
private readonly int PARTICLE_KICK_HASH = "Boss Kick".GetHashCode();
|
|
|
|
private readonly int ANIMATOR_ATTACK_HASH = Animator.StringToHash("Attack");
|
|
private readonly int ANIMATOR_DIE_HASH = Animator.StringToHash("Death");
|
|
private readonly int ANIMATOR_ENTER_HASH = Animator.StringToHash("Enter");
|
|
private readonly int ANIMATOR_SHOOTING_HASH = Animator.StringToHash("Shooting");
|
|
private readonly int ANIMATOR_KICK_HASH = Animator.StringToHash("Kick");
|
|
|
|
[SerializeField] GameObject graphicsObject;
|
|
|
|
[Header("Fighting")]
|
|
[SerializeField] GameObject bulletPrefab;
|
|
[SerializeField] float bulletSpeed;
|
|
[SerializeField] int shotsPerAttempt;
|
|
[SerializeField] float attackDistanceMin;
|
|
[SerializeField] float attackDistanceMax;
|
|
[SerializeField] float kickDistance;
|
|
[SerializeField] DuoInt damage;
|
|
[SerializeField] float hitCooldown;
|
|
[SerializeField] LayerMask targetLayer;
|
|
[SerializeField] AnimationCurve yBombMovementCurve;
|
|
|
|
public float KickDistance => kickDistance;
|
|
public float AttackDistanceMin => attackDistanceMin;
|
|
public float AttackDistanceMax => attackDistanceMax;
|
|
public float HitCooldown => hitCooldown;
|
|
|
|
[Space]
|
|
[SerializeField] float bombDamageMin;
|
|
[SerializeField] float bombDamageMax;
|
|
[SerializeField] float bombExplosionDuration;
|
|
[SerializeField] float bombExplosionRadius;
|
|
[SerializeField] float bombShakeDurationMin;
|
|
[SerializeField] float bombShakeDurationMax;
|
|
[SerializeField] float bombShakeDistance;
|
|
|
|
[Header("Weapon")]
|
|
[SerializeField] Transform weaponHolderTransform;
|
|
[SerializeField] Transform weaponTransform;
|
|
[SerializeField] Transform shootPointTransform;
|
|
[SerializeField] ParticleSystem shootParticleSystem;
|
|
|
|
[Header("Boss")]
|
|
[SerializeField] Transform leftFootTransform;
|
|
[SerializeField] Transform rightFootTransform;
|
|
[SerializeField] Transform backTransform;
|
|
|
|
[Space]
|
|
[SerializeField] Collider bossCollider;
|
|
[SerializeField] WeaponRigBehavior weaponRigBehavior;
|
|
|
|
private float sqrSpeed;
|
|
|
|
private Vector3 bombPoint;
|
|
|
|
private int shotsAmount;
|
|
|
|
private VirtualCamera gameCameraCase;
|
|
|
|
public event SimpleCallback OnEntered;
|
|
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
|
|
bossCollider.enabled = false;
|
|
}
|
|
|
|
public override void Init()
|
|
{
|
|
base.Init();
|
|
|
|
sqrSpeed = stats.MoveSpeed * stats.MoveSpeed;
|
|
|
|
// Reset weapon position
|
|
weaponTransform.SetParent(weaponHolderTransform);
|
|
weaponTransform.ResetLocal();
|
|
weaponTransform.gameObject.SetActive(true);
|
|
|
|
isDead = true;
|
|
|
|
gameCameraCase = CameraController.GetCamera(CameraType.Gameplay);
|
|
|
|
// Disable graphics
|
|
graphicsObject.SetActive(false);
|
|
healthbarBehaviour.HealthBarTransform.gameObject.SetActive(false);
|
|
|
|
weaponRigBehavior.enabled = true;
|
|
|
|
weaponTransform.gameObject.SetActive(false);
|
|
}
|
|
|
|
public void PerformHit()
|
|
{
|
|
navMeshAgent.SetDestination(transform.position);
|
|
navMeshAgent.isStopped = true;
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, false);
|
|
|
|
transform.LookAt(target);
|
|
|
|
navMeshAgent.isStopped = true;
|
|
|
|
shotsAmount = shotsPerAttempt;
|
|
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, false);
|
|
animatorRef.SetBool(ANIMATOR_SHOOTING_HASH, true);
|
|
animatorRef.SetTrigger(ANIMATOR_ATTACK_HASH);
|
|
|
|
bombPoint = target.position;
|
|
}
|
|
|
|
public void PerformKick()
|
|
{
|
|
transform.LookAt(target);
|
|
|
|
navMeshAgent.isStopped = true;
|
|
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, false);
|
|
animatorRef.SetBool(ANIMATOR_SHOOTING_HASH, false);
|
|
animatorRef.SetTrigger(ANIMATOR_KICK_HASH);
|
|
|
|
Tween.DelayedCall(0.3f, () =>
|
|
{
|
|
AudioController.PlaySound(AudioController.GetClip("punch1"));
|
|
});
|
|
}
|
|
|
|
public void ChasePlayer()
|
|
{
|
|
navMeshAgent.SetDestination(target.position);
|
|
navMeshAgent.isStopped = false;
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, true);
|
|
}
|
|
|
|
public void Idle()
|
|
{
|
|
navMeshAgent.SetDestination(transform.position);
|
|
navMeshAgent.isStopped = true;
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, false);
|
|
}
|
|
|
|
protected override void FixedUpdate()
|
|
{
|
|
base.FixedUpdate();
|
|
|
|
if (!LevelController.IsGameplayActive)
|
|
return;
|
|
|
|
if (isDead)
|
|
return;
|
|
|
|
if (navMeshAgent.isStopped)
|
|
{
|
|
var targetRotation = Quaternion.LookRotation(target.position - transform.position);
|
|
|
|
// Smoothly rotate towards the target point.
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 50);
|
|
}
|
|
|
|
healthbarBehaviour.FollowUpdate();
|
|
|
|
animatorRef.SetFloat(ANIMATOR_SPEED_HASH, navMeshAgent.velocity.sqrMagnitude / sqrSpeed);
|
|
}
|
|
|
|
public override void TakeDamage(float damage, Vector3 projectilePosition, Vector3 projectileDirection)
|
|
{
|
|
if (isDead)
|
|
return;
|
|
|
|
base.TakeDamage(damage, projectilePosition, projectileDirection);
|
|
|
|
if (hitAnimationTime < Time.time)
|
|
HitAnimation(Random.Range(0, 2));
|
|
}
|
|
|
|
protected override void OnDeath()
|
|
{
|
|
if (isDead)
|
|
return;
|
|
|
|
isDead = true;
|
|
|
|
StateMachine.StopMachine();
|
|
|
|
navMeshAgent.enabled = false;
|
|
|
|
// Disable healthbar
|
|
healthbarBehaviour.DisableBar();
|
|
|
|
animatorRef.Play(ANIMATOR_DIE_HASH, -1, 0);
|
|
|
|
AudioController.PlaySound(AudioController.GetClip("boss_scream"), 0.6f);
|
|
DropResources();
|
|
|
|
LevelController.OnEnemyKilled(this);
|
|
|
|
OnDiedEvent?.Invoke(this);
|
|
|
|
weaponRigBehavior.enabled = false;
|
|
|
|
ActivateRagdollOnDeath();
|
|
}
|
|
|
|
public void OnBombExploded(BossBombBehaviour bossBombBehaviour, bool playerHitted)
|
|
{
|
|
if (playerHitted)
|
|
{
|
|
gameCameraCase.Shake(0.04f, 0.04f, bombShakeDurationMax, 1.4f);
|
|
|
|
return;
|
|
}
|
|
|
|
float distanceMultiplier = 1.0f - Mathf.InverseLerp(0, bombShakeDistance, Vector3.Distance(characterBehaviour.transform.position, bossBombBehaviour.transform.position));
|
|
if (distanceMultiplier != 0.0f)
|
|
gameCameraCase.Shake(0.04f, 0.04f, Mathf.Lerp(bombShakeDurationMin, bombShakeDurationMax, distanceMultiplier), 1.4f);
|
|
}
|
|
|
|
public override void OnAnimatorCallback(EnemyCallbackType enemyCallbackType)
|
|
{
|
|
switch (enemyCallbackType)
|
|
{
|
|
case EnemyCallbackType.Hit:
|
|
|
|
OnBossHit();
|
|
break;
|
|
|
|
case EnemyCallbackType.BossKick:
|
|
|
|
if (Vector3.Distance(transform.position, target.position) <= kickDistance)
|
|
{
|
|
ParticlesController.PlayParticle(PARTICLE_KICK_HASH).SetPosition(leftFootTransform.position);
|
|
|
|
characterBehaviour.TakeDamage(characterBehaviour.MaxHealth * 0.5f);
|
|
}
|
|
break;
|
|
|
|
case EnemyCallbackType.BossLeftStep:
|
|
ParticlesController.PlayParticle(PARTICLE_STEP_HASH).SetPosition(leftFootTransform.position);
|
|
break;
|
|
|
|
case EnemyCallbackType.BossRightStep:
|
|
ParticlesController.PlayParticle(PARTICLE_STEP_HASH).SetPosition(rightFootTransform.position);
|
|
break;
|
|
|
|
case EnemyCallbackType.BossEnterFall:
|
|
|
|
OnBossEnterFall();
|
|
break;
|
|
|
|
case EnemyCallbackType.BossEnterFallFinished:
|
|
|
|
OnEntered?.Invoke();
|
|
|
|
CharacterBehaviour.GetBehaviour().TryAddClosestEnemy(this);
|
|
break;
|
|
|
|
case EnemyCallbackType.BossDeathFall:
|
|
ParticlesController.PlayParticle(PARTICLE_DEATH_FALL_HASH).SetPosition(backTransform.position);
|
|
|
|
gameCameraCase.Shake(0.04f, 0.04f, 0.3f, 1.4f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnBossHit()
|
|
{
|
|
shootParticleSystem.Play();
|
|
|
|
GameObject bombObject = Instantiate(bulletPrefab);
|
|
bombObject.transform.position = shootPointTransform.position;
|
|
bombObject.transform.LookAt(bombPoint);
|
|
|
|
BossBombBehaviour bossBombBehaviour = bombObject.GetComponent<BossBombBehaviour>();
|
|
bossBombBehaviour.Init(this, bombExplosionDuration, Random.Range(bombDamageMin, bombDamageMax), bombExplosionRadius);
|
|
|
|
bossBombBehaviour.transform.DOMoveXZ(bombPoint.x, bombPoint.z, 1.0f);
|
|
bossBombBehaviour.transform.DOMoveY(bombPoint.y, 1.0f).SetCurveEasing(yBombMovementCurve).OnComplete(delegate
|
|
{
|
|
bossBombBehaviour.OnPlaced();
|
|
});
|
|
|
|
shotsAmount--;
|
|
|
|
// Recalculate bomb point
|
|
bombPoint = target.position + new Vector3(Random.Range(-4, 4), 0, Random.Range(-4, 4));
|
|
|
|
if (shotsAmount <= 0)
|
|
{
|
|
// move closer to the player
|
|
navMeshAgent.SetDestination(target.position);
|
|
navMeshAgent.isStopped = false;
|
|
animatorRef.SetBool(ANIMATOR_SHOOTING_HASH, false);
|
|
animatorRef.SetBool(ANIMATOR_RUN_HASH, true);
|
|
}
|
|
|
|
AudioController.PlaySound(AudioController.GetClip("shoot2"), 0.3f);
|
|
}
|
|
|
|
private void OnBossEnterFall()
|
|
{
|
|
isDead = false;
|
|
|
|
healthbarBehaviour.HealthBarTransform.gameObject.SetActive(true);
|
|
|
|
ParticlesController.PlayParticle(PARTICLE_ENTER_FALL_HASH).SetPosition(transform.position);
|
|
|
|
gameCameraCase.Shake(0.04f, 0.04f, 0.3f, 1.4f);
|
|
|
|
AudioController.PlaySound(AudioController.GetClip("jump_landing"));
|
|
|
|
CharacterBehaviour.GetBehaviour().TryAddClosestEnemy(this);
|
|
|
|
weaponTransform.gameObject.SetActive(true);
|
|
|
|
Tween.DelayedCall(0.4f, () =>
|
|
{
|
|
AudioController.PlaySound(AudioController.GetClip("boss_scream"), 0.6f);
|
|
});
|
|
}
|
|
|
|
public void Enter()
|
|
{
|
|
// Enable graphics
|
|
graphicsObject.SetActive(true);
|
|
healthbarBehaviour.HealthBarTransform.gameObject.SetActive(true);
|
|
|
|
animatorRef.Play(ANIMATOR_ENTER_HASH, -1, 0);
|
|
}
|
|
|
|
public override void Attack()
|
|
{
|
|
|
|
}
|
|
}
|
|
} |