using System.Collections; using UnityEngine; public enum WeaponType { Cannon = 0, Laser, Slow, Buff, Random, Gold, Penetration } public enum WeaponState { SearchTarget = 0, TryAttackCannon, TryAttackLaser, OnGoldTower } public class TowerWeapon : MonoBehaviour { [Header("Commons")] [SerializeField] private TowerTemplate towerTemplate; // 타워 정보 (공격력, 공격속도 등) [SerializeField] private Transform spawnPoint; // 발사체 생성 위치 [SerializeField] private WeaponType weaponType; // 무기 속성 설정 public TowerWeapon buffTower; [Header("Cannon")] [SerializeField] private GameObject projectilePrefab; // 발사체 프리팹 [Header("Laser")] [SerializeField] private LineRenderer lineRenderer; // 레이저로 사용되는 선(LineRenderer) [SerializeField] private Transform hitEffect; // 타격 효과 [SerializeField] private LayerMask targetLayer; // 광선에 부딪히는 레이어 설정 [Header("Random Damage")] [SerializeField] private GameObject randomDamageProjectilePrefab; [Header("Long")] [SerializeField] private GameObject longProjectilePrefab; [Header("Power")] [SerializeField] private GameObject powerProjectilePrefab; [Header("Penetration")] [SerializeField] private GameObject penetrationProjectilePrefab; private int level = 0; // 타워 레벨 private WeaponState weaponState = WeaponState.SearchTarget; // 타워 무기의 상태 private Transform attackTarget = null; // 공격 대상 private SpriteRenderer spriteRenderer; // 타워 오브젝트 이미지 변경용 private TowerSpawner towerSpawner; private EnemySpawner enemySpawner; // 게임에 존재하는 적 정보 획득용 private PlayerGold playerGold; // 플레이어의 골드 정보 획득 및 설정 private Tile ownerTile; // 현재 타워가 배치되어 있는 타일 private float addedDamage; // 버프에 의해 추가된 데미지 private float addedPenetration; private int buffLevel; // 버프를 받는지 여부 설정 (0 : 버프X, 1~3 : 받는 버프 레벨) [SerializeField] private Animator animatorTower01; [SerializeField] private Animator animatorTower02; [SerializeField] private Animator animatorTower03; [SerializeField] private Animator animatorTower04; [SerializeField] private Animator animatorTower05; [SerializeField] private Animator animatorTower06; [SerializeField] private Animator animatorTower08; [SerializeField] private Animator animatorTower09; [SerializeField] private Animator animatorTower10; public Sprite TowerSprite => towerTemplate.weapon[level].sprite; public float Damage => towerTemplate.weapon[level].damage; public float Rate => towerTemplate.weapon[level].rate; public float Range => towerTemplate.weapon[level].range; public int UpgradeCost => Level < MaxLevel ? towerTemplate.weapon[level + 1].cost : 0; public int SellCost => towerTemplate.weapon[level].sell; public int Level => level + 1; public int MaxLevel => towerTemplate.weapon.Length; public float Slow => towerTemplate.weapon[level].slow; public float Buff => towerTemplate.weapon[level].buff; public float LowerDamage => towerTemplate.weapon[level].lowerDamage; public float UpperDamage => towerTemplate.weapon[level].upperDamage; public int Gold => towerTemplate.weapon[level].gold; public float Penetration => towerTemplate.weapon[level].penetration; public WeaponType WeaponType => weaponType; public float AddedDamage { set => addedDamage = Mathf.Max(0, value); get => addedDamage; } public int BuffLevel { set => buffLevel = Mathf.Max(0, value); get => buffLevel; } public float AddedPenetration { set => addedPenetration = Mathf.Max(0, value); get => addedPenetration; } public void Awake() { animatorTower01 = GetComponent(); animatorTower02 = GetComponent(); animatorTower03 = GetComponent(); animatorTower04 = GetComponent(); animatorTower05 = GetComponent(); animatorTower06 = GetComponent(); animatorTower08 = GetComponent(); animatorTower09 = GetComponent(); animatorTower10 = GetComponent(); spriteRenderer = GetComponent(); } public void Setup(TowerSpawner towerSpawner, EnemySpawner enemySpawner, PlayerGold playerGold, Tile ownerTile) { spriteRenderer = GetComponent(); this.towerSpawner = towerSpawner; this.enemySpawner = enemySpawner; this.playerGold = playerGold; this.ownerTile = ownerTile; // 무기 속성이 캐논, 레이저일 때 if (weaponType == WeaponType.Cannon || weaponType == WeaponType.Laser || weaponType == WeaponType.Random || weaponType == WeaponType.Penetration) { // 최초 상태를 WeaponState.SearchTarget으로 설정 ChangeState(WeaponState.SearchTarget); } else if (weaponType == WeaponType.Gold) { ChangeState(WeaponState.OnGoldTower); } } public void ChangeState(WeaponState newState) { // 이전에 재생중이던 상태 종료 StopCoroutine(weaponState.ToString()); // 상태 변경 weaponState = newState; // 새로운 상태 재생 StartCoroutine(weaponState.ToString()); } private void Update() { if (attackTarget != null) { RotateToTarget(); } //Tower01 animation if (level == 1) { animatorTower01.SetBool("Tower01Level2", true); animatorTower02.SetBool("Tower02Level2", true); animatorTower03.SetBool("Tower03Level2", true); animatorTower04.SetBool("Tower04Level2", true); animatorTower05.SetBool("Tower05Level2", true); animatorTower06.SetBool("Tower06Level2", true); animatorTower08.SetBool("Tower08Level2", true); animatorTower09.SetBool("Tower09Level2", true); animatorTower10.SetBool("Tower10Level2", true); } if (level == 2) { animatorTower01.SetBool("Tower01Level3", true); animatorTower02.SetBool("Tower02Level3", true); animatorTower03.SetBool("Tower03Level3", true); animatorTower04.SetBool("Tower04Level3", true); animatorTower05.SetBool("Tower05Level3", true); animatorTower06.SetBool("Tower06Level3", true); animatorTower08.SetBool("Tower08Level3", true); animatorTower09.SetBool("Tower09Level3", true); animatorTower10.SetBool("Tower10Level3", true); } } private void RotateToTarget() { // 원점으로부터의 거리와 수평축으로부터의 각도를 이용해 위치를 구하는 극 좌표계 이용 // 각도 = arctan(y/x) // x, y 변위값 구하기 float dx = attackTarget.position.x - transform.position.x; float dy = attackTarget.position.y - transform.position.y; // x, y 변위값을 바탕으로 각도 구하기 // 각도가 radian 단위이기 때문에 Mathf.Rad2Deg를 곱해 도 단위를 구함 /*float degree = Mathf.Atan2(dy, dx) * Mathf.Rad2Deg; transform.rotation = Quaternion.Euler(0, 0, degree);*/ if (attackTarget.position.x - transform.position.x < 0) { spriteRenderer.flipX = false; } else { spriteRenderer.flipX = true; } } private IEnumerator SearchTarget() { while (true) { // 현재 타워에 가장 가까이 있는 공격 대상(적) 탐색 attackTarget = FindClosestAttackTarget(); if (attackTarget != null) { if (weaponType == WeaponType.Cannon) { ChangeState(WeaponState.TryAttackCannon); } else if (weaponType == WeaponType.Laser) { ChangeState(WeaponState.TryAttackLaser); } else if (weaponType == WeaponType.Random) { ChangeState(WeaponState.TryAttackCannon); } else if (weaponType == WeaponType.Penetration) { ChangeState(WeaponState.TryAttackLaser); } } yield return null; } } private IEnumerator TryAttackCannon() { while (true) { // target을 공격하는게 가능한지 검사 if (IsPossibleToAttackTarget() == false) { ChangeState(WeaponState.SearchTarget); break; } // attackRate 시간만큼 대기 yield return new WaitForSeconds(towerTemplate.weapon[level].rate); // 캐논 공격 (발사체 생성) SpawnProjectile(); } } private IEnumerator TryAttackLaser() { // 레이저, 레이저 타격 효과 활성화 EnableLaser(); while (true) { // target을 공격하는게 가능한지 검사 if (IsPossibleToAttackTarget() == false) { // 레이저, 레이저 타격 효과 비활성화 DisableLaser(); ChangeState(WeaponState.SearchTarget); break; } // 레이저 공격 SpawnLaser(); yield return null; } } public void OnBuffAroundTower() { // 현재 맵에 배치된 "Tower" 태그를 가진 모든 오브젝트 탐색 GameObject[] towers = GameObject.FindGameObjectsWithTag("Tower"); for (int i = 0; i < towers.Length; ++i) { TowerWeapon weapon = towers[i].GetComponent(); // 이미 버프를 받고 있고, 현재 버프 타워의 레벨보다 높은 버프이면 패스 if (weapon.BuffLevel > Level) { continue; } // 현재 버프 타워와 다른 타워의 거리를 검사해서 범위 안에 타워가 있으면 if (Vector3.Distance(weapon.transform.position, transform.position) <= towerTemplate.weapon[level].range) { // 공격이 가능한 캐논, 레이저 타워이면 if (weapon.WeaponType == WeaponType.Cannon || weapon.WeaponType == WeaponType.Laser || weapon.WeaponType == WeaponType.Random) { // 버프에 의해 공격력 증가 weapon.AddedDamage = weapon.Damage * (towerTemplate.weapon[level].buff); // 타워가 받고 있는 버프 레벨 설정 weapon.BuffLevel = Level; weapon.buffTower = this; } else if (weapon.WeaponType == WeaponType.Penetration) { weapon.addedPenetration = weapon.Penetration * (towerTemplate.weapon[level].buff); weapon.BuffLevel = Level; weapon.buffTower = this; } } } } private IEnumerator OnGoldTower() { while (true) { yield return new WaitForSeconds(towerTemplate.weapon[level].rate); if (enemySpawner.EnemyList.Count != 0) { playerGold.CurrentGold += towerTemplate.weapon[level].gold; } } } private Transform FindClosestAttackTarget() { // 제일 가까이 있는 적을 찾기 위해 최초 거리를 최대한 크게 설정 float closestDistSqr = Mathf.Infinity; // EnemySpawner의 EnemyList에 있는 현재 맵에 존재하는 모든 적 검사 for (int i = 0; i < enemySpawner.EnemyList.Count; ++i) { float distance = Vector3.Distance(enemySpawner.EnemyList[i].transform.position, transform.position); // 현재 검사중인 적과의 거리가 공격범위 내에 있고, 현재까지 검사한 적보다 거리가 가까우면 if (distance <= towerTemplate.weapon[level].range && distance <= closestDistSqr) { closestDistSqr = distance; attackTarget = enemySpawner.EnemyList[i].transform; } } return attackTarget; } private bool IsPossibleToAttackTarget() { // target이 있는지 검사 (다른 발사체에 의해 제거, Goal 지점까지 이동해 삭제 등) if (attackTarget == null) { return false; } // target이 공격 범위 안에 있는지 검사 (공격 범위를 벗어나면 새로운 적 탐색) float distance = Vector3.Distance(attackTarget.position, transform.position); if (distance > towerTemplate.weapon[level].range) { attackTarget = null; return false; } return true; } private void SpawnProjectile() { GameObject clone = Instantiate(projectilePrefab, spawnPoint.position, Quaternion.identity); // 생성된 발사체에게 공격대상(attackTarget) 정보 제공 // 공격력 = 타워 기본 공격력 + 버프에 의해 추가된 공격력 float damage = towerTemplate.weapon[level].damage + AddedDamage; if (weaponType == WeaponType.Random) { clone.GetComponent().Setup(attackTarget, (damage + AddedDamage) * Random.Range(LowerDamage, UpperDamage)); } else { clone.GetComponent().Setup(attackTarget, damage); } } private void EnableLaser() { lineRenderer.gameObject.SetActive(true); hitEffect.gameObject.SetActive(true); } private void DisableLaser() { lineRenderer.gameObject.SetActive(false); hitEffect.gameObject.SetActive(false); } private void SpawnLaser() { Vector3 direction = attackTarget.position - spawnPoint.position; RaycastHit2D[] hit = Physics2D.RaycastAll(spawnPoint.position, direction, towerTemplate.weapon[level].range, targetLayer); // 같은 방향으로 여러 개의 광선을 쏴서 그 중 현재 attackTarget과 동일한 오브젝트를 검출 for (int i = 0; i < hit.Length; ++i) { if (hit[i].transform == attackTarget) { // 선의 시작지점 lineRenderer.SetPosition(0, spawnPoint.position); // 선의 목표지점 lineRenderer.SetPosition(1, new Vector3(hit[i].point.x, hit[i].point.y, 0) + Vector3.back); // 타격 효과 위치 설정 hitEffect.position = hit[i].point; // 적 체력 감소 (1초에 damage만큼 감소) // 공격력 = 타워 기본 공격력 + 버프에 의해 추가된 공격력 if (weaponType == WeaponType.Laser) { float damage = towerTemplate.weapon[level].damage + AddedDamage; attackTarget.GetComponent().TakeDamage(damage * Time.deltaTime); } else if (weaponType == WeaponType.Penetration) { float penetration = towerTemplate.weapon[level].penetration + AddedPenetration; attackTarget.GetComponent().TakeDownArmour(penetration * Time.deltaTime); } } } } public bool Upgrade() { // 타워 업그레이드에 필요한 골드가 충분한지 검사 if (playerGold.CurrentGold < towerTemplate.weapon[level + 1].cost) { return false; } // 타워 레벨 증가 level++; // 타워 외형 변경 (Sprite) spriteRenderer.sprite = towerTemplate.weapon[level].sprite; // 골드 차감 playerGold.CurrentGold -= towerTemplate.weapon[level].cost; // 무기 속성이 레이저이면 if (weaponType == WeaponType.Laser) { // 레벨에 따라 레이저의 굵기 설정 lineRenderer.startWidth = 0.1f + level * 0.05f; lineRenderer.endWidth = 0.3f + level * 0.1f; } // 타워가 업그레이드 될 때 모든 버프 타워의 버프 효과 갱신 // 현재 타워가 버프 타워인 경우, 현재 타워가 공격 타워인 경우 towerSpawner.OnBuffAllBuffTowers(); return true; } public void Sell() { // 골드 증가 playerGold.CurrentGold += towerTemplate.weapon[level].sell; // 현재 타일에 다시 타워 건설이 가능하도록 설정 ownerTile.IsBuildTower = false; // 현재 맵에 배치된 "Tower" 태그를 가진 모든 오브젝트 탐색 GameObject[] towers = GameObject.FindGameObjectsWithTag("Tower"); for (int i = 0; i < towers.Length; ++i) { towers[i].GetComponent().BuffLevel = 0; towers[i].GetComponent().AddedDamage = 0; towers[i].GetComponent().AddedPenetration = 0; } towerSpawner.OnBuffAllBuffTowers(); // 타워 파괴 Destroy(gameObject); } } /* * File : TowerWeapon.cs * Desc * : 적을 공격하는 타워 무기 * * Functions * : ChangeState() - 코루틴을 이용한 FSM에서 상태 변경 함수 * : RotateToTarget() - target 방향으로 회전 * : SearchTarget() - 현재 타워에 가장 근접한 적 탐색 * : TryAttackCannon() - target으로 설정된 대상에게 캐논 공격 * : TryAttackLaser() - target으로 설정된 대상에게 레이저 공격 * : FindClosestAttackTarget() - 현재 타워에 가장 근접한 공격 대상(적) 탐색 * : IsPossibleToAttackTarget() - AttackTarget이 있는지, 공격 가능한지 검사 * : SpawnProjectile() - 캐논 발사체 생성 * : EnableLaser() - 레이저, 레이저 타격 효과 활성화 * : DisableLaser() - 레이저, 레이저 타격 효과 비활성화 * : SpawnLaser() - 레이저 공격, 레이저 타격 효과, 적 체력 감소 * : Upgrade() - 타워 업그레이드 * : Sell() - 타워 판매 * */