https://01149.tistory.com/116

 

Unity3D :: Skybox에 대해서

Q.SkyBox가 뭐야?A.월드스페이스의 배경을 둘러싸는 환경 매핑 기술 이다. Unity에서는 Scene의 BackGround로 주로 사용되며 보통 자연적인 환경(하늘,구름,산)을 표현할때 많이 사용미리 구성된 스카이

01149.tistory.com

https://01149.tistory.com/117

 

Unity :: Rigidbody컴포넌트의 Rigidbody.AddForce() , ForceMode

Q.Rigidbody컴포넌트가 뭐야?A. Unity 내에서 제공하는 물리연산 컴포넌트이다.  Q.AddForce() 메서드는 어디다가 써?A.Rigidbody가 적용된 오브젝트에 AddForce 메서드를 사용하면 외부에서 물리적인 힘을

01149.tistory.com

https://01149.tistory.com/118

 

Unity3D :: Ray,RayCast,RayCastHit

https://01149.tistory.com/102 Unity :: 비트연산자와 레이어마스크Q.비트연산자는 뭐야?A.컴퓨터가 나타내는 제일 작은 단위인 비트(bit = 0 또는 1)비트 연산자는 bit(0,1)을 이동하거나 논리 연산을 할떄 사

01149.tistory.com

 

https://01149.tistory.com/102

 

Unity :: 비트연산자와 레이어마스크

Q.비트연산자는 뭐야?A.컴퓨터가 나타내는 제일 작은 단위인 비트(bit = 0 또는 1)비트 연산자는 bit(0,1)을 이동하거나 논리 연산을 할떄 사용하는 것이다.논린 연산을 사용하면 이런식의 결과값을

01149.tistory.com

RayCast를 이용하여 충돌체크시 레이어를 사용하여 최적화 및 가독성을 올릴 수있다.

 

위에 참고한글은 Unity2D,3D 가릴것 없이 RayCast를 사용하여 레이어를 기준으로 충돌 처리를 적용하는 글이다.

나중에 찾아보게 될때 연관이 있어 링크를 달아놨다.

 


 

Q.Ray,RayCast,RayCastHit 가 뭐야?

A.

Ray : 레이저 총 쏠때 그 레이저를 의미하며 , Ray라는 자료형 타입을 이용하여 원하는 방향으로 쏠 수있다.

Unity 내부 코드의 Ray 원문

origin은 레이저의 시작점 , direction은 레이저가 발사되는 방향을 의미한다. 

Ray ray = new Ray(transfrom.position,transform.forward); //오브젝트에서 발사
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f,0.5f,0)); //카메라 중심에서 발사
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); //마우스에서 발사

 

위의 예제로 적은 Camera.main 메서드들은 전부 Ray를 반환하고 있다.

 


RayCast : 눈에 보이지 않는 레이저(Ray)에 맞은 오브젝트가 무엇인지 판단할때 사용되는 메서드이다.

RayCast() 메서드의 원문

RayCast는 Physics 포함되어있는 메서드이며 함수오버로딩도 15개나 있는 메서드이다.

 

 //지상에 붙어있는지 확인하는 메서드
 bool IsGrounded()
 {
     //오브젝트를 기준으로 동,서,남,북 방향에서 아랫방향으로 Ray를 발사하는 코드
     Ray[] rays = new Ray[4]
     {
         new Ray(transform.position + (transform.forward * 0.2f) + (transform.up *0.01f), Vector3.down),
         new Ray(transform.position + (-transform.forward * 0.2f) + (transform.up *0.01f), Vector3.down),
         new Ray(transform.position + (transform.right * 0.2f) + (transform.up *0.01f), Vector3.down),
         new Ray(transform.position + (-transform.forward * 0.2f) + (transform.up *0.01f), Vector3.down),
     };

     //발사된 4개의 Ray를 반복문으로 체크하여 조사
     for (int i = 0; i < rays.Length; i++)
     {
         //Physics.Raycast(탐색하고 싶은 Ray,Ray의 길이,해당 Ray와 충돌시 비교할 레이어마스크)
         if (Physics.Raycast(rays[i], 0.1f, groundLayerMask))
         {
             return true;
         }
     }

     return false;
 }

해당 예제에서 사용된 RayCast 메서드로 bool값을 반환하고 있다.

 

해당 예제 코드는 점프할때 캐릭터가 지상에 붙어있는지 체크하는 메서드이며 동,서,남,북으로 아랫방향으로 Ray를 쏴서

하나라도 Player를 제외한 레이어와 Ray가 충돌시 점프 가능이라고 true를 반환하는 메서드이다.

 

p.s 레이어마스크를 Inspector로 간단하게 설정하기

더보기

위에 참고글을 사용하면 스크립트로도 레이어의 충돌 체크 유무 판단을 내릴 수 있다.

하지만 Inspector로도 간단하게 되기에 적어보았다.

 

예시) 점프시에 자기 자신 레이어와 겹쳐서 점프가 무조건 된다는 판단을 내리면 안됨

Player 레이어를 제외하고 다른 레이어와 충돌시 점프가 가능하다고 판단

 

레이어 마스크 변수 선언
Inspector에서 해당 레이어를 이렇게도 조절이 가능하다.

이렇게도 설정이 가능하다. 

 

해당 밑에 글을 참고하면 더욱 쉽게 Raycast와 LayerMask를 쉽게 사용 할 수 있을것이다.

https://dallcom-forever2620.tistory.com/18

 

[Unity] Raycast Layermask 설정

Raycast 를 사용하여 타겟에게 ray 를 쏴서 처리하고 있을때 자신과 타겟사이에 오브젝트가 끼어들면 ray를 쏘지 못하게 되죠. 당연한 말이지만, raycast 사용중 자신과 타겟사이에 오브젝트가 끼어들

dallcom-forever2620.tistory.com

 


RayCastHit : Physics.RayCast() 메서드를 사용하여 Ray에 검출된 오브젝트의 충돌 정보만 아닌 충돌된 오브젝트의 정보가 필요할때 사용되는 [ 구조체 ]이다. 

RayCastHit의 구조체 원문 , 정보를 담고 있는 구조체라 정말 필드값이 많다. 직접 찾아보는것을 추천

 

Ray ray;
RayCastHit hit;

void Update()
{
	ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    
    //true 면 RaycastHit에 데이터를 가지고 나옴
    if(Physics.Raycast(ray,out hit))
    {
        Debug.Log("hit.distance"); //Ray의 원점에서 충돌한 객체와의 거리
        Debug.Log("hit.point"); //RayCas가 감지된 위치
        Debug.Log("hit.transform"); // 충돌객체의 transform에 대한 참조 
        //충돌한 객체의 콜라이더 컴포넌트에 접근하여 이름 호출
        Debug.Log("hit.collider.gameObject.name"); 
    }
}

해당 예제에서 사용된 Physics.RayCast 메서드

 

Q.Rigidbody컴포넌트가 뭐야?

A. Unity 내에서 제공하는 물리연산 컴포넌트이다. 

 

Q.AddForce() 메서드는 어디다가 써?

A.Rigidbody가 적용된 오브젝트에 AddForce 메서드를 사용하면 외부에서 물리적인 힘을 가하는 물리현상을 나타낼때 사용한다.

 

Q.AddForce()를 어떡해 써?

A.코드는 기본적으로 이런식으로 쓴다.

private Rigidbody _rigidbody;

 private void Awake()
 {
     _rigidbody = GetComponent<Rigidbody>(); //오브젝트에 있는 Rigidbody 컴포넌트 호출 
 }
 
 private void OnJump()
 {
       //rigidbody컴포넌트에서 AddForce() 메서드를 호출하여 사용
 	  _rigidbody.AddForce(Vector2.up * jumpPower, ForceMode.Impulse);
 }

 

AddForce() 인자값으로 ForceMode라는 enum 값이 아래와 같이 들어가게된다.

아래의 코드는 유니티 엔진 내부 코드이다.

ForceMode Type 설명
Force 힘을 지속적으로 적용할때 사용됨
Acceleration 가속도를 적용할떄 사용되며, 전 힘의 누적에 따라서 점진적으로 더 빠르게 움직이게 됨
Impulse 순간적인 힘을 적용하여, 짧은시간에 빠른 움직임이 필요할때 사용
VelocityChange 변화하는 속도를 적용, 오브젝트의 현재 속도를 변경하면서 사용될때 사용

 

Q.SkyBox가 뭐야?

A.월드스페이스의 배경을 둘러싸는 환경 매핑 기술 이다.

 

Unity에서는 Scene의 BackGround로 주로 사용되며 보통 자연적인 환경(하늘,구름,산)을 표현할때 많이 사용

미리 구성된 스카이박스를 가져와서 사용 할수 있어 Asset으로도 활용도가 높음

 

이 SkyBox를 동적으로 변경하여 낮과 밤 등의 시간대나 특정 이벤트 연출을 줄 수있지만

단점으로는 아무래도 텍스쳐로 맵핑하는 그래픽렌더링이 들어가니 최적화는 좋지 않다.

 

Q.SkyBox를 그럼 어떡해 써??

A.SkyBox는 Material로 구성되어있으면 Material의 Shader를 변경하여 사용하면된다.

 

Skybox는 Material로 구성되어있으며 Shader의 Type을 변경하여 사용하면된다.
Skybox-Procedural Type으로 구성한 배치

 

어떤 Type을 선택하든 공통적으로 Tint Color(Procedural은 Sky와 Ground 2개로 설정가능) , Exposure를 제어 할 수 있다.

기능 이름 설명
Tint Color Skybox의 색상을 결정 합니다. 
Exposure 스카이박스의 밝기를 조정합니다.

 

Skybox를 선택하면 4가지 타입에 대한 설명입니다. 

Skybox의 Type 설명
6-Sided 여섯개의 텍스처를 사용하여 위,아래,왼쪽,오른쪽,앞,뒤에 배치하여 사용됩니다.

하지만 이 Type의 단점은 6면의 리소스(이미지)를 전부 구해야하니 관리 측면에서는 힘들수가 있다.
CubeMap 여섯개의 텍스처를 사용하여 위,아래,왼쪽,오른쪽,앞,뒤에 배치하여 사용됩니다.

그대신 하나의 리소스(이미지)로 6면을 전부 맵핑 할 수 있습니다.
Panoramic(파노라마) 파노라마가 구체형 스카이박스리소스(이미지)를 구체형으로 맵핑하여 사용 할 수있습니다.

360도(수평) ,180도(수직) 이 두종류중 하나를 골라 사용합니다.
Procedural(절차적) 미리 정의된 텍스처가 아닌 유니티의 절차적 알고리즘을 통해 하늘을 동적으로 생성하는 방식으로 시간의 흐름을 나타내기 좋습니다.

하늘의 색상,태양 및 달의 위치, 대기현상 , 낮과 밤 등 다양한 시간적 흐름 연출을 나타낼 수 있습니다.

 

Q.Skybox Material도 만들고 Shader도 선택해서 inspector에 속성값도 다  조절했어 어떡해 적용해야돼?

A. Window - Rendering - Lighting → Lighting(Environment) → Material을 변경하면 적용 됩니다.

Lighting - Environment 창에서 Material을 변경해주면 된다.

 

Skybox가 적용된 월드

게임 개발을 공부하면 오브젝트 풀에 대해 배우면서

오브젝트를 미리 생성하고 필요할때 꺼내서 사용하고 사용이 다되면 풀에 반납하여 비활성화 시켜주는 것을

오브젝트 풀링이라고 배워왔다.

 

하지만 유니티에서는 이 오브젝트 풀을 사용 할수 있게 패키지로 제공하고 있다.

 

using UnityEngine.Pool;

해당 패키지를 사용하기 위해서는 해당 코드를 작성하고 사용해야된다.

 

해당 Pool를 사용하려면 어떡해 해야되는가?

public IObjectPool<GameObject> objectPool;
public ObjectPool<GameObject> pool;

변수로 선언할때는 이 두방식을 이용하여 변수를 초기화 하여 사용하면된다.

 

초기화시 필요한 생성자 매개변수들

 pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);
 objectPool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);

이런식으로 선언을 하면된다. 

 

namespace UnityEngine.Pool
{
    public interface IObjectPool<T> where T : class
    {
        int CountInactive { get; }

        T Get();

        PooledObject<T> Get(out T v);

        void Release(T element);

        void Clear();
    }
}

 

해당 코드가 IObjectPool의 내부 구조이다.

 

using System.Collections.Generic;
using System.ComponentModel;
using UnityEditor.Presets;
using UnityEngine;
using UnityEngine.Pool;


//최소 50개의 오브젝트 수 보장, 부족할 경우 누적 300개까지 추가 생성, 300개가 넘어갈 경우 가장 오래전에 생성된 오브젝트를 반환 후 재사용
public class Week2_OjbectPool_Q4 : Singleton<Week2_OjbectPool_Q4>
{

    [SerializeField] private GameObject prefab;
    public ObjectPool<GameObject> pool;

    public bool collectionChecks = true;
    private const int minSize = 50;
    private const int maxSize = 300;

    private GameObject container;
    void Awake()
    {
        container = new GameObject(prefab.name + "_Container");

        /*
        createFunc: 오브젝트 생성 함수 (Func)
        actionOnGet: 풀에서 오브젝트를 가져오는 함수 (Action)
        actionOnRelease: 오브젝트를 비활성화할 때 호출하는 함수 (Action)
        actionOnDestroy: 오브젝트 파괴 함수 (Action)
        collectionCheck: 중복 반환 체크 (bool)
        defaultCapacity: ObjectPool의 List<T>에 미리 자리 만드는것(오브젝트 생성x)(int)
        maxSize: 저장할 오브젝트의 최대 갯수 (int)
         */
        pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);
    }

    private GameObject CreateObject()
    {
        // [요구스펙 1] Create Object

        //Instantiate();
        GameObject obj = Instantiate(prefab, container.transform);
        return obj;
    }

    public void GetObject(GameObject obj)
    {
        // [요구스펙 2] Get Object
        obj.gameObject.SetActive(true);
    }

    public void ReleaseObject(GameObject obj)
    {
        // [요구스펙 3] Release Object
        obj.gameObject.SetActive(false);
    }

    public void DestroyObject(GameObject obj)
    {
        //오브젝트 파괴 함수
        Destroy(obj.gameObject);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            for (int i = 0; i < 10; i++)
            {
                pool.Get();
            }
        }
    }
}

해당 코드는 UnityEngine.Pool을 이용하여 오브젝트 풀을 구성한 예제 코드이다. 

사용시에는 따로 풀링할 오브젝트를 넣어 등록해서 사용해야된다. 

 

 

해당 Docs를 참고하여 글을 작성하였습니다. 

https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html

 

Unity - Scripting API: ObjectPool<T0>

Object Pooling is a way to optimize your projects and lower the burden that is placed on the CPU when having to rapidly create and destroy new objects. It is a good practice and design pattern to keep in mind to help relieve the processing power of the CPU

docs.unity3d.com

 

https://01149.tistory.com/115

 

Unity :: UnityEngine에서 제공하는 Pool 패키지(오브젝트 풀)

게임 개발을 공부하면 오브젝트 풀에 대해 배우면서오브젝트를 미리 생성하고 필요할때 꺼내서 사용하고 사용이 다되면 풀에 반납하여 비활성화 시켜주는 것을오브젝트 풀링이라고 배워왔다.

01149.tistory.com

 

오늘 유니티 2D 팀프로젝트 하고 튜터님들이 각 팀들마다 피드백 해주셨는데

내 설계에도 도움이 될것 같아서 작성해본다.

 

0.프리팹화 한거 Resources를 사용하여 동적 생성하여 Scene 채우기 할것


1.매니저에 다 모든 기능을 넣지마라


2.Find 메서들 지양해라


3.문자열을 사용하는 메서드들을 해쉬코드로 변경하여 사용할것


4.오브젝트 충돌 검사시 태그 검사가 아닌
오브젝트 비트연산자 + 레이어마스크 검사를 하거나
컴포넌트를 호출하여 검사하도록 하자


5.이름이니셜로 폴더링 하지말것, 만약 그 팀원 퇴사했다 치면
어떤 기능일 구현했는지 모르기 때문에 매우 큰 문제가 된다.결국 코드 작업시 남의 스크립트 영역에 침범하게 될일이 있으므로 기능 별로 폴더링하여 사용하여야된다.


6.게임매니저가 Player를 찾게 하지말고
Player에서 게임매니저를 접근하게 하는 로직을 생성하자
ex)Player 오브젝트에서 게임 저장
GameManager.Instance.GameSave() ....


7.매직 넘버 지양하자

코드에 하드코딩으로 숫자 집어넣는거 


8.파입 입출력을 이용하자 스테이지 구성을 지향하자


9.프로젝트 진행할때 플랫폼 설정하기-> 안드로이드,IOS,윈도우 등등...


10.프로젝트 세팅에서 fixedUpdate의 시간을 조정 가능하다. 


11.GetComponent<>는 최적화가 잘되어있지만 수시로 불러 사용하는건 지양해야된다.


12.Dirctinoray의 키값을 enum으로 해서 가독성을 올릴것 


13.코루틴은 효과만 관리하고 플래그(변수)는 메서드에서 관리하는것이 좋다.


14.코루틴이 실행중인데 중복 실행 방지 로직은 항상 생각할것

15.어디서든 UI매니저를 통해서 호출하게하면 좋다.

 

각팀들에게 해주셨던 피드백을 간단하게 정리한 내용이다.

설계할때 한번씩 보고 설계하도록 하자 

Q.제네릭을 왜 사용하는걸까?

A.간단히 말해서 클래스를 자료형에 상관없이 자유롭게 쓸 수 있게 하는것

즉, 클래스를 변수처럼 사용 하고 싶을때 주로 사용한다.

정수,문자열 데이터 를 담는공간을 변수라 하고

함수를 변수처럼 사용하면 delegate를 사용하고

class를 변수처럼 사용하기 위해 제네릭을 사용한다. 

이런 느낌으로 보면 된다.

 

Q.제네릭을 쓰면 뭐가 좋아?

A. 클래스에 자료형 제약을 걸고 사용 하여 처음 구조 설계시 시간이 걸리지만

유지 보수에 매우 효율적이기 때문이다.

 

Q.어떨때 써??

A. 자주 사용되는 클래스인데 자료형이 다양한 클래스인경우에 주로 사용된다.

첫번쨰 예시 ) 탄막슈팅게임에서 총알,아이템,이펙트 등등 다양하게 객체를 생성해야 되는 류의 게임에서

오브젝트 풀링을 제너릭 구조로 설계해서 사용하기

 

자료형을 담을 오브젝트 Pool을 선언해 준다. 그리고 제약조건으로 MonoBehaviour를 선언하여 오브젝트 및 컴포넌트로 사용 할수 있는 class만 T로 선언 할 수있다.

public class ObjectPool<T> where T : MonoBehaviour
{
    public Queue<T> PoolQueue = new Queue<T>();

    public bool InitPushObject(T poolObject)
    {
        if (poolObject == null) 
        {
            Debug.Log(poolObject.name + "이 null 입니다.");
            return false;
        }

        poolObject.gameObject.SetActive(false);
        PoolQueue.Enqueue(poolObject);

        return true;
    }

    public T PoolObject(Vector2 pos)
    {
        if (PoolQueue.Count <= 5)
        {
            Debug.Log(typeof(T).Name + "오브젝트 갯수 부족!");

            return null;
        }

        T obj = PoolQueue.Dequeue();
        obj.gameObject.transform.position = pos;
        obj.gameObject.SetActive(true);
        return obj;
    }

    public void PushObject(T pushObj)
    {
        pushObj.gameObject.SetActive(false);
        PoolQueue.Enqueue(pushObj);
        //Debug.Log("총알 큐 갯수"+PoolQueue.Count);
    }



}

 

T로 해당 자료형을 선언하고 Dictionary의 value 값으로 ObjectPool<T>를 선언하여 

SpawnManager도 제네릭으로 설계한다.

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

public abstract class SpawnManager<T> : MonoBehaviour, iManager where T : MonoBehaviour,iPoolable<T>
{
    //부모 클래스를 저장
    [SerializeField] protected List<T> prefabesList;

    protected Dictionary<string, ObjectPool<T>> objectPools = new Dictionary<string, ObjectPool<T>>();
    public virtual void Initialize() { }
}

 

결국 최종적으로 사용하는 EnemySpanwManager에 SpawnManager<EnemyController>를 상속하여 제네릭으로 생성하였다. 이렇게되면 자료형만 바꿔 작성하면 Item,Effect 생상선이 올라가기 때문이다.

 

또한 이 구조에서 T는 부모 클래스를 넣고서 EnemyController의 자식 클래스로 형변환하여 확장성도 확보가 가능해진다.

public class EnemySpawnManager : SpawnManager<EnemyController>
{
    [SerializeField] private ItemSpawnManager itemSpawnManager;
    [SerializeField] private EffectManager effectManager;
    public override void Initialize()
    {
        Debug.Log(gameObject.name + "Initalize 완료!");
    }

    private void Awake()
    {
        EnemyController gameObj;

        foreach (EnemyController prefab in prefabesList)
        {
            GameObject poolContainer = new GameObject("Pool_Container_" + prefab.name);

            ObjectPool<EnemyController> objectPool = new ObjectPool<EnemyController>();

            for (int i = 0; i < prefab.EnemySO.PoolCount; i++)
            {
                gameObj = Instantiate(prefab, poolContainer.transform);
                gameObj.OnEventDieObject += GameManager.Instance.GetScore;
                gameObj.OnEventPushObject += PushObject;
                gameObj.OnEventDropItem += itemSpawnManager.RandomPoolObject;
                gameObj.OnEventDieEffectObject += effectManager.PoolObject;
                objectPool.InitPushObject(gameObj);
            }

            objectPools.Add(prefab.EnemySO.enemyName, objectPool);
        }

        Initialize();
    }

    public EnemyController PoolObject(string objName, Vector2 spawnPos)
    {
        return objectPools[objName].PoolObject(spawnPos);
    }

    public EnemyController PoolObject(EnemyController enemy, Vector2 spawnPos)
    {
        return objectPools[enemy.EnemySO.enemyName].PoolObject(spawnPos);
    }

    private void PushObject(EnemyController enemy)
    {
        //objectPools[testmonster.monsterName].PushObject(testmonster);
        objectPools[enemy.EnemySO.enemyName].PushObject(enemy);
    }

}

 

예시2) 싱글톤 구조를 제네릭으로 구성 

싱글톤 구조는 Manager 클래스에서 정말 많이 쓰이기에 제네릭으로 구조를 잡고 상속으로 사용하게되면

효율성이 매우증가한다.

 

이 외에도 많은 사용용도가 있지만 나는 이 목적으로 많이 사용을 했던것 같다. 

+ Recent posts