전략 패턴 ( Strategy Pattern )

더보기

목적을 이루기 위해 정해진 공통의 행동이 있지만 그 안에서 공통의 행동전략들을 만들어 접근하는 패턴

예시 1)

A가 공항을 가기야된다라는 목적
1.차를 타고 간다. -> 이동한다.
2.비행기를 타고 간다. -> 이동한다.
3.걸어간다. -> 이동한다.
이동이라는 행동이 있지만 각 전략(동작)들이 다 다르다.

예시 2)

Player가 게임 아이템과 충돌했을때 사용되려는 목적
1.회복 아이템을 획득 했다. -> 아이템의 획득 및 사용
2.공격 아이템을 획득 했다. -> 아이템의 획득 및 사용
이런식으로 사용이 가능해진다.

그럼 이때 행동전략의 회복아이템,공격 아이템의 동작방식이 다를 경우 각 class의 로직을 따로 작성을 해줄수도 있다.

Interface를 이용하여 전략의 틀(규칙)을 정한뒤  
각 Item들은 Interface를 상속하여 사용하고 (Item의 부모클래스에만 해도 사용 가능)
그 아이템을 먹은 Player class에서 해당 인터페이스를 변수를 호출하여 사용하는것이다.

즉, 이 위의 행동들을 간단하게 요약하면 [일반적으로 같은 작업을 수행하는 다양한 방법을 선택] 하는 것이다.
 

예시 코드 

예시 코드는 플레이어가 월드에 뿌려진 아이템을 Ray 로 검출시 UI에 아이템 설명이 출력되고

상호작용 키를 누르면 아이템이 플레이어 클래스의 저장되고 해당 아이템은 사라진다다. 즉 이 두 목적은 모든 아이템에서 동일하게 일어나기에 목적으로 설정하여 전략패턴으로 사용이 가능해지는 것이다.

 

IInteractable.cs & itemObject.cs

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

//아이템은 보통 여러 종류가 있고 기능도 다르게 동작하는 것들도 있을것이다.
//그럴때마다 클래스를 새로 만들거나 if문으로 계속 연결하면서 만들면 작업 효율 및 가독성이 떨어지고 결합성은 올라가 스파게티 코드가 되어버린다.
//그런점을 방지하기 위해 Interface를 해서 Item에서 공통적으로 사용하는 기능들을 미리 선언하여 묶어 사용하기로 한다.

public interface IInteractable //상호작용이 가능한 오브젝트 인터페이스
{
    public string GetinteractPrompt(); // 화면에 띄워줄 Prompt 함수들을 작성
    public void OnInteract(); // 상호작용시 어떤 효과를 발동할건지 결정해주는 함수
}

public class itemObject : MonoBehaviour, IInteractable
{
    public ItemData data;

    public string GetinteractPrompt()
    {
        string str = $"{data.displayName}\n{data.description}";
        return str;
    }

    public void OnInteract()
    {
        CharacterManager.Instance.Player.itemData = data;
        CharacterManager.Instance.Player.additem?.Invoke();
        Destroy(gameObject); //상호작용하면 월드에 있는 아이템은 기능을 다했으므로 사라져야한다.

    }
}

Player 객체가 아이템에 다가가면(Ray로 검사) GetinteractPrompt() 메서드로  UI에 아이템 이름 및 설명을 출력하고 

 OnInteract() 메서드로 상호작용 키를 누르면 각 아이템의 해당 기능(행동 전략)을 동작하는 기능이다. 

 

InterAction.cs ( 플레이어 Class 내에 참조도어 사용중 )

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;

//상호작용 기능을 하는 Class
//카메라에서 Ray를 쏴서 충돌되는 오브젝트들의 IInteractalbe 컴포넌트가 있는지 로 체크하여 사용할 예정

public class Interaction : MonoBehaviour
{
    public float checkRate = 0.05f; //Ray를 다시 쏘게하는 체크 주기(최신화)
    private float lastCheckTime; //마지막에 Ray를 쏜 시간 저장
    public float maxCheckDistance; // Ray의 측정거리
    public LayerMask layerMask; //Raycast시 사용되는 레이어마스크(어떤 레이어가 달려있는 게임오브젝트 체크기준)

    //캐싱하는 자료가 이 두 변수에 담겨져있음
    public GameObject curInteractGameObject; // 상호작용 성공시 해당 아이템의 게임오브젝트를 저장할 변수 
    private IInteractable curInteractable; // 해당 게임오브젝트를 캐싱할 Interface 변수

    //인강 수업 진행중에는 Inspector에서 넣어서 사용하지만 나중에는 스크립트로 불러오게 하는것을 생각해봐야함
    public TextMeshProUGUI promptText; // 상호작용시 뜨는 prompt, //UI와 기능 분리시 어떡해 할지 리팩토링 추천
    
    private Camera camera; //Ray를 카메리 기준으로 발사할것이기에 카메라를 멤버변수로 선얺

    // Start is called before the first frame update
    void Start()
    {
        camera = Camera.main;
    }

    // Update is called once per frame
    void Update()
    {

        if(Time.time - lastCheckTime > checkRate)
        {
            lastCheckTime = Time.time;

            //new Vector3(Screen.width / 2, Screen.height / 2) : 화면의 정 중앙에서 Ray를 쏘게 하기위해 /2 연산을 함
            Ray ray = camera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit, maxCheckDistance, layerMask))
            {
                if (hit.collider.gameObject != curInteractGameObject)
                {
                    curInteractGameObject = hit.collider.gameObject;
                    curInteractable = hit.collider.GetComponent<IInteractable>();
                    SetPromptText();
                }
            }
            else
            {
                curInteractGameObject = null;
                curInteractable = null;
                promptText.gameObject.SetActive(false);
            }
        }
    }

    private void SetPromptText()
    {
        promptText.gameObject.SetActive(true);
        promptText.text = curInteractable.GetinteractPrompt();
    }

    public void OnInteractInput(InputAction.CallbackContext context)
    {
        /*curInteractable != null : 인터액션 상호작용을 하려면 현재 상호작용이 가능한 
         타겟(IInteractable 상속클래스)가 있어야되기때문에 조건문에 추가*/
        if (context.phase == InputActionPhase.Started && curInteractable != null)
        {
            curInteractable.OnInteract();
            curInteractGameObject = null;
            curInteractable = null;
            promptText.gameObject.SetActive(false);
        }
    }
}

프로토타입 패턴 ( ProtyType Pattern  )  

더보기

Prototype은 실제 산업에서는 대량생산전 각종 테스트를 수행하는데 사용된다.
하지만 코드영역 에서는 Prototype은 자기 자신을 복사본으로 찎어내는 틀에 가깝습니다.

유니티에는 기본적으로 Prototype 디자인패턴이 적용되어있는데 
Prefab에 적용되어 있습니다. 우리가 각종 오브젝트들과 컴포넌트를 합쳐 하나의 프리팹으로 만들고
그 프리팹을 호출하여 월드에 배치하는 것이 프로토타입패턴입니다.

Prefab 자체가 프로토 타입이다.

컴포지트 패턴 ( Composite Pattern  )

더보기

Composite 패턴은 단일 객체와 복합 객체를 동일하게 다룰 수 있게 해주는 디자인 패턴입니다.

이 패턴은 객체들을 트리 구조로 구성하여, 부분-전체 계층 구조를 표현할 수 있습니다.

복합 객체(Composite)와 단일 객체(Leaf)를 같은 인터페이스로 다루게 되므로, 클라이언트는 객체의 구성 요소가 단일 객체인지, 복합 객체인지 신경 쓸 필요 없이 동일한 방식으로 조작할 수 있습니다. 그렇기에 각종 컴포지트에서 편하게 동일한 메서드를 호출하여 사용 할 수 있게 하는것이 장점입니다. 

Unity에서 GameObject의 Inspector에 들어가는 다양한 컴포넌트들이 Composite 패턴입니다. GameObject는 여러 컴포넌트를 가질 수 있으며, 각각을 독립적으로 관리하면서도 GameObject 하나에 모두 붙일 수 있습니다GameObject는 복합 객체로, 각 컴포넌트는 단일 객체로 작동하며, 둘 모두 동일한 방식으로 조작됩니다.

컴포지트 패턴에서 각종 구성요소 설명

 

컴포지트 패턴의 간단한 UML
Unity에서의 컴포넌트 시스템은 컴포지트 디자인패턴의 일종이다.

 

 

퍼사드 패턴 ( Facade Pattern 

더보기

나의 객체에서 동작해야되는 하위 동작들을 전부 참조하여 사용하는 패턴을 퍼사드 패턴이라고한다.

예시1)

플레이어 객체는 

조작(Controller),

모델 출력(SpriteRenderer,MeshRenderer),

스테이터스(StatusHandler),

상호작용(Interaction)

등등 많은 하위 동작들이 필요하다.


플레이어 Class내에 조작,모델출력,스테이터스,상호작용 등등을 참조해서 사용하고 조작 같은경우 이동,공격,점프,회피 등등 여러 행동들이 있는데 이것은 또 추가의 파서드 패턴이 되어 관리하게된다.

즉, 하나의 객체에 여러 하위 동작들을 담아 사용하는것을 퍼사드 패턴이라고 한다. 

 

Player.cs

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

public class Player : MonoBehaviour
{
    public PlayerController controller;
    public PlayerCondition condition;
    public Equipment equip;

    public ItemData itemData; //상호작용하는 아이템 데이터를 저장하는 변수 (타겟 오브젝트)
    public Action additem; //event를 선언하여 아이템을 추가하는 함수

    public Transform dropPosition; //인벤토리에서 아이템을 버리는 위치 변수 

    private void Awake()
    {
        CharacterManager.Instance.Player = this;//캐릭터 매니저에 자기 자신을 등록
        controller = GetComponent<PlayerController>();
        condition = GetComponent<PlayerCondition>();
        equip = GetComponent<Equipment>();
    }
}

 플레이어 클래스의 컨트롤러,컨디션(스테이터스관리),장착,Item 등등 하위 동작들을 참조하여  사용하고

그 행동에서도 또 다른 행동이 있을떄 또 퍼사드패턴으로 구현되어 있게 해놓았다.

 


참고자료

https://refactoring.guru/ko/design-patterns/flyweight/csharp/example

 

+ Recent posts