🎮 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;
}
}
🧷 1. 근접무기 변수 및 효과 추가
- 변수 추가
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 스크립트에 끌여당겨 넣어줍니다.
🧷 2. 공격 로직
- 무기 스크립트 공격 추가 (코루틴)
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()를 통해 구현
🧷 3. 스윙 애니메이션
- 스윙 애니메이션을 넣어, doSwing이란 트리거도 만들어줍시다.
출처: 골든메탈님 유튜브
https://www.youtube.com/watch?v=Zfoyagdz1y0&list=PLO-mt5Iu5TeYkrBzWKuTCl6IUm_bA6BKy&index=6
'🎮 Game Dev (게임개발) > PC (데스크탑, 노트북, 터치패널)' 카테고리의 다른 글
[3D 액션게임] 07-1. 원거리 무기 재장전시 모션캔슬 (0) | 2022.04.04 |
---|---|
[3D 액션게임] 07. 원거리무기 공격구현 (0) | 2022.04.02 |
[3D 액션게임] 05.아이템 획득과 공전효과 (0) | 2022.03.31 |
[3D 액션게임] 04.무기 획득과 변경 (0) | 2022.03.30 |
[3D 액션게임] 03.아이템 만들기 (0) | 2022.03.29 |
Contents
소중한 공감 감사합니다