Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

게임 프로그래밍

[UNITY] Scriptableobject에 대해서 본문

프로그래밍/유니티

[UNITY] Scriptableobject에 대해서

Junwe 2020. 1. 7. 02:24

https://unity3d.com/kr/how-to/architect-with-scriptable-objects

 

Architect your code in Unity the smart way with Scriptable Objects - Unity

ScriptableObject is a serializable Unity class that allows you to store large quantities of shared data independent from script instances. Using Scriptable Objects makes it easier to build in a level of flexible communication between the different systems

unity3d.com

이번 게임을 만들 때 scriptableobject를 사용하였다.

 

일단 scriptableobject가 무엇이냐면 

 

스크립터블 오브젝트는 스크립트 인스턴스에 관계 없이 대량의 공유 데이터를 저장하는 데 사용할 수 있는 직렬화 가능한 Unity 클래스입니다. 스크립터블 오브젝트를 사용하면 변경 사항과 디버깅을 관리하기가 더 쉬워집니다. 진행하면서 필요한 것을 더 쉽게 변경하고 적절히 수정할 수 있도록 게임 내 여러 시스템 사이에 유연한 커뮤니케이션 레벨을 빌드하고 컴포넌트를 재사용할 수도 있습니다.

 

이라고 한다.

 

변수

스크립터블 오브젝트(Scriptable Object)로 빌드할 수 있는 가장 간단한 것 중 하나는 독립적인 에셋 기반 변수입니다. 다음은 FloatVariable의 예지만, 다른 직렬화 가능한 타입으로도 확장할 수 있습니다.

이를 통해 모든 팀원은 기술적 수준에 상관 없이 새로운 FloatVariable 에셋을 생성하여 새로운 게임 변수를 정의할 수 있습니다. MonoBehaviour 또는 스크립터블 오브젝트는 이 공유 값을 참조하기 위해 공용 플로트 대신 공용 FloatVariable을 사용할 수 있습니다.

더 좋은 점은 MonoBehaviour 하나가 FloatVariable 값을 변경하면 다른 MonoBehaviour도 해당 변경 사항을 확인할 수 있다는 점입니다. 이 경우 시스템 간에 일종의 메시징 레이어가 생성되므로 서로에 대한 참조가 필요하지 않습니다.

이에 대한 예로 플레이어의 HP를 들 수 있습니다. 로컬 플레이어 1인용 게임에서 플레이어의 HP는 이름이 PlayerHP인 FloatVariable일 수 있습니다. 플레이어가 데미지를 입으면 PlayerHP에서 차감되고 플레이어가 치유되면 PlayerHP에 가산됩니다.

이제 씬에 상태 표시줄 프리팹이 있다고 상상해 보세요. 상태 표시줄은 PlayerHP 변수를 모니터링하여 디스플레이를 업데이트합니다. 코드를 변경하지 않으면 다른 것(예: PlayerMP 변수)을 포인트하기 쉽습니다. 상태 표시줄은 씬의 플레이어에 대한 아무런 정보도 갖고 있지 않으며, 플레이어가 기록하는 변수와 동일한 변수에서 값을 읽어올 뿐입니다.

이렇게 설정해 두면 PlayerHP와 연동할 요소를 추가하기가 쉽습니다. PlayerHP가 낮아지면 음악이 바뀔 수 있고, 플레이어가 약해진 것을 알면 적의 공격 패턴이 변할 수 있으며, 다음 공격을 받으면 위험하다는 상태를 화면 효과로 알릴 수 있습니다. 여기서 중요한 것은 플레이어(Player) 스크립트에서 이러한 시스템에 메시지를 보내지 않으며, 시스템이 플레이어 게임 오브젝트에 대해 알 필요도 없다는 점입니다. 게임 실행 중에 인스펙터에 들어가서 PlayerHP의 값을 변경하여 테스트할 수도 있습니다.

FloatVariable의 값을 편집하는 경우 데이터를 런타임 값으로 복사하고 디스크에 ScriptableObject로 저장된 값을 변경하지 않는 것이 좋습니다. 이렇게 하면 MonoBehaviours가 RuntimeValue에 액세스하여 디스크에 저장된 InitialValue를 편집하는 것을 방지하게 됩니다.

 

이벤트

스크립터블 오브젝트 위에 빌드할 수 있는 기능 중에 필자가 가장 즐겨 사용하는 것 중 하나는 이벤트 시스템입니다. 이벤트 아키텍처는 서로에 대한 직접적인 정보를 갖고 있지 않은 시스템 간에 메시지를 보내 코드를 모듈화하는 데 도움이 됩니다. 이를 통해 업데이트 루프에서 상태 변경을 계속 모니터링하지 않아도 상태 변경에 대응할 수 있습니다.

이 이벤트 시스템은 GameEvent ScriptableObject와 GameEventListener MonoBehaviour로 구성됩니다. 디자이너는 중요 메시지를 나타내는 GameEvent를 프로젝트에 생성하여 보낼 수 있습니다. GameEventListener는 특정 GameEvent가 발생할 때까지 대기하고 UnityEvent(실제 이벤트가 아니고 직렬화된 함수 호출에 더 가까움)를 호출하여 대응합니다.

예로는 게임에서 플레이어의 죽음을 처리하는 것을 들 수 있습니다. 이 시점에 아주 다양한 실행 관련 항목이 바뀔 수 있지만, 각 로직을 코딩할 시점을 결정하기가 어려울 수 있습니다. Player 스크립트가 게임 종료 UI나 음악 변경을 트리거해야 할까요? 적이 매 프레임마다 플레이어가 아직 살아있는지 확인해야 할까요? 이벤트 시스템을 활용하면 이와 같은 종속성 문제를 해결할 수 있습니다.

플레이어가 죽으면 Player 스크립트가 OnPlayerDied 이벤트에 대해 Raise를 호출합니다. Player 스크립트는 어떤 시스템이 이벤트와 연관되어 있는지 알 필요가 없습니다. 단순히 브로드캐스트일 뿐이기 때문입니다. 게임 오버(Game Over) UI는 OnPlayerDied 이벤트에 반응하여 애니메이션화를 시작하며, 카메라 스크립트는 화면을 검은색으로 채우기 시작할 수 있으며, 음악 시스템은 음악을 변경할 수 있습니다. 적 캐릭터도 각각 OnPlayerDied 이벤트에 반응하도록 하여 도발 애니메이션이나 대기 동작으로 돌아가는 상태 변경을 트리거할 수 있습니다.

이 패턴에 따라 플레이어의 죽음에 대한 새로운 리스폰스를 매우 쉽게 추가할 수 있습니다. 또한, 특정 테스트 코드에서 이벤트에 대해 Raise를 호출하거나 인스펙터의 버튼을 사용하여 플레이어의 죽음에 대한 리스폰스를 쉽게 테스트할 수도 있습니다.

Schell Games에서 필자가 빌드한 이벤트 시스템은 훨씬 더 복잡하면서 데이터를 전달하고 타입을 자동으로 생성할 수 있는 기능이 있는 시스템으로 발전했습니다. 세부적인 내용을 모두 공개할 수는 없지만, 현재 저희가 사용하고 있는 시스템의 시초라고 말씀드릴 수 있습니다.

 

시스템

Scriptable Object는 단순 데이터가 아니어도 됩니다. MonoBehaviour에서 아무 시스템이나 선택하여 구현을 ScriptableObject로 이동할 수 있는지 확인하세요. InventoryManager를 DontDestroyOnLoad MonoBehaviour에 포함시키지 않고 ScriptableObject에 대신 놓으려고 시도해 보세요.

씬에 연결되지 않았기 때문에 Transform이 없고 Update 함수를 가져오지 않지만, 특별히 초기화하지 않아도 서로 다른 씬이 로드되는 동안 상태를 유지합니다. 인벤토리에 액세스하기 위한 스크립트가 필요한 경우 싱글톤 대신 인벤토리 시스템 오브젝트에 대한 공용 레퍼런스를 사용하세요. 그러면 싱글톤을 사용하는 경우보다 테스트 인벤토리나 튜토리얼 인벤토리를 바꿔 넣기가 쉬워집니다.

이제 Player 스크립트가 인벤토리 시스템을 참조하는 것을 가정해 볼 수 있습니다. 플레이어가 스폰될 때 시스템이 플레이어가 소유한 모든 오브젝트의 인벤토리를 요청하고 장비를 스폰할 수 있습니다. 착용 UI도 인벤토리를 참조하고 여러 아이템을 루프한 다음 무엇을 드로우할지 결정할 수 있습니다.

 

대충 이러한 이점이 있다고 한다.

 

유니티에서 제공하는 직렬화 가능한 클래스인데 json이나 xml보다 unity에서 editor에서 직접 수정이 가능하고. vector3나 animation같은 유니티 관련 클래스도 넣을 수 있으니 유니티에서 사용하기 좋은것같다. 또 위에 설명한 데이터, 이벤트 공유로 인한 느슨한 결합도 만들 수 있을것이다.

 

그렇다면 어떻게 만들어서 쓸까?

간단하게 만들어보자

 

생성방법

[CreateAssetMenu(fileName = "New HeroData", menuName = "Sword HeroData", order = 2)]

public class test : ScriptableObject

{

 

}

유니티에서 스크립트를 생성하면 Monobehaviour를 자동으로 상속 받게 되는데 지우고

ScriptableObject를 상속받아온다.

 

[CreateAssetMenu] : 없어도 되지만 있으면 편함

fileName : 새로 제작하게되면 임시로 생성된다. 

menuName : 유니티 메뉴에 표기되는 이름

order : 메뉴에서 보일 순서

Assets -> Create 보면 방금 생성한 HeroData가 보이게 된다.

 

SerializeField의 원본 데이터들은 수정되면 안되기 때문에 

데이터 값을 받을 게터를 public으로 추가해준다.

public class test : ScriptableObject

{

    [SerializeField]

    string heroName;

    [SerializeField]

    string description;

    [SerializeField]

    int goldCost;

 

    public string sName => heroName;

    public string sDesc => description;

    public int    nCoin => goldCost;

}

 

사용법

데이터 값 입력

ScriptableObject를 받을 코드 작성

public class Scene : MonoBehaviour

{

    public Text m_txtUI;

    public test m_Data;

    void Start()

    {

        m_txtUI.text = string.Format(m_Data.sName + " // " + m_Data.sDesc);

    }

}

Data 매칭 인스펙터 창에서 끌어다 놓는다

생성방법 참고 : https://funfunhanblog.tistory.com/81

Comments