나는 오버워치2의 투사체 시스템을 2D Unity를 통해 직접 구현해보기로 했다. 땅 혹은 벽에 맞으면 사라지고, 적에게 맞으면 사라지며 데미지를 입히고, 아군에게 맞으면 회복시킬 수 있는 기능 등을 구현할 예정이다. 일단 오늘은 스페이스 바를 통해 발사하고, 아군에게 맞으면 관통하며, 적에게 맞으면 체력이 닳고, 이를 체력 UI를 통해 확인할 수 있도록 하겠다.
public class PlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("공격!");
}
}
}
2D Object > Sprite, 파란색, Collider 추가, Tag는 "Ally"public class Enemy : MonoBehaviour
{
public int hp = 3;
public int maxHp = 3;
public Slider hpSlider;
void Start()
{
if (hpSlider != null)
{
hpSlider.maxValue = maxHp;
hpSlider.value = hp;
}
}
public void OnHit(Bullet bullet)
{
hp -= bullet.damage;
if (hpSlider != null)
hpSlider.value = hp;
if (hp <= 0)
{
Destroy(gameObject);
}
}
}
public Slider hpSlider;?
2D Object > Sprite, 이름: Ground, Scale: (20, 2), Y = -4BoxCollider2D + Rigidbody2D(Static) 추가Rigidbody2D(Dynamic, Gravity Scale: 1) + CircleCollider2D(Is Trigger 체크)public class Shooter : MonoBehaviour
{
public GameObject bulletPrefab;
public float shootForce = 50f;
public Vector2 offset = new Vector2(1f, 0f);
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Fire();
}
}
void Fire()
{
Vector2 spawnPos = (Vector2)transform.position + offset;
GameObject bullet = Instantiate(bulletPrefab, spawnPos, Quaternion.identity);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.AddForce(Vector2.right * shootForce, ForceMode2D.Impulse);
}
}
AddForcerb.AddForce(Vector2.right * shootForce, ForceMode2D.Impulse);
public class Bullet : MonoBehaviour
{
public int damage = 1;
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Enemy"))
{
Enemy enemy = collision.GetComponent<Enemy>();
if (enemy != null)
{
enemy.OnHit(this);
Destroy(gameObject);
return;
}
}
if (collision.CompareTag("Ally"))
{
Ally ally = collision.GetComponent<Ally>();
if (ally != null && ally.hp < ally.maxHp)
{
Destroy(gameObject);
return;
}
return; // 체력이 가득 찬 아군은 관통
}
Destroy(gameObject);
}
}
public class GameManager : MonoBehaviour
{
public GameObject enemyPrefab;
public GameObject allyPrefab;
public GameObject sliderPrefab;
public Vector2 enemySpawnPosition = new Vector2(4, -2.5f);
public Vector2 allySpawnPosition = new Vector2(-2, -2.5f);
void Start()
{
SpawnUnit(enemyPrefab, enemySpawnPosition);
SpawnUnit(allyPrefab, allySpawnPosition);
}
void SpawnUnit(GameObject unitPrefab, Vector2 position)
{
GameObject unit = Instantiate(unitPrefab, position, Quaternion.identity);
Transform canvas = GameObject.Find("Canvas").transform;
GameObject slider = Instantiate(sliderPrefab, canvas);
slider.GetComponent<HealthUI>().target = unit.transform;
if (unit.TryGetComponent<Enemy>(out var enemy))
{
enemy.hpSlider = slider.GetComponent<Slider>();
}
else if (unit.TryGetComponent<Ally>(out var ally))
{
ally.hpSlider = slider.GetComponent<Slider>();
}
}
}
public class HealthUI : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0, 2.0f, 0);
void Start()
{
if (target == null && transform.parent != null)
target = transform.parent;
}
void Update()
{
if (target == null) return;
Vector3 screenPos = Camera.main.WorldToScreenPoint(target.position + offset);
transform.position = screenPos;
}
}
public class Ally : MonoBehaviour
{
public int hp = 5;
public int maxHp = 5;
public Slider hpSlider;
void Start()
{
if (hpSlider != null)
{
hpSlider.maxValue = maxHp;
hpSlider.value = hp;
}
}
public void Heal(int amount)
{
hp = Mathf.Min(hp + amount, maxHp);
if (hpSlider != null)
{
hpSlider.value = hp;
}
}
}
최종적으로 현재까지 구현한 바는 다음과 같다.

slider.GetComponent<HealthUI>().target = unit.transform 코드를 통해 유닛과 Slider를 정확히 연결함.Ally 자동 회복 시스템
일정 시간 경과 또는 특정 이벤트(예: 투사체 적중) 발생 시 체력이 자동 회복되도록 한다.
UI 정보 확장
체력 외에도 유닛의 이름, 상태 효과, 속성 등을 Slider UI 상단 또는 하단에 표시하도록 개선한다.
데미지 이펙트 및 애니메이션
Bullet 또는 유닛이 데미지를 입었을 때 피격 이펙트나 애니메이션이 재생되도록 한다.
투사체 조준 시스템
Player가 마우스 방향 또는 자동 타겟팅을 기반으로 투사체를 발사하도록 개선한다.
바닥 충돌 처리
Bullet이 일정 시간 경과 또는 바닥(Ground)과 충돌했을 때 파티클 또는 효과와 함께 소멸하도록 처리한다.
쉽지 않다. 강의 내용을 되짚어 보면서 이것저것 갖다붙이고, 구글링도 해가면서 구현 방법을 찾았는데, 오류도 많이 발생했다. 하지만, 실제로 구현되는 걸 보고 다시 스크립트들을 보니 이해가 되는 부분도 있었다. 이런 식으로 직접 만들어 보는 방식이 확실히 도움이 되는 것 같다.