EscapeShooter/My project/Assets/Project Files/Game/Scripts/Characters/CharacterBehaviour.cs

752 lines
22 KiB
C#

using UnityEngine;
using UnityEngine.AI;
using Watermelon;
using Watermelon.LevelSystem;
namespace Watermelon.SquadShooter
{
public class CharacterBehaviour : MonoBehaviour, IEnemyDetector, IHealth, INavMeshAgent
{
private static readonly int SHADER_HIT_SHINE_COLOR_HASH = Shader.PropertyToID("_EmissionColor");
private static CharacterBehaviour characterBehaviour;
[SerializeField] NavMeshAgent agent;
[SerializeField] EnemyDetector enemyDetector;
[Header("Health")]
[SerializeField] HealthbarBehaviour healthbarBehaviour;
public HealthbarBehaviour HealthbarBehaviour => healthbarBehaviour;
[SerializeField] ParticleSystem healingParticle;
[SerializeField] ParticleSystem godModeParticle;
[Header("Target")]
[SerializeField] GameObject targetRingPrefab;
[SerializeField] Color targetRingActiveColor;
[SerializeField] Color targetRingDisabledColor;
[SerializeField] Color targetRingSpecialColor;
[Space(5)]
[SerializeField] AimRingBehavior aimRingBehavior;
// Character Graphics
private BaseCharacterGraphics graphics;
public BaseCharacterGraphics Graphics => graphics;
private GameObject graphicsPrefab;
private SkinnedMeshRenderer characterMeshRenderer;
private MaterialPropertyBlock hitShinePropertyBlock;
private TweenCase hitShineTweenCase;
private CharacterStats stats;
public CharacterStats Stats => stats;
// Gun
private BaseGunBehavior gunBehaviour;
public BaseGunBehavior Weapon => gunBehaviour;
private GameObject gunPrefabGraphics;
// Health
private float currentHealth;
public float CurrentHealth => currentHealth;
public float MaxHealth => stats.Health;
public bool FullHealth => currentHealth == stats.Health;
public bool IsInvulnerable { get; private set; }
public bool IsActive => isActive;
private bool isActive;
public static Transform Transform => characterBehaviour.transform;
// Movement
private MovementSettings movementSettings;
private MovementSettings movementAimingSettings;
private MovementSettings activeMovementSettings;
public MovementSettings MovementSettings => activeMovementSettings;
private bool isMoving;
private float speed = 0;
private Vector3 movementVelocity;
public Vector3 MovementVelocity => movementVelocity;
public EnemyDetector EnemyDetector => enemyDetector;
public bool IsCloseEnemyFound => closestEnemyBehaviour != null;
public bool IsAttackingAllowed { get; private set; } = true;
private BaseEnemyBehavior closestEnemyBehaviour;
public BaseEnemyBehavior ClosestEnemyBehaviour => closestEnemyBehaviour;
private Transform playerTarget;
private GameObject targetRing;
private Renderer targetRingRenderer;
private TweenCase ringTweenCase;
private bool isMovementActive = false;
public bool IsMovementActive => isMovementActive;
public static bool IsDead { get; private set; } = false;
public static SimpleCallback OnDied;
private void Awake()
{
agent.enabled = false;
}
public void Init()
{
characterBehaviour = this;
hitShinePropertyBlock = new MaterialPropertyBlock();
isActive = false;
enabled = false;
// Create target
GameObject tempTarget = new GameObject("[TARGET]");
tempTarget.transform.position = transform.position;
tempTarget.SetActive(true);
playerTarget = tempTarget.transform;
// Initialise enemy detector
enemyDetector.Init(this);
// Set health
currentHealth = MaxHealth;
// Initialise healthbar
healthbarBehaviour.Init(transform, this, true, CharactersController.SelectedCharacter.GetCurrentStage().HealthBarOffset);
aimRingBehavior.Init(transform);
targetRing = Instantiate(targetRingPrefab, new Vector3(0f, 0f, -999f), Quaternion.identity);
targetRingRenderer = targetRing.GetComponent<Renderer>();
aimRingBehavior.Hide();
IsDead = false;
GameSettings settings = GameSettings.GetSettings();
IsAttackingAllowed = !settings.UseAttackButton;
if (settings.UseAttackButton)
{
AttackButtonBehavior.onStatusChanged += OnAttackButtonStatusChanged;
}
}
private void OnAttackButtonStatusChanged(bool isPressed)
{
IsAttackingAllowed = isPressed;
}
public void Reload(bool resetHealth = true)
{
// Set health
if (resetHealth)
{
currentHealth = MaxHealth;
}
IsDead = false;
healthbarBehaviour.EnableBar();
healthbarBehaviour.RedrawHealth();
enemyDetector.Reload();
enemyDetector.gameObject.SetActive(false);
graphics.DisableRagdoll();
graphics.Reload();
gunBehaviour.Reload();
gameObject.SetActive(true);
}
public void ResetDetector()
{
var radius = enemyDetector.DetectorRadius;
enemyDetector.SetRadius(0);
Tween.NextFrame(() => enemyDetector.SetRadius(radius), framesOffset: 2, updateMethod: UpdateMethod.FixedUpdate);
}
public void Unload()
{
if (graphics != null)
graphics.Unload();
if (playerTarget != null)
Destroy(playerTarget.gameObject);
if (aimRingBehavior != null)
Destroy(aimRingBehavior.gameObject);
if (healthbarBehaviour != null)
healthbarBehaviour.Destroy();
}
public void OnLevelLoaded()
{
if (gunBehaviour != null)
gunBehaviour.OnLevelLoaded();
}
public void OnNavMeshUpdated()
{
if (agent.isOnNavMesh)
{
agent.enabled = true;
agent.isStopped = false;
}
}
public void ActivateAgent()
{
agent.enabled = true;
agent.isStopped = false;
}
public static void DisableNavmeshAgent()
{
characterBehaviour.agent.enabled = false;
}
public void MakeInvulnerable(float duration)
{
IsInvulnerable = true;
godModeParticle.Play();
Tween.DelayedCall(duration, () => {
IsInvulnerable = false;
godModeParticle.Stop(true, ParticleSystemStopBehavior.StopEmitting);
});
}
// Returns true if taken damage
public virtual bool TakeDamage(float damage)
{
if (currentHealth <= 0 || IsInvulnerable)
return false;
currentHealth = Mathf.Clamp(currentHealth - damage, 0, MaxHealth);
healthbarBehaviour.OnHealthChanged();
VirtualCamera gameCameraCase = CameraController.GetCamera(CameraType.Gameplay);
gameCameraCase.Shake(0.04f, 0.04f, 0.3f, 1.4f);
if (currentHealth <= 0)
{
healthbarBehaviour.DisableBar();
OnCloseEnemyChanged(null);
isActive = false;
enabled = false;
enemyDetector.gameObject.SetActive(false);
aimRingBehavior.Hide();
OnDeath();
graphics.EnableRagdoll();
OnDied?.Invoke();
#if MODULE_HAPTIC
Haptic.Play(Haptic.HAPTIC_MEDIUM);
#endif
}
HitEffect();
AudioController.PlaySound(AudioController.GetClip("player_hit"));
#if MODULE_HAPTIC
Haptic.Play(Haptic.HAPTIC_LIGHT);
#endif
FloatingTextController.SpawnFloatingText("PlayerHit", "-" + damage.ToString("F0"), transform.position + new Vector3(Random.Range(-0.3f, 0.3f), 3.75f, Random.Range(-0.1f, 0.1f)), Quaternion.identity, 1.0f, Color.white);
return true;
}
public void OnDeath()
{
graphics.OnDeath();
IsDead = true;
Tween.DelayedCall(0.5f, LevelController.OnPlayerDied);
}
public void SetPosition(Vector3 position)
{
playerTarget.position = position.AddToZ(10f);
transform.position = position;
transform.rotation = Quaternion.identity;
if (agent.isActiveAndEnabled && agent.isOnNavMesh)
{
agent.Warp(position);
}
}
protected void HitEffect()
{
hitShineTweenCase.KillActive();
characterMeshRenderer.GetPropertyBlock(hitShinePropertyBlock);
hitShinePropertyBlock.SetColor(SHADER_HIT_SHINE_COLOR_HASH, Color.white);
characterMeshRenderer.SetPropertyBlock(hitShinePropertyBlock);
hitShineTweenCase = characterMeshRenderer.DOPropertyBlockColor(SHADER_HIT_SHINE_COLOR_HASH, hitShinePropertyBlock, Color.black, 0.32f);
graphics.PlayHitAnimation();
}
#region Gun
public void SetGun(WeaponData weapon, WeaponUpgrade weaponUpgrade, bool playBounceAnimation = false, bool playAnimation = false, bool playParticle = false)
{
if (gunBehaviour != null)
gunBehaviour.OnGunUnloaded();
// Check if graphics isn't exist already
if (gunPrefabGraphics != weaponUpgrade.WeaponPrefab)
{
// Store prefab link
gunPrefabGraphics = weaponUpgrade.WeaponPrefab;
if (gunBehaviour != null)
{
Destroy(gunBehaviour.gameObject);
}
if (gunPrefabGraphics != null)
{
GameObject gunObject = Instantiate(gunPrefabGraphics);
gunObject.SetActive(true);
gunBehaviour = gunObject.GetComponent<BaseGunBehavior>();
if (graphics != null)
{
gunBehaviour.InitCharacter(graphics);
gunBehaviour.PlaceGun(graphics);
graphics.SetShootingAnimation(gunBehaviour.GetShootAnimationClip());
gunBehaviour.UpdateHandRig();
}
}
}
if (gunBehaviour != null)
{
gunBehaviour.Init(this, weapon);
Vector3 defaultScale = gunBehaviour.transform.localScale;
if (playAnimation)
{
gunBehaviour.transform.localScale = defaultScale * 0.8f;
gunBehaviour.transform.DOScale(defaultScale, 0.15f).SetEasing(Ease.Type.BackOut);
}
if (playBounceAnimation)
gunBehaviour.PlayBounceAnimation();
if (playParticle)
gunBehaviour.PlayUpgradeParticle();
}
enemyDetector.SetRadius(weaponUpgrade.RangeRadius);
aimRingBehavior.SetRadius(weaponUpgrade.RangeRadius);
}
public void OnGunShooted()
{
graphics.OnShoot();
}
#endregion
#region Graphics
public void SetStats(CharacterStats stats)
{
this.stats = stats;
currentHealth = stats.Health;
if (healthbarBehaviour != null)
healthbarBehaviour.OnHealthChanged();
}
public void SetGraphics(GameObject newGraphicsPrefab, bool playParticle, bool playAnimation)
{
// Check if graphics isn't exist already
if (graphicsPrefab != newGraphicsPrefab)
{
// Store prefab link
graphicsPrefab = newGraphicsPrefab;
AnimatorParameters animatorParameters = null;
if (graphics != null)
{
animatorParameters = new AnimatorParameters(graphics.CharacterAnimator);
if (gunBehaviour != null)
gunBehaviour.transform.SetParent(null);
graphics.Unload();
Destroy(graphics.gameObject);
}
GameObject graphicObject = Instantiate(newGraphicsPrefab);
graphicObject.transform.SetParent(transform);
graphicObject.transform.ResetLocal();
graphicObject.SetActive(true);
graphics = graphicObject.GetComponent<BaseCharacterGraphics>();
graphics.Init(this);
movementSettings = graphics.MovementSettings;
movementAimingSettings = graphics.MovementAimingSettings;
activeMovementSettings = movementSettings;
characterMeshRenderer = graphics.MeshRenderer;
if (gunBehaviour != null)
{
gunBehaviour.InitCharacter(graphics);
gunBehaviour.PlaceGun(graphics);
graphics.SetShootingAnimation(gunBehaviour.GetShootAnimationClip());
gunBehaviour.UpdateHandRig();
}
if (playParticle)
graphics.PlayUpgradeParticle();
if (playAnimation)
graphics.PlayBounceAnimation();
if (animatorParameters != null)
animatorParameters.ApplyTo(graphics.CharacterAnimator);
}
}
#endregion
public void Activate(bool check = true)
{
if (check && isActive)
return;
isActive = true;
enabled = true;
enemyDetector.gameObject.SetActive(true);
aimRingBehavior.Show();
graphics.Activate();
NavMeshController.InvokeOrSubscribe(this);
}
public void Disable()
{
if (!isActive)
return;
isActive = false;
enabled = false;
agent.enabled = false;
aimRingBehavior.Hide();
targetRing.SetActive(false);
targetRing.transform.SetParent(null);
graphics.Disable();
closestEnemyBehaviour = null;
if (isMoving)
{
isMoving = false;
speed = 0;
}
}
public void MoveForwardAndDisable(float duration)
{
agent.enabled = false;
transform.DOMove(transform.position + Vector3.forward * activeMovementSettings.MoveSpeed * duration, duration).OnComplete(() =>
{
Disable();
});
}
public void DisableAgent()
{
agent.enabled = false;
}
public void ActivateMovement()
{
isMovementActive = true;
aimRingBehavior.Show();
}
private void Update()
{
if (gunBehaviour != null)
gunBehaviour.UpdateHandRig();
if (!isActive)
return;
var joystick = Control.CurrentControl;
if (joystick.IsMovementInputNonZero && joystick.MovementInput.sqrMagnitude > 0.1f)
{
if (!isMoving)
{
isMoving = true;
speed = 0;
graphics.OnMovingStarted();
}
float maxAlowedSpeed = Mathf.Clamp01(joystick.MovementInput.magnitude) * activeMovementSettings.MoveSpeed;
if (speed > maxAlowedSpeed)
{
speed -= activeMovementSettings.Acceleration * Time.deltaTime;
if (speed < maxAlowedSpeed)
{
speed = maxAlowedSpeed;
}
}
else
{
speed += activeMovementSettings.Acceleration * Time.deltaTime;
if (speed > maxAlowedSpeed)
{
speed = maxAlowedSpeed;
}
}
movementVelocity = transform.forward * speed;
transform.position += joystick.MovementInput * Time.deltaTime * speed;
graphics.OnMoving(Mathf.InverseLerp(0, activeMovementSettings.MoveSpeed, speed), joystick.MovementInput, IsCloseEnemyFound);
if (!IsCloseEnemyFound)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(joystick.MovementInput.normalized), Time.deltaTime * activeMovementSettings.RotationSpeed);
}
}
else
{
if (isMoving)
{
isMoving = false;
movementVelocity = Vector3.zero;
graphics.OnMovingStoped();
speed = 0;
}
}
if (IsCloseEnemyFound)
{
playerTarget.position = Vector3.Lerp(playerTarget.position, new Vector3(closestEnemyBehaviour.transform.position.x, transform.position.y, closestEnemyBehaviour.transform.position.z), Time.deltaTime * activeMovementSettings.RotationSpeed);
transform.LookAt(new Vector3(playerTarget.position.x, transform.position.y, playerTarget.position.z));
}
targetRing.transform.rotation = Quaternion.identity;
if (healthbarBehaviour != null)
healthbarBehaviour.FollowUpdate();
aimRingBehavior.UpdatePosition();
}
private void FixedUpdate()
{
graphics.CustomFixedUpdate();
if (gunBehaviour != null)
gunBehaviour.GunUpdate();
}
public void OnCloseEnemyChanged(BaseEnemyBehavior enemyBehavior)
{
if (!isActive) return;
if (enemyBehavior != null)
{
if (closestEnemyBehaviour == null)
{
playerTarget.position = transform.position + transform.forward * 5;
}
activeMovementSettings = movementAimingSettings;
closestEnemyBehaviour = enemyBehavior;
targetRing.SetActive(true);
targetRing.transform.rotation = Quaternion.identity;
ringTweenCase.KillActive();
targetRing.transform.SetParent(enemyBehavior.transform);
targetRing.transform.localScale = Vector3.one * enemyBehavior.Stats.TargetRingSize * 1.4f;
targetRing.transform.localPosition = Vector3.zero;
ringTweenCase = targetRing.transform.DOScale(Vector3.one * enemyBehavior.Stats.TargetRingSize, 0.2f).SetEasing(Ease.Type.BackIn);
// old camera
//CameraController.SetEnemyTarget(enemyBehavior);
SetTargetActive();
return;
}
activeMovementSettings = movementSettings;
closestEnemyBehaviour = null;
targetRing.SetActive(false);
targetRing.transform.SetParent(null);
// old camera
//CameraController.SetEnemyTarget(null);
}
public static BaseEnemyBehavior GetClosestEnemy()
{
return characterBehaviour.enemyDetector.ClosestEnemy;
}
public static CharacterBehaviour GetBehaviour()
{
return characterBehaviour;
}
public void TryAddClosestEnemy(BaseEnemyBehavior enemy)
{
EnemyDetector.TryAddClosestEnemy(enemy);
}
public void SetTargetActive()
{
if (closestEnemyBehaviour != null && closestEnemyBehaviour.Tier == EnemyTier.Elite)
{
targetRingRenderer.material.color = targetRingSpecialColor;
}
else
{
targetRingRenderer.material.color = targetRingActiveColor;
}
}
public void SetTargetUnreachable()
{
targetRingRenderer.material.color = targetRingDisabledColor;
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag(PhysicsHelper.TAG_ITEM))
{
BaseDropBehavior item = other.GetComponent<BaseDropBehavior>();
if (item.IsPickable(this) && !item.IsPicked)
{
item.Pick();
}
}
else if (other.CompareTag(PhysicsHelper.TAG_CHEST))
{
other.GetComponent<AbstractChestBehavior>().ChestApproached();
}
}
private void OnTriggerStay(Collider other)
{
if (other.CompareTag(PhysicsHelper.TAG_ITEM))
{
BaseDropBehavior item = other.GetComponent<BaseDropBehavior>();
if (item.IsPickable(this) && !item.IsPicked)
{
item.Pick();
}
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag(PhysicsHelper.TAG_CHEST))
{
other.GetComponent<AbstractChestBehavior>().ChestLeft();
}
}
public void Heal(int healAmount)
{
currentHealth = Mathf.Clamp(currentHealth + healAmount, 0, MaxHealth);
healthbarBehaviour.OnHealthChanged();
healingParticle.Play();
}
public void Jump()
{
graphics.Jump();
gunBehaviour.transform.localScale = Vector3.zero;
gunBehaviour.gameObject.SetActive(false);
}
public void SpawnWeapon()
{
graphics.EnableRig();
gunBehaviour.gameObject.SetActive(true);
gunBehaviour.DOScale(1, 0.2f).SetCustomEasing(Ease.GetCustomEasingFunction("BackOutLight"));
}
private void OnDestroy()
{
if (healthbarBehaviour.HealthBarTransform != null)
Destroy(healthbarBehaviour.HealthBarTransform.gameObject);
if (aimRingBehavior != null)
aimRingBehavior.OnPlayerDestroyed();
AttackButtonBehavior.onStatusChanged -= OnAttackButtonStatusChanged;
}
}
}