본문 바로가기
2D 콘텐츠 제작/[언데드 서바이벌] 제작 일지

[언데드 서바이벌 04] 공격 구현(근거리, 원거리)&무기 장착

by 잰쟁 2023. 9. 16.
728x90

 

 

1. 공격 구현 : 근거리(삽)

 

몬스터와 충돌체크 위해 Is Trigger 체크

 

프리팹화

 

오브젝트 풀링으로 만들어진 Bullet(삽)을 관리하는 Weapon을 Player에 추가 

 

Weapon 스크립트 추가  및 작성

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Weapon : MonoBehaviour
{ 
    //무기id, 프리팹id, damage, 갯수, 속도
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    [SerializeField]
    private Bullet bullet;

    void Start()
    {
        this.Init();
    }
    void Update()
    {
        //id 별로 관리
        switch (id)
        {
            case 0:
                //bullet 회전시키기
                this.transform.Rotate(Vector3.forward * this.speed * Time.deltaTime);
                break;
            default:
                break;
        }
    }

    //id별로 초기화
    private void Init()
    {
        switch (id) 
        {
            case 0:
                this.speed = -150;
                this.Batch();
                break;
            default:
                break;
        }
    }

    //bullet 배치하기
    void Batch()
    {
        for(int i = 0; i < this.count; i++)
        {
            //...bullet 게임오브젝트 생성 후
            //Weapon의 자식으로 넣기 위해 transform가져와 지역변수에 넣기
            Transform bulletTrans = GameManager.instance.pool.Get(prefabId).transform;
            //이 transform을 부모로 설정
            bulletTrans.parent = this.transform;

            //...회전 각도(z축) 설정
            //count(갯수)만큼 회전각 나누어서 배치
            Vector3 rot = Vector3.forward * 360 * i / count;
            bulletTrans.Rotate(rot);
            //Player로 부터 1.5f 거리만큼 떨어져서 위치
            bulletTrans.Translate(bulletTrans.up * 1.5f, Space.World);

            //bullet 초기화
            this.bullet.Init(this.damage, -1);  // -1 == 무한, 무한 관통
        }
    }
}

 

count(개수)를 5로 설정하고

 

실행한 결과

count를 10으로 설정하고 실행한 결과

 

 


레벨업 추가

 

 

- Update 부분에 test구문 추가하여 test하기

- 마우스왼쪽 누르면 damage:20, count: 5로 변경

 

 

test로 레벨업한 결과 => 개수 증가 및 배치가 이상함

이상하게 배치 및 추가 됨

 

 

**문제되는 점들

1. LevelUp을 하면 기존 count에 새로 입력한 count가 추가됨

(ex)

2 --> 5 (O)

2 --> 7 (X)

 

2. bullet의 위치 조정 필요 : LevelUp할 때 마다 player의 위치로 초기화 되어야함

 

.

.

.

 

Weapon - Batch() 부분 스크립트 수정하기

 

 

수정 후 실행 결과

 


 

2. 공격구현 : 원거리(총알)

 

 

- 범위 내에 Player와 거리가 가장 가까운 Monster에게 총알 발사

 

1) Scanner : 가장 가까운 Monster(target)를 구하는 스크립트 작성

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class Scanner : MonoBehaviour
{
    public float scanRange;
    public LayerMask targetLayer;
    public RaycastHit2D[] targets;
    public Transform nearestTarget;

    private void FixedUpdate()
    {
        //scanRange 내에 해당하는 targetLayer의 위치를 배열에 저장
        targets = Physics2D.CircleCastAll(transform.position, scanRange, Vector2.zero, 0,targetLayer);
        nearestTarget = GetNearest();
    }

    //가장 가까운 target 위치 반환 메서드
    Transform GetNearest()
    {
        Transform result = null;
        float dis = 100;
        foreach(RaycastHit2D target in targets)
        {
            Vector3 myPos = transform.position;
            Vector3 targetPos = target.transform.position;
            float curDis = Vector3.Distance(myPos, targetPos);

            //더 가까운 위치를 갱신
            if (curDis < dis)
            {
                dis = curDis;
                result = target.transform;
            }
        }

        //가장 가까운 target위치 반환
        return result;
    }
}

 

2) Player에 스크립트 부착 후 값 넣어주기

(Nearest Targetd은 GetNearest의 값으로 입력되어짐)

 

 

3) Bullet 스크립트 수정

 

- Init의 매개변수에 Vector3 dir(위치) 추가

- per(관통력)에 따라 Bullet(총알) 비활성화하기 추가

- Bullet(총알)에 속도 추가

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float damage;   //데미지
    public int per;   //관통

    private Rigidbody2D rBody;
   
    private void Awake()
    {
        this.rBody = this.GetComponent<Rigidbody2D>();
    }

    public void Init(float damage, int per, Vector3 dir)
    {
        this.damage = damage;
        this.per = per;

        if(per > -1)
        {
            //속도 부여 (위치 * 속력)
            this.rBody.velocity = dir * 15f ;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        //태그가 Monster가 아니거나 관통력이 -1인 경우
        if (!collision.CompareTag("Monster")| per == -1)
        {
            return;
        }

        //충돌마다 관통력 감소
        per--;

        //관통력이 -1이 되면
        if (per == -1)
        {
            //속도 = 0 , 오브젝트 비활성화
            rBody.velocity = Vector2.zero;
            gameObject.SetActive(false);
        }
    }
}

 

 

4) Weapon 스크립트 수정

 

-원거리 Bullet(총알) Init에 추가

- 총알 발사 메서드 추가

 

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

public class Weapon : MonoBehaviour
{ 
    //무기id, 프리팹id, damage, 갯수, 속도
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    private float timer;

    private Player player;


    private void Awake()
    {
        //부모 오브젝트에 있는 컴포넌트 가져오기
        this.player = this.GetComponentInParent<Player>();
    }
    void Start()
    {
        this.Init();
    }
    void Update()
    {
        //id 별로 관리
        switch (id)
        {
            case 0:
                //bullet(삽) 회전시키기
                this.transform.Rotate(Vector3.forward * this.speed * Time.deltaTime);
                break;
            default:
                this.timer += Time.deltaTime;

                if(this.timer > this.speed)
                {
                    //timer 초기화
                    this.timer = 0f;
                    //총알 발사   
                    this.Fire();
                }
                break;
        }

        //...TEST...
        if (Input.GetMouseButtonDown(0))
        {
            this.LevelUp(20, 5);
        }
    }

    //id별로 초기화
    private void Init()
    {
        switch (id) 
        {
            case 0:
                this.speed = -150;
                this.Batch();
                break;
            default:
                //총알 발사 속도
                speed = 0.3f;
                break;
        }
    }

    //...레벨업 메서드
    //레벨업 시 damage와 count 조절하기
    public void LevelUp(float damage, int count)
    {
        this.damage = damage;
        this.count = count;

        //레벨 = 0 , 근접무기일 경우
        if(id == 0)
        {
            //배치해아함
            this.Batch();
        }
    }


    //...근거리 bullet(삽) 배치하기
    void Batch()
    {
        for(int i = 0; i < this.count; i++)
        {
            //...bullet 게임오브젝트 생성 후
            //Weapon의 자식으로 넣기 위해 transform가져와 지역변수에 넣기
            Transform bullet;

            //...i가 childCount 범위 내라면
            if(i < this.transform.childCount)
            {
                //현재 child를 사용
                bullet = this.transform.GetChild(i);
            }
            else
            {
                //...i가 범위를 넘어서면
                //'모자란 수 만큼' 풀링으로 가져옴
                bullet= GameManager.instance.pool.Get(prefabId).transform;
                //현재 transform을 부모로 설정
                bullet.parent = this.transform;
            }
           
            //...bullet위치 player의 위치로 초기화
            //levelup 했을때 위치 오류 방지
            bullet.localPosition = Vector3.zero;
            bullet.localRotation = Quaternion.identity;

            //...회전 각도(z축) 설정
            //count(갯수)만큼 회전각 나누어서 배치s
            Vector3 rot = Vector3.forward * 360 * i / count;
            bullet.Rotate(rot);
            //Player로 부터 1.5f 거리만큼 떨어져서 위치
            bullet.Translate(bullet.up * 1.5f, Space.World);

            //bullet 초기화
            bullet.GetComponent<Bullet>().Init(this.damage, -1,Vector3.zero);  // -1 == 무한, 무한 관통
        }
    }

    //...원거리 Bullet(총알) 발사
    void Fire()
    {
        if(!this.player.scanner.nearestTarget)
        {
            return;
        }

        //가장 가까운 몬스터 위치
        Vector3 targetPos = this.player.scanner.nearestTarget.position;
        //...총알 방향 설정
        Vector3 dir = targetPos - transform.position;
        //현재 Vector의 방향은 유지, 크기는 1로 변환
        dir = dir.normalized;

        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = this.transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir);

        //bullet 초기화
        bullet.GetComponent<Bullet>().Init(this.damage, count, dir);  // -1 == 무한, 무한 관통
    } 
}

 

스크립트 수정 후 실행 결과

(Weapon(삽)은 비활성화 상태로 꺼둠)

 

가장 가까운 거리의 Monster에게 총알 발사

 

 


 

 

3. 무기 장착

 

 

Player 자식으로 왼손 오른손 추가하고 Hand 스크립트 만들기

 

 

Hand 스크립트 : 무기 방향 및 위치, 레이어 관리

using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.VisualScripting;
using UnityEngine;

public class Hand : MonoBehaviour
{
    public bool isLeft;
    public SpriteRenderer spriter;

    //오른손 위치
    private Vector3 rightPos = new Vector3(0.35f, -0.15f, 0);
    //오른손 반전 위치
    private Vector3 rightPosReverse = new Vector3(-0.15f, -0.1f, 0);
    //왼손 각도
    private Quaternion leftRot = Quaternion.Euler(0, 0, -35f);
    //왼손 반전 각도
    private Quaternion leftRotReverse = Quaternion.Euler(0,180f,210f);

    private void Awake()
    {
        this.spriter = this.GetComponent<SpriteRenderer>();
    }

    private void Update()
    {
        if (isLeft) //근거리 무기
        {
            //Player가 왼쪽을 보면
            if (GameManager.instance.player.inputVec.x == -1)
            {
                //반전
                this.transform.localRotation = this.leftRotReverse;
                //sprite layer 설정
                this.spriter.sortingOrder = 4;
            }
            else if(GameManager.instance.player.inputVec.x == 1)
            {
                this.transform.localRotation = this.leftRot;
                //sprite layer 설정
                this.spriter.sortingOrder = 6;
            }
        }
        else  //원거리 무기
        {
            //Player가 왼쪽을 보면
            if (GameManager.instance.player.inputVec.x == -1)
            {
                //반전
                this.transform.localPosition = this.rightPosReverse;
                //sprite layer 설정
                this.spriter.sortingOrder = 6;
            }
            else if(GameManager.instance.player.inputVec.x == 1)
            {
                this.transform.localPosition = this.rightPos;
                //sprite layer 설정
                this.spriter.sortingOrder = 4;
            }
        }
    }
}

 

 

** 의문점

나는 Player 방향 전환을 spriter.flip을 안 쓰고 localScale을 사용하여서 Hand 반전을 줘보려고 하였다.

그러나 나머지는 다 잘 되는데, 왼손 반전 각도를 골드메탈님이 설정한 것처럼

Quaternion.Euler(0, 0, -135f);로 설정했지만 이상하게 반전이 되었다..

 

localRotation은 부모의 영향을 받는다고 하는데

아마도 내가 부모인 Player에서 spriter.flip을 사용하지 않아서 문제가 발생한 것 같다.

(혹시 몰라 this.transform.flipY = GameManager.instance.player.inputVec.x == -1; 

이라고 설정해봤는데도 반전되지 않았다.)

 

따라서 Quaternion.Euler 값을 내가 직접 설정하였더니 좌우 반전이 잘 되었다.

 

 

실행 모습

아직 총알 위치를  총 끝으로 잡지 않은 상태!

 

 

 

**잘 이해가 안 갔던 부분들 정리

 

 

1. per(관통력)의 의미와 작동 원리

2. Scanner - GetNearest() 작동 원리

 

 

++ 

만약에 다 완성하고 시간이 좀 남으면 가장 가까운 거리를 구하는 로직을 우리가 배웠던 Gizmos를 사용하여 바꿔보려고 한다! 새로운 방식들이 조금 많아 찬찬히 이해하고 넘어가려고 하느라 진도가 조금 느리다..