유니티 공부.. 원거리 무기 만들기, 골드메탈님
unity 유니티 공부 2024. 12. 8. 18:22 |https://www.youtube.com/watch?v=07q9RUTRq4M&t=4s
일단 총알부터 만들자
엠티 오브젝트 생성
이름은 Bullet HandGun
x y z 0 0 0 으로 해두고
Trail Renderer 추
트레일 렌더러 세부설정
width 빨간 그래프
0.5 부터 시작해서 점점 감소되게 설정
time 0.3 정도
min vertex distance 0
마테리알은 default line 적용.
리지드 바디, sphere 콜라이더도 만들기
스피어 콜라이더에
x y z 0 0 0 으로 설정하고
radius 조금 줄여줌
ctrl D 눌러서 복붙후
아래 이름은 Bullet SubMachineGun 으로 명명
+
네모난 탄피 오브젝트 만들기
(대충 cube 써서 만들어도 될 듯)
탄피, bullet case 는
바닥에 떨어지는 이펙트 용도임
탄피에 리지드 바디, 박스 콜라이더 컴포넌트추
콜라이더 크기 오브젝트에 맞추기
+
Bullet 이라는 스크립트 하나 생성
만든 불렛 오브젝트들 세 개 다중선택후
컴포넌트 스크립트 Bullet 적용
일단 지금까지 스크립트 내용은 :
using UnityEngine;
public class Bullet : MonoBehaviour
{
public int damage;
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Floor")
{
Destroy(gameObject, 3); // 부딪친후 3초 후 삭제
}
else if (collision.gameObject.tag == "Wall")
{
Destroy(gameObject, 0);
}
}
}
오브젝트 명 | Damage |
Bullet HandGun | 20 |
Bullet SubMachineGun | 7 |
Bullet Case | 0 |
인스펙터 창에서 이렇게 데미지 설정
+
프리팹 만든 후
x y z 0 0 0 으로 설정
세 bullet 들 프리팹으로 만들었으면 하이어라키 창에서 지움
+
플레이어의 발사 구현, 애니메이션 설정
Animator 창에서
플레이어 오브젝트 열어서 Shot 애니메이션을 드래그.
예전 Swing 만들듯이 만듦
Any State 에서 우클 화살 트랜지션 연결후 , Exit 로 우클 연결
파라메터 + 로 추가후 trigger 섵낵
이름은 doShot
Any state 에서 Shot 애니로 들어오는 트랜지션의
컨디션은 doShot 으로 지정
+
플레이어의 발사 구현, 스크립트 만지기
Player 스크립트 로 간 후
anim.SetTrigger(equipWeapon.type
== Weapon.Type.Melee ? "doSwing": "doShot");
웨펀 타입이 밀리인가?
맞으면 doSwing 을 하기
아니면 doShot 을 하기
라는 뜻임
하이어라키 창에서
플레이어 오른손 아래 gun 과 sub machine gun 의
타입을 range 로 설정
총알이 데미지 있는거지 총은 데미지가 없으니
데미지는 0
melee area 설정 안함
trail effect 설정 안
핸드건 rate, 공속은 0.4 로 설정
서브 머신건은 rate 0.15 로 설정
총에겐
총알이 발사되는 위치, 탄피가 나오는 위치
가 필요하다
weapon 스크립트에 추가함 :
public Transform bulletPos; //총알생성위치
public GameObject bullet;
public Transform bulletCasePos; //탄피생성위치
public GameObject bulletCase;
플레이어 아래 엠티 오브젝트 추가
Bullet Pos 로 이름 변경
총알 나가는 위치 즈음으로
Bullet Pos 의 x y z 를 이동시킴
일단 대략적인 위치를 지정함
탄피가 나가는 위치는
오른손 아래 , 웨펀 포인트 아래, 핸드건 아래 크리에이트 엠티
이름은 Case Pos 로 명명
Case Pos 오브젝트 위치는 총알 옆 탄피 배출구 쪽으로 이동
위치 이동이 힘들면 Global / Local 로 전환
서브머신건 아래 자식오브젝트도 동일하게 Case Pos 만들기
이제 핸드건, 서브머신건의 스크립트에서 변수를 지정
Vector3 caseVec = bulletCasePos.forward * -10
파란 Z 축 반대 방향으로 탄피를 나가겠다는 뜻
음수를 곱함
Vector3 caseVec = bulletCasePos.forward * Random.Range(-3, -2)
+ Vector3.up * Random.Range(2, 3)
탄피가 z 축 반대 방향으로 나가는데
-2 ~ -3 의 랜덤값으로 나간다
근데 위쪽으로 2~3의 랜덤값으로 튄다
는 뜻
caseRigid.AddForce(caseVec, ForceMode.Impulse);
나가는 힘을 주겠다는 뜻
~~~
근데 비주얼 스튜디오에서 오류남
instantBullet 인가 intantBullet 인가 빨간줄 생김
그리고 velocity 에 취소선이 생김
~~~
벽에 Wall 태그 넣기
태그 Wall 만든 후 적용
테스트 모드 해보기
무기 앞에서 e 눌러 무기 줍기
2 번 3번 키로 무기 스왑
마우스 왼손으로 발사
+
재장전 구현하기
if (type == Type.Range && curAmmo > 0 )
무기 타입이 원거리 이고
지금 탄환이 0보다 클 때 (1이상일때)
라는 뜻
curAmmo--;
curAmmo 라는 변수를 한개씩 줄어들게 한다는 뜻
인풋 매니저
재장전 Reload
포지티브 버튼 키보드 R 로 설정
if(equipWeapon == null) return;
if(equipWeapon.type == Weapon.Type.Melee) return;
if (ammo == 0) return;
만약 무기가 없으면 하지 않겠다
만약 무기 타입이 근거리면 하지 않겠다
만약 총알이 0 개면 하지 않겠다
라는 뜻.
재장전 조건에 쓰일 거임
animator 에서 재장전
shot 애니 만들듯이 만들기
파라메터 트리거, doReload 로 함
핸드건 cur ammo 변수, max ammo 변수 설정
처음엔 탄이 다 충전되어있음을 연출시키려고 7 , 7로 설정
서브 머신건은 30 , 30 으로 설정
+
마우스 위치 로 총알 발사시키기
public Camera followCamera;
플레이어 스크립트에 추
플레이어 오브젝트에서 팔로우 카메라 변수를 메인 카메라를 연결
// 마우스에 의한 회전 구현
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
화면상 마우스 위치를 반영한다는 뜻
out 함수는 리턴저럼 값을 저장하는 개념
nextVec.y = 0;
플레이어가 y 회전, 눕혀지는 걸 방지하는 것
void Turn()
{
// 키보드에 의한 회전 구현
transform.LookAt(transform.position + moveVec);
// 마우스에 의한 회전 구현
if (fDown)
{
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100))
{
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 0;
transform.LookAt(transform.position + nextVec);
}
}
}
마우스 왼쪽 버튼, 파이어 다운 을 누르면
플레이어가 마우스 위치 쪽을 바라보겠다 라는 뜻
~~~~~~
지금까지
풀 웨펀 스크립트
using Febucci.UI.Effects;
using System.Collections;
using UnityEngine;
public class Weapon : MonoBehaviour
{
public enum Type { Melee, Range }; // ������ ���Ÿ���
public Type type;
public int damage; //���� ������
public float rate; //���ݼӵ�
public int maxAmmo;
public int curAmmo;
public BoxCollider meleeArea; //���ݹ���
public TrailRenderer trailEffect; //�ܻ�����Ʈ
public Transform bulletPos; //�Ѿ˻�����ġ
public GameObject bullet;
public Transform bulletCasePos; //ź�ǻ�����ġ
public GameObject bulletCase;
public void Use()
{
if (type == Type.Melee )
{
StopCoroutine("Swing");
StartCoroutine("Swing");
}
else if (type == Type.Range && curAmmo > 0 )
{
curAmmo--;
StopCoroutine("Shot");
StartCoroutine("Shot");
}
}
IEnumerator Swing()
{
// �ִϸ��̼ǰ� �� �� ������ �� �Ʒ� �κ��� ����
yield return new WaitForSeconds(0.12f); // 0.1�� ���
meleeArea.enabled = true;
trailEffect.enabled = true;
yield return new WaitForSeconds(0.1f);
meleeArea.enabled = false;
yield return new WaitForSeconds(0.1f);
trailEffect.enabled = false;
ResetTrailRenderer(trailEffect); // Trail Renderer ����
yield break;
}
void ResetTrailRenderer // Trail Renderer ����
(TrailRenderer trail)
{ trail.Clear(); }
IEnumerator Shot()
{
// 총알 발사
GameObject instantBullet = Instantiate(bullet, bulletPos.position,bulletPos.rotation);
Rigidbody bulletRigid = instantBullet.GetComponent<Rigidbody>();
bulletRigid.linearVelocity = bulletPos.forward * 50;
yield return null;
// 탄피 발사
GameObject instantCase = Instantiate(bulletCase, bulletCasePos.position, bulletCasePos.rotation);
Rigidbody caseRigid = instantCase.GetComponent<Rigidbody>();
Vector3 caseVec = bulletCasePos.forward * Random.Range(-3, -2)
+ Vector3.up * Random.Range(2, 3);
caseRigid.AddForce(caseVec, ForceMode.Impulse);
caseRigid.AddTorque(Vector3.up * 10, ForceMode.Impulse);
}
}
풀 플레이어 스크립트 :
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering;
public class PlayerSC : MonoBehaviour
{
public float speed;
public GameObject[] weapons;
public bool[] hasWeapons;
public GameObject[] grenades;
public int hasGrenades;
public Camera followCamera;
public int ammo;
public int coin;
public int health;
public int maxAmmo;
public int maxCoin;
public int maxHealth;
public int maxHasGrenades;
float hAxis;
float vAxis;
bool wDown; // Walk를 위한 shift 꾹
bool jDown;
bool fDown; // 공격키
bool rDown; // 원거리 무기 재장전
bool iDown;
bool sDown1;
bool sDown2;
bool sDown3;
bool isJump;
bool isDodge;
bool isSwap;
bool isReload;
bool isFireReady = true;
Vector3 moveVec;
Vector3 dodgeVec;
Rigidbody rigid;
Animator anim;
GameObject nearObject;
Weapon equipWeapon;
int equipWeaponIndex = -1;
float fireDelay;
void Awake()
{
rigid = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>();
}
void Update()
{
GetInput();
Move();
Turn();
Jump();
Attack();
Reload();
Dodge();
Swap();
Interaction(); // 키보드 e 키 눌르면 무기 입수
}
void FixedUpdate()
{
Move();
}
void GetInput()
{
hAxis = Input.GetAxisRaw("Horizontal");
vAxis = Input.GetAxisRaw("Vertical");
wDown = Input.GetButton("Walk"); // GetButton은 꾹 누를 때만 적용
jDown = Input.GetButtonDown("Jump");
fDown = Input.GetButtonDown("Fire1");
rDown = Input.GetButtonDown("Reload");
iDown = Input.GetButtonDown("Interaction");
sDown1 = Input.GetButtonDown("Swap1");
sDown2 = Input.GetButtonDown("Swap2");
sDown3 = Input.GetButtonDown("Swap3");
}
void Move()
{
moveVec = new Vector3(hAxis, 0, vAxis).normalized;
// Walk 걸을 때 속도 감소
if (isDodge)
moveVec = dodgeVec;
if (isSwap || isReload || !isFireReady)
moveVec = Vector3.zero;
if (wDown)
transform.position += moveVec * speed * 0.2f * Time.deltaTime;
else
transform.position += moveVec * speed * 1.0f * Time.deltaTime;
anim.SetBool("isRun", moveVec != Vector3.zero);
anim.SetBool("isWalk", wDown);
}
void Turn()
{
// 키보드에 의한 회전 구현
transform.LookAt(transform.position + moveVec);
// 마우스에 의한 회전 구현
if (fDown)
{
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100))
{
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 0;
transform.LookAt(transform.position + nextVec);
}
}
}
void Jump()
{
// 제자리에서 점프는 점프
if (jDown
&& moveVec == Vector3.zero
&& !isJump && !isDodge && !isSwap)
{
rigid.AddForce(Vector3.up * 7, ForceMode.Impulse);
anim.SetBool("isJump", true);
anim.SetTrigger("doJump");
isJump = true;
}
}
// 방향키 눌러서 점프는 닷지
void Attack()
{
if (equipWeapon == null)
return;
fireDelay += Time.deltaTime;
isFireReady = equipWeapon.rate < fireDelay;
if ( fDown && isFireReady && !isDodge && !isSwap )
{
equipWeapon.Use();
anim.SetTrigger(equipWeapon.type
== Weapon.Type.Melee ? "doSwing": "doShot");
fireDelay = 0;
}
}
void Reload()
{
if(equipWeapon == null) return;
if(equipWeapon.type == Weapon.Type.Melee) return;
if (ammo == 0) return;
if(rDown && isFireReady &&
!isJump && !isDodge && !isSwap )
{
anim.SetTrigger("doReload");
isReload = true;
Invoke("ReloadOut", 0.5f);
}
}
void ReloadOut()
{
int reAmmo = ammo < equipWeapon.maxAmmo ? ammo : equipWeapon.maxAmmo;
equipWeapon.curAmmo = reAmmo;
ammo -= reAmmo;
isReload = false;
}
void Dodge()
{
if (jDown
&& moveVec != Vector3.zero
&& !isJump && !isDodge && !isSwap)
{
dodgeVec = moveVec;
speed *= 1.5f;
anim.SetTrigger("doDodge");
isDodge = true;
Invoke("DodgeOut", 0.5f);
}
}
void DodgeOut()
{
speed /= 1.5f; // 원래 속도로 되돌리기
isDodge = false;
}
void Swap()
{
if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0))
return;
if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1))
return;
if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2))
return;
int weaponIndex = -1;
if (sDown1) weaponIndex = 0;
if (sDown2) weaponIndex = 1;
if (sDown3) weaponIndex = 2;
if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge)
{
if (equipWeapon != null)
equipWeapon.gameObject.SetActive(false);
equipWeaponIndex = weaponIndex;
equipWeapon = weapons[weaponIndex].GetComponent<Weapon>() ;
equipWeapon.gameObject.SetActive(true);
anim.SetTrigger("doSwap");
isSwap = true;
Invoke("SwapOut", 0.4f);
}
}
void SwapOut()
{
isSwap = false;
}
void Interaction()
{
if (iDown && nearObject != null
&& !isJump && !isDodge)
{
if (nearObject.tag == "Weapon")
{
Item item = nearObject.GetComponent<Item>();
int weaponIndex = item.value;
hasWeapons[weaponIndex] = true;
Destroy(nearObject);
}
}
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Floor")
{
anim.SetBool("isJump", false);
isJump = false;
}
}
void OnTriggerEnter(Collider other)
{
if(other.tag == "Item")
{
Item item = other.GetComponent<Item>();
switch(item.type)
{
case Item.Type.Ammo:
ammo += item.value;
if (ammo > maxAmmo) ammo = maxAmmo;
break;
case Item.Type.Coin:
coin += item.value;
if (coin > maxCoin) coin = maxCoin;
break;
case Item.Type.Heart:
health += item.value;
if (health > maxHealth) health = maxHealth;
break;
case Item.Type.Grenade:
if (hasGrenades < maxHasGrenades)
grenades[hasGrenades].SetActive(true);
hasGrenades += item.value;
if (hasGrenades == maxHasGrenades)
return;
break;
}
Destroy(other.gameObject);
}
}
void OnTriggerStay(Collider other)
{
if (other.tag == "Weapon")
nearObject = other.gameObject;
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Weapon")
nearObject = null;
}
}
+
참고로,
무기 스왑 1 키 2 키 3 키 눌렀는데 제대로 변경안되는 버그가 있을 땐
int equipWeaponIndex = -1;
이렇게 맨위에 적혀있어야 함
원래 양수 이게 맞나 확인해야함
그냥 양수 1로 하면 안됨