UnityGameProjectsCode / DiceAndMenGame / Unit.cs
Unit.cs
Raw
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Tilemaps;
using UnityEngine.UI;

public class Unit : MonoBehaviour
{
    public Vector3Int Position => transform.localPosition.TruncateToInt();
    public UnitStatBlock UnitStats { get => unitStats; set => unitStats = value; }
    public int RemainingMovement { get => remainingMovement; set => remainingMovement = value; }
    public SpriteRenderer UnitVisual { get => unitVisual; set => unitVisual = value; } //set at start of battle
    public int CurrentHealth { get => currentHealth; set => currentHealth = value; } //set for health after fights
    public bool CanAttack { get => canAttack; set => canAttack = value; }

    [SerializeField] private int remainingMovement = 2;
    [SerializeField] private float moveDuration = .25f;
    Tilemap world;
    bool canAttack = true;

    [SerializeField] private int currentHealth;
    [SerializeField] Slider healthSlider;
    [SerializeField] TextMeshProUGUI healthTextLight, healthTextDark;
    [SerializeField] private UnitStatBlock unitStats;
    private AudioSource selectionAudio;
    private SpriteRenderer unitVisual;

    [SerializeField] public UnityEvent<Unit> OnSelect = new UnityEvent<Unit>();
    [SerializeField] public UnityEvent<Unit> OnDie = new UnityEvent<Unit>();

    private void Awake()
    {
        world = GameObject.Find("Terrain").GetComponent<Tilemap>();
        selectionAudio = GameObject.Find("Unit Select Audio").GetComponent<AudioSource>();
        unitVisual = GetComponentInChildren<SpriteRenderer>();
    }

    private void Start()
    {
        currentHealth = unitStats.maxHealth;

        UpdateHealthBar();
    }

    public void Select()
    {
        OnSelect.Invoke(this);
    }

    /// <summary>
    /// Moves unity in x, then y directions over moveDuration seconds
    /// </summary>
    /// <param name="delta">Distance to move in each direction (z ignored)</param>
    public void Move(Vector3Int delta)
    {
        Move((Vector3)delta);

        RemainingMovement -= Mathf.Abs(delta.x) + Mathf.Abs(delta.y);
    }

    /// <summary>
    /// Moves unity in x, then y directions over moveDuration seconds
    /// </summary>
    /// <param name="delta">Distance to move in each direction (z ignored)</param>
    public void Move(Vector3 delta)
    {
        StartCoroutine(BeginMove(delta));
    }

    /// <summary>
    /// Moves unity in x, then y directions over moveDuration seconds
    /// </summary>
    /// <param name="delta">Distance to move in each direction (z ignored)</param>
    /// <returns></returns>
    public IEnumerator BeginMove(Vector3 delta)
    {
        Debug.Log(string.Format("Moving ({0},{1})", delta.x, delta.y));

        Vector3 start = transform.position;
        Vector3 current = Vector3.zero;

        //Move X direction
        float time = 0;
        while(time < 1)
        {
            transform.position = start + current;

            yield return null;

            time += Time.deltaTime / moveDuration / .5f;

            current.x = Mathf.Lerp(0, delta.x, time);
        }

        //Move Y direction
        time = 0;
        while (time < 1)
        {
            transform.position = start + current;

            yield return null;

            time += Time.deltaTime / moveDuration / .5f;

            current.y = Mathf.Lerp(0, delta.y, time);
        }

        transform.position = start + delta;
    }

    /// <summary>
    /// Enumerates over valid tiles within the unit's remaining move distance
    /// </summary>
    /// <returns>Tuple of type (TerrainTile, Vector3Int) which represents a nearby tile and the associated relative position</returns>
    internal IEnumerable<(TerrainTile, Vector3Int)> GetTilesInMoveRange()
    {
        //Get square that is 2 * moveRange + 1 wide
        for(int x = -RemainingMovement; x <= RemainingMovement; x++)
        {
            for(int y = -RemainingMovement; y <= RemainingMovement; y++)
            {
                //Skip center tile that unit is standing on
                if (x == 0 && y == 0)
                    continue;

                //skip tiles beyond move range
                if (Mathf.Abs(x) + Mathf.Abs(y) > RemainingMovement)
                    continue;

                Vector3Int pos = new Vector3Int(x, y, 0);

                yield return (world.GetTile<TerrainTile>(Position + pos), pos);
            }
        }
    }

    internal IEnumerable<(T, Vector3Int)> GetUnitsInMoveRange<T>() where T : Unit
    {
        foreach (var unit in FindObjectsOfType<T>())
        {
            if (unit == this)
                continue;

            var offset = (unit.transform.position - transform.position).RoundToInt();

            //Skip units beyond range
            if (Mathf.Abs(offset.x) + Mathf.Abs(offset.y) > RemainingMovement + 1)
                continue;

            yield return (unit, offset);
        }
    }

    public void UpdateHealthBar()
    {
        healthSlider.maxValue = unitStats.maxHealth;
        healthSlider.value = currentHealth;
        healthTextLight.text = currentHealth + " / " + unitStats.maxHealth;
        healthTextDark.text = currentHealth + " / " + unitStats.maxHealth;
    }

    public void Damage(int damage)
    {
        currentHealth = Mathf.Clamp(currentHealth - damage, 0, unitStats.maxHealth);
        UpdateHealthBar();

        if (currentHealth == 0)
            OnDie.Invoke(this);
    }

    /// <summary>
    /// Resets state to start of turn state. TODO
    /// </summary>
    [EasyButtons.Button]
    public void StartTurn()
    {
        remainingMovement = unitStats.moveRange;
        canAttack = true;
    }

    private void OnMouseDown()
    {
        Select();

        selectionAudio.pitch = UnityEngine.Random.Range(0.95f, 1.05f);
        selectionAudio.Play();
    }
}