기록 보관소

[Unity/유니티] 기초-뱀서라이크: 플레이어 무기 장착 표현하기[11+] 본문

유니티 프로젝트/뱀서라이크

[Unity/유니티] 기초-뱀서라이크: 플레이어 무기 장착 표현하기[11+]

JongHoon 2023. 7. 13. 21:05

개요

유니티 독학을 위해 아래 링크의 골드메탈님의 영상들을 보고 직접 따라 해보면서 진행 상황을 쓰고 배웠던 점을 요약한다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

📚유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com


뱀서라이크: 플레이어 무기 장착 표현하기[11+]

1. 양손 배치

Player 자식 오브젝트로 Sprite 생성 및 변경. Sprite는 어차피 변경할 것이므로 어떤 모양으로 생성하든 상관 없다.
앞서 생성했던 Hand Left를 복사해서 Hand Right를 만들어준다
실행 모습.
방향 전환에도 그대로..


2. 반전 컨트롤 구현

Hand 스크립트 생성

//Hand Script

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

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

    SpriteRenderer player;

    Vector3 rightPos = new Vector3(0.35f, -0.15f, 0);
    Vector3 rightPosReverse = new Vector3(-0.15f, -0.15f, 0);
    Quaternion leftRot = Quaternion.Euler(0, 0, -35);
    Quaternion leftRotReverse = Quaternion.Euler(0, 0, -135);

    void Awake() {
        player = GetComponentsInParent<SpriteRenderer>()[1];
    }

    void LateUpdate() {
        bool isReverse = player.flipX;

        if (isLeft) {   //근접 무기
            transform.localRotation = isReverse ? leftRotReverse : leftRot;
            spriter.flipY = isReverse;
            spriter.sortingOrder = isReverse ? 4 : 6;
        }
        else {  //원거리 무기
            transform.localPosition = isReverse ? rightPosReverse : rightPos;
            spriter.flipX = isReverse;
            spriter.sortingOrder = isReverse ? 6 : 4;
        }
    }
}

Hand 스크립트를 Hand Left 오브젝트에 추가 및 변경
Hand Right도 마찬가지로 진행
테스트 실행. 일단 이전과 똑같다.
방향 전환을 해도 잘 돌아간다.
한 번 더 돌아도 잘 반전 된다.


3. 데이터 추가

아이템을 먹었을 때만 무기가 반영되도록 두 Hand 오브젝트의 스프라이트를 지우고, 비활성화 한다

//ItemData Script

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

[CreateAssetMenu(fileName = "Item", menuName = "Scriptble Object/ItemData")]

public class ItemData : ScriptableObject {
    public enum ItemType { Melee, Range, Glove, Shoe, Heal }

    [Header("# Main Info")]
    public ItemType itemType;
    public int itemId;
    public string itemName;
    public string itemDesc;
    public Sprite itemIcon;

    [Header("# Level Info")]
    public float baseDamage;
    public int baseCount;
    public float[] damages;
    public int[] counts;

    [Header("# Weapon")]
    public GameObject projectile;
    public Sprite hand;
}

Item 0에 앞선 수정으로 생긴 Hand에 스프라이트를 넣어준다
Item 1도 똑같이 진행


4. 데이터 연동

//Player Script

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

public class Player : MonoBehaviour {
    public Vector2 inputVec;   //키보드 입력 값 변수
	public float speed; //속도 관리 변수
    public Scanner scanner;
    public Hand[] hands;

    Rigidbody2D rigid;
    SpriteRenderer spriter;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
		spriter = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        scanner = GetComponent<Scanner>();
        hands = GetComponentsInChildren<Hand>(true);    //인자의 true를 통해서 비활성화된 오브젝트도 가능
	}

    void FixedUpdate() {
		//위치 이동
		Vector2 nextVec = inputVec * speed * Time.fixedDeltaTime;
		rigid.MovePosition(rigid.position + nextVec);
    }

    void OnMove(InputValue value) {
        inputVec = value.Get<Vector2>();
    }

    void LateUpdate() {
        anim.SetFloat("Speed", inputVec.magnitude); //Magnitude : 벡터의 순수한 크기 값
        
        if (inputVec.x != 0) {
            spriter.flipX = inputVec.x < 0;
        }
    }
}
//Weapon Script

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

public class Weapon : MonoBehaviour {
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    float timer;
    Player player;

    void Awake() {
        player = GameManager.instance.player;
    }

    void Update() {
        switch (id) {
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;
            default:
                timer += Time.deltaTime;

                if (timer > speed) {
                    timer = 0f;
                    Fire();
                }
                break;
        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")) {
            LevelUp(10, 1);
        }
    }
    
    public void LevelUp(float damage, int count) {
        this.damage = damage;
        this.count += count;

        if (id == 0)
            Batch();

        player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
    }

    public void Init(ItemData data) {
        // Basic Set
        name = "Weapon " + data.itemId;
        transform.parent = player.transform;
        transform.localPosition = Vector3.zero;

        // Property Set
        id = data.itemId;
        damage = data.baseDamage;
        count = data.baseCount;

        for (int index = 0; index < GameManager.instance.pool.prefabs.Length; index++) {
            if (data.projectile == GameManager.instance.pool.prefabs[index]) {  //프리펩이 같은건지 확인
                prefabId = index;
                break;
            }
        }

        switch (id) {
            case 0:
                speed = 150;
                Batch();
                break;
            default:
                speed = 0.4f;
                break;
        }

        // Hand Set
        Hand hand = player.hands[(int)data.itemType];
        hand.spriter.sprite = data.hand;
        hand.gameObject.SetActive(true);

        //플레이어가 가지고 있는 모든 Gear에 대해 ApplyGear를 실행하게 함. 나중에 추가된 무기에도 영향을 주기 위함.
        player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
    }

    void Batch() {  //생성된 무기를 배치하는 함수
        for (int index = 0; index < count; index++) {
            Transform bullet;

            if (index < transform.childCount) {
                bullet = transform.GetChild(index); //기존 오브젝트가 있으면 먼저 활용
            }
            else {
                bullet = GameManager.instance.pool.Get(prefabId).transform; //모자라면 풀링에서 가져옴
                bullet.parent = transform;
            }

            bullet.localPosition = Vector3.zero;        //무기 위치 초기화
            bullet.localRotation = Quaternion.identity; //무기 회전값 초기화

            Vector3 rotVec = Vector3.forward * 360 * index / count; //개수에 따라 360도 나누기
            bullet.Rotate(rotVec);
            bullet.Translate(bullet.up * 1.5f, Space.World); //무기 위쪽으로 이동
            bullet.GetComponent<Bullet>().Init(damage, -1, Vector3.zero); //-1 is Infinity Per. 무한 관통.
        }
    }

    void Fire() {
        if (!player.scanner.nearestTarget)
            return;

        Vector3 targetPos = player.scanner.nearestTarget.position;
        Vector3 dir = targetPos - transform.position;
        dir = dir.normalized;

        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir);   //FromToRotation(지정된 축을 중심으로 목표를 향해 회전하는 함수
        bullet.GetComponent<Bullet>().Init(damage, count, dir);
    }
}

테스트 실행. 무기가 추가될 때, 해당 무기를 장착하는 모습과 반전도 잘 적용되는 것을 확인할 수 있다.


+) 번외 : 무기가 목표물을 바라보도록 만들기

골드메탈님 유튜브(이번 강의 영상)에 직접 쓰신 댓글

추가 답글에 따르면 해당 파트는 모든 코드(모든 영상) 완성했다는 전제 하에 수정해서 적용할 수 있다고 한다.

나중에 뱀서라이크를 다 완성하면 추가해 봐야겠다.