유닛의 공격범위를 만들고, 공격범위 내에 몬스터가 감지되면 몬스터에 공격데미지를 입히게 한다
각각의 유닛 공격범위 스크립트는 따로 지정하였다
참고: https://velog.io/@yangju058/타워디펜스게임-12.-몬스터-공격-범위
가장 중요한 것 : 몬스터든 유닛이든 제잎 앞에 있는 객체부터 데미지를 입힐 수 있다
나중에 세로 공격 범위가 넓은 유닛이나 몬스터는 로직을 다르게 짜야겠지만, 지금의 유닛과 몬스터는 모두 가로 공격 범위 뿐
MonsterGizmo스크립트와 동일하게 OverlapBoxAll메소드와 공격범위를 확인하기 위한 OnDrawGizmo()를 사용하여 기즈모를 그리고 확인한다
근거리 공격은 몬스터1,2와 동일한 방법으로 데미지 처리를 하면 되지만, 원거리 공격의 경우는 다르다.
원거리 공격의 경우 : 유닛의 공격 범위에 몬스터가 들어왔을 경우, 데미지를 입힐 수 있는 무기를 발사한다
-> 즉, 데미지 처리를 무기에서 작성해주어야 하는 것
현재 지정한 유닛은 2개. -> 나머지도 차차 추가 할 계획이다
Unit1AttackGizmo : 간이 사격 장치 (활)
Unit2AttackGizmo : 소형 대포 (눈덩이)


유닛의 공격범위에 닿은 객체들을 배열에 저장
-> 레이어 11 (Monster)

태그가 Monster인 게임 오브젝트와 충돌했고, 아직 활(무기)이 발사되지 않았다면 monsterDetected true
아직 활이 발사되지 않았다면 활 생성 시작
-> Unit1Arrow 스크립트에 함수 정의
-> Unit1Arrow 스크립트에는 화살 생성, 화살 파괴 로직이 작성되어있다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Unit1AttackGizmo : MonoBehaviour
{
public Transform Pos;
private Vector2 BoxSize = new Vector2(13.2f, 1.3f);
private Vector2 Offset = new Vector2(7.4f, 0f);
public Unit1Arrow unit1Arrow;
Unit1Anime Unit1Anime;
private bool hasArrow = false;
private void Start()
{
// 스크립트 가져오기
unit1Arrow = GetComponent<Unit1Arrow>();
Unit1Anime = GetComponent<Unit1Anime>();
}
void Update()
{
Vector2 boxCenter = (Vector2)Pos.position + Offset;
// 카메라의 뷰포트 좌표를 가져오기
Camera mainCamera = Camera.main;
Vector3 viewportPosition = mainCamera.WorldToViewportPoint(boxCenter);
if (viewportPosition.x >= 0 && viewportPosition.x <= 1 && viewportPosition.y >= 0 && viewportPosition.y <= 1)
{
// OverlapBoxAll(시작위치, 박스크기, 레이어)
Collider2D[] collider2Ds = Physics2D.OverlapBoxAll(boxCenter, BoxSize, 11);
bool monsterDetected = false;
foreach (Collider2D collider in collider2Ds)
{
if (collider.CompareTag("Monster") && !hasArrow)
{
//Debug.Log("몬스터와 충돌");
// 몬스터와 충돌함
monsterDetected = true;
break;
}
}
if (monsterDetected && !unit1Arrow.hasArrow)
{
//Debug.Log("몬스터 감지: 화살 생성 시작");
unit1Arrow.StartArrowCreation(); // 화살 생성 반복 시작
Unit1Anime.StartAttack();
}
else if (!monsterDetected)
{
//Debug.Log("몬스터 없음: 화살 생성 중단");
unit1Arrow.StopArrowCreation(); // 반복 중단
Unit1Anime.EndAttack();
}
}
else
{
//Debug.Log("충돌 범위가 카메라 화면 밖");
}
}
IEnumerator ResetArrowFlag()
{
yield return new WaitForSeconds(1f); // Reset flag after 1 second
hasArrow = false;
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube((Vector2)Pos.position + Offset, BoxSize);
}
}
화살을 생성하는 ArrowCreate 함수
화살을 파괴하는 ArrowDestroyed 함수
Unit1AttackGizmo에서 공격범위에 몬스터가 없다면 활 생성 중지
-> StopArrowCreation 함수
공격 범위에 들어왔을때 화살이 계속 생성되면 안된다
-> 중복 호출을 방지하기 위해서 Invoke()함수를 사용한다

Invoke()Coroutine과 비슷하게 시간을 지연시킬 수 있는 함수이다
내가 사용할 것은 InvokeRepeating()으로, 일정 시간 후에 반복적으로 해당 함수를 호출하는 것이다
사용 방법 : InvokeRepeating(호출 함수 이름, 실행 시작 시간, 반복 시간 간격)
IsInvoking()은 메서드가 현재 Invoke 또는 InvokeRepeating으로 실행되고 있는지를 확인하는 함수
-> true면 이미 실행 중이고, false면 실행되지 않고 있다는 뜻
아래의 코드는 ArrowCreate 함수가 IsInvoking가 false면 이제 일정 간격으로 활을 반복 생성 하라는 의미

유니티는 Invoke 함수 대신 Coroutine 함수를 사용할 것을 권장한다. Coroutine 함수가 성능 항샹, 가독성, 유지보수에 용이하기 때문이다.
-> 나중에 꼭 꼭 꼭 바꾸자 Coroutine으로
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Unit1Arrow : MonoBehaviour
{
public GameObject arrow;
public Transform pos;
public bool hasArrow = false;
public void ActiveFalse()
{
hasArrow = false;
}
public void StartArrowCreation()
{
if (!IsInvoking("ArrowCreate")) // 중복 호출 방지
{
InvokeRepeating("ArrowCreate", 1f, 2f); // 2초 간격으로 반복
}
}
public void StopArrowCreation()
{
if (IsInvoking("ArrowCreate"))
{
CancelInvoke("ArrowCreate"); // 반복 중단
}
}
public void ArrowCreate()
{
if (!hasArrow)
{
Debug.Log("활 생성");
GameObject newArrow = Instantiate(arrow, pos.position, transform.rotation);
// 생성된 화살을 유닛의 자식으로 설정
newArrow.transform.parent = this.transform;
hasArrow = true;
// 생성된 화살에 Destroy 이벤트 연결
Arrow unit1Arrow = newArrow.GetComponent<Arrow>();
if (unit1Arrow != null)
{
unit1Arrow.onDestroyed += ArrowDestroyed;
}
}
}
private void ArrowDestroyed()
{
Debug.Log("활 파괴, hasArrow 초기화");
hasArrow = false; // 화살이 파괴될 때 다시 생성 가능하도록 설정
}
}
생성된 활이 몬스터에 닿으면 데미지를 입히게 해주는 스크립트
활이 몬스터에 닿으면 활이 파괴되게 하는 함수 포함
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class Arrow : MonoBehaviour
{
public Action onDestroyed;
Unit1Arrow unit1Arrow;
private float speed = 3;
public int damage = 20;
// 충돌 확인
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Monster"))
{
Debug.Log("몬스터와 닿아서 활 비활성화");
MonsterStat monsterStat = other.GetComponent<MonsterStat>();
if (monsterStat != null)
{
if (monsterStat is Monster1Stat)
{
Monster1Stat monster1Stat = (Monster1Stat)monsterStat;
monster1Stat.TakeDamage(damage);
}
else if (monsterStat is Monster2Stat)
{
Monster2Stat monster2Stat = (Monster2Stat)monsterStat;
monster2Stat.TakeDamage(damage);
}
else if (monsterStat is Monster3Stat)
{
Monster3Stat monster3Stat = (Monster3Stat)monsterStat;
monster3Stat.TakeDamage(damage);
}
}
gameObject.SetActive(false);
}
}
private void Start()
{
unit1Arrow = GetComponent<Unit1Arrow>();
Invoke("DestroyArrow", 4);
}
void Update()
{
transform.Translate(transform.right * speed * Time.deltaTime);
}
void DestroyArrow()
{
//Debug.Log("활 파괴");
onDestroyed?.Invoke();
// Collider 비활성화
GetComponent<Collider2D>().enabled = false;
Destroy(gameObject);
}
void DestroyArrow2()
{
// Collider 비활성화
GetComponent<Collider2D>().enabled = false;
Destroy(gameObject);
}
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Unit2AttackGizmo : MonoBehaviour
{
public Transform Pos;
private Vector2 BoxSize = new Vector2(5.8f, 1.3f);
private Vector2 Offset = new Vector2(3.7f, 0f);
public Unit2Snow unit2Snow;
Unit2Anime Unit2Anime;
private bool hasSnow = false;
private void Start()
{
// 스크립트 가져오기
unit2Snow = GetComponent<Unit2Snow>();
Unit2Anime = GetComponent<Unit2Anime>();
}
void Update()
{
Vector2 boxCenter = (Vector2)Pos.position + Offset;
// 카메라의 뷰포트 좌표를 가져오기
Camera mainCamera = Camera.main;
Vector3 viewportPosition = mainCamera.WorldToViewportPoint(boxCenter);
if (viewportPosition.x >= 0 && viewportPosition.x <= 1 && viewportPosition.y >= 0 && viewportPosition.y <= 1)
{
// OverlapBoxAll(시작위치, 박스크기, 레이어)
Collider2D[] collider2Ds = Physics2D.OverlapBoxAll(boxCenter, BoxSize, 11);
bool monsterDetected = false;
foreach (Collider2D collider in collider2Ds)
{
if (collider.CompareTag("Monster") && !hasSnow)
{
//Debug.Log("몬스터와 충돌");
// 몬스터와 충돌함
monsterDetected = true;
break;
}
}
if (monsterDetected && !unit2Snow.hasSnow)
{
//Debug.Log("몬스터 감지: 눈 생성 시작");
unit2Snow.StartSnowCreation(); // 눈 생성 반복 시작
Unit2Anime.StartAttack();
}
else if (!monsterDetected)
{
//Debug.Log("몬스터 없음: 눈 생성 중단");
unit2Snow.StopSnowCreation(); // 반복 중단
Unit2Anime.EndAttack();
}
}
else
{
//Debug.Log("충돌 범위가 카메라 화면 밖");
}
}
IEnumerator ResetSnowFlag()
{
yield return new WaitForSeconds(1f); // Reset flag after 1 second
hasSnow = false;
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube((Vector2)Pos.position + Offset, BoxSize);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Unit2Snow : MonoBehaviour
{
public GameObject snow;
public Transform pos;
public bool hasSnow = false;
public void ActiveFalse()
{
hasSnow = false;
}
public void StartSnowCreation()
{
if (!IsInvoking("SnowCreate")) // 중복 호출 방지
{
InvokeRepeating("SnowCreate", 1f, 2f); // 2초 간격으로 반복
}
}
public void StopSnowCreation()
{
if (IsInvoking("SnowCreate"))
{
CancelInvoke("SnowCreate"); // 반복 중단
}
}
public void SnowCreate()
{
if (!hasSnow)
{
Debug.Log("눈 생성");
GameObject newSnow = Instantiate(snow, pos.position, transform.rotation);
// 생성된 화살을 유닛의 자식으로 설정
newSnow.transform.parent = this.transform;
hasSnow = true;
// 생성된 화살에 Destroy 이벤트 연결
Snow unit2snow = newSnow.GetComponent<Snow>();
if (unit2snow != null)
{
unit2snow.onDestroyed += SnowDestroyed;
}
}
}
private void SnowDestroyed()
{
Debug.Log("눈 파괴, hasSnow 초기화");
hasSnow = false; // 눈이 파괴될 때 다시 생성 가능하도록 설정
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class Snow : MonoBehaviour
{
public Action onDestroyed;
Unit2Snow unit2snow;
private float speed = 2;
public int damage = 20;
// 충돌 확인
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Monster"))
{
Debug.Log("몬스터와 닿아서 눈 비활성화");
MonsterStat monsterStat = other.GetComponent<MonsterStat>();
if (monsterStat != null)
{
if (monsterStat is Monster1Stat)
{
Monster1Stat monster1Stat = (Monster1Stat)monsterStat;
monster1Stat.TakeDamage(damage);
}
else if (monsterStat is Monster2Stat)
{
Monster2Stat monster2Stat = (Monster2Stat)monsterStat;
monster2Stat.TakeDamage(damage);
}
else if (monsterStat is Monster3Stat)
{
Monster3Stat monster3Stat = (Monster3Stat)monsterStat;
monster3Stat.TakeDamage(damage);
}
}
gameObject.SetActive(false);
}
}
private void Start()
{
unit2snow = GetComponent<Unit2Snow>();
Invoke("DestroySnow", 4);
}
void Update()
{
transform.Translate(transform.right * speed * Time.deltaTime);
}
void DestroySnow()
{
//Debug.Log("눈 파괴");
onDestroyed?.Invoke();
// Collider 비활성화
GetComponent<Collider2D>().enabled = false;
Destroy(gameObject);
}
void DestroySnow2()
{
// Collider 비활성화
GetComponent<Collider2D>().enabled = false;
Destroy(gameObject);
}
}
