Implement IDamageable interface and Health management for player and enemy controllers
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 627 KiB After Width: | Height: | Size: 627 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 624 KiB After Width: | Height: | Size: 624 KiB |
|
Before Width: | Height: | Size: 480 KiB After Width: | Height: | Size: 480 KiB |
@ -24,6 +24,13 @@ namespace RR.Common.Interfaces
|
||||
public bool TryPickup(GameObject newOwner, Transform optionaAttachPoint = null);
|
||||
}
|
||||
|
||||
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(int amount);
|
||||
void Heal(int amount);
|
||||
void Die();
|
||||
}
|
||||
}
|
||||
|
||||
namespace RR.Game2D.Core.Interfaces
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
using UnityEngine;
|
||||
using RR.Game2D.Core;
|
||||
using RR.Common.Interfaces;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
[RequireComponent(typeof(Rigidbody2D),
|
||||
typeof(Collider2D),
|
||||
typeof(Animator))]
|
||||
public class EnemyController : MonoBehaviour
|
||||
public class EnemyController : MonoBehaviour, IDamageable
|
||||
{
|
||||
|
||||
[Header("Обзор")]
|
||||
[SerializeField] private float viewDistance = 5f;
|
||||
[SerializeField] private float viewAngle = 60f;
|
||||
@ -19,12 +21,21 @@ namespace RR.Game2D.Behaviour
|
||||
[Header("Препятствия")]
|
||||
[SerializeField] private LayerMask obstacleMask;
|
||||
|
||||
[Header("Здоровье")]
|
||||
[SerializeField] private int maxHealth = 1;
|
||||
private Health health;
|
||||
public int CurrentHealth => health?.CurrentHealth ?? 0;
|
||||
public int MaxHealth => health?.MaxHealth ?? 0;
|
||||
|
||||
private Animator animator;
|
||||
private Rigidbody2D rb;
|
||||
private Collider2D col;
|
||||
|
||||
|
||||
private GameObject playerObj;
|
||||
|
||||
// Аниматор
|
||||
private static readonly int Speed = Animator.StringToHash("Speed");
|
||||
|
||||
void Start()
|
||||
{
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
@ -36,13 +47,22 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
playerObj = GameObject.FindGameObjectWithTag("Player");
|
||||
}
|
||||
|
||||
health = GetComponent<Health>();
|
||||
if (health == null)
|
||||
{
|
||||
health = gameObject.AddComponent<Health>();
|
||||
}
|
||||
health.ResetHealth();
|
||||
health.SetOwner(this);
|
||||
health.SetMaxHealth(maxHealth);
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (!playerObj) return;
|
||||
|
||||
Vector2 playerCenter = (Vector2)playerObj.transform.position;
|
||||
Vector2 playerCenter = playerObj.transform.position;
|
||||
|
||||
if (PlayerInSight())
|
||||
{
|
||||
@ -60,7 +80,7 @@ namespace RR.Game2D.Behaviour
|
||||
rb.linearVelocity, Vector2.zero, acceleration * Time.fixedDeltaTime);
|
||||
}
|
||||
|
||||
animator.SetFloat("Speed", Mathf.Abs(rb.linearVelocity.magnitude));
|
||||
animator.SetFloat(Speed, Mathf.Abs(rb.linearVelocity.magnitude));
|
||||
}
|
||||
|
||||
void OnCollisionEnter2D(Collision2D collision)
|
||||
@ -70,20 +90,21 @@ namespace RR.Game2D.Behaviour
|
||||
Rigidbody2D playerRb = collision.collider.GetComponent<Rigidbody2D>();
|
||||
playerObj.TryGetComponent(out PlayerController playerController);
|
||||
if (playerRb == null) return;
|
||||
|
||||
Vector2 contactNormal = collision.GetContact(0).normal;
|
||||
|
||||
if (contactNormal.y < -0.5f)
|
||||
{
|
||||
playerRb.linearVelocity = new Vector2(playerRb.linearVelocity.x, 35f);
|
||||
Die();
|
||||
TakeDamage(MaxHealth);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerController.Die();
|
||||
playerController.TakeDamage(1);
|
||||
}
|
||||
}
|
||||
else if (collision.collider.CompareTag("Projectile")) Die();
|
||||
else if (collision.collider.CompareTag("Projectile"))
|
||||
{
|
||||
TakeDamage(1);
|
||||
}
|
||||
}
|
||||
|
||||
private bool PlayerInSight()
|
||||
@ -116,9 +137,16 @@ namespace RR.Game2D.Behaviour
|
||||
return true;
|
||||
}
|
||||
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
health.TakeDamage(amount);
|
||||
}
|
||||
public void Heal(int amount)
|
||||
{
|
||||
health.Heal(amount);
|
||||
}
|
||||
public void Die()
|
||||
{
|
||||
//animator.Play("Death", 0); // TODO: Анимация смерти, потом смерть
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
@ -131,8 +159,8 @@ namespace RR.Game2D.Behaviour
|
||||
Vector2 forward = transform.localEulerAngles.y >= 180f ? Vector2.right : Vector2.left;
|
||||
float half = viewAngle * 0.5f;
|
||||
|
||||
Vector2 edgeA = (Vector2)(Quaternion.Euler(0, 0, +half) * (Vector3)forward);
|
||||
Vector2 edgeB = (Vector2)(Quaternion.Euler(0, 0, -half) * (Vector3)forward);
|
||||
Vector2 edgeA = (Quaternion.Euler(0, 0, +half) * forward);
|
||||
Vector2 edgeB = (Quaternion.Euler(0, 0, -half) * forward);
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(origin, origin + edgeA * viewDistance);
|
||||
|
||||
46
Assets/Scripts/Health.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using RR.Common.Interfaces;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Health : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int maxHealth = 3;
|
||||
[SerializeField] private int currentHealth;
|
||||
private IDamageable owner;
|
||||
|
||||
public int MaxHealth => maxHealth;
|
||||
public int CurrentHealth => currentHealth;
|
||||
|
||||
public void SetOwner(IDamageable owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void SetMaxHealth(int value)
|
||||
{
|
||||
maxHealth = value;
|
||||
currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
currentHealth -= amount;
|
||||
if (currentHealth <= 0)
|
||||
{
|
||||
owner?.Die();
|
||||
}
|
||||
}
|
||||
|
||||
public void Heal(int amount)
|
||||
{
|
||||
currentHealth = Mathf.Min(currentHealth + amount, maxHealth);
|
||||
}
|
||||
|
||||
public void ResetHealth()
|
||||
{
|
||||
currentHealth = maxHealth;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Health.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a9fe08f70754d1fa12c13ac4cd190e3
|
||||
timeCreated: 1759909224
|
||||
3
Assets/Scripts/IDamageable.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace RR.Game2D.Core
|
||||
{
|
||||
}
|
||||
3
Assets/Scripts/IDamageable.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebc3129eb7104067b88015c32e09cdf8
|
||||
timeCreated: 1759909224
|
||||
@ -1,14 +1,15 @@
|
||||
using RR.Game2D.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using RR.Common.Interfaces;
|
||||
using RR.Game2D.Core;
|
||||
|
||||
namespace RR.Game2D.Behaviour
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Rigidbody2D),
|
||||
typeof(Collider2D),
|
||||
typeof(Animator))]
|
||||
public class PlayerController : MonoBehaviour
|
||||
typeof(Collider2D),
|
||||
typeof(Animator))]
|
||||
public class PlayerController : MonoBehaviour, IDamageable
|
||||
{
|
||||
|
||||
[Header("Управление")]
|
||||
@ -32,9 +33,15 @@ namespace RR.Game2D.Behaviour
|
||||
[Header("Аниматор [Опционально]")]
|
||||
[SerializeField] private Animator animator; // оставить в инспекторе на всякий случай
|
||||
|
||||
[Header("Здоровье")]
|
||||
[SerializeField] private int maxHealth = 3;
|
||||
private Health health;
|
||||
public int CurrentHealth => health?.CurrentHealth ?? 0;
|
||||
public int MaxHealth => health?.MaxHealth ?? 0;
|
||||
|
||||
private Rigidbody2D rb;
|
||||
//private Collider2D col;
|
||||
|
||||
|
||||
private float inputX;
|
||||
private bool jumpPressed, jumpReleased, jumpHold;
|
||||
|
||||
@ -73,9 +80,17 @@ namespace RR.Game2D.Behaviour
|
||||
animator = GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
animator = GetComponentInChildren<Animator>(true);
|
||||
|
||||
if (animator == null)
|
||||
Debug.LogError($"[{name}] Animator не найден!");
|
||||
|
||||
health = GetComponent<Health>();
|
||||
if (health == null)
|
||||
{
|
||||
health = gameObject.AddComponent<Health>();
|
||||
}
|
||||
health.ResetHealth();
|
||||
health.SetOwner(this);
|
||||
health.SetMaxHealth(maxHealth);
|
||||
}
|
||||
|
||||
void Update()
|
||||
@ -145,9 +160,26 @@ namespace RR.Game2D.Behaviour
|
||||
{
|
||||
GameManager.Instance.NextLevel();
|
||||
}
|
||||
|
||||
if (collision.CompareTag("Enemy"))
|
||||
{
|
||||
TakeDamage(1);
|
||||
}
|
||||
}
|
||||
|
||||
//private void OnDestroy() => Debug.Log("Player destroyed");
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
health.TakeDamage(amount);
|
||||
}
|
||||
public void Heal(int amount)
|
||||
{
|
||||
health.Heal(amount);
|
||||
}
|
||||
|
||||
public void Die()
|
||||
{
|
||||
GameManager.Instance.ResetLevel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод для проверки соприкосновения с землей
|
||||
@ -164,14 +196,5 @@ namespace RR.Game2D.Behaviour
|
||||
|
||||
return isGrounded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Метод смерти персонажа.
|
||||
/// Базовое поведение - сброс уровня в GameManager
|
||||
/// </summary>
|
||||
public void Die()
|
||||
{
|
||||
GameManager.Instance.ResetLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class ReadOnlyInInspectorAttribute : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
using UnityEditor;
|
||||
|
||||
#endif
|
||||
|
||||
public class ReadOnlyInInspectorAttribute : PropertyAttribute {}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyInInspectorAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
=> EditorGUI.GetPropertyHeight(property, label, true);
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
|
||||
GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||