새소식

🎮 Game Dev (게임개발)/PC (데스크탑, 노트북, 터치패널)

[3D 액션게임] 06. 근접무기 공격구현

  • -

🔔 유튜브 크리에이터 골든메탈님의 유니티강의 3D 쿼터뷰 액션게임 [BE5] 를 보고 공부하여 작성한 게시글입니다! 🔔

 

전체코드보기

더보기
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float speed; public GameObject[] weapons; public bool[] hasWeapons; public GameObject[] grenades; public int hasGrenades; public int ammo; public int health; public int coin; public int maxAmmo; public int maxHealth; public int maxCoin; public int maxHasGrenades; float hAxis; float vAxis; bool rDown; bool jDown; bool fDown; bool iDown; bool sDown1; bool sDown2; bool sDown3; bool isJump; bool isDodge; bool isSwap; 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>(); } // Update is called once per frame void Update() { GetInput(); Move(); Turn(); Jump(); Dodge(); Interation(); Swap(); Attack(); } void GetInput() { // 키보드입력에 따라 0 ~ 1로 변환 left right up down hAxis = Input.GetAxisRaw("Horizontal"); vAxis = Input.GetAxisRaw("Vertical"); rDown = Input.GetButton("Run"); jDown = Input.GetButtonDown("Jump"); fDown = Input.GetButtonDown("Fire1"); iDown = Input.GetButtonDown("Interation"); sDown1 = Input.GetButtonDown("Swap1"); sDown2 = Input.GetButtonDown("Swap2"); sDown3 = Input.GetButtonDown("Swap3"); } void Move() { // x y z moveVec = new Vector3(hAxis, 0, vAxis).normalized; // 회피 시 업데이트 안되게 if (isDodge) moveVec = dodgeVec; // 스왑 및 공격 시 못움직이게 if (isSwap || !isFireReady) moveVec = Vector3.zero; // transform transform.position += moveVec * (rDown ? 1.3f : 1f) * speed * Time.deltaTime; // animator anim.SetBool("isWalk", moveVec != Vector3.zero); anim.SetBool("isRun", rDown); } void Turn() { // Rotation transform.LookAt(transform.position + moveVec); } void Jump() { if(jDown && moveVec == Vector3.zero && !isJump && !isDodge) { // 물리엔진에 힘을 준다. 여기선 즉발적인 Impulse rigid.AddForce(Vector3.up * 15, 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("doSwing"); fireDelay = 0; } } void Dodge() { // 이동하면서 점프할때, if (jDown && moveVec != Vector3.zero && !isJump && !isDodge) { dodgeVec = moveVec; speed *= 2; anim.SetTrigger("doDodge"); isDodge = true; Invoke("DodgeOut", 0.5f); // 시간지연 라이브러리 기능 } } void DodgeOut() { speed *= 0.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; // 1, 2 ,3 버튼 누를때 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 Interation() { 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"){ isJump = false; anim.SetBool("isJump", false); } } // 아이템 획득 private 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) return; grenades[hasGrenades].SetActive(true); hasGrenades += item.value; break; } Destroy(other.gameObject); } } private void OnTriggerStay(Collider other) { if (other.tag == "Weapon") nearObject = other.gameObject; } private void OnTriggerExit(Collider other) { if (other.tag == "Weapon") nearObject = null; } }

 

 

public class Weapon : MonoBehaviour { public enum Type { Melee, Range }; public Type type; public int damage; public float rate; public BoxCollider meleeArea; public TrailRenderer trailEffect; }
  • 무기 타입, 데미지, 공격속도, 콜라이더, 렌더이펙트로 변수를 만들어줍시다.
  • public으로 만들어 default 값은 유니티에서 설정 할 예정입니다.

  • Box Collider를 추가해 피격 범위를 설정합니다.

  • Trail Renderer을 이용해 움직일때 따라오는 이펙트를 만들어줍시다.
  • Width 곡선을 곡선형태로 만들어 시간이 가면갈수록 자연스레 사라지게 해줍니다.
  • 컬러 설정을 무기 색과 비슷하게 하였습니다
  • Min Vertex Distance 를 설정해 이펙트가 더 각지게 만들어 줬습니다.

 

이후 만든 이펙트, 박스 콜라이더를 플레이어 Weapon 스크립트에 끌여당겨 넣어줍니다.

 

public class Weapon : MonoBehaviour { public void Use() { if(type == Type.Melee) { StopCoroutine("Swing"); StartCoroutine("Swing"); } } // IEnumerator : 열거형 함수 클래스 IEnumerator Swing() { // 중요한 개념인 코루틴 // 기존 : Use() 메인루틴 -> Swing() 서브루틴 -> Use() 메인루틴 (교차실행) // 코루틴 : Use() 메인루틴 + Swing() 코루틴 Co - (동시실행) // yield : 결과를 전달하는 키워드, 여러 개 사용해 시간차 로직 구현가능 //1 yield return new WaitForSeconds(0.1f); // 0.1 초 대기 meleeArea.enabled = true; trailEffect.enabled = true; //2 yield return new WaitForSeconds(0.3f); // 0.3 초 대기 meleeArea.enabled = false; //3 yield return new WaitForSeconds(0.3f); // 0.3 초 대기 trailEffect.enabled = false; } }
  • Swing 이란 기능에 yield를 넣어 임의의 대기시간을 만들어줍시다.
  • WaitForSeconds(x) :  실제시간 x초 대기 해줍니다.
  • 이후 콜라이더와, 이펙트의 효과 시간을 조절해줍시다.
  • Use() : 다시 시전시, 혹시 모르는 미리 시전된 코루틴을 종료시켜주면서 다시 시작해줍니다.

 

/* Player 스크립트, 근접무기 공격 추가, 추가된 내용만 올려놓겠습니다. */ public class Player : MonoBehaviour { bool fDown; // 공격버튼 bool isFireReady = true; // 공격가능 여부 Weapon equipWeapon; // GameObject -> Weapon 스크립트로 변경 float fireDelay; // 공격 딜레이 타임 void Update(){ Attack(); } void GetInput(){ fDown = Input.GetButtonDown("Fire1") } void Move(){ // 스왑 및 공격 시 못움직이게 if (isSwap || !isFireReady) moveVec = Vector3.zero; } // 이번 스크립트 중요부분 void Attack() { // 공격할 조건만 플레이어에 두고, 공격로직은 무기에 위임한다. if (equipWeapon == null) return; fireDelay += Time.deltaTime; isFireReady = equipWeapon.rate < fireDelay; if(fDown && isFireReady && !isDodge && !isSwap) { equipWeapon.Use(); anim.SetTrigger("doSwing"); fireDelay = 0; } } void Swap() { // equipWeapon이 GameObject -> Weapon으로 바뀌기에 맞춰서 바꿔줍니다 if((sDown1 || sDown2 || sDown3) && !isJump && !isDodge) { if(equipWeapon != null) equipWeapon.gameObject.SetActive(false); equipWeaponIndex = weaponIndex; // weapons[weaponIndex] -> weapons[weaponIndex].GetComponent<Weapon>(); equipWeapon = weapons[weaponIndex].GetComponent<Weapon>(); // equipWeapon.SetActive(true) -> equipWeapon.gameObject.SetActive(true); equipWeapon.gameObject.SetActive(true); anim.SetTrigger("doSwap"); // 스왑 중 isSwap = true; Invoke("SwapOut", 0.4f); } } }
  • 공격 딜레이 시간, 공격가능 여부
  • GameObject를 스크립트로 변경하여 수정점 변경
  • Attack() 함수를 통해 공격조건 구현 나머지는 장착한 무기의 Use()를 통해 구현

  • 스윙 애니메이션을 넣어, doSwing이란 트리거도 만들어줍시다.

 

 

 

 

 

 

 

 

 

출처: 골든메탈님 유튜브

https://www.youtube.com/watch?v=Zfoyagdz1y0&list=PLO-mt5Iu5TeYkrBzWKuTCl6IUm_bA6BKy&index=6 

 

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.