오늘 배운 것
1. GamePanel이 컨트롤하지 않는, QuizCardController에 카드 관련 작업 위임.
2. 1을 바탕으로 애니메이션 제작
3. 각자 완성시켜보기.
01. GamePanel이 컨트롤하지 않는, QuizCardController에 카드 관련 작업 위임.
public interface IQuizCardPositionState
{
void Trasition(bool withAnimation, Action onComplete = null);
}
// 퀴즈 카드의 위치 상태 전이를 관리할 목적
public class QuizCardPositionStateContext
{
private IQuizCardPositionState _currentState;
public void SetState(IQuizCardPositionState state, bool withAnimation, Action onComplete = null)
{
if (_currentState == state) return;
_currentState = state;
_currentState.Trasition(withAnimation, onComplete);
}
}
public class QuizCardPositionState
{
protected QuizCardController _quizCardController;
protected RectTransform _rectTransform;
protected CanvasGroup _canvasGroup;
public QuizCardPositionState(QuizCardController quizCardController)
{
_quizCardController = quizCardController;
_rectTransform = _quizCardController.gameObject.GetComponent<RectTransform>();
_canvasGroup = _quizCardController.gameObject.GetComponent<CanvasGroup>();
}
}
// 퀴즈 카드가 첫 번째 위치에 나타날 상태를 처리할 상태 클래스
public class QuizCardPositionStateFirst: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateFirst(QuizCardController quizCardController) : base(quizCardController) { }
public void Trasition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DOAnchorPos(Vector2.zero, animationDuration);
_rectTransform.DOScale(1f, animationDuration);
_canvasGroup.DOFade(1f, animationDuration).OnComplete(() => onComplete?.Invoke());
_rectTransform.SetAsLastSibling();
}
}
// 퀴즈 카드가 두 번째 위치에 나타날 상태를 처리할 상태 클래스
public class QuizCardPositionStateSecond: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateSecond(QuizCardController quizCardController) : base(quizCardController) { }
public void Trasition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DOAnchorPos(new Vector2(0f, 160f), animationDuration);
_rectTransform.DOScale(0.9f, animationDuration);
_canvasGroup.DOFade(0.7f, animationDuration).OnComplete(() => onComplete?.Invoke());
_rectTransform.SetAsFirstSibling();
}
}
// 퀴즈 카드가 사라질 상태를 처리할 상태 클래스
public class QuizCardPositionStateRemove: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateRemove(QuizCardController quizCardController) : base(quizCardController) { }
public void Trasition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DOAnchorPos(new Vector2(0f, -280f), animationDuration);
_canvasGroup.DOFade(0f, animationDuration).OnComplete(() => onComplete?.Invoke());
}
}
퀴즈 카드 컨트롤러에 들어갈 것
각각의 상태들을 만들고, 그 상태들의 Transition을 호출했을 때 그 상태에 맞는 위치로 올믹는 애니메이션을 재생하고
받아온 onComplete 함수를 호출한다.
private IQuizCardPositionState _positionStateFirst;
private IQuizCardPositionState _positionStateSecond;
private IQuizCardPositionState _positionStateRemove;
private QuizCardPositionStateContext _positionStateContext;
private void Awake()
{
// 숨겨진 패널의 좌표 저장
_correctBackPanelPosition = correctBackPanel.GetComponent<RectTransform>().anchoredPosition;
_incorrectBackPanelPosition = incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition;
//상태 관리를 위한 Context 객체 생성 -- 추가
_positionStateContext = new QuizCardPositionStateContext();
// this는 현재 퀴즈카드 컨트롤러를 전달.
_positionStateFirst = new QuizCardPositionStateFirst(this);
_positionStateSecond = new QuizCardPositionStateSecond(this);
_positionStateRemove = new QuizCardPositionStateRemove(this);
//애니메이션 없이 Remove상태로 바뀔 수 있게 초기화
_positionStateContext.SetState(_positionStateRemove, false);
}
앞에서는 클래스를 선언만 했기 때문에 함수를 기능시키기 위해서 퀴즈 카드가 생길 때 상태 클래스 3개를 생성해준다.
그리고 생성자에 퀴즈카드 스테이트를 넣어줘 동자갛게 한다.
앞에 있는 것만 보이게 해 주었던
public void SetVisible(bool isVisible)
{
if (isVisible)
{
timer.InitTimer();
timer.StartTimer();
}
else
{
timer.InitTimer();
}
}
다음 코드도 통합할 것이다. (바로 밑의 코드에 통합될 예정이다)
저 위의 세 상태를 묶어줄 주고, 호출을 쉽게 해 줄 코드는 다음과 같다.
public void SetQuizCardPosition(QUizCardPositionType quizCardPositionType, bool withAnimation, Action onComplete = null)
{
switch (quizCardPositionType)
{
case QUizCardPositionType.First:
_positionStateContext.SetState(_positionStateFirst, withAnimation);
break;
case QUizCardPositionType.Second:
_positionStateContext.SetState(_positionStateSecond, withAnimation);
break;
case QUizCardPositionType.Remove:
_positionStateContext.SetState(_positionStateRemove, withAnimation);
break;
}
}
원래 코드와 비교해보자면 !
원래는 이런 식으로, 게임 패널 컨트롤러에서 카드를 직접 움직였다.
if (_quizCardQueue.Count > 0)
{
// 2. 가장 첫 번째 오브젝트를 Peek 해서 사이즈와 위치 조절하고
var firstQuizCardObject = _quizCardQueue.Peek();
firstQuizCardObject.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
firstQuizCardObject.transform.localScale = Vector3.one;
firstQuizCardObject.transform.SetAsLastSibling();
firstQuizCardObject.GetComponent<QuizCardController>().SetVisible(true);
}
이제는 퀴즈 카드 컨트롤러 자체에서 움직임을 갖고 있고(상태에 따른) 우리는 게임패널 컨트롤러에서 간단하게 SetQuizCardPosition만 불러줄 뿐, 위치나 투명도 등은 퀴즈 카드 컨트롤러에 위임한다.
//변경한 이유, SetQuizCardPosition함수를 사용하기 위해
private Queue<QuizCardController> _quizCardQueue = new Queue<QuizCardController>();
public void AddQuizCardObject(QuizData? quizData, bool isInit = false)
{
//원래 있었던, 빼낼 퀴즈 카드를 저장하는 컨트롤러 객체
QuizCardController tempQuizCardController = null;
// 1단계 : First 영역의 카드 제거
void RemoveFirstQuizCard(Action onComplete = null)
{
tempQuizCardController = _quizCardQueue.Dequeue();
tempQuizCardController.SetQuizCardPosition(QuizCardController.QUizCardPositionType.Remove,true, onComplete);
}
// 2단계 : Second 영역의 카드를 First 영역으로 이동
void SecondQuizCardToFirst(Action onComplete = null)
{
var firstQuizCardController = _quizCardQueue.Peek();
firstQuizCardController.SetQuizCardPosition(QuizCardController.QUizCardPositionType.First, true, onComplete);
}
// 3단계 : 새로운 퀴즈 카드를 Second 영역에 생성
void AddNewQuizCard(Action onComplete = null)
{
if (quizData.HasValue)
{
var quizCardObject = ObjectPool.Instance.GetObject();
var quizCardController = quizCardObject.GetComponent<QuizCardController>();
quizCardController.SetQuiz(quizData.Value, OnCompletedQuiz);
_quizCardQueue.Enqueue(quizCardController);
quizCardController.SetQuizCardPosition(QuizCardController.QUizCardPositionType.Second, ture, onComplete);
}
}
}
이제 조건문을 토대로 카드를 생성, 넘겨준다.
if (_quizCardQueue.Count > 0)
{
if (isInit)
{
SecondQuizCardToFirst();
AddNewQuizCard();
}
else
{
RemoveFirstQuizCard(()=>
{
SecondQuizCardToFirst(() =>
{
AddNewQuizCard(() =>
{
if ( tempQuizCardController != null)
ObjectPool.Instance.ReturnObject(tempQuizCardController.gameObject);
});
});
});
}
}
// 하나도 없다 : 만들어진 퀴즈가 없다.
else
{
}
동작은 똑같다.
02. 1을 바탕으로 애니메이션 제작
새로운 스테이트인 flip 상태를추가했다
미처 생각하지 못한 방식이었는데 상당히 신기했다.
public class QuizCardFlip : QuizCardPositionState, IQuizCardPositionState
{
public QuizCardFlip(QuizCardController quizCardController) : base(quizCardController)
{
}
public void Trasition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DORotate(new Vector3(0f, 90f, 0f),
animationDuration / 2).OnComplete(() =>
{
_rectTransform.DORotate(new Vector3(0f, -90f, 0f),
animationDuration / 2)
.OnComplete(() => onComplete?.Invoke());
});
}
}
노가다로 풀던 나와는 다르신 모습......
퀴즈 카드 객체에서 자동으로 돌아버린다. (마찬가지로 SetQuizCardPosition에 넣고 호출함)
private void SetQuizCardPanelActive(QuizCardPanelType quizCardPanelType)
{
switch (quizCardPanelType)
{
case QuizCardPanelType.Front:
frontPanel.SetActive(true);
correctBackPanel.SetActive(false);
incorrectBackPanel.SetActive(false);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = _correctBackPanelPosition;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = _incorrectBackPanelPosition;
break;
case QuizCardPanelType.CorrectBackPanel:
frontPanel.SetActive(false);
incorrectBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlip, true,() =>
{
correctBackPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = _incorrectBackPanelPosition;
});
break;
case QuizCardPanelType.InCorrectBackPanel:
frontPanel.SetActive(false);
correctBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlip, true, () =>
{
incorrectBackPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = _correctBackPanelPosition;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
});
break;
}
}
잘 되는줄 알았으나 버그가 있다.
현재 코드에서
public void SetState(IQuizCardPositionState state, bool withAnimation, Action onComplete = null)
{
if (_currentState == state) return;
_currentState = state;
_currentState.Trasition(withAnimation, onComplete);
}
같은 상태이면 return 해 버리는 문제가 있다. 따라서 flip을 할 때에 currentState를 flip 상태로 설정해 놨기 때문에,
이를 바꿔주지 않으면 flip을 다시 할 수 없다.
그래서 이름만 다른 상태를 하나 만들어준다.
public class QuizCardFlipNormal : QuizCardPositionState, IQuizCardPositionState
{
public QuizCardFlipNormal(QuizCardController quizCardController) : base(quizCardController)
{
}
public void Trasition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DORotate(new Vector3(0f, 90f, 0f),
animationDuration / 2).OnComplete(() =>
{
_rectTransform.DORotate(new Vector3(0f, 0f, 0f),
animationDuration / 2)
.OnComplete(() => onComplete?.Invoke());
});
}
}
밑을 올바르게 바꿔주면 Clear ~
private void SetQuizCardPanelActive(QuizCardPanelType quizCardPanelType , bool withAnimaiton = true)
{
switch (quizCardPanelType)
{
case QuizCardPanelType.Front:
correctBackPanel.SetActive(false);
incorrectBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlipNormal, withAnimaiton, () =>
{
frontPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = _correctBackPanelPosition;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = _incorrectBackPanelPosition;
} );
break;
case QuizCardPanelType.CorrectBackPanel:
frontPanel.SetActive(false);
incorrectBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlip, withAnimaiton,() =>
{
correctBackPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = _incorrectBackPanelPosition;
});
break;
case QuizCardPanelType.InCorrectBackPanel:
frontPanel.SetActive(false);
correctBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlip, withAnimaiton, () =>
{
incorrectBackPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = _correctBackPanelPosition;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
});
break;
}
}
이렇게 front로 갈 때에 항상 다른 State로 설정을 할 수 있게 해 두어야 한다.
03. 각자 디테일 수정
levelPanel (스테이지 정보 표시) 애니메이션 제작
private void InitQuizCards(int stageIndex)
{
levelPanel.SetActive(true);
RectTransform rectTransform = levelPanel.GetComponent<RectTransform>();
Image image = levelPanel.GetComponent<Image>();
Sequence levelPanelsequence = DOTween.Sequence();
rectTransform.localScale = Vector3.one * 1.5f;
image.color = new Color(1f, 1f, 1f, 0f);
levelPanelsequence.Append(rectTransform.DOScale(Vector3.one, 1f).SetEase(Ease.OutBack))
.Join(image.DOFade(1f, 1f));
levelPanelsequence.AppendInterval(1f);
levelPanelsequence.Append(rectTransform.DOScale(Vector3.one * 1.5f, 1f))
.Join(image.DOFade(0, 1f));
levelPanelsequence.OnComplete(() =>
{
_quizDataList = QuizDataController.LoadQuizData(stageIndex);
AddQuizCardObject(_quizDataList[0], true);
AddQuizCardObject(_quizDataList[1], true);
levelPanel.SetActive(false);
});
levelPanelsequence.Play();
OnClickOptionButton( 정답과 오답에 따른 애니메이션 처리) 제작
public void OnClickOptionButton(int buttonIndex)
{
//var button = optionButtons[buttonIndex];
var buttons = this.gameObject.GetComponentsInChildren<Button>();
var button = buttons[buttonIndex];
// Timer 일시 정시
timer.PauseTimer();
if (buttonIndex == _answer)
{
Debug.Log("정답!");
button.GetComponent<Image>().color = Color.green;
button.transform.DOPunchScale(Vector3.one * 0.2f, 1f).OnComplete(() =>
{
SetQuizCardPanelActive(QuizCardPanelType.CorrectBackPanel);
button.GetComponent<Image>().color = Color.white;
});
}
else
{
Debug.Log("오답~");
button.GetComponent<Image>().color = Color.red;
button.transform.DOShakePosition(1f,10f,10,90).OnComplete(() =>
{
SetQuizCardPanelActive(QuizCardPanelType.InCorrectBackPanel);
button.GetComponent<Image>().color = Color.white;
});
}
}
HeartPanel을 재사용해 RetryButton에 접목
public void OnClickRetryQuizButton()
{
if (GameManager.Instance.heartCount > 0)
{
_heartPanel.RemoveHeart(() =>
{
SetQuizCardPanelActive(QuizCardPanelType.Front);
// 타이머 초기화 및 시작
timer.InitTimer();
timer.StartTimer();
//하트 패널 부숴라.
Destroy(_heartPanel.gameObject);
});
//heartCountText.text = GameManager.Instance.heartCount.ToString();
}
else
{
// 하트가 부족해서 다시도전 불가
// TODO: 하트 부족 알림
_heartPanel.EmptyHeart();
}
}
오늘 헤맸던 것
_rectTransform.DORotate(new Vector3(0f, 0f, 0f),
충격 : 어제 안되던 이유
90 - 90하면 0이 될 것이라는 '안일한 생각' 때문이다.
저 DO 뒤에 나오는 숫자들은 전부 '목표값' 이라고 생각하면 편하다.
오늘의 목표
1. 코딩테스트 2문제 풀기
2. 비동기 관련 정리
3. 구글 플레이 콘솔, AdMob 관련 실행
'TIL' 카테고리의 다른 글
[멋쟁이 사자처럼 부트캠프 TIL회고] (0) | 2025.02.21 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL회고] (0) | 2025.02.20 |
[멋쟁이사자처럼 부트캠프 TIL 회고] (0) | 2025.02.18 |
[멋쟁이사자처럼 부트캠프 TIL 회고] (0) | 2025.02.17 |
[멋쟁이사자처럼 부트캠프 TIL회고] 75일차 (0) | 2025.02.14 |