말감로그

24.09.30 Unity - 3D 쿼터뷰 액션게임(무기 장착, 아이템 먹기, 근거리, 원거리 공격) 본문

TIL

24.09.30 Unity - 3D 쿼터뷰 액션게임(무기 장착, 아이템 먹기, 근거리, 원거리 공격)

habbn 2024. 9. 30. 16:21
728x90

 

유니티를 놓은지 너무 오래된거 같아서 골드메탈 유튜브를 보고 다시 익히고 있다.

 

 

0.아이템 타입

 

아이템의 종류는 총 5가지이므로, enum으로 ItemType을 열거해준다. 

transform.Rotate()를 이용하여 아이템을 회전시켜준다.

public class Item : MonoBehaviour
{
    public enum ItemType { Ammo, Coin, Grenade, Heart, Weapon };
    public ItemType itemType;
    public int value;

    void Update()
    {
        transform.Rotate(Vector3.up * 20 * Time.deltaTime);
    }
}

 

 

1. 무기 입수

 

플레이어가 무기를 먹으면(OnTrigger) 어떤 무기를 입수했는지 확인을 위해 hasWeapon이라는 bool형 배열을 만들어 확인해준다.

해당 오브젝트의 태그가 "Weapon"이면 해당 오브젝트에 달려있는 Item 스크립트를 가져와서 그 오브젝트의 value 값으로 입수한 아이템을 체크한다.

그리고 해당 오브젝트는 Destroy 시킨다.

    void Interation()
    {
        if(iDown && nearObject != null && !isJump && !isDodge)
        {
            if(nearObject.tag == "Weapon")
            {
                Item item  = nearObject.GetComponent<Item>();
                int weaponIndex = item.value;
                hasWeapon[weaponIndex] = true;

                Destroy(nearObject);
            }
        }
    }

 

 

2. 무기 장착 & 스왑

 

무기를 장착시키기 위해선 Weapon 배열을 만들어서 해당하는 인덱스의 weapon 오브젝트를 SetActive(true) 해준다.

없는 무기나 이미 장착한 무기를 다시 스왑할 필요는 없기 때문에 기존에 장착된 무기를 저장하는 변수(equipWeapon) 를 만들어서 예외처리를 해준다.

 void Swap()
{
    // 없는 무기나 이미 장착한 무기인 경우 스왑하지 않도록
    if(sDown1 && (!hasWeapon[0] || equipWeaponIndex == 0))
        return;
    if(sDown2 && (!hasWeapon[1] || equipWeaponIndex == 1))
        return;
    if(sDown3 && (!hasWeapon[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.SetActive(false);
        equipWeaponIndex = weaponIndex;
        equipWeapon = weapon[weaponIndex];
        equipWeapon.SetActive(true);

        anim.SetTrigger("doSwap");
        isSwap = true;

        Invoke("SwapOut", 0.4f);
    }
}

 

해당 코드는 중복되는 조건문이 많아보여서 좀 더 간결하게 수정해봤다.

void Swap()
{
    int weaponIndex = -1;

    if (sDown1) weaponIndex = 0;
    else if (sDown2) weaponIndex = 1;
    else if (sDown3) weaponIndex = 2;

    // 없는 무기나 이미 장착한 무기인 경우 스왑하지 않도록
    if(weaponIndex == -1 || !hasWeapon[weaponIndex] || equipWeaponIndex == weaponIndex)
        return;

    if(!isJump && !isDodge)
    {
        // 현재 장착한 무기가 있으면 비활성화
        if(equipWeapon != null)
            equipWeapon.SetActive(false);

        equipWeaponIndex = weaponIndex;
        equipWeapon = weapon[weaponIndex];
        equipWeapon.SetActive(true);

        anim.SetTrigger("doSwap");
        isSwap = true;

        Invoke("SwapOut", 0.4f);
    }
}

 

 

3. 아이템 먹기

 

enum 열거형으로 열거해둔 ItemType을 switch문으로 해당 아이템의 종류에 따라 관리해준다.

void OnTriggerEnter(Collider other) 
{
    if(other.tag == "Item")
    {
        Item item = other.GetComponent<Item>();
        switch(item.itemType)
        {
            case Item.ItemType.Ammo:
                ammo += item.value;
                if(ammo > maxAmmo)
                    ammo = maxAmmo;
                break;
            case Item.ItemType.Coin:
                coin += item.value;
                if(coin > maxCoin)
                    coin = maxCoin;
                break;
            case Item.ItemType.Heart:
                health += item.value;
                if(health > maxHealth)
                    health = maxHealth;
                break;
            case Item.ItemType.Grenade:
                grenades[hasGrenades].SetActive(true);
                hasGrenades += item.value;
                if(hasGrenades > maxHasGrenades)
                    hasGrenades = maxHasGrenades;
                break;
        }
        Destroy(other.gameObject);
    }
}

 

 

4. 공전물체 만들기

 

플레이어를 중심으로 돌아가는 수류탄을 만들 것이다. 우선 수류탄에 Orbit 스크립트를 추가하고 플레이어를 중심으로 회전하는 코드를 추가한다.

public class Orbit : MonoBehaviour
{
    public Transform target;    // 공전 목표
    public float orbitSpeed;    // 공전 속도
    Vector3 offset;             // 목표와의 거리

 

RotateAround() 함수는 타겟 주위를 회전하는 함수이다. 

RotateAround(타겟 위치, 회전축, 회전 수치)

 

하지만 타겟이 움직이면 일그러지는 단점이 있다. 

그래서 , offset 에 수류탄의 위치와 목표 위치의 차이를 저장해서 이 값을 이용해 수류탄이 목표를 기준으로 일정한 거리를 유지하면서 회전하게 만든다.

매 프레임마다 물체의 새로운 위치와 목표 위치 간의 거리를 다시 계산하여 offset을 업데이트한다. 

void Start()
{
    offset = transform.position - target.position;
}

void Update()
{
    transform.position = target.position + offset;
    transform.RotateAround(target.position, Vector3.up, orbitSpeed * Time.deltaTime);
    offset = transform.position - target.position;
}

 

 

4-1. 위치 변화에 따라 파티클 생성

 

Rate overDistance : 파티클의 위치 변화에 따라 입자를 생성한다. (오브젝트의 움직임 거리에 따라서 발생하게 됨)

Rate over Time : 시간에 따라 입자 생성

 

 

Simulation Space - Local : 오브젝트의 움직임에 따라서 파티클 입자도 같이 움직임 

Simulation Space - World : 입자를 뿌리면서 가는 형태로 생성됨

 

 

5. 근거리 공격 설정

 

공격 범위가 될 콜라이더의 위치와 크기를 조정하고 SetActive는 false 해준다. IsTrigger 체크!!

근접 공격 태그 새로 생성하고 지정한다.

 

 

5- 1. 근접 공격 효과 - Trail Renerer

 

Trail Render : 잔상을 그려주는 컴포넌트

 

1. Material 설정 - line Default

2. 꼬리 모양을 내도록 Width Curve 설정 -> 빨간 선 오른쪽 클릭 Add key -> 반대쪽을 아래로 쭉 내림

3. Time 값을 줄여서 잔상 길이를 짧게 조절

4. Min Vertex Distance로 잔상 꺾임을 조절 (작을수록 더 각져짐)

5. Color 설정

 

코루틴을 사용하여 Swing 효과를 준다.

 public void Use()
{
    if(type == Type.Melee)
    {
        StopCoroutine("Swing");
        StartCoroutine("Swing");
    }
}

// IEnumerator 열거형 함수 클래스
IEnumerator Swing()
{
    yield return new WaitForSeconds(0.1f);
    meleeArea.enabled = true;
    trailEffect.enabled = true;

    yield return new WaitForSeconds(0.3f);
    meleeArea.enabled = false;

    yield return new WaitForSeconds(0.3f);
    trailEffect.enabled = false;
}

 

 

5-2. 공격 로직

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;
    }
}

 

 

6. 원거리 공격

 

총알이 날라갈 Bullet 위치 생성

 

탄피 위치 생성

 

총알 생성 - Trail Renderer , Rigidbody , Collider  컴포넌트 , Bullet 스크립트 추가

 

 

Player.cs 의 Attack() 함수부분에서 애니메이션을 Weapon Type에 따라 설정되게 수정 (삼항연산자)

 anim.SetTrigger(equipWeapon.type == Weapon.Type.Melee ? "doSwing" : "doShot");

 

 

총알 발사는 Instantiate 함수를 사용해서 생성하고 발사하면 된다.

탄피 배출 또한 동일하나, 랜덤한 위치에 떨어지도록 하기 위해 Random.Range() 함수를 사용하여 힘을 가하고(AddForce),

물체에 회전력을 추가하기 위해 AddTorque() 함수를 사용한다. 

    public Transform bulletPos; 
    public GameObject bullet;
    public Transform bulletCasePos;
    public GameObject bulletCase;
    
    IEnumerator Shot()
    {
        //총알 발사
        GameObject instantBullet = Instantiate(bullet, bulletPos.position, bulletPos.rotation);
        Rigidbody bulletRigid = instantBullet.GetComponent<Rigidbody>();
        bulletRigid.velocity = 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); //회전
    }

 

 

6-1. 재장전

 

장착된 무기가 있어야하고, 원거리 무기인 경우에만 , 탄착이 0이 아니어야 하기 때문에 예외처리를 해준다.

    void Reload()
    {
        if(equipWeapon == null || equipWeapon.type == Weapon.Type.Melee || ammo == 0)
            return;
        
        if(rDown && !isJump && !isDodge && !isSwap && isFireReady)
        {
            anim.SetTrigger("doReload");
            isReload = true;

            Invoke("ReloadOut", 3f);
        }
    }
    
    //탄약이 채워짐
    void ReloadOut()
    {
        int reAmmo = ammo < equipWeapon.maxAmmo ? ammo : equipWeapon.maxAmmo; //재장전할 탄약 수
        equipWeapon.curAmmo = reAmmo;
        ammo -= reAmmo;     // 남은 탄약에서 장전한 탄약 수만큼 차감    
        isReload = false;
    }

 

reAmmo 는 재장전할 탄약 수를 결정한다.

남은 탄약(ammo)이 최대 탄약(equipWeapon.maxAmmo)보다 적다면, 남은 탄약만큼을 장전하고, 그렇지 않다면 최대탄약만큼 장전한다.

장전할 탄약 수를 현재 장착된 무기의 현재 탄약 으로 설정하여 무기에 탄약을 채운다.

 

 

728x90