0. 들어가기 전에
다 자란 점토를 판매 버튼 UI 에 드래그&드롭하면 팔리도록 하는 기능을 만들었다.
1. 게임 오브젝트
점토 판매 기능을 구현할 때 사용하는 스크립트로는 Clay, ClaySellController, PoolManager 가 있다. 이를 컴포넌트로 갖는 게임 오브젝트들은 다음과 같다.
1. PoolManager
poolManager 게임 오브젝트의 상태는 다음과 같다.
2. Sell Button
SellButton 게임 오브젝트의 상태는 다음과 같다.
3. Clay
Clay 게임 오브젝트의 상태는 다음과 같다.
2. 스크립트
이번에 새로 만든 스크립트는 ClaySellController 이고, Clay 와 PoolManager 스크립트를 수정했다.
2.1 ClaySellController 스크립트
ClaySellController 스크립트는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
public class ClaySellController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[Header("Clay Info")]
public PoolManager poolManager;
public Clay sellClay; // 판매할 점토
private void Start()
{
poolManager.OnSellClay += SellClay; // 델리게이트에 메서드 연결
}
private void OnDestroy()
{
poolManager.OnSellClay -= SellClay; // 이벤트 구독 해제
}
public void OnPointerEnter(PointerEventData eventData)
{
poolManager.isSellZone = true;
// 만약 점토가 점토 판매 버톤으로 드래그되어서 들어온다면..
if (poolManager.curClay.isDragging)
sellClay = poolManager.curClay; // 판매할 점토를 현재 드래그 중인 클레이로 설정..
}
public void OnPointerExit(PointerEventData eventData)
{
poolManager.isSellZone = false;
sellClay = null;
}
public void SellClay()
{
if (sellClay != null)
{
if (sellClay.clayLevel == 5) // 성체만 판매 가능
{
poolManager.ReturnToPool(sellClay.gameObject); // 비활성화시키기..
GameManager.instance.GetGold(sellClay.sellPrice); // 돈 얻기
}
}
}
}
2.2 ClaySellController 스크립트 설명
1. 변수
poolManager 와 sellClay 를 변수로 갖는다.
poolManager 가 필요한 이유는 현재 마우스가 판매 버튼에 진입했는지 여부를 저장하고, 현재 판매할 점토를 지정하고, poolManager 의 델리게이트에 점토 판매 메서드를 연결해주기 위함이다.
sellClay 변수가 필요한 이유는 점토 판매 메서드의 대상이 필요하기 때문이다.
[Header("Clay Info")]
public PoolManager poolManager;
public Clay sellClay; // 판매할 점토
2. Start(), OnDestroy()
Start() 메서드에서 poolManager 의 OnSellClay 델리게이트에 SellClay 메서드를 연결한다. 이렇게 하면 Update 문에서 판매 가능 여부를 계속해서 판단하지 않아도 된다.
그리고 게임 오브젝트가 파괴될 때 이벤트 구독을 해제한다.
private void Start()
{
poolManager.OnSellClay += SellClay; // 델리게이트에 메서드 연결
}
private void OnDestroy()
{
poolManager.OnSellClay -= SellClay; // 이벤트 구독 해제
}
3. OnPointerEnter(PointerEvenetData eventData)
마우스가 점토 판매 버튼 UI 에 진입하면 호출되는 메서드이다.
메서드에 진입하면 poolManager.isSellZone 값을 true 로 설정해준다. 현재 마우스가 판매 버튼에 있는지 여부를 확인할 수 있도록 하기 위함이다.
그리고 poolManager.curClay.isDragging 변수값이 true 라면 sellClay 변수에 poolManager.curClay 를 할당해줬다. poolManager 의 curClay 는 현재 마우스로 클릭한 점토이다.
public void OnPointerEnter(PointerEventData eventData)
{
poolManager.isSellZone = true;
// 만약 점토가 점토 판매 버톤으로 드래그되어서 들어온다면..
if (poolManager.curClay.isDragging)
sellClay = poolManager.curClay; // 판매할 점토를 현재 드래그 중인 클레이로 설정..
}
4. OnPointerExit(PointerEventData eventData)
마우스가 점토 판매 버튼을 벗어나면 isSellZone 값을 false 로 설정해준다. 그리고 sellClay 도 null 로 바꿔준다.
public void OnPointerExit(PointerEventData eventData)
{
poolManager.isSellZone = false;
sellClay = null;
}
5. SellClay()
본격적으로 성체가 된 점토를 파는 메서드이다. 이 메서드는 poolManager 의 델리게이트에 연결되어서 필요한 때에 호출된다.
public void SellClay()
{
if (sellClay != null)
{
if (sellClay.clayLevel == 5) // 성체만 판매 가능
{
poolManager.ReturnToPool(sellClay.gameObject); // 비활성화시키기..
GameManager.instance.GetGold(sellClay.sellPrice); // 돈 얻기
}
}
}
2.3 Clay 스크립트
Clay 스크립트는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using TreeEditor;
using UnityEngine;
public class Clay : MonoBehaviour
{
[Header("Clay Data")]
public float[] loves; // 점토를 클릭했을 때 얻는 애정 수치(1~5 레벨)
public int[] touchCnts; // 해당 요소만큼 클릭되면 레벨업
public int clayLevel = 1; // 1레벨에서 시작(5레벨까지 있음)
public int curTouchCnt = 0;
public int clayIdx; // 점토 소환할 때 필요한 인덱스
public int buyPrice; // 구매 가격
public int sellPrice; // 판매 가격
[Header("Animation")]
Animator anim; // 점토가 터치될 때 애니메이션 실행하기 위함
public RuntimeAnimatorController[] animators;
[Header("Sprites")]
public Sprite clay; // 점토 모습
public Sprite animal; // 다 자란 동물 모습
[Header("Drag Clay")]
public float targetTime = 1; // 드래그 시작할 수 있는 시간
public float curTime; // 현재 시간
public Vector3 prevPos; // 점토를 드래그 하기 전 위치
public bool isDragging; // 드래그 중인지 확인
[Header("Pool Manager")]
public PoolManager poolManager;
private void Awake()
{
anim = GetComponent<Animator>();
}
private void Start()
{
poolManager = GameObject.Find("PoolManager").GetComponent<PoolManager>();
}
// 점토가 터치되면 저절로 호출됨
private void OnMouseDown()
{
prevPos = transform.position; // 드래그 하기 전 위치 저장
poolManager.curClay = gameObject.GetComponent<Clay>(); // 현재 선택된 클레이 지정
Debug.Log("터치됨!");
anim.SetTrigger("doTouch");
GameManager.instance.GetLove(loves[clayLevel - 1]); // 레벨에 맞는 수치를 함수로 넘겨주기
curTouchCnt++;
// 이미 점토의 레벨이 최고 레벨에 도달했으면 밑으로 진입 안 하도록..
if (clayLevel != 5)
{
if (curTouchCnt == touchCnts[clayLevel - 1])
{
clayLevel++; // 레벨 1 증가
curTouchCnt = 0; // 초기화
anim.runtimeAnimatorController = animators[clayLevel - 1]; // 레벨에 맞는 애니메이터로 바꿔주기
if (clayLevel == 5)
gameObject.GetComponent<SpriteRenderer>().sprite = animal;
}
}
}
// 점토 드래그할 때 호출되는 함수
private void OnMouseDrag()
{
curTime += Time.deltaTime;
isDragging = true;
// 만약 현재 시간이 targetTime 보다 크거나 같다면 점토가 마우스를 따라오도록 하기..
if (curTime >= targetTime)
{
// 마우스 위치를 가져온 후 Z 축을 카메라와의 거리로 설정한다
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = Camera.main.WorldToScreenPoint(transform.position).z;
// 스크린 좌표를 월드 좌표로 변환
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
// 오브젝트의 위치를 마우스의 월드 좌표로 이동
transform.position = worldPosition;
// 이 경우에는 UI 보다도 앞에 갈 수 있도록..
gameObject.GetComponent<SpriteRenderer>().sortingOrder = 10;
}
}
// 점토 내려놓을 때 호출되는 함수
private void OnMouseUp()
{
curTime = 0;
isDragging = false;
if (transform.position.x < -6.5 || transform.position.x > 6.5 || transform.position.y < -3 || transform.position.y > 0.6)
transform.position = prevPos;
// 다시 UI 보다 아래로 가도록..
gameObject.GetComponent<SpriteRenderer>().sortingOrder = 1;
if (poolManager.isSellZone == true)
{
poolManager.isPossibleSell = true; // 판매 신호 주기
poolManager.TrySellClay(); // 이벤트 호출
}
}
public void ResetInfo(int level, int cnt)
{
if (level == 5)
gameObject.GetComponent<SpriteRenderer>().sprite = animal; // 동물 모습으로 바꿔주기..
// 점토 정보 설정
clayLevel = level;
curTouchCnt = cnt;
anim.runtimeAnimatorController = animators[clayLevel - 1]; // 레벨에 맞는 애니메이터로 바꿔주기..
}
public void ResetInfo()
{
// 점토 정보 초기화
clayLevel = 1;
curTouchCnt = 0;
gameObject.GetComponent<SpriteRenderer>().sprite = clay; // 점토 모습으로 바꿔주기..
anim.runtimeAnimatorController = animators[clayLevel - 1]; // 애니메이터도 맨 처음걸로 바꿔주기..
}
}
2.4 Clay 스크립트 변경사항 설명
Clay 스크립트 변경사항에 대한 설명은 다음과 같다.
1. 변수
추가된 변수는 다음과 같다.
buyPrice, sellPrice 는 점토를 구매하고 판매하는데 이용하기 위해 추가했다.
isDragging 은 현재 점토가 드래깅 중인지를 확인하기 위한 변수이다.
poolManager 변수를 만들어서 poolManager 의 변수 값을 바꾼다.
public int buyPrice; // 구매 가격
public int sellPrice; // 판매 가격
public bool isDragging; // 드래그 중인지 확인
[Header("Pool Manager")]
public PoolManager poolManager;
2. Start()
poolManager 변수에 현재 게임 상에 존재하는 오브젝트 중 PoolManager 라는 이름을 가진 애를 찾도록 했다. 찾은 다음 PoolManager 컴포넌트를 가져와서 할당해줬다.
private void Start()
{
poolManager = GameObject.Find("PoolManager").GetComponent<PoolManager>();
}
3. OnMouseDown()
아래와 같은 구문이 추가되었다. 점토를 클릭하면 poolManager 의 curClay 변수에 자기 자신의 Clay 컴포넌트 속성을 할당한다.
poolManager.curClay = gameObject.GetComponent<Clay>(); // 현재 선택된 클레이 지정
4. OnMouseDrag()
아래와 같은 구문이 추가되었다. 점토가 드래그 되고 있는 중임을 알리기 위해 isDragging 값을 true 로 설정해주었다.
isDragging = true;
5. OnMouseUp()
아래와 같은 구문이 추가되었다. 점토가 드래그 되고 있지 않음을 알리기 위해 값을 false 로 설정했다.
isDragging = false;
2.5 PoolManager 스크립트
PoolManager 스크립트는 다음과 같다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
[Header("Clay Info")]
public GameObject[] clayPrefabs;
[Header("Clay Pool")]
public List<GameObject>[] pools;
[Header("Clay Spawn")]
public GameObject spawnPoint; // 중앙에서 스폰될 것..
[Header("Clay Sell")]
public Clay curClay; // 현재 클릭된 대상 클레이
public bool isSellZone; // 판매 버톤에 들어가있는지 확인용
public bool isPossibleSell; // 이 값이 true 면 팔아!
public delegate void SellClayHandler();
public event SellClayHandler OnSellClay;
private void Awake()
{
pools = new List<GameObject>[clayPrefabs.Length];
for (int index = 0; index < pools.Length; index++)
pools[index] = new List<GameObject>();
GetGameObject(4);
}
private void Start()
{
DataManager.instance.LoadGameData();
for (int index=0; index<DataManager.instance.data.clayInfos.Count; index++)
{
ClayDatas tmpData = DataManager.instance.data.clayInfos[index];
int idx = tmpData.clayIdx;
int level = tmpData.clayLevel;
int cnt = tmpData.curTouchCnt;
GetGameObject(index).GetComponent<Clay>().ResetInfo(level, cnt); // 점토 정보 설정
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
SaveClayData();
}
public void TrySellClay()
{
if (isPossibleSell)
{
isPossibleSell = false; // 상태 초기화
OnSellClay?.Invoke(); // 이벤트 호출
}
}
public GameObject GetGameObject(int index)
{
GameObject select = null;
foreach (GameObject gameObj in pools[index])
{
// 풀을 돌면서 놀고 있는 게임 오브젝트 찾기
if (gameObj.activeSelf == false)
{
// 찾으면 반환
select = gameObj;
ActivateClay(select);
break;
}
}
// 놀고 있는 게임 오브젝트가 없다면..
if (!select)
{
// 새로 생성해서 반환..
// 새로 생성한 게임 오브젝트는 풀 매니저 하위에 놓을 것.. 그래서 부모에 transform 넣어줌..
select = Instantiate(clayPrefabs[index], transform);
pools[index].Add(select);
ActivateClay(select);
}
return select;
}
public void ActivateClay(GameObject clay)
{
clay.SetActive(true);
clay.transform.position = spawnPoint.transform.position; // 점토의 위치를 중앙으로..
clay.GetComponent<Clay>().ResetInfo(); // 점토 상태 초기화시키기..
}
public void ReturnToPool(GameObject clay)
{
clay.SetActive(false);
}
// 점토 데이터를 저장할 것(활성돠 된 점토만..)
public void SaveClayData()
{
// 프리팹 사이즈만큼 점토 정보 저장할 리스트 만들기
DataManager.instance.data.clayInfos = new List<ClayDatas>();
for (int index = 0; index < pools.Length; index++)
{
foreach (GameObject gameObj in pools[index])
{
// 풀을 돌면서 활성화 되어 있는 애들만 저장
if (gameObject.activeSelf == true)
{
// 저장할 내용 만들기
ClayDatas clayData = new ClayDatas();
Clay tmpClay = gameObj.GetComponent<Clay>();
clayData.clayIdx = tmpClay.clayIdx;
clayData.clayLevel = tmpClay.clayLevel;
clayData.curTouchCnt = tmpClay.curTouchCnt;
Debug.Log(clayData.clayIdx + " " + clayData.clayLevel + " " + clayData.curTouchCnt);
DataManager.instance.data.clayInfos.Add(clayData); // 리스트에 추가하기
Debug.Log("추가했어영");
}
}
}
DataManager.instance.SaveGameData(); // 데이터 저장하기
}
}
2.6 PoolManager 스크립트 변경사항 설명
PoolManager 스크립트 변경사항에 대한 설명은 다음과 같다.
1. 변수
[Header("Clay Sell")]
public Clay curClay; // 현재 클릭된 대상 클레이
public bool isSellZone; // 판매 버톤에 들어가있는지 확인용
public bool isPossibleSell; // 이 값이 true 면 팔아!
public delegate void SellClayHandler();
public event SellClayHandler OnSellClay;
2. TrySellClay()
isPossibleSell 값이 true 이면 OnSellClay 에 연결된 메서드를 호출하도록 하는 메서드이다. OnSellClay 에는 ClaySellController 의 SellClay() 메서드가 연결되어 있다.
public void TrySellClay()
{
if (isPossibleSell)
{
isPossibleSell = false; // 상태 초기화
OnSellClay?.Invoke(); // 이벤트 호출
}
}
3. ActivateClay(GameObject clay), ReturnToPool(GameObject clay)
기존에 존재하던 로직을 메서드로 따로 빼서 만들었다.
public void ActivateClay(GameObject clay)
{
clay.SetActive(true);
clay.transform.position = spawnPoint.transform.position; // 점토의 위치를 중앙으로..
clay.GetComponent<Clay>().ResetInfo(); // 점토 상태 초기화시키기..
}
public void ReturnToPool(GameObject clay)
{
clay.SetActive(false);
}
3. 결과물
4. 참고자료
델리게이트에 대해 설명해놓은 글이 있길래 가져왔다.
4.1 델리게이트
[Unity] 델리게이트(Dlegate) & 이벤트(Event) 쉽게 접근하기.
[유니티 C# 강좌] 17. 이벤트(Event), 대리자 (델리게이트, Delegate)