목적을 이루기 위해 정해진 공통의 행동이 있지만 그 안에서 공통의 행동전략들을 만들어 접근하는 패턴
예시 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);
}
}
}