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(); } }