
Scanner.cs
플레이어 주변의 목표(적, 아이템 등)를 탐색하는 스크립트로, 가장 가까운 적을 찾아 자동 조준하는 역할을 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Scanner : MonoBehaviour
{
public float scanRange; // 스캔 반경(범위)
public LayerMask targetLayer; // 스캔 대상 레이어(감지 대상이 속한)
public RaycastHit2D[] targets; // 스캔 결과 리스트(감지된 적들을 저장할 배열)
public Transform nearstTarget; // 가장 가까운 대상(적)
// 주기적으로 주변을 탐색
void FixedUpdate()
{
/* .CircleCastAll = 원형 탐색, 원형의 Cast를 쏘고 모든 결과를 반환하는 함수
* 속성 설명
* 1. 캐스팅 시작 위치
* 2. 원의 반지름
* 3. 캐스팅 방향(Vector2.zero = 특정 방향 없이 정적인 원형 탐색)
* 4. 캐스팅 거리(0 = 추가 거리 없이 현재 위치에서 즉시 스캔)
* 5. 대상 레이어 */
// 현재 위치에서 반지름 scanRange 크기의 원을 생성하여 해당 범위 안의 targetLayer에 속한 오브젝트를 찾아 리스트에 저장
targets = Physics2D.CircleCastAll(transform.position, scanRange, Vector2.zero, 0, targetLayer);
nearstTarget = GetNearest(); // 가장 가까운 적 찾기
}
// 가장 가까운 적 찾기
Transform GetNearest()
{
Transform result = null; // 가장 가까운 적
float diff = 100; // 최소한의 거리, 큰 값을 넣어 초기 설정
// RaycastHit = 충돌한 오브젝트 대한 2D 물리 충돌 정보를 담는 구조체
foreach (RaycastHit2D target in targets) // 스캔 결과 오브젝트를 하나씩 접근
{
Vector3 myPos = transform.position; // 플레이어 위치
Vector3 targetPos = target.transform.position; // 감지된 적 위치
// .Distance = 둘의 거리를 자동 계산해주는 함수
float curDiff = Vector3.Distance(myPos, targetPos);// 위 둘의 거리 계산
// 가장 가까운 적 갱신
if (curDiff < diff) // 현재 적과의 거리가 기존 최소 거리보다 가까우면
{
diff = curDiff; // 더 짧은 거리로 최소 거리 업데이트
result = target.transform; // 해당 적을 가장 가까운 적으로 설정
}
}
return result; // 가장 가까운 적 반환
}
}


Bullet.cs
총알을 관리하는 스크립트로, 플레이어가 발사하는 원거리 공격(투사체, 예: 총알, 마법탄 등)을 관리하는 역할을 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public float damage; // 데미지
public int per; // 관통력
/* per > 0 -> 일정 횟수까지만 관통
* per == -1 -> 무한 관통(예: 레이저 같은 무기)
* per == -100 -> 근접 무기 판정(투사체가 아니라 타격 효과만 존재) */
Rigidbody2D rigid; // 원거리 공격의 물리적 움직임 설정을 위해 가져옴
void Awake()
{
rigid = GetComponent<Rigidbody2D>();
}
// 총알의 초기 설정
public void Init(float damage, int per, Vector3 dir) // dir = 원거리 공격이 날아갈 방향
{
// this = 해당 Class의 변수로 접근
this.damage = damage;
this.per = per;
if (per >= 0) // 관통력이 -1(무한)이 아닌 경우에만
{
// velocity = 속도
rigid.velocity = dir * 15f; // 특정 방향(dir)으로 15의 속도로 이동
}
}
// 적과 충돌 시 처리(관통력 설정)
void OnTriggerEnter2D(Collider2D collision)
{
// || = or
if (!collision.CompareTag("Enemy") || per == -100) // 충돌한 오브젝트가 적이 아니라면 or 근접 무기라면
return;
per--;
if (per < 0) // 관통력이 사라지면
{
rigid.velocity = Vector2.zero; // 재활용을 위해 속도 초기화
gameObject.SetActive(false); // 오브젝트 풀링으로 재사용하기 위해 오브젝트 비활성화
}
}
// 화면을 벗어나면(일정 거리를 벗어난) 투사체 비활성화
void OnTriggerExit2D(Collider2D collision)
{
if (!collision.CompareTag("Area") || per == -100) // 총알이 화면 밖으로 나가면 or 근접 무기라면
return;
gameObject.SetActive(false); // 비활성화하여 리소스 절약
}
}


Weapon.cs
플레이어가 사용하는 무기를 관리하는 스크립트로, 장착하는 무기가 공격 방식에 맞게 동작하도록 설정하는 역할을 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Weapon : MonoBehaviour
{
public int id; // 무기의 고유 ID
public int prefabId; // Object Pool에서 무기 프리팹을 가져오기 위한 ID
public float damage; // 무기 데미지
public int count; // 무기 개수
public float speed; // 회전 속도(근거리), 연사 속도(원거리)
float timer; // 발사 시간 간격
Player player; // 플레이어 캐릭터 정보
void Awake()
{
// 이전 코드의 단점 = 무기가 플레이어 오브젝트의 자식이어야만 작동
// player = GetComponentInParent<Player>(); // 부모 오브젝트에 있는 플레이어 컴포넌트 가져오기
// 현재 코드의 장점 = 무기가 어디에 있든지 플레이어 정보를 가져올 수 있음
// 게임 내 플레이어를 전역적으로 가져올 수 있어 더 안정적
player = GameManager.instance.player;
/* 기존 방식은 부모-자식 관계를 강제하는 구조였기 때문에 유연성이 떨어짐
* 새로운 방식은 언제 어디서든 플레이어를 가져올 수 있어 유지보수하기 쉽고, 코드 안정성이 증가함
* 플레이어가 무조건 하나만 존재하는 게임이라면 GameManager를 통해 접근하는 게 더 나은 선택 */
}
void Update()
{
if (!GameManager.instance.isLive) // 게임이 끝나면 무기 작동 종료
return;
switch (id)
{
case 0: // 근거리 무기
// Vector3.back = (0, 0, 1) -> Z축을 기준으로 회전
// speed값을 곱해 회전 속도 조절
transform.Rotate(Vector3.back * speed * Time.deltaTime);
break;
default: // 원거리 무기
timer += Time.deltaTime;
if (timer > speed) // 일정 시간이 지나면 실행(speed 만큼 연사 속도 설정)
{
timer = 0f; // 초기화해서 다시 0부터 카운트
Fire();
}
break;
}
}
// 무기 레벨업
public void LevelUp(float damage, int count)
{
this.damage = damage * Character.Damage; // 캐릭터의 능력치에 따라 데미지 조정
this.count += count; // 무기 개수 증가
/* 근거리 무기는 플레이어 주변을 회전하며 공격하는 방식이기 때문에
* 무기의 개수가 늘어나거나 줄어들면 균등한 간격으로 재배치 필요 */
if (id == 0)
Batch();
/* BroadcastMessage() 함수란?
* 특정 함수를 자신과 모든 자식 오브젝트에게 전달하는 함수
* 현재 오브젝트와 그 아래 계층(Hierarchy) 모든 오브젝트가 해당 함수를 실행하려고 시도함
* 사용 방법 = gameObject.BroadcastMessage("함수명", 매개변수(생략가능), 옵션);
* SendMessageOptions.DontRequireReceiver 옵션은?
* 객채에 해당 함수가 존재하지 않아도 오류 없이 무시
* 무기뿐만 아니라 다른 장비에도 능력치 변화가 반영되어야 하므로
* 한 번에 모든 장비의 ApplyGear() 함수를 실행시키기 위해 사용 */
// 플레이어의 모든 자식 오브젝트(무기, 장비 등)에 ApplyGear() 함수가 있다면 실행
player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
}
// 무기 초기화
public void Init(ItemData data)
{
// 기본 설정
name = "Weapon " + data.itemId; // itemId를 가져와 무기의 이름을 "Weapon [ID]" 형태로 설정
transform.parent = player.transform; // 현재 무기의 부모를 플레이어로 설정
transform.localPosition = Vector3.zero; // 무기의 위치를 플레이어 기준 (0, 0, 0)으로 설정
// 속성 설정, data에 저장된 무기의 기본 공격력과 개수를 적용
id = data.itemId;
damage = data.baseDamage * Character.Damage;
count = data.baseCount + Character.Count;
// 프리팹 ID 찾기
// Object Pool에서 해당 무기의 발사체(projectile) 프리팹을 찾아 그 index를 prefebId에 저장
for (int index = 0; index < GameManager.instance.pool.prefabs.Length; index++)
{
if (data.projectile == GameManager.instance.pool.prefabs[index])
{
prefabId = index;
break;
}
}
// id에 따라 다른 초기화 방식(무기를 다르게 설정)
switch (id)
{
case 0: // 근거리 무기
speed = 150 * Character.WeaponSpeed; // 회전 속도 설정
Batch(); // 무기 생성
break;
default: // 원거리 무기
speed = 0.5f * Character.WeaponRate; // 연사 속도(총알 생성 속도) 설정
break;
}
// 무기에 맞는 손 설정
Hand hand = player.hands[(int)data.itemType]; // data.itemType이 enum값 이므로 (int)로 강제 형변환
hand.spriter.sprite = data.hand; // 현재 장착한 무기의 손 이미지(Sprite 타입)
hand.gameObject.SetActive(true); // 오브젝트 활성화
player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
}
// 근거리 무기 배치
void Batch()
{
for (int index=0; index < count; index++) // count만큼 무기 생성
{
Transform bullet; // 무기를 저장할 변수 선언
// 기존 오브젝트를 먼저 재사용하고, 부족하면 새로 생성
// childCount = 자신의 자식 오브젝트 개수 확인하는 속성
if (index < transform.childCount)
{
// 기존 무기가 있으면 GetChild(index)로 가져오기
bullet = transform.GetChild(index);
}
else
{
bullet = GameManager.instance.pool.Get(prefabId).transform; // 부족하면 Object Pool에서 새로운 무기를 가져와 생성
// .parent = 현재 오브젝트의 자식으로 설정, 플레이어를 따라 움직이게 만듦
bullet.parent = transform; // 현재 무기를 부모로 설정
}
// Vector3.zero == (0, 0, 0)
bullet.localPosition = Vector3.zero; // 위치를 플레이어와 동일한 위치로 초기화
bullet.localRotation = Quaternion.identity; // 회전값 초기화(무기 방향 재설정)
// count = 4일 때 -> index 0 = 0도, index 1 = 90도, index 2 = 180도, index 3 = 270도
Vector3 rotVec = Vector3.forward * 360 * index / count; // 360도를 count로 나눠 각도를 계산
bullet.Rotate(rotVec); // 해당 각도만큼 회전, 이게 없으면 모든 무기가 같은 방향을 보게 됨
// bullet.up == (0, 1, 0)
/* Space.World = 월드 좌표계, 전체 게임 씬(혹은 세계)에서의 절대적인 위치
* Space.Self = 부모 객체를 기준으로 오브젝트의 위치, 회전, 크기를 정의
* Self 사용 시 무기가 현재 회전 상태에 따라 이동 방향이 달라질 수 있어
* World를 사용하여 회전 상태에 관계없이 고정된 방향으로 이동하게 함 */
bullet.Translate(bullet.up * 1.5f, Space.World); // 무기와 플레이어가 겹치지 않게 일정 거리만큼 떨어지도록 설정
// 방향이 필요 없는 근거리 무기이므로 Vector3.zero로 설정
bullet.GetComponent<Bullet>().Init(damage, -100, Vector3.zero); // 생성된 무기의 데미지와 관통력 설정
}
}
// 원거리 공격 발사
void Fire()
{
if (!player.scanner.nearstTarget) // 공격할 적이 없으면 함수 종료
return;
Vector3 targetPos = player.scanner.nearstTarget.position; // 가장 가까운 적 위치
// 이 벡터는 거리에 대한 정보(크기)도 포함하므로, normalized(현재 Vector의 방향은 유지하고 크기를 1로 변환) 사용
Vector3 dir = (targetPos - transform.position).normalized; // 플레이어에서 적 위치 까지의 방향
Transform bullet = GameManager.instance.pool.Get(prefabId).transform; // Object Pool에서 미리 생성된 총알 가져옴
bullet.position = transform.position; // 총알이 플레이어 위치에서 시작하도록 설정
// FromToRotation(A, B) = A 방향을 B 방향으로 회전시키는 함수\
// 총알의 기본 방향이 Vector.up(위쪽 (0, 1, 0))이므로 dir(적 방향)으로 회전
bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir); // 지정된 축을 중심으로 target을 향해 회전하는 함수
bullet.GetComponent<Bullet>().Init(damage, count, dir); // 총알의 속성 설정
AudioManager.instance.PlaySfx(AudioManager.Sfx.Range);
}
}
'Unity > Undead Survivor' 카테고리의 다른 글
스크립터블 오브젝트(Scriptable Object) / 능력 업그레이드(Ability Upgrade) (0) | 2025.03.18 |
---|---|
열거형(enum) / 월드 좌표(World Position)와 스크린 좌표(Screen Position) / HUD(Head-Up Display) (0) | 2025.03.18 |
배열과 리스트의 차이 / 오브젝트 풀링(Object Pooling) (0) | 2025.03.17 |
코루틴(Corutine) / 적 생성(Enemy, Spawn) (0) | 2025.03.13 |
유니티(Unity)의 주요 이벤트 함수(Event Functions) (0) | 2025.03.12 |