본문 바로가기
Development/C#

[멋쟁이사자처럼 부트캠프 TIL 회고] Unity 게임 개발 3기 17일차

by Mobics 2024. 12. 13.

 

목차


    Unity 다뤄보기

    GameObject 범위 제한

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class MaterialColorChange : MonoBehaviour
    {
        public GameObject cube;
        
        void Update()
        {
            BoxCollider boxCollider = cube.GetComponent<BoxCollider>();
    
            //if (transform.position.x <= boxCollider.bounds.min.x ||
            //    transform.position.z <= boxCollider.bounds.min.z ||
            //    transform.position.x >= boxCollider.bounds.max.x ||
            //    transform.position.z >= boxCollider.bounds.max.z )
            // Clamp에 이미 이 조건이 포함되어 있어서 삭제
            
            transform.position =
            new Vector3(
                Mathf.Clamp(transform.position.x,
                    boxCollider.bounds.min.x,
                    boxCollider.bounds.max.x), 0, 
                Mathf.Clamp(transform.position.z, 
                    boxCollider.bounds.min.z,
                    boxCollider.bounds.max.z));
    
            float scaleX = boxCollider.size.x * boxCollider.transform.localScale.x;
            float scaleZ = boxCollider.size.z * boxCollider.transform.localScale.z;
            //float scaleX = boxCollider.size.x * boxCollider.transform.lossyScale.x;
            //float scaleZ = boxCollider.size.z * boxCollider.transform.lossyScale.z;
            
            GetComponent<MeshRenderer>().material.color = new Color(
                (transform.position.x + scaleX * 0.5f) /
                scaleX, 0,
                (transform.position.z + scaleZ * 0.5f) /
                scaleZ);
        }
    }

    ※ Mathf.Clamp(position, min, max) : position을 min부터 max까지의 범위로 제한

    ※ lossyScale : 수정이 불가능한 WorldScale이라고 보면 된다.

    0.5f를 곱하는 이유 : 중간을 0.5로 설정하기 위해 --> 최소 0 최대 1이 된다.

    boxCollider.size vs boxCollider.bounds.size

    • boxCollider.size : Inspector로 적용한 Component의 로컬 크기
    • boxCollider.bounds.size : Component의 Scale까지 고려된 월드 좌표계에서의 크기 --> Cube의 X Scale * boxCollider의 X size

    대포 만들기

    0. 땅 만들기

    : Cube 생성 후, 적당히 크기 조절

     

    1. 대포 모양 만들기

     

    2. 빈 게임 오브젝트(FirePosition) 만들어서 포탄이 발사될 위치 잡기

    : Rotation의 X를 -90으로 설정

     

    3. 포탄 만들기

    : Sphere 생성(CannonBall)

    >> Material 추가 --> 다시 보니 Default랑 다른게 없더라..?

    >> Rigidbody 추가

    >> Prefab 하기

     

    4. UI - Slider 생성

    >> Anchor를 아래쪽으로 설정 후, 위치 조정

    >> Handle Slide Area 삭제

    >> Fill Area의 Left 0으로 설정, Fill Area - Fill 의 Width 0으로 설정 + 게이지 색 바꾸려면 Fill의 Color 수정

     

    5. Cannon Script 생성 후 FirePosition에 넣고 코드 작성

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class Cannon : MonoBehaviour
    {
        public GameObject cannonBall;
    
        public Slider slider;
        public float maxPower;
        public float currentPower;
        public float fillSpeed;
    
        void Update()
        {
        	if (Input.GetKey(KeyCode.Space))
            {
                currentPower += fillSpeed * Time.deltaTime;
                currentPower = Mathf.Clamp(currentPower, 0, maxPower);
                slider.value = currentPower / maxPower;
            }
            
            if (Input.GetKeyUp(KeyCode.Space))
            {
                GameObject cannonBallInstance = Instantiate(cannonBall, transform.position, transform.rotation);
                
                cannonBallInstance.GetComponent<Rigidbody>().AddForce(transform.forward * currentPower, ForceMode.Impulse);
                
                currentPower = 0.0f; 
                slider.value = 0.0f;
            }
        }
    }

    6. CannonBall, Slider 각각 바인딩

     

    ※ 강사님은 Cannon Script를 최상위 Object에 넣고 아래 코드로 구동하시더라 --> 수정하려해도 수직으로 발사만 되더라

    >> 나중에 원인을 찾아보자

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class Cannon : MonoBehaviour
    {
        public GameObject cannonBall;
        public GameObject firePoint;
        
        public Slider slider;
        public float maxPower;
        public float currentPower;
        public float fillSpeed;
        
        void Update()
        {
            if (Input.GetKey(KeyCode.Space))
            {
                currentPower += fillSpeed * Time.deltaTime;
                currentPower = Mathf.Clamp(currentPower, 0, maxPower);
                slider.value = currentPower / maxPower;
            }
            
            if (Input.GetKeyUp(KeyCode.Space))
            {
                GameObject cannonBallInstance = Instantiate(cannonBall, 
                    firePoint.transform.position, 
                    Quaternion.identity);
    
                Vector3 forward = Quaternion.Euler(-90, 0, 0) * transform.forward;
                
                cannonBallInstance.
                    GetComponent<Rigidbody>().
                    AddForce(forward* currentPower, ForceMode.Impulse);
    
                currentPower = 0.0f;
                slider.value = 0.0f;
            }
        }
    }

    └ 포탄이 땅에 닿으면 포탄이 사라지면서 파편이 튀도록 구현

    1. Layer 추가

    : Add Layer로 Ground, Projectile 생성

    >> Cube는 Ground, CannonBall Prefab은 Projectile로 Layer 설정

     

    2. 파편 만들기

    : Cube 생성(Piece), Scale은 전부 0.5로 설정

    >> Rigidbody 추가

     

    3. Projectile Script 생성 후 CannonBall에 넣고 코드 작성

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Random = UnityEngine.Random;
    
    public class Projectile : MonoBehaviour
    {
        public GameObject piece;
        public int MinPieceCount = 3;
        public int MaxPieceCount = 8;
    
        private void OnTriggerEnter(Collider other)
        {
            Vector3 hitDirection = GetComponent<Rigidbody>().velocity * -1;
    
            int count = Random.Range(MinPieceCount, MaxPieceCount + 1);
    
            for (int i = 0; i < count; i++)
            {
                Vector3 randomDirection = Random.insideUnitSphere;
                //Quaternion randomRotation = Random.rotation;
                Vector3 lastDirection = Quaternion.LookRotation(randomDirection) * hitDirection;
            
                GameObject instance = Instantiate(piece, transform.position, Quaternion.LookRotation(lastDirection));
            
                instance.GetComponent<Rigidbody>().AddForce(lastDirection, ForceMode.Impulse);
                Destroy(this.gameObject);
            }
        }
    }

     

    4. CannonBall Prefab에 추가 설정

    >> Is Trigger 체크

    >> Layer Overrides에서 Include Layers를 Ground로, Exclude Layers를 Default와 Projectile 설정

    ※ Exclude로 Projectile을 설정하면 공끼리 부딪혔을 때 깨지는 걸 방지

    >> Projectile Script에 Piece 바인딩

    공의 속도가 너무 빠를 때, 공이 바닥을 통과하는 현상 방지하는 법

     

    5. 파편이 3초 뒤에 사라지게 만들기

    : Piece Script 생성 후 Piece에 넣고 코드 작성

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Piece : MonoBehaviour
    {
        IEnumerator Start()
        {
            yield return new WaitForSeconds(3.0f);
            Destroy(this.gameObject);
        }
    }

    └ 게이지가 꽉 차면 다시 줄어들도록 구현

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class Cannon : MonoBehaviour
    {
        public GameObject cannonBall;
    
        public Slider slider;
        public float maxPower;
        public float currentPower;
        public float fillSpeed;
    
        public bool isMax = false;
        
        void Update()
        {
            if (currentPower == maxPower)
                isMax = true;
            else if (currentPower < 0)
                isMax = false;
            
            if (Input.GetKey(KeyCode.Space))
            {
                if (!isMax)
                {
                    currentPower += fillSpeed * Time.deltaTime;
                    currentPower = Mathf.Clamp(currentPower, 0, maxPower);
                    slider.value = currentPower / maxPower;
                }
                else if (isMax)
                {
                    currentPower -= fillSpeed * Time.deltaTime;
                    slider.value = currentPower / maxPower;
                }
            }
            
            if (Input.GetKeyUp(KeyCode.Space))
            {
                GameObject cannonBallInstance = Instantiate(cannonBall, transform.position, transform.rotation);
                
                cannonBallInstance.GetComponent<Rigidbody>().AddForce(transform.forward * currentPower, ForceMode.Impulse);
                
                currentPower = 0.0f; 
                slider.value = 0.0f;
            }
        }
    }

    └ 대포를 회전시키고 포신을 위아래로 움직이도록 구현

    1. 대포 전체를 회전시키기 위해 최상위 Obejct로 CannonPivot 생성 후 대포를 구성하는 Object 전부 자식 Object로 넣기

    2. 포신이 움직이면 포탄이 발사하는 지점도 같이 움직이도록 포신에 자식 Object로 FirePosition 넣기

    3. 코드 작성

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class Cannon : MonoBehaviour
    {
        public GameObject cannonBall;
        public GameObject capsule;
        public GameObject cannonPivot;
    
        public Slider slider;
        public float maxPower;
        public float currentPower;
        public float fillSpeed;
    
        public bool isMax = false;
    
        public float rotateSpeed = 100f;
        private float _capsuleZ;
        void Update()
        {
            if (Input.GetKey(KeyCode.W))
            {
                capsule.transform.Rotate(Vector3.forward * (rotateSpeed * Time.deltaTime));
            }
            if (Input.GetKey(KeyCode.S))
            {
                capsule.transform.Rotate(Vector3.back * (rotateSpeed * Time.deltaTime));
            }
            if (Input.GetKey(KeyCode.A))
            {
                cannonPivot.transform.Rotate(Vector3.down * (rotateSpeed * Time.deltaTime));
            }
            if (Input.GetKey(KeyCode.D))
            {
                cannonPivot.transform.Rotate(Vector3.up * (rotateSpeed * Time.deltaTime));
            }
            
            
            if (currentPower == maxPower)
                isMax = true;
            else if (currentPower < 0)
                isMax = false;
            
            if (Input.GetKey(KeyCode.Space))
            {
                if (!isMax)
                {
                    currentPower += fillSpeed * Time.deltaTime;
                    currentPower = Mathf.Clamp(currentPower, 0, maxPower);
                    slider.value = currentPower / maxPower;
                }
                else if (isMax)
                {
                    currentPower -= fillSpeed * Time.deltaTime;
                    slider.value = currentPower / maxPower;
                }
            }
            
            if (Input.GetKeyUp(KeyCode.Space))
            {
                GameObject cannonBallInstance = Instantiate(cannonBall, transform.position, transform.rotation);
                
                cannonBallInstance.GetComponent<Rigidbody>().AddForce(transform.forward * currentPower, ForceMode.Impulse);
                
                currentPower = 0.0f; 
                slider.value = 0.0f;
            }
        }
    }

     

    4. CannonPivot과 Capsule 각각 바인딩

     

    └ 추가 보완점

    1. W로 각도를 올릴 때 Z값을 최대 0까지, S로 각도를 내릴 때 Z값을 최대 -90까지만 움직이도록

    2. 대포가 움직이게 구현

     

    └ Quest

    1. 사각 땅 안에 랜덤으로 몬스터 스폰, 캐논이 방향을 회전하면서 게이지를 조절해서 몬스터를 맞추면 몬스터가 죽고 그 자리에 파편이 남다가 3초 뒤 삭제되게 하기

     

    2. 대포가 아니라 캐릭터가 공격 모션을 할 때 손에서 발사체가 나가게 하기 (상급자용)