[Unity] Unit Hierarchy Structure

녹차·2024년 7월 31일

Unity

목록 보기
5/8
post-thumbnail

Introduction

What is Unit Hierarchy Structure?

Unit Hierarchy Structure란? 오브젝트 마다 계층적 구조를 만들어 관리하는 것을 뜻합니다.

Unit Hierarchy Structure가 필요한 이유

2d Platform Game을 만들 때 많은 Unit 있습니다. 예를 들면 NPC, Enemy, Player등 있습니다. 그리고 Unit 뿐 아니라 장애물이나 아이템을 포함하면 오브젝트의 수 배로 증가할 것입니다.

그 상황에서 PlayerController, EnemyController, ETC 등등 직접 스크립트를 짜보면 상당히 중복되는 코드가 자주 사용됩니다.

그렇기 때문에 이를 없애기 위해 계층적 구조를 만들어 유용하게 사용할 수 있습니다.

Body

본론을 먼저 적어내기 앞서 Unreal 클래스 계층 구조에 영향을 받아 code가 제공되기 때문에

아작 Unreal 계층 구조에 대해 모르시는 분들이 계시다면 얻어가실 수 있는 포스팅이 되실 겁니다.

Unreal Class Structure Diagram

위 그림을 보면 ACharacter가 AObject까지 상속을 받고 있습니다. 각각 어떠한 계층이 어떤 역할을 하는지 다른 포스팅에도 정리잘 되어 있기 때문에 필요한 부분을 설명하겠습니다.

What is AActor?

언리얼 엔진의 레벨에 배치할 수 있는 오브젝트를 의미
-> Unity의 GameObject와 비슷

[Actor에 대한 Unreal 공식 문서]
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/actors?application_version=4.27

what is ACharacter

  • Character는 걸어다닐 수 있는 능력을 지닌 특수 유형 Pawn 을 말합니다.
  • 이족보행을 하는 인간형 오브젝트에 매우 적합합니다

[ACharacter 대한 Unreal 공식 문서]
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/character?application_version=4.27

상속

unity 응용에 앞서 C#에서 간단히 상속 개념에 알아보려고 합니다.

자세한 설명이 글의 논점을 흐리게 만들 수 있기 때문에 짧은 설명과 이해로 상속을 설명하려고 합니다.

상속이란?

상속이란? OOP의 한 개념으로, 한 클래스가 다른 클래스의 특성과 동작을 물려받는 것을 의미합니다.

자세한 설명보다 예시 코드를 보여주며 설명을 하겠습니다.

// 기본 몬스터 클래스
public class Monster
{
    public virtual void Attack()
    {
        Console.WriteLine("몬스터가 공격합니다!");
    }

    public virtual void Defend()
    {
        Console.WriteLine("몬스터가 방어합니다!");
    }
}

위 코드에서 수 많은 몬스터의 부모 클래스인 Monster를 만들어주었습니다.

public class Orc : Monster
{
    public override void Attack()
    {
        Console.WriteLine("오크가 강하게 공격합니다!");
    }

    public override void Defend()
    {
        Console.WriteLine("오크가 방어 자세를 취합니다!");
    }
}

위 코드에서는 Orc라는 몬스터가 Monster의 상속을 받으며 기능을 구현하였습니다.

// 오크 보스 클래스 (오크를 상속)
public class OrcBoss : Orc
{
    public override void Attack()
    {
        Console.WriteLine("오크 보스가 강력하게 공격합니다!");
    }

    public override void Defend()
    {
        Console.WriteLine("오크 보스가 방어막을 사용합니다!");
    }
}

마지막으로 OrcBoss가 Orc를 상속받으며 마찬가지로 OrcBoss에 대한 공격, 방어 함수 구현 한 뒤, main 함수를 제시하겠습니다.

// 사용 예시
public class Program
{
    public static void Main(string[] args)
    {
        Monster monster = new Monster();
        monster.Attack(); // 몬스터가 공격합니다!
        monster.Defend(); // 몬스터가 방어합니다!

        Orc orc = new Orc();
        orc.Attack(); // 오크가 강하게 공격합니다!
        orc.Defend(); // 오크가 방어 자세를 취합니다!

        OrcBoss orcBoss = new OrcBoss();
        orcBoss.Attack(); // 오크 보스가 강력하게 공격합니다!
        orcBoss.Defend(); // 오크 보스가 방어막을 사용합니다!
    }
}

해당 실행 결과를 보면 쉅게 이해할 수 있을 겁니다. 그럼 여기서 질문 하나 드리겠습니다.

OrcBoss에서 Orc의 Attack 함수와 OrcBoss의 Attack 함수를 
동시에 부르고 싶으면 어떻게 해야하나요?

만약 답을 하기 힘들다면 상속에 대해서 다시 학습할 필요성이 있습니다. 간단한 개념 문제이기에 바로 정답으로 아래 코드를 제시하겠습니다.

public class OrcBoss : Orc
{
    public override void Attack()
    {
    	base.Attack(); //해당 코드가 없으면 Orc의 Attack 실행 X
    	
        Console.WriteLine("오크 보스가 강력하게 공격합니다!");
    }

    public override void Defend()
    {
        Console.WriteLine("오크 보스가 방어막을 사용합니다!");
    }
}

해당 문제를 낸 이유는 Unity 응용에서 똑같이 사용되기에 포스팅을 이해하는데 무리 없이 하기 위해서 였습니다.

마지막으로 상속 학습을 위한 참고할만한 문서를 올려놓았습니다.

https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/tutorials/inheritance

Unity에서 응용

의미를 축소하다 보니 그냥 상속이랑 다를게 없다고 생각할 순 있습니다. 하지만 왜 이렇게 스크립트 이름이 정해졌고, 어떠한 역할을 하는지 이해한다면 조금 더 넓은 시각을 가질 수 있게 될 것 입니다.

Unreal에서의 Actor와 Character의 역할만 가져와서 프로젝트에 맞게 사용했습니다.

/// <summary>
/// 모든 객체의 Actor의 역할을 한다.
/// </summary>
public abstract class Actor : MonoBehaviour
{
    public abstract void ActorInit();

    public abstract void ActorUpdate();

    public abstract void ActorDestory();
}

위 코드는 Actor를 추상 클래스로 만들어 초기화, 런타임, 종료시 3가지를 구분하여 선언했습니다.

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

/// <summary>
/// 적, 아군 캐릭터 Character 역할을 한다.
/// </summary>
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(Rigidbody2D))]
public abstract class Character : Actor
{
    protected Transform tr; 
    protected Animator anim;
    protected Rigidbody2D rb;

    #region Actor 구현부
    public override void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        tr = GetComponent<Transform>();
        anim = GetComponent<Animator>();
    }

    public override void Update() { }

    public override void ActorDestory() { }
    #endregion

    public abstract void CharacterMove();

    public abstract void CharacterAttack();

    //ETC
}

위의 코드에 대한 설명을 한 줄로 요약하면 '움직일 수 있는 캐릭터'를 위한 Code입니다.

"움직일 수 있는 캐릭터'를 위한 Code"

그러면 계층 구조를 만들어 보았으니 활용할 차례입니다. 글에서 Player만 제시했지만 그 외에도 NPC, 오브젝트 등에서 다양하게 활용할 수 있습니다.

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

[RequireComponent(typeof(RedHoodInput))]
public class RedHoodController : Character
{
	bool isDash = true;
	bool isAttack = true;
	bool isGround = false;
	bool isJump = false;
	bool isDashing;
	bool isHit;

	[SerializeField] RedHoodStat stat;
	[SerializeField] Transform jumpPos;

	RedHoodInput redHoodInput;

	public override void Start()
	{
		base.Start();

		redHoodInput = GetComponent<RedHoodInput>();
    }

    public override void Update()
    {
        base.Update();
		
        CharacterMove();
		RedHoodJump();
		RedHoodDash();
        CharacterAttack();
		RedHoodArrowLaunch();
	}
    
    public override void CharacterMove() {
    	...
    }
    
     public override void CharacterAttack() {
    	...
    }
}

...

계층 구조를 완벽하게 이해했다면 SOLID원칙에 대해 얘기해보겠습니다.

Conclusion

언리얼의 계층 구조를 유니티에서 커스텀 하여 적용해보았습니다. 2d 플랫포머 게임을 만들다 보면 다양한 곳에 상속이 활용되는데 이를 보다 더 구조적이고 단단하게 만들어 봤습니다.

profile
CK23 Game programmer

0개의 댓글