0. 들어가기 전에
기본 UI 창(점토 구매, 집 업그레이드, 옵션) 을 만들고 애니메이션을 적용했다.
세 개의 UI 창 중에서 이번엔 점토 구매 UI 창만 본격적으로 만들었다. 나머지는 다음에 만들 예정이다. 일단 구매창을 만들었기 때문에 구매 기능까지 적용해놨다.
1. 게임 오브젝트
2. 스크립트
이번에 새로 만든 스크립트는 ClayPanel 이고 ButtonMove, Clay, GameManager 스크립트를 수정했다.
2.1 GameManager 스크립트
GameManager 스크립트의 내용은 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
[Header("Game Data")]
public float love; // 애정
public float gold; // 골드
public bool[] unLockedClays; // 점토들의 해금 여부
[Header("Game Manager")]
public static GameManager instance; // 싱글톤 이용하기 위함
[Header("GameDataUI")]
public GameDataUIController gameDataUI;
[Header("Pool Manager")]
public PoolManager poolManager;
[Header("Coroutine")]
public Coroutine updateTextUICoroutine;
private void Awake()
{
// 싱글톤 패턴
if (instance != null && instance != this)
{
// 이미 존재하면 새로 만든거 없애
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject); // 얘는 다른 씬으로 전환되어도 안 없앨 거임
poolManager = GameObject.Find("PoolManager").GetComponent<PoolManager>(); // 풀매니저 찾아서 할당
}
void Start()
{
GetLove(0);
GetGold(1000);
}
// 재화 얻는 함수
public void GetGold(float capacity)
{
// 이미 코루틴이 종료되지 않은 중에 동일한게 또 들어오면 겹쳐서 반영이 돼서 이상하게 될 수 있으므로 null 인지 판단해야함.
if (updateTextUICoroutine != null)
{
StopCoroutine(updateTextUICoroutine);
}
updateTextUICoroutine = StartCoroutine(gameDataUI.UpdateTextUI("gold", gold + capacity, gold));
gold += capacity;
//PlayerPrefs.SetFloat("Gold", gold); // 데이터 저장
}
public void GetLove(float capacity)
{
// 이미 코루틴이 종료되지 않은 중에 동일한게 또 들어오면 겹쳐서 반영이 돼서 이상하게 될 수 있으므로 null 인지 판단해야함.
if (updateTextUICoroutine != null)
{
StopCoroutine(updateTextUICoroutine);
}
updateTextUICoroutine = StartCoroutine(gameDataUI.UpdateTextUI("love", love + capacity, love));
love += capacity;
//PlayerPrefs.SetFloat("Love", love); // 데이터 저장
}
}
2.2 GameManager 스크립트 변경 사항 설명
1. 변수
unLockedClays 와 poolManager 변수가 추가되었다. unLockedClays 변수는 점토들의 해금 여부를 다른 클래스들이 GameManager 를 통해서 알 수 있도록 하기 위함이고 poolManager 는 다른 클래스들이 poolManager 게임 오브젝트의 PoolManager 컴포넌트를 GameManager 를 통해 이용할 수 있도록 하기 위함이다.
public bool[] unLockedClays; // 점토들의 해금 여부
[Header("Pool Manager")]
public PoolManager poolManager;
2. Awake()
다음과 같은 구문이 추가되었다. 인스펙터 창에서 직접 추가하지 않고 코드상으로 PoolManager 게임 오브젝트를 찾아 PoolManager 컴포넌트를 할당해주었다.
poolManager = GameObject.Find("PoolManager").GetComponent<PoolManager>(); // 풀매니저 찾아서 할당
3. Start()
일단 임시로 1000 골드를 가지고 게임을 시작하도록 해놨다. 점토 구매 기능을 확인하기 위해 수정한건데 나중에 바꿀수도..
void Start()
{
GetLove(0);
GetGold(1000);
}
2.3 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; // 판매 가격
public string clayName; // 점토 이름
[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 스크립트 변경 사항 설명
1. 변수
변수로 clayName 이 추가되었다. 점토 구매 창에서 구매하려는 점토의 이름을 표시할 수 있도록 하기 위함이다.
public string clayName; // 점토 이름
2.5 ButtonMove 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ButtonMove : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
[Header("Animation")]
Animator anim;
[Header("Sprite")]
public Sprite nonClicked;
public Sprite clicked;
[Header("Button UI")]
public Button uiButton;
public Button[] otherButtons;
[Header("Panel")]
public GameObject panel; // 해당 버튼에 맞는 판넬 게임 오브젝트 할당
[Header("Close Button")]
public Button closeButton;
void Start()
{
anim = GetComponent<Animator>();
uiButton = GetComponent<Button>();
}
public void ButtonMoveStart()
{
// 이미 버튼이 눌려있는 상태에서 마우스를 가져다대면 위아래로 움직이는 모션이 실행되지 않도록..
if (!anim.GetBool("clicked"))
{
anim.SetBool("draged", true); // 값을 true 로 줘서 Draged 애니메이션 실행
}
else
{
// 버튼이 눌린 상태면 버튼으로 마우스 갖다대도 false 로..
anim.SetBool("draged", false);
}
}
public void ButtonMoveEnd()
{
anim.SetBool("draged", false); // 값을 false 로 줘서 Idle 애니메이션 실행
}
public void ButtonClicked()
{
for (int i=0; i<otherButtons.Length; i++)
{
// 자신을 제외한 다른 버튼 모두 끄기
otherButtons[i].GetComponent<ButtonMove>().ResetButtonSprite();
if (otherButtons[i].GetComponent<ButtonMove>().panel != null)
{
otherButtons[i].GetComponent<ButtonMove>().panel.GetComponent<Animator>().SetBool("isOpened", false); // 창 닫힘 애니메이션 실행
}
}
uiButton.image.sprite = clicked; // 스프라이트를 click 상태로
if (panel != null)
{
panel.GetComponent<Animator>().SetBool("isOpened", true); // 창 열림 애니메이션 실행
}
anim.SetBool("clicked", true);
anim.SetBool("closed", false);
closeButton.gameObject.SetActive(true); // 클로즈 버튼 활성화
}
// Event Handler 이용
public void OnPointerEnter(PointerEventData eventData)
{
ButtonMoveStart();
}
public void OnPointerExit(PointerEventData eventData)
{
ButtonMoveEnd();
}
public void OnPointerClick(PointerEventData eventData)
{
ButtonClicked();
}
public void ResetButtonSprite()
{
// 버튼 스프라이트를 nonClick 상태로 다시 바꿔주기
uiButton.image.sprite = nonClicked;
anim.SetBool("clicked", false);
anim.SetBool("closed", true);
}
// 자기 자신을 끄는 함수
public void CloseButton()
{
gameObject.GetComponent<ButtonMove>().ResetButtonSprite();
if (gameObject.GetComponent<ButtonMove>().panel != null)
{
gameObject.GetComponent<ButtonMove>().panel.GetComponent<Animator>().SetBool("isOpened", false); // 창 닫힘 애니메이션 실행
}
}
}
2.6 ButtonMove 스크립트 변경 사항 설명
1. 변수
panel 과 closeButton 변수가 추가되었다. ButtonMove 는 게임 화면 속 왼쪽 하단에 존재하는 세 개의 버튼에 부착되어 있는 스크립트(컴포넌트) 이다.
점토 구매 버튼을 클릭하면 점토 구매 창을 띄울 수 있도록 하기 위해 panel 변수에 알맞는 게임 오브젝트(점토 구매 창)를 할당해주었다(업그레이드, 야생 창도 마찬가지).
closeButton 은 창이 켜질 때 같이 활성화되는 버튼이다. 창이 아닌 게임 화면을 클릭하면 창을 끌 수 있도록 하기 위해 필요한 변수이다.
[Header("Panel")]
public GameObject panel; // 해당 버튼에 맞는 판넬 게임 오브젝트 할당
[Header("Close Button")]
public Button closeButton;
2. ButtonClicked()
자신을 제외한 다른 버튼을 모두 끄는 로직에 판넬을 끄는 로직도 추가해주었다. 판넬이 null 이 아닐 때만 수행될 수 있도록 조건문으로 그렇지 않은 경우를 걸러냈다.
if (otherButtons[i].GetComponent<ButtonMove>().panel != null)
{
otherButtons[i].GetComponent<ButtonMove>().panel.GetComponent<Animator>().SetBool("isOpened", false); // 창 닫힘 애니메이션 실행
}
자신을 제외한 다른 버튼들을 끈 후에는 자신을 켜야한다. 아래는 바로 그 로직이다.
if (panel != null)
{
panel.GetComponent<Animator>().SetBool("isOpened", true); // 창 열림 애니메이션 실행
}
창이 열릴 때 동시에 클로즈 버튼을 활성화 될 수 있도록 아래와 같은 구문을 추가했다.
closeButton.gameObject.SetActive(true); // 클로즈 버튼 활성화
3. CloseButton()
클로즈 버튼을 누르면 호출되는 함수이다. 클로즈 버튼의 OnClick 에 다음과 같은 메서드를 추가했다. 점토 구매, 업그레이드, 야생 버튼 모두 CloseButton() 메서드를 가지고 있으므로 이를 다 클로즈 버튼에 추가했다는 의미이다.
// 자기 자신을 끄는 함수
public void CloseButton()
{
gameObject.GetComponent<ButtonMove>().ResetButtonSprite();
if (gameObject.GetComponent<ButtonMove>().panel != null)
{
gameObject.GetComponent<ButtonMove>().panel.GetComponent<Animator>().SetBool("isOpened", false); // 창 닫힘 애니메이션 실행
}
}
2.7 ClayPanel 스크립트
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class ClayPanel : MonoBehaviour
{
[Header("UI")]
public Button pageLeftButton; // 페이지 왼쪽 버튼
public Button pageRightButton; // 페이지 오른쪽 버튼
public Button buyClayButton; // 점토 구매 버튼
public Button unlockClayButton; // 점토 해금 버튼
public Image lockedImage; // 잠금된 점토 이미지
public Image clayImage; // 점토 이미지
public Text clayName; // 점토 이름
public Text buyPrice; // 점토 가격
public GameObject lockedPage; // 잠금 페이지
[Header("Control")]
public int pageIdx = 0; // 페이지 인덱스
public int maxIdx;
public int minIdx = 0;
private void Start()
{
maxIdx = GameManager.instance.poolManager.clayPrefabs.Length;
pageLeftButton.onClick.AddListener(DownPageIdx);
pageLeftButton.onClick.AddListener(SetClayPanel);
pageRightButton.onClick.AddListener(UpPageIdx);
pageRightButton.onClick.AddListener(SetClayPanel);
}
private void OnEnable()
{
// 활성화 될 때 호출되는 함수
ResetClayPanel();
}
private void ResetClayPanel()
{
pageIdx = 0;
unlockClayButton.onClick.AddListener(() => Unlock(pageIdx)); // Unlock 메서드 추가
}
public void SetClayPanel()
{
Clay clay = GameManager.instance.poolManager.clayPrefabs[pageIdx].GetComponent<Clay>();
if (GameManager.instance.unLockedClays[pageIdx] == false)
{
lockedPage.SetActive(true); // 해금이 안된 경우엔 잠금 판넬 활성화..
}
else
{
lockedPage.SetActive(false); // 해금된 경우엔 잠금 판넬 비활성화..
}
// 페이지 인덱스에 맞게 설정
lockedImage.sprite = clay.clay; // 점토의 이미지 가져오기
clayImage.sprite = clay.clay;
clayName.text = clay.clayName; // 점토 이름 가져오기
buyPrice.text = clay.buyPrice + ""; // 점토 가격 가져오기
}
public void UpPageIdx()
{
if (pageIdx >= maxIdx - 1) return; // 빠져나가기..
pageIdx++;
SetButtonInfo();
}
public void DownPageIdx()
{
if (pageIdx <= 0) return; // 빠져나가기..
pageIdx--;
SetButtonInfo();
}
private void SetButtonInfo()
{
unlockClayButton.onClick.RemoveAllListeners(); // 일단 다 지웡
unlockClayButton.onClick.AddListener(() => Unlock(pageIdx)); // Unlock 메서드 추가
buyClayButton.onClick.RemoveAllListeners(); // 일단 다 지웡
buyClayButton.onClick.AddListener(() => BuyClay(pageIdx)); // BuyClay 메서드 추가
}
public void ResetPanel()
{
pageIdx = 0;
}
public void Unlock(int idx)
{
GameManager.instance.unLockedClays[idx] = true;
SetClayPanel();
}
public void BuyClay(int idx)
{
int price = GameManager.instance.poolManager.clayPrefabs[idx].GetComponent<Clay>().buyPrice;
// 돈이 충분하면 구매
if (GameManager.instance.gold >= price)
{
GameManager.instance.GetGold(-price);
GameManager.instance.poolManager.GetGameObject(idx); // 동물 get!
}
}
}
2.8 ClayPanel 스크립트 설명
1. 변수
일단 점토 구매 창에서 존재하는 게임 오브젝트 중 스크립트에 이용할 수 있는 것들을 변수로 선언해서 가져온다. 인스펙터 창에서 직접 할당해줬다.
pageIdx 는 진짜 말 그대로 현재 페이지의 인덱스를 의미한다. 이 값이 맥스 값보다 크거나 민 값보다 작은 경우에는 버튼을 눌러도 인덱스에 변화가 없도록 하는데 사용하기도 하고, 이미 만들어 놓은 프리팹들을 저장해놓은 배열에 접근할 때 이용하기도 한다.
[Header("UI")]
public Button pageLeftButton; // 페이지 왼쪽 버튼
public Button pageRightButton; // 페이지 오른쪽 버튼
public Button buyClayButton; // 점토 구매 버튼
public Button unlockClayButton; // 점토 해금 버튼
public Image lockedImage; // 잠금된 점토 이미지
public Image clayImage; // 점토 이미지
public Text clayName; // 점토 이름
public Text buyPrice; // 점토 가격
public GameObject lockedPage; // 잠금 페이지
[Header("Control")]
public int pageIdx = 0; // 페이지 인덱스
public int maxIdx;
public int minIdx = 0;
2. Start()
maxIdx 값을 GameManager 를 통해서 poolManager 의 clayPrefabs 에 접근하여 해당 배열의 길이로 할당해줬다.
그리고 점토 구매 창의 왼쪽 버튼과 오른쪽 버튼에 idx 를 조정하는 메서드와, 점토 구매 창의 정보를 조정하는 메서드를 연결해줬다.
그리고 해금 버튼에도 점토 구매 창의 정보를 조정하는 메서드를 연결했다.
private void Start()
{
maxIdx = GameManager.instance.poolManager.clayPrefabs.Length;
pageLeftButton.onClick.AddListener(DownPageIdx);
pageLeftButton.onClick.AddListener(SetClayPanel);
pageRightButton.onClick.AddListener(UpPageIdx);
pageRightButton.onClick.AddListener(SetClayPanel);
}
3. OnEnable()
이는 해당 게임 오브젝트가 활성화 될 때 자동으로 호출되는 메서드이다. 여기서 ResetClayPanel() 메서드를 호출했다.
private void OnEnable()
{
// 활성화 될 때 호출되는 함수
ResetClayPanel();
}
4. ResetClayPanel()
얘는 점토 구매 창을 완전 초기 상태로 리셋하는 메서드이다. 점토 구매 창을 켜는 버튼을 눌렀을 때 맨 처음 인덱스부터 시작해야 하고, 이를 반영해야 하기 때문에 이와 같은 메서드를 만들었다.
private void ResetClayPanel()
{
pageIdx = 0;
unlockClayButton.onClick.AddListener(() => Unlock(pageIdx)); // Unlock 메서드 추가
}
5. SetClayPanel()
점토 구매 창의 정보를 세팅하는 메서드이다.
현재 페이지 인덱스에 맞는 점토의 정보를 가져와서 UI 에 반영해줬다. 만약 해당 점토가 해금이 안 된 상태라면 잠금 창을 볼 수 있게, 해금 되었다면 해금된 상태를 볼 수 있도록 했다.
public void SetClayPanel()
{
Clay clay = GameManager.instance.poolManager.clayPrefabs[pageIdx].GetComponent<Clay>();
if (GameManager.instance.unLockedClays[pageIdx] == false)
{
lockedPage.SetActive(true); // 해금이 안된 경우엔 잠금 판넬 활성화..
}
else
{
lockedPage.SetActive(false); // 해금된 경우엔 잠금 판넬 비활성화..
}
// 페이지 인덱스에 맞게 설정
lockedImage.sprite = clay.clay; // 점토의 이미지 가져오기
clayImage.sprite = clay.clay;
clayName.text = clay.clayName; // 점토 이름 가져오기
buyPrice.text = clay.buyPrice + ""; // 점토 가격 가져오기
}
6. UpPageIdx(), DownPageIdx()
페이지 인덱스 값을 조정하는 메서드이다.
public void UpPageIdx()
{
if (pageIdx >= maxIdx - 1) return; // 빠져나가기..
pageIdx++;
SetButtonInfo();
}
public void DownPageIdx()
{
if (pageIdx <= 0) return; // 빠져나가기..
pageIdx--;
SetButtonInfo();
}
7. SetButtonInfo()
인덱스가 바뀔 때마다 구매 버튼과 해금 버튼의 정보를 새로 설정해줘야 한다. 이를 위해 존재하는 메서드이다.
private void SetButtonInfo()
{
unlockClayButton.onClick.RemoveAllListeners(); // 일단 다 지웡
unlockClayButton.onClick.AddListener(() => Unlock(pageIdx)); // Unlock 메서드 추가
buyClayButton.onClick.RemoveAllListeners(); // 일단 다 지웡
buyClayButton.onClick.AddListener(() => BuyClay(pageIdx)); // BuyClay 메서드 추가
}
8. ResetPanel()
얘는 판넬을 초기화하려는 목적으로 작성한 메서드인데 이제보니 필요없을 것같기도.. 그래도 일단 남겨놓겠다.
public void ResetPanel()
{
pageIdx = 0;
}
9. Unlock(int idx)
점토를 해금하는 메서드이다. 이는 해금하기 버튼에 연결된다. 해금 여부를 true 로 설정해준 다음 바로 SetClayPanel() 메서드를 호출해서 점토 구매 창에 반영되도록 했다.
public void Unlock(int idx)
{
GameManager.instance.unLockedClays[idx] = true;
SetClayPanel();
}
10. BuyClay(int idx)
점토 구매 버튼에 연결되는 메서드이다.
public void BuyClay(int idx)
{
int price = GameManager.instance.poolManager.clayPrefabs[idx].GetComponent<Clay>().buyPrice;
// 돈이 충분하면 구매
if (GameManager.instance.gold >= price)
{
GameManager.instance.GetGold(-price);
GameManager.instance.poolManager.GetGameObject(idx); // 동물 get!
}
}
3. 결과물
정상적으로 작동하는 걸 확인할 수 있다.
4. 참고자료
OnClick 에 대해 정리해놓은 글이 있길래 가져왔다.
4.1 OnClick 함수
Unity 유니티 UI Button 버튼 OnClick 함수 등록 방법
[Unity][ClickEvent] 버튼 클릭 이벤트를 처리하는 n가지 방법 :: solar_system_11