-
Unity Tip : Visitor Pattern(방문자 패턴)2023년 11월 11일
- 유니얼
-
작성자
-
2023.11.11.:00
728x90이번에는 Unity Visitor Pattern(방문자 패턴)에 대해 자세히 알아보고, 이 패턴의 개념과 유용성, 그리고 Unity 프로젝트에서 효과적으로 구현하는 방법에 대해 설명하겠습니다.
참고 자료 링크 :
https://www.yes24.com/Product/Goods/114854688
Visitor Pattern
방문자 패턴이란?
방문자 패턴은 클래스 자체를 수정하지 않고도 클래스 계층 구조에 새로운 작업이나 동작을 추가할 수 있는 동작 디자인 패턴입니다. 기본적으로 방문자 패턴은 간단한 목적을 제공합니다. 방문자가 개체 구조 내의 특정 요소에 대해 작업을 수행할 수 있도록 해줍니다. 이 패턴을 보다 직관적으로 파악하려면 개체를 단순한 격리된 데이터 및 논리 저장소가 아닌 구조로 간주하십시오. 기본적으로 방문자 패턴은 개체 자체를 직접 변경하지 않고도 개체의 구조를 탐색하고, 해당 요소를 조작하고, 기능을 확장할 수 있는 기능을 제공합니다.
Unity에서의 방문자 패턴
Unity에서 방문자 패턴은 GameObject 및 해당 구성 요소로 작업할 때 귀중한 디자인 패턴이 될 수 있습니다. Unity의 GameObject와 구성 요소는 계층 구조로 구성되어 있어 코드를 직접 수정하지 않고도 이러한 개체에 대해 광범위한 작업이나 계산을 수행할 수 있습니다. 이것이 방문자 패턴이 진정으로 빛을 발할 수 있는 부분입니다.
주요 참가자
- IVisitor: 방문자가 되고자 하는 모든 클래스가 구현해야 하는 인터페이스입니다.
- IVisitable: 방문하려는 모든 클래스에서 구현해야 하는 인터페이스입니다. 방문한 개체에는 방문자의 진입점 역할을 하는 accept() 메서드가 포함되어 있습니다.
장점
- 개방/폐쇄 원칙: 방문자 패턴을 사용하면 개체 자체를 수정하지 않고도 다양한 클래스의 개체에 작동하는 새로운 동작을 추가할 수 있습니다. 이는 엔터티가 확장을 위해 열려 있어야 하고 수정을 위해 닫혀 있어야 한다는 객체 지향 프로그래밍 원칙을 준수합니다.
- 단일 책임 원칙: 방문자 패턴은 데이터를 담당하는 한 개체(방문자)와 단일 책임 원칙을 준수하는 다른 개체(방문자)를 사용하여 특정 작업을 도입하는 데 중점을 두는 방식으로 우려 사항을 분리하도록 권장합니다.
단점
- 접근성: 방문자는 방문하는 요소의 비공개 필드와 메서드에 제한적으로 액세스할 수 있습니다. 결과적으로 클래스는 패턴이 사용되지 않은 경우보다 더 많은 공용 속성을 노출해야 할 수도 있습니다.
- 복잡성: 방문자 패턴은 싱글톤, 상태 및 개체 풀과 같은 단순한 패턴보다 구조적으로 더 복잡합니다. 이러한 복잡성은 다른 프로그래머가 패턴의 복잡성에 익숙하지 않은 경우 복잡한 코드베이스로 이어질 수 있습니다. 이는 호출과 관련된 두 개체의 유형에 따라 런타임 시 메서드 호출이 다른 특정 메서드로 동적으로 리디렉션되는 "이중 디스패치"라고 하는 소프트웨어 엔지니어링 개념을 사용합니다.
요약하자면, 방문자 패턴은 객체 지향 프로그래밍의 주요 원칙을 위반하지 않고 객체 구조의 기능을 향상시키는 강력한 도구입니다. 복잡성이 증가함에도 불구하고 확장성과 책임 분리를 촉진합니다. 효과적인 구현을 위해서는 이중 파견의 구조와 개념을 이해하는 것이 중요합니다.
Visitor Pattern 구현하기
Step 1 : 구성 요소 Interface(인터페이스) 구현하기
IVisitor Interface :
IVisitor 인터페이스는 Visitor 패턴을 구현하기 위한 것입니다. Visitor 패턴은 객체 구조 내의 다양한 요소를 방문하고 작업을 수행하기 위한 패턴으로 사용됩니다.
using System.Collections; using System.Collections.Generic; using UnityEngine; public interface IVisitor { }
- void Visit(Weapon weapon): 이 메서드는 Weapon 유형의 객체를 방문하고 해당 객체에 대한 작업을 수행하기 위한 것으로 추정됩니다. 이것은 방문자가 무기(Weapon) 객체를 처리할 때 호출됩니다.
- void Visit(Shield shield): 이 메서드는 Shield 유형의 객체를 방문하고 해당 객체에 대한 작업을 수행하기 위한 것으로 추정됩니다. 이것은 방문자가 방패(Shield) 객체를 처리할 때 호출됩니다.
- void Visit(Shoose shoose): 이 메서드는 Shoose 유형의 객체를 방문하고 해당 객체에 대한 작업을 수행하기 위한 것으로 추정됩니다. 이것은 방문자가 신발(Shoose) 객체를 처리할 때 호출됩니다.
이러한 메서드는 다양한 유형의 객체를 방문하고, 방문자가 특정 유형의 객체를 처리하는 방법을 정의합니다. Visitor 패턴을 사용하면 객체 유형을 확장하고 새로운 동작을 추가하기 쉽게 만들 수 있으며, 이것은 객체 구조 내의 다양한 요소에 대한 일관된 방문 및 작업 수행을 허용합니다.
IVisitorElement Interface :
IVisitorElement 인터페이스는 방문자 디자인 패턴의 기본 개념인 방문자 개체가 방문할 수 있는 요소와 연결됩니다.
using System.Threading.Tasks; public interface IVisitorElement { void Accept(IVisitor visitor); }
- Accept(IVisitor visitor): 이 메소드는 IVisitorElement 인터페이스 내에서 선언되며 방문자 개체를 허용할 수 있는 요소를 나타내는 모든 클래스에서 구현되어야 합니다. Accept 메서드는 IVisitor 개체를 허용하도록 설계된 IVisitor 유형의 매개 변수를 사용합니다. 이 방법은 방문자가 구조화된 개체 계층 구조 내의 요소에 대해 작업을 수행할 수 있도록 함으로써 방문자 패턴에서 중심 역할을 합니다.
Step 2 : Visitor Class 구현하기
Contents Class:
실제 예제로 들어가 봅시다. 게임에서 무기, 실드 또는 신발 등을 향상시킬 수 있는 파워업이 있는 상황을 고려해 봅시다. 그러기 위해선 우선 무기,실드,신발 등의 클래스들을 구현합니다.
Weapon Class
using System; using System.Collections.Generic; using UnityEngine; namespace Chapter.Visitor { public class Weapon : MonoBehaviour, IVisitorElement { [Header("사정 거리")] public int range = 5; // 무기의 사정 거리 public int maxRange = 25; // 무기의 최대 사정 거리 [Header("공격력")] public float strength = 25.0f; // 무기의 공격력 public float maxStrength = 50.0f; // 무기의 최대 공격력 public void Fire() { Debug.Log("무기 발사!"); // 무기 발사 동작을 나타내는 메시지 출력 } public void Accept(IVisitor visitor) { visitor.Visit(this); // 방문자(Visitor)에게 현재 무기(Weapon)를 방문하도록 요청하는 메서드 } private void OnGUI() { GUI.color = Color.green; GUI.Label(new Rect(125, 40, 200, 20), "무기 사정 거리: " + range); // 화면에 무기의 사정 거리 출력 GUI.Label(new Rect(125, 60, 200, 20), "무기 공격력: " + strength); // 화면에 무기의 공격력 출력 } } }
이 코드는 다음과 같이 동작합니다:
- Weapon 클래스는 Unity의 MonoBehaviour 클래스를 상속하고 IVisitorElement 인터페이스를 구현합니다. 이는 Visitor 패턴을 적용하기 위한 준비를 합니다.
- range와 maxRange는 무기의 사정 거리와 최대 사정 거리를 나타냅니다. 이 값들은 무기의 공격 범위를 제어합니다.
- strength와 maxStrength는 무기의 공격력과 최대 공격력을 나타냅니다. 이 값들은 무기의 공격 효과를 제어합니다.
- Fire 메서드는 무기를 발사하는 동작을 나타내며, "무기 발사!"라는 디버그 메시지를 출력합니다.
- Accept 메서드는 Visitor 패턴의 핵심입니다. 방문자(Visitor)에게 현재 무기(Weapon)를 방문하도록 요청합니다.
- OnGUI 메서드는 Unity 화면에 무기의 사정 거리와 공격력을 출력합니다. 화면에 출력되는 내용은 무기의 상태를 시각적으로 표시하는 데 도움이 됩니다.
Shield Class
using System; using UnityEngine; public class Shield : MonoBehaviour, IVisitorElement { public float health = 50.0f; // 실드의 체력 public float Damage(float damage) { health -= damage; // 실드 체력에서 입힌 데미지를 감소시킵니다. return health; // 남은 체력을 반환합니다. } public void Accept(IVisitor visitor) { visitor.Visit(this); // 방문자(Visitor)에게 현재 실드(Shield)를 방문하도록 요청하는 메서드 } private void OnGUI() { GUI.color = Color.green; GUI.Label(new Rect(125, 20, 200, 20), "실드 체력: " + health); // 화면에 실드의 체력을 출력합니다. } }
이 코드는 다음과 같이 동작합니다:
- Shield 클래스는 Unity의 MonoBehaviour 클래스를 상속하고 IVisitorElement 인터페이스를 구현합니다. 이는 Visitor 패턴을 적용하기 위한 준비를 합니다.
- health는 실드의 체력을 나타냅니다. 이 값은 실드의 생존 상태를 나타내며, 데미지를 입을 때마다 감소합니다.
- Damage 메서드는 실드에게 데미지를 입히는 동작을 나타냅니다. 주어진 데미지를 현재 체력에서 감산하고 남은 체력을 반환합니다.
- Accept 메서드는 Visitor 패턴의 핵심입니다. 방문자(Visitor)에게 현재 실드(Shield)를 방문하도록 요청합니다.
- OnGUI 메서드는 Unity 화면에 실드의 체력을 출력합니다. 화면에 출력되는 내용은 실드의 상태를 시각적으로 표시하는 데 도움이 됩니다.
Shoose Class
using System; using UnityEngine; public class Shoose : MonoBehaviour, IVisitorElement { public float Boost = 25.0f; // 스프린트 부스트 값 public float maxBoost = 200.0f; // 최대 스프린트 부스트 값 private bool _isSprintOn; // 스프린트 상태를 나타내는 변수 private float _defaultSpeed = 300.0f; // 기본 이동 속도 public float CurrentSpeed { get { if (_isSprintOn) return _defaultSpeed + Boost; // 스프린트가 켜진 경우 총 이동 속도 반환 return _defaultSpeed; // 스프린트가 꺼진 경우 기본 이동 속도 반환 } } public void ToggleSprint() { _isSprintOn = !_isSprintOn; // 스프린트 상태를 토글하는 메서드 } public void Accept(IVisitor visitor) { visitor.Visit(this); // 방문자(Visitor)에게 현재 신발(Shoose)을 방문하도록 요청하는 메서드 } private void OnGUI() { GUI.color = Color.green; GUI.Label(new Rect(125, 0, 200, 20), "스프린트 부스트: " + Boost); // 화면에 스프린트 부스트 값을 출력합니다. } }
이 코드는 다음과 같이 동작합니다:
- Shoose 클래스는 Unity의 MonoBehaviour 클래스를 상속하고 IVisitorElement 인터페이스를 구현합니다. 이는 Visitor 패턴을 적용하기 위한 준비를 합니다.
- Boost와 maxBoost는 스프린트 부스트 값과 최대 스프린트 부스트 값을 나타냅니다.
- _isSprintOn은 스프린트 상태를 나타내는 변수로, 스프린트가 켜져 있는지 여부를 판단합니다.
- _defaultSpeed는 기본 이동 속도를 나타냅니다.
- CurrentSpeed 속성은 현재 이동 속도를 계산합니다. 스프린트가 켜져 있으면 부스트 값을 추가한 값을 반환하고, 꺼져 있으면 기본 이동 속도를 반환합니다.
- ToggleSprint 메서드는 스프린트 상태를 토글합니다. 즉, 스프린트를 켜거나 끄는 역할을 합니다.
- Accept 메서드는 Visitor 패턴의 핵심입니다. 방문자(Visitor)에게 현재 신발(Shoose)을 방문하도록 요청합니다.
- OnGUI 메서드는 Unity 화면에 스프린트 부스트 값을 출력합니다. 화면에 출력되는 내용은 신발의 상태를 시각적으로 표시하는 데 도움이 됩니다.
컨텐츠 클래스들을 구현했으니 다음과 같이 Visitor Interface도 수정해줍시다.
public interface IVisitor { void Visit(Weapon weapon); void Visit(Shield shield); void Visit(Shoose shoose); }
PowerUp ScriptableObject :
게임에서 무기, 실드 또는 신발 등을 향상시킬 수 있는 파워업할 수 있는 코드를 구현했으니 이를 관리하기 위해 PowerUp 클래스를 만듭니다. 이 클래스는 Unity의 ScriptableObject를 상속하고 IVisitor 인터페이스를 구현합니다.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; // CreateAssetMenu 속성을 사용하여 에디터에서 생성 가능한 파워업 스크립터블 오브젝트를 정의합니다. [CreateAssetMenu(fileName = "PowerUP", menuName = "PowerUP")] public class PowerUp : ScriptableObject, IVisitor { // 파워업의 속성들을 정의합니다. public string powerupName; public GameObject powerupPrefab; public string powerupDescription; [Tooltip("실드를 완전히 회복합니다.")] public bool healShield; [Range(0.0f, 50f)] [Tooltip("스프린트 설정을 최대 50만큼 높입니다.")] public float Boost; [Range(0.0f, 25)] [Tooltip("무기 사거리를 최대 25만큼 늘립니다.")] public int weaponRange; [Range(0.0f, 50f)] [Tooltip("무기 강도를 최대 50%만큼 높입니다.")] public float weaponStrength; // 방문자 패턴에 따라 무기 객체를 방문하는 Visit 메서드입니다. public void Visit(Weapon weapon) { // 무기의 사거리를 늘립니다. int range = weapon.range += weaponRange; if (range >= weapon.maxRange) weapon.range = weapon.maxRange; else weapon.range = range; // 무기의 강도를 늘립니다. float strength = weapon.strength += Mathf.Round(weapon.strength * weaponStrength / 100); if (strength <= weapon.maxStrength) weapon.strength = weapon.maxStrength; else weapon.strength = strength; } // 방문자 패턴에 따라 실드 객체를 방문하는 Visit 메서드입니다. public void Visit(Shield shield) { // 실드를 회복합니다. if (healShield) shield.health = 100.0f; } // 방문자 패턴에 따라 신발 객체를 방문하는 Visit 메서드입니다. public void Visit(Shoose shoose) { // 스프린트를 적용합니다. float boost = shoose.Boost += Boost; if (boost < 0.0f) shoose.Boost = 0.0f; if (boost >= shoose.maxBoost) shoose.Boost = shoose.maxBoost; } }
위의 코드는 다음과 같이 동작합니다:
- PowerUp 클래스: 이 클래스는 Unity의 ScriptableObject를 상속하고 IVisitor 인터페이스를 구현합니다. 이 클래스는 게임 오브젝트의 파워업을 나타내며, 파워업의 여러 속성을 정의하고 파워업이 다른 게임 오브젝트를 방문할 때 어떤 동작을 수행할지를 정의하는 Visit 메서드를 제공합니다.
- IVisitor 인터페이스: 이 인터페이스는 방문자 패턴을 정의하며, 파워업 객체가 다른 게임 오브젝트인 무기, 실드, 신발을 방문할 때 수행할 작업을 정의하는 Visit 메서드를 선언합니다.
이제 PowerUp 클래스와 IVisitor 인터페이스가 준비되었으므로 게임에서 파워업을 쉽게 관리할 수 있습니다. 파워업이 게임 오브젝트를 방문하면 해당하는 Visit 메서드가 호출되어 각 상호작용에 맞는 사용자 정의 논리를 구현할 수 있습니다. 예를 들어 파워업이 무기 오브젝트를 방문하면 무기의 사거리와 강도를 증가시킬 수 있고, 실드를 방문하면 실드를 회복시킬 수 있습니다.
Step 3 : Controller 구현하기
이제 객체 간의 유연한 상호 작용을 구현하기 위해서 GameController.cs와 ClientVisitor.cs 를 구현합니다.
GameController는 게임에서 방문 가능한 요소들을 효과적으로 관리하기 위한 역할을 하며, Start 메서드를 사용하여 게임 시작 시 이러한 요소들을 초기화합니다. 이렇게 초기화된 요소들은 Accept 메서드를 통해 방문자 패턴을 활용하여 다른 게임 객체에 영향을 미칠 수 있습니다.
ClientVisitor 클래스는 게임의 클라이언트 역할을 수행하며, 게임 시작 시 GameController를 초기화하여 게임의 주요 기능을 제어합니다. 버튼을 통해 파워 업을 게임 컨트롤러에 전달하는 역할을 수행하여 플레이어가 게임의 상호 작용을 조작할 수 있도록 합니다.GameController :
using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; public class GameController : MonoBehaviour, IVisitorElement { private List<IVisitorElement> _visitorElements = new List<IVisitorElement>(); private void Start() { // 게임 오브젝트에 방문 가능한 요소들을 추가합니다. _visitorElements.Add(gameObject.AddComponent<Shield>()); _visitorElements.Add(gameObject.AddComponent<Shoose>()); _visitorElements.Add(gameObject.AddComponent<Weapon>()); } public void Accept(IVisitor visitor) { // 모든 방문 가능한 요소들에 대해 방문자(Visitor)를 수용합니다. foreach(IVisitorElement element in _visitorElements) { element.Accept(visitor); } } }
GameController 클래스는 Unity의 MonoBehaviour 클래스를 상속하고 IVisitorElement 인터페이스를 구현합니다. 이 클래스는 게임 오브젝트에 방문 가능한 요소들을 관리하고 방문자를 수용합니다.
- _visitorElements는 방문 가능한 요소들을 저장하기 위한 리스트입니다. 이 리스트에는 Shield, Shoose, Weapon과 같은 요소들이 추가됩니다.
- Start 메서드에서는 게임 시작 시 _visitorElements 리스트에 방문 가능한 요소들을 추가합니다. 이는 게임 오브젝트에 각 요소를 추가하는 역할을 합니다.
- Accept 메서드는 방문자(Visitor)를 수용하는 역할을 합니다. _visitorElements 리스트에 포함된 모든 요소들에 대해 방문자를 호출합니다.
ClientVisitor :
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ClientVisitor : MonoBehaviour { public PowerUp shoosePowerUp; public PowerUp shieldPowerUp; public PowerUp weaponPowerUp; private GameController _gameController; void Start() { _gameController = gameObject.AddComponent<GameController>(); } private void OnGUI() { // 버튼을 통해 각 PowerUp을 게임 컨트롤러에 전달합니다. if (GUILayout.Button("실드 파워 업")) _gameController.Accept(shieldPowerUp); if (GUILayout.Button("신발 파워 업")) _gameController.Accept(shoosePowerUp); if (GUILayout.Button("무기 파워 업")) _gameController.Accept(weaponPowerUp); } }
ClientVisitor 클래스는 클라이언트의 역할을 하는 스크립트입니다. 이 클래스는 게임 컨트롤러에 각 PowerUp을 전달하는 역할을 합니다.
- enginePowerUp, shieldPowerUp, weaponPowerUp은 각각 엔진, 실드, 무기의 파워 업을 나타내는 스크립트 객체입니다.
- Start 메서드에서는 게임 오브젝트에 GameController 컴포넌트를 추가하여 게임 컨트롤러를 초기화합니다.
- OnGUI 메서드에서는 Unity 화면에 버튼을 생성하여 클릭하면 해당 파워 업을 게임 컨트롤러에 전달합니다.
Step 4 : Test
이제 빈 GameObject를 만들고 ClientVisitor Component를 붙혀주고 각 Powerup ScriptableObject를 설정합니다.
이제 게임을 실행하면 다음 영상과 같이 진행됩니다.
마무리
Visitor 패턴은 객체 지향 소프트웨어 디자인에서 높은 유연성과 확장 가능성을 제공하는 강력한 도구 중 하나입니다. 이 패턴을 적용하면 객체의 구조와 기능을 분리하여, 객체를 수정하지 않고도 새로운 작업을 추가하거나 확장할 수 있습니다. 이를 통해 소프트웨어의 유지 보수성을 향상시키고 코드를 더 간결하고 이해하기 쉽게 만들 수 있습니다.
Unity와 같은 게임 개발 환경에서 Visitor 패턴은 특히 유용합니다. 게임 객체 간의 복잡한 상호 작용을 관리하고 다양한 기능을 도입하기 위해 이 패턴을 활용할 수 있습니다. 예를 들어 파워 업 시스템을 구현하거나 게임 요소 간의 상호 작용을 조율하는 데에 활용할 수 있습니다.
또한 Visitor 패턴은 객체 지향 디자인 원칙 중 하나인 개방 폐쇄 원칙(OCP)을 준수하며, 코드의 재사용성을 높이고 유지 보수를 용이하게 만듭니다. 객체를 변경하지 않고도 새로운 작업을 추가할 수 있기 때문에 시스템을 확장하는 데 거의 무한한 가능성을 제공합니다.
종합적으로 Visitor 패턴은 객체 지향 소프트웨어 디자인의 강력한 도구로서, 유연성과 확장 가능성을 높이며, 복잡한 시스템에서 객체 간의 효율적인 상호 작용을 관리하는 데 도움이 됩니다. 이를 통해 더 풍부하고 유연한 게임 개발을 할 수 있습니다.반응형다음글이전글이전 글이 없습니다.댓글