지난번에 이어 게임을 만들어보자.
이번에 참고할 강의는 아래 강의이다.
https://www.youtube.com/watch?v=r8JTwe6dewU&list=PLFt_AvWsXl0ctd4dgE1F8g3uec4zKNRV0&index=21
이번 강의에서는 무기 반동, 재장전 기능을 추가해본다.
시작해보자.
먼저 총의 조준 정밀도를 수정해보자.
지금은 총구 위치와 조준점의 위치가 살짝 어긋나 있는 것을 볼 수 있다.
이렇게 위치가 어긋나있다.
Gun, GunController, Player 스크립트에서 이를 보정하자.
| Gun 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour
{
public enum FireMode {Auto, Burst, Single}; // 총 격발 모드 (자동, 점사, 단발)
public FireMode fireMode; // 격발 모드 변수
public Transform[] projectileSpawn; // 총알 발사 위치 배열
public Projectile projectile; // 총알
public float msBetweenShots = 100; // 총알 발사 시간 간격
public float muzzleVelocity = 35; // 총알 발사 속도
public int burstCount; // 점사 모드 총알 발사 개수
public int shotsRemainingInBurst; // 점사 모드 남은 총알
public Transform shell; // 탄피 레퍼런스
public Transform shellEjection; // 탄피 배출 위치 레퍼런스
MuzzleFlash muzzleFlash; // 총구 화염 레퍼런스
float nextShotTime; // 다음 총알 발사 시간
bool triggerReleasedSinceLastShoot; // 지난 발사 후 방아쇠(마우스 좌클릭)을 놓았는지
void Start() {
muzzleFlash = GetComponent<MuzzleFlash>(); // 총구 화염 레퍼런스 할당
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
}
void Shoot() // 총알 발사 메소드
{
if(Time.time > nextShotTime) // 총알 발사 시간이 지났는지 확인
{
if(fireMode == FireMode.Burst) { // 무기 발사 모드가 점사인 경우
if(shotsRemainingInBurst == 0) { // 발사 할 남은 총알이 없는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
shotsRemainingInBurst--; // 남은 총알 개수 감소
}
else if (fireMode == FireMode.Single) { // 무기 발사 모드가 단발인 경우
if (!triggerReleasedSinceLastShoot) { // 이미 총알을 발사 하고 방아쇠를 당기고(마우스 좌클릭 중) 있는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
}
for (int i = 0; i < projectileSpawn.Length; i++) { // 총알 발사 개수만큼 반복
nextShotTime = Time.time + msBetweenShots / 1000; // 다음 총알 발사 시작 시간 저장
Projectile newProjectile = Instantiate(projectile, projectileSpawn[i].position, projectileSpawn[i].rotation) as Projectile;
// 총알 프리팹을 통해 오브젝트를 인스턴스화 하여 생성.
newProjectile.SetSpeed(muzzleVelocity); // 총알 발사 속도 설정
}
Instantiate(shell, shellEjection.position, shellEjection.rotation); // 탄피 인스턴스화 하여 생성
muzzleFlash.Activate(); // 총구 화염 생성
}
}
// ■ 에임 보정 메소드
public void Aim(Vector3 aimPoint) {
transform.LookAt(aimPoint); // 전달받은 위치를 보도록 방향 설정
}
// ■ 방아쇠(마우스 좌클릭)를 누르고 있을 때
public void OnTriggerHold() {
Shoot(); // 총알 발사
triggerReleasedSinceLastShoot = false; // 방아쇠 잡은 상태로
}
// ■ 방아쇠(마우스 좌클릭)를 놓았을 때
public void OnTriggerRelease() {
triggerReleasedSinceLastShoot = true; // 방아쇠 놓은 상태로
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
}
}
에임 보정 메소드 추가.
| GunController 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunController : MonoBehaviour {
public Transform weaponHold; // 플레이어 손(총)위치
public Gun startingGun; // 시작 총
Gun equippedGun; // 착용하는 총
private void Start() {
if (startingGun != null) { // 시작 총이 설정된 경우
EquipGun(startingGun); // 시작 총을 착용
}
}
// ■ 총 착용 메소드
public void EquipGun(Gun gunToEquip) {
if (equippedGun != null) { // 착용중인 총이 있다면
Destroy(equippedGun.gameObject); // 착용중인 총(오브젝트)를 제거(파괴)
}
equippedGun = Instantiate(gunToEquip, weaponHold.position, weaponHold.rotation) as Gun;
// 착용중인 총에 전달받은 총 오브젝트를 인스턴스화하여 생성
equippedGun.transform.parent = weaponHold;
// 착용중인 총의 위치를 설정, 부모 오브젝트를 설정하여 위치를 부모 오브젝트의 위치로.
}
// ■ 방아쇠 당김 메소드
public void OnTriggerHold()
{
if(equippedGun != null) // 착용중인 총이 있다면
{
equippedGun.OnTriggerHold(); // 방아쇠 당김
}
}
// ■ 방아쇠 놓음 메소드
public void OnTriggerRelease()
{
if (equippedGun != null) // 착용중인 총이 있다면
{
equippedGun.OnTriggerRelease(); // 방아쇠 놓음
}
}
// ■ 무기 높이 반환 메소드
public float GunHeight {
get {
return weaponHold.position.y; // 무기의 y좌표(높이)반환
}
}
// ■ 에임 보정 메소드
public void Aim(Vector3 aimPoint) {
if (equippedGun != null) { // 착용중인 총이 있다면
equippedGun.Aim(aimPoint); // 착용한 총의 에임 보정 메소드 호출
}
}
}
에임 보정 메소드 추가
| Player 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(GunController))] // GunController 스크립트를 포함
[RequireComponent(typeof(PlayerController))] // PlayerController 스크립트를 포함
public class Player : LivingEntity
{
public float moveSpeed = 5.0f; // 플레이어 이동속도
public Crosshair crosshairs; // 조준점 레퍼런스
Camera viewCamera; // 카메라
PlayerController controller; // 플레이어 컨트롤러
GunController gunController; // 총 컨트롤러
protected override void Start() { // override 로 부모 클래스의 메소드를 재정의.
base.Start(); // base 를 통해 부모 클래스의 기존 메소드를 호출.
controller = GetComponent<PlayerController>();
// 현재 오브젝트의 플레이어 컨트롤러를 가져옴
gunController = GetComponent<GunController>();
// 현재 오브젝트의 총 컨트롤러를 가져옴
viewCamera = Camera.main;
// 카메라
}
void Update() {
// 이동 입력
Vector3 moveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxis("Vertical"));
// GetAxisRaw 로 스무딩 X
Vector3 moveVelocity = moveInput.normalized * moveSpeed;
// normalized 로 단위벡터로 변환, 이동 속도를 계산
controller.Move(moveVelocity);
// 이동 속도를 이동 메소드로 전달
// 방향 입력
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
// 카메라 -> 마우스 커서 위치로 레이 발사.
Plane groundPlane = new Plane(Vector3.up, Vector3.up * gunController.GunHeight);
// 평면의 법선벡터 생성.
float rayDistance;
// 위에서 쏜 레이와 평면의 교차지점까지 거리
if (groundPlane.Raycast(ray, out rayDistance)) {
// 레이가 평면과 교차하는 경우 교차지점까지의 거리를 rayDistance 에 저장.
Vector3 point = ray.GetPoint(rayDistance);
// GetPoint 와 교차지점까지의 거리로 교차지점의 위치를 저장.
// 카메라부터 교차지점까지 선으로 표시 : Debug.DrawLine(ray.origin, point, Color.red);
controller.LookAt(point);
// 플레이어 컨트롤러의 방향 전환 메소드에 교차 지점(방향) 전달.
crosshairs.transform.position = point;
// 조준점 위치 설정
crosshairs.DetectTarget(ray);
// 조준점 레이캐스트
if((new Vector2(point.x, point.z) - new Vector2(transform.position.x, transform.position.z)).sqrMagnitude > 1) {
// 조준점(커서)와 플레이어 사이 거리가 1 이상일 때
gunController.Aim(point);
// 총 에임 보정 위치 전달
}
}
// 무기 조작 입력
if (Input.GetMouseButton(0)) // 마우스 왼쪽 버튼 클릭 시
{
gunController.OnTriggerHold(); // 방아쇠 당김 메소드 호출
}
if (Input.GetMouseButtonUp(0)) // 마우스 왼쪽 버튼 클릭 후 손을 뗄 시
{
gunController.OnTriggerRelease(); // 방아쇠 놓음 메소드 호출
}
}
}
조준점이 플레이어와 일정 거리 이상일 때 에임(총구 위치)조정하도록 함.
이제 실행해보자.
총구 위치와 조준점이 맞는 것을 볼 수 있다.
이번에는 무기 반동과 재장전 기능을 추가해보자.
총알을 발사할 때 마다 무기가 뒤쪽과 위쪽으로 조금씩 움직이게 하자.
총알 수 제한을 주고 총알 사용하고 R 키를 누르거나 다 쓰면 재장전되도록 하자.
총의 반동, 총알 수 제한을 위해 Gun, GunController 스크립트를 수정하고
R 키 입력을 받기 위해 Player 스크립트를 수정하자.
| Gun 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour
{
public enum FireMode {Auto, Burst, Single}; // 총 격발 모드 (자동, 점사, 단발)
public FireMode fireMode; // 격발 모드 변수
public Transform[] projectileSpawn; // 총알 발사 위치 배열
public Projectile projectile; // 총알
public float msBetweenShots = 100; // 총알 발사 시간 간격
public float muzzleVelocity = 35; // 총알 발사 속도
public int burstCount; // 점사 모드 총알 발사 개수
public int projectilesPerMag; // 탄창 총알 수
public float reloadTime = .3f; // 재장전 속도
[Header("Recoil")]
public Vector2 kickMinMax = new Vector2(.05f, .2f); // 뒤쪽 반동 최소, 최대값
public Vector2 recoilAngleMinMax = new Vector2(3, 5); // 위쪽 반동 최소, 최대값
public float recoilMoveSettleTime = .1f; // 뒤쪽 반동 회복 시간
public float recoilRotationSettleTime = .1f; // 뒤쪽 반동 회복 시간
[Header("Effect")]
public Transform shell; // 탄피 레퍼런스
public Transform shellEjection; // 탄피 배출 위치 레퍼런스
MuzzleFlash muzzleFlash; // 총구 화염 레퍼런스
float nextShotTime; // 다음 총알 발사 시간
bool triggerReleasedSinceLastShoot; // 지난 발사 후 방아쇠(마우스 좌클릭)을 놓았는지
int shotsRemainingInBurst; // 점사 모드 남은 총알
int projectilesRemainingInMag; // 탄창에 남아있는 총알
bool isReloading; // 장전중인지 여부
Vector3 recoilSmoothDampVelocity; // 반동 회복 속도
float recoilRotSmoothDampVelocity; // 반동 회복 속도
float recoilAngle; // 위쪽 반동 각도
void Start() {
muzzleFlash = GetComponent<MuzzleFlash>(); // 총구 화염 레퍼런스 할당
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
projectilesRemainingInMag = projectilesPerMag; // 기본 탄창 총알 개수 설정
}
void LateUpdate() {
transform.localPosition = Vector3.SmoothDamp(transform.localPosition, Vector3.zero, ref recoilSmoothDampVelocity, recoilMoveSettleTime);
// SmoothDamp 를 통해 부드러운 움직임으로 현재 위치에서 원지점인 zero 까지 지정한 속도로 지정한 시간동안 움직인다.
recoilAngle = Mathf.SmoothDamp(recoilAngle, 0, ref recoilRotSmoothDampVelocity, recoilRotationSettleTime);
// 위와 같이 각도 또한 부드럽게 원지점으로 복구하도록 함
transform.localEulerAngles = transform.localEulerAngles + Vector3.left * recoilAngle;
// 위쪽 반동 각도 적용
if(!isReloading && projectilesRemainingInMag == 0) { // 재장전중이 아니고 탄창에 총알이 없는 경우
Reload(); // 재장전 메소드 호출
}
}
void Shoot() // 총알 발사 메소드
{
if(!isReloading && Time.time > nextShotTime && projectilesRemainingInMag > 0) {
// 장전중이지 않고, 총알 발사 시간이 지났고, 탄창에 총알이 있는지 확인
// □ 무기 모드 설정에 따른 발사
if (fireMode == FireMode.Burst) { // 무기 발사 모드가 점사인 경우
if(shotsRemainingInBurst == 0) { // 발사 할 남은 총알이 없는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
shotsRemainingInBurst--; // 남은 총알 개수 감소
}
else if (fireMode == FireMode.Single) { // 무기 발사 모드가 단발인 경우
if (!triggerReleasedSinceLastShoot) { // 이미 총알을 발사 하고 방아쇠를 당기고(마우스 좌클릭 중) 있는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
}
// □ 총알 생성 및 발사
for (int i = 0; i < projectileSpawn.Length; i++) { // 총알 발사 개수만큼 반복
if(projectilesRemainingInMag == 0) { // 탄창에 총알이 없는 경우
break; // 루프 탈출(총알 생성 취소)
}
projectilesRemainingInMag--; // 탄창에서 총알 감소
nextShotTime = Time.time + msBetweenShots / 1000; // 다음 총알 발사 시작 시간 저장
Projectile newProjectile = Instantiate(projectile, projectileSpawn[i].position, projectileSpawn[i].rotation) as Projectile;
// 총알 프리팹을 통해 오브젝트를 인스턴스화 하여 생성.
newProjectile.SetSpeed(muzzleVelocity); // 총알 발사 속도 설정
}
// □ 무기 이펙트(탄피, 총구 화염)
Instantiate(shell, shellEjection.position, shellEjection.rotation); // 탄피 인스턴스화 하여 생성
muzzleFlash.Activate(); // 총구 화염 생성
// □ 무기 반동
transform.localPosition -= Vector3.forward * Random.Range(kickMinMax.x, kickMinMax.y); // 뒤쪽 반동값 설정
recoilAngle += Random.Range(recoilAngleMinMax.x, recoilAngleMinMax.y); // 위쪽 반동값 설정
recoilAngle = Mathf.Clamp(recoilAngle, 0, 30); // 무기 반동 각도 최소, 최대 범위 지정
}
}
// ■ 재장전 메소드
public void Reload() {
if(!isReloading && projectilesRemainingInMag != projectilesPerMag) { // 재장전중이 아니고 탄창에 총알이 꽉 차있지 않은 경우
StartCoroutine(AnimateReload()); // 재장전 애니메이션 코루틴 실행
}
}
// ■ 재장전 애니메이션 코루틴
IEnumerator AnimateReload() {
isReloading = true; // 재장전중으로 변경
yield return new WaitForSeconds(.2f); // 다음 코드 실행까지 0.2 초 대기
float reloadSpeed = 1 / reloadTime; // 재장전 속도 설정
float percent = 0; // 애니메이션 실행 비율
Vector3 initialRot = transform.localEulerAngles; // 기존 각도
float maxReloadAngle = 30; // 최대 반동 각도 설정
while (percent < 1) {
percent += Time.deltaTime * reloadSpeed; // 애니메이션 실행 비율 설정
float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4; // 재장전 각도를 위한 보간값 설정
float reloadAngle = Mathf.Lerp(0, maxReloadAngle, interpolation); // 재장전 각도 설정
transform.localEulerAngles = initialRot + Vector3.left * reloadAngle; // 각도 적용
yield return null; // 다음 프레임 스킵
}
isReloading = false; // 재장전중이 아님으로 변경
projectilesRemainingInMag = projectilesPerMag; // 탄창 총알 수 설정
}
// ■ 에임 보정 메소드
public void Aim(Vector3 aimPoint) {
if(!isReloading) { // 재장전중이 아닐 때
transform.LookAt(aimPoint); // 전달받은 위치를 보도록 방향 설정
}
}
// ■ 방아쇠(마우스 좌클릭)를 누르고 있을 때
public void OnTriggerHold() {
Shoot(); // 총알 발사
triggerReleasedSinceLastShoot = false; // 방아쇠 잡은 상태로
}
// ■ 방아쇠(마우스 좌클릭)를 놓았을 때
public void OnTriggerRelease() {
triggerReleasedSinceLastShoot = true; // 방아쇠 놓은 상태로
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
}
}
| GunController 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunController : MonoBehaviour {
public Transform weaponHold; // 플레이어 손(총)위치
public Gun startingGun; // 시작 총
Gun equippedGun; // 착용하는 총
private void Start() {
if (startingGun != null) { // 시작 총이 설정된 경우
EquipGun(startingGun); // 시작 총을 착용
}
}
// ■ 총 착용 메소드
public void EquipGun(Gun gunToEquip) {
if (equippedGun != null) { // 착용중인 총이 있다면
Destroy(equippedGun.gameObject); // 착용중인 총(오브젝트)를 제거(파괴)
}
equippedGun = Instantiate(gunToEquip, weaponHold.position, weaponHold.rotation) as Gun;
// 착용중인 총에 전달받은 총 오브젝트를 인스턴스화하여 생성
equippedGun.transform.parent = weaponHold;
// 착용중인 총의 위치를 설정, 부모 오브젝트를 설정하여 위치를 부모 오브젝트의 위치로.
}
// ■ 방아쇠 당김 메소드
public void OnTriggerHold()
{
if(equippedGun != null) // 착용중인 총이 있다면
{
equippedGun.OnTriggerHold(); // 방아쇠 당김
}
}
// ■ 방아쇠 놓음 메소드
public void OnTriggerRelease()
{
if (equippedGun != null) // 착용중인 총이 있다면
{
equippedGun.OnTriggerRelease(); // 방아쇠 놓음
}
}
// ■ 무기 높이 반환 메소드
public float GunHeight {
get {
return weaponHold.position.y; // 무기의 y좌표(높이)반환
}
}
// ■ 에임 보정 메소드
public void Aim(Vector3 aimPoint) {
if (equippedGun != null) { // 착용중인 총이 있다면
equippedGun.Aim(aimPoint); // 착용한 총의 에임 보정 메소드 호출
}
}
// ■ 재장전 메소드
public void Reload() {
if (equippedGun != null) { // 착용중인 총이 있다면
equippedGun.Reload(); // 착용한 총의 재장전 메소드 호출
}
}
}
| Player 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(GunController))] // GunController 스크립트를 포함
[RequireComponent(typeof(PlayerController))] // PlayerController 스크립트를 포함
public class Player : LivingEntity
{
public float moveSpeed = 5.0f; // 플레이어 이동속도
public Crosshair crosshairs; // 조준점 레퍼런스
Camera viewCamera; // 카메라
PlayerController controller; // 플레이어 컨트롤러
GunController gunController; // 총 컨트롤러
protected override void Start() { // override 로 부모 클래스의 메소드를 재정의.
base.Start(); // base 를 통해 부모 클래스의 기존 메소드를 호출.
controller = GetComponent<PlayerController>();
// 현재 오브젝트의 플레이어 컨트롤러를 가져옴
gunController = GetComponent<GunController>();
// 현재 오브젝트의 총 컨트롤러를 가져옴
viewCamera = Camera.main;
// 카메라
}
void Update() {
// 이동 입력
Vector3 moveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxis("Vertical"));
// GetAxisRaw 로 스무딩 X
Vector3 moveVelocity = moveInput.normalized * moveSpeed;
// normalized 로 단위벡터로 변환, 이동 속도를 계산
controller.Move(moveVelocity);
// 이동 속도를 이동 메소드로 전달
// 방향 입력
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
// 카메라 -> 마우스 커서 위치로 레이 발사.
Plane groundPlane = new Plane(Vector3.up, Vector3.up * gunController.GunHeight);
// 평면의 법선벡터 생성.
float rayDistance;
// 위에서 쏜 레이와 평면의 교차지점까지 거리
if (groundPlane.Raycast(ray, out rayDistance)) {
// 레이가 평면과 교차하는 경우 교차지점까지의 거리를 rayDistance 에 저장.
Vector3 point = ray.GetPoint(rayDistance);
// GetPoint 와 교차지점까지의 거리로 교차지점의 위치를 저장.
// 카메라부터 교차지점까지 선으로 표시 : Debug.DrawLine(ray.origin, point, Color.red);
controller.LookAt(point);
// 플레이어 컨트롤러의 방향 전환 메소드에 교차 지점(방향) 전달.
crosshairs.transform.position = point;
// 조준점 위치 설정
crosshairs.DetectTarget(ray);
// 조준점 레이캐스트
if((new Vector2(point.x, point.z) - new Vector2(transform.position.x, transform.position.z)).sqrMagnitude > 1) {
// 조준점(커서)와 플레이어 사이 거리가 1 이상일 때
gunController.Aim(point);
// 총 에임 보정 위치 전달
}
}
// 무기 조작 입력
if (Input.GetMouseButton(0)) // 마우스 왼쪽 버튼 클릭 시
{
gunController.OnTriggerHold(); // 방아쇠 당김 메소드 호출
}
if (Input.GetMouseButtonUp(0)) // 마우스 왼쪽 버튼 클릭 후 손을 뗄 시
{
gunController.OnTriggerRelease(); // 방아쇠 놓음 메소드 호출
}
if (Input.GetKeyDown(KeyCode.R)) // 키보드 R 버튼 누를 경우
{
gunController.Reload(); // 재장전 메소드 호출
}
}
}
이제 실행해보자.
위와 같이 총을 쏠 때 반동이 생기고, 지정한 수의 총알을 다 쓰면
다시 장전하는 것을 볼 수 있다.
다음 강의에서는 총을 수정하고 UI를 추가해본다.
'Unity > 게임개발' 카테고리의 다른 글
[Unity] 게임 개발 일지 | 탑다운 슈팅 22 (0) | 2023.12.14 |
---|---|
[Unity] 게임 개발 일지 | 탑다운 슈팅 21 (0) | 2023.12.13 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 19 (1) | 2023.12.11 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 18 (0) | 2023.12.11 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 17 (0) | 2023.12.08 |