마우스 클릭 시 화면 중앙(크로스헤어) 방향으로 총알을 발사하고,
적에 맞으면 죽는 기능을 구현한 과정을 기록한다.
1. 두 개의 스크립트 추가
| PlayerShooting.cs | 마우스 클릭 감지 → 총알 생성 → 발사 | Player 오브젝트 |
| Bullet.cs | 날아가기 → 충돌 감지 → 사라지기 | Bullet 프리팹 |
(프리팹은 밑에서 설명)
2. Fire 액션 추가 (Input Actions)
지난번 Move, Jump, Sprint를 만들었던 것처럼 Fire 액션을 추가했다.

설정은
- Action 이름: Fire
- Action Type: Button
- Binding: Left Button [Mouse]
3. Bullet 프리팹 만들기
프리팹(Prefab)이란?
동일한 오브젝트를 다시 생성할 수 있도록 만든 오브젝트 템플릿(틀)을 의미한다. 여기서는 총알 하나를 만들어두고 발사할 때마다 복제된 총알이 나가도록 할 것이다.
만드는 과정
- Hierarchy → 3D Object → Sphere
- Scale: 0.2, 0.2, 0.2 (작은 총알 크기)
- Rigidbody 추가 → Use Gravity 해제
- Bullet.cs 스크립트 추가
- Hierarchy에서 Project 탭 Assets/Prefabs로 드래그 → 프리팹 생성!
- Hierarchy에서 원본 삭제

Use Gravity 해제 이유
Use Gravity 켜면:
발사! → → → ↘ ↘ ↓ ↓ (포물선으로 떨어짐)
Use Gravity 끄면:
발사! → → → → → → → (직선으로 날아감)
일단 이번 프로젝트에서는 직선으로 그대로 날아가도록 해보았다.
4. PlayerShooting.cs - 발사 로직
Raycast (레이캐스트)
레이캐스트는 눈에 안 보이는 광선을 쏴서 어디를 겨냥하고 있는지 찾는 것이다.
카메라 ──── Ray(광선) ────→ 벽에 부딪힘 (hit.point = 조준 위치)
👁️ 🎯
화면 중앙에서 앞으로 보이지 않는 선을 쏜다.
뭔가에 부딪히면 → 그 지점이 조준 위치
아무것도 없으면 → 100m 앞 허공
코드 흐름은 읽기 → 계산 → 적용
// === 1단계: 읽기 — 어디를 조준하는지 찾기 ===
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
// (0.5, 0.5) = 화면 정중앙
if (Physics.Raycast(ray, out hit))
targetPoint = hit.point; // 부딪힌 지점
else
targetPoint = ray.GetPoint(100f); // 허공이면 100m 앞
// === 2단계: 계산 — 발사 방향 구하기 ===
Vector3 fireDirection = (targetPoint - firePoint.position).normalized;
// 004 문서에서 배운 벡터 뺄셈: 목표 - 나 = 나→목표 방향
// === 3단계: 적용 — 총알 생성 및 발사 ===
Vector3 spawnPosition = firePoint.position + fireDirection * 1.5f;
GameObject bullet = Instantiate(bulletPrefab, spawnPosition, Quaternion.LookRotation(fireDirection));
rb.linearVelocity = fireDirection * bulletSpeed;
Fire Point란?
"총알이 어디서 생성될지" 위치. Main Camera를 연결하면 카메라 위치에서 발사된다.

5. Bullet.cs - 총알의 동작
// 활성화되면 3초 후 자동 파괴 예약
private void OnEnable()
{
Invoke(nameof(Deactivate), lifeTime);
}
// 뭔가에 부딪히면
private void OnCollisionEnter(Collision collision)
{
// Player는 무시 (자기 자신을 쏘면 안 되니까)
if (collision.gameObject.CompareTag("Player")) return;
// Enemy면 죽이기 (STEP 1에서 만든 메서드!)
Enemy enemy = collision.gameObject.GetComponent<Enemy>();
if (enemy != null)
enemy.Die();
// 총알 파괴
Deactivate();
}
총알의 생명주기
생성 → 날아감 → 뭔가에 부딪힘 → 사라짐
또는
3초 지남 → 자동으로 사라짐
사용자가 마우스 좌클릭을 하면 다음처럼 오브젝트가 복제되어 생성된다!

6. 이슈 기록
이슈 1. 화면에 총알이 안 보이고 플레이어가 떠오름
원인: 총알이 카메라 위치(Player 안쪽)에서 생성되면서 Player의 CharacterController와 충돌 → 서로 밀어냄
해결:
- 총알 생성 위치를 카메라 앞 1.5m로 이동: firePoint.position + fireDirection * 1.5f
- Bullet.cs에서 Player 태그와 충돌 시 무시: CompareTag("Player") return
이슈 2. 총알이 벽을 뚫고 지나감 (터널링)
원인: 총알이 너무 빨라서 프레임 사이에 벽을 통과
프레임1: ●─────벽─────
프레임2: ─────벽─────● (벽을 뛰어넘음!)
해결: Bullet 프리팹의 Rigidbody에서 Collision Detection을 Discrete → Continuous로 변경

- Discrete(이산): 프레임마다 위치만 확인 → 총알이 빠르면 통과될 수 있음
- Continuous(연속): 프레임 사이 경로도 확인 → 정확하게 충돌 감지
이슈 3. 총알이 적(오리)을 통과함
원인: Enemy 오브젝트에 Collider가 없었다!

충돌 판정이 일어나려면 Collider(충돌 영역)가 있어야 한다.
벽(Cube)은 만들 때 자동으로 Box Collider가 붙지만, 외부 모델은 자동으로 안 붙는 경우가 있다.
해결: Enemy에 Capsule Collider 오리 크기에 맞게 조절하여 추가

7. 충돌 판정의 조건 정리
유니티에서 두 오브젝트가 충돌하려면 다음의 조건을 충족해야 한다.
오브젝트 A: Collider 필요
오브젝트 B: Collider + Rigidbody 필요 (둘 중 하나에 Rigidbody가 있어야 함)
여기에 더해, 둘 중 하나라도 Is Trigger = true면 물리 충돌(밀림/튕김)은 발생 하지 않는다. Is Trigger 는 "이 콜라이더는 물리 계산에서 제외하고 이벤트만 받을게"라는 뜻이기 때문이다.
단, Trigger여도 겹쳤다는 사실 자체는 감지된다. 하나라도 Trigger면 '충돌'이 아니라 '통과 + 이벤트'로 바뀌는 셈.
이번 프로젝트의 경우에는 다음과 같다.
- Bullet: Sphere Collider + Rigidbody ✅
- Enemy: Capsule Collider ✅ (Bullet에 Rigidbody가 있으니 OK)
- 벽(Cube): Box Collider ✅ (자동 생성됨)
8. 이번 작업에서 배운 것 요약
- Raycast = 보이지 않는 광선으로 조준 위치를 찾는 기술
- Instantiate = 프리팹을 복제해서 씬에 생성
- 총알 생성 위치를 Player 바깥으로 해야 자기 충돌 방지
- Collision Detection: Continuous = 빠른 물체의 터널링 방지
- 충돌에는 양쪽 모두 Collider가 필요 (+ 한쪽은 Rigidbody 필수)
- 외부 모델은 Collider가 자동으로 안 붙을 수 있으므로 확인 후 추가해주자.
- CompareTag()로 특정 대상과의 충돌을 무시할 수 있다
지금은 총알을 Instantiate()로 매번 복제 생성하고 Destroy()로 파괴한다. 다음에는 총알을 매번 생성하는게 아니라 "미리 만들어두고 재사용"하는 방식으로 성능을 개선해보자.
'Unity 궁둥이 쪼물쪼물' 카테고리의 다른 글
| 007. 넉백(Knockback) 구현 (0) | 2026.04.06 |
|---|---|
| 006. 총알에 오브젝트 풀링 적용하기 (0) | 2026.04.06 |
| 004. Enemy(적) 애니메이션 구현 (0) | 2026.04.06 |
| 003. 적 AI 구현 & 맵 꾸미기 (0) | 2026.04.06 |
| 002. CharacterController로 플레이어 움직임 구현 (0) | 2026.04.03 |