목차
퀴즈 게임 만들기
25.02.27
게임 광고 넣기
└ 전면 광고
: 초기화는 지난 시간에 해뒀기 때문에 안 해도 된다.
※ 광고 시작 가이드
: Google에 'Admob Unity SDK' 검색
https://developers.google.com/admob/unity/quick-start?hl=ko
시작하기 | Unity | Google for Developers
Unity에서 앱을 제작 중인 AdMob 게시자를 위한 모바일 광고 SDK입니다.
developers.google.com
>> AdmobAdsManager.cs에 전면 광고 코드 작성
>> 'Main Panel'의 자식으로 'Interstitial Ad Test Button' 생성 후, OnClick()에 함수 바인딩
: UI-Button으로 생성, 에디터 상에서 테스트를 하기 위함
└ 보상형 광고
>> AdmobAdsManager.cs에 전면 광고 코드 작성
>> 'Main Panel'의 자식으로 'Interstitial Ad Test Button'을 복사하여 'Reward Ad Test Button'을 생성 후, OnClick()에 함수 바인딩
: 버튼의 PosY만 살짝 아래로 내림
모바일 환경에서 광고 테스트
: 51일차 글을 참고하여 모바일에서 Build하자
>> 모바일 환경에서 실행되는 Log 확인하는 방법
: cmd에서 아래 명령어로 명령 실행 --> 다시 빠져나오려면 Ctrl + C
adb logcat -s Unity
--> 모바일에서 혹시 게임을 킨 상태라면 완전히 종료 시키고 logcat 실행, 이후 게임을 켜서 작동하면 Log를 볼 수 있다.
※ 에뮬도 구동 가능하다.
※ 'adb devices' 를 했을 때, 여러 Device가 나온다면 실행이 불가능하다. 1개만 남기자
활동
: Quiz Game 광고 적용 및 게임 완성 --> 강사님의 인싸퀴즈와 최대한 유사하게 만들기
- 지금까지 제작한 Quiz Game의 파츠를 조합해서 게임을 완성해주세요.
- 배너 광고, 전면 광고, 보상형 광고를 적용해 주세요
- 게임이 종료 되었거나 다시 실행했을 때 마지막 스테이지와 하트 정보가 유지되게 만들어 주세요
>> 우선 현재 있는 버그 해결
- 게임을 시작하면 Quiz 내용이 안 나오고 보기 버튼이 안 눌러진다. --> 다시 도전을 하면 제대로 Quiz 내용이 표시된다. 정답을 맞추고 다음 문제로 넘어가면 처음과 동일한 문제가 발생한다.
- 'QuizCardIncorrect' Animation이 동작하면 QuizCard의 Rotation.Z가 0이 아니라 -10으로 고정된다. --> 강사님의 Project를 받아서 실행해봐도 동일한 현상이 발생한다. (강사님은 -15.7에 고정)
--> 오른쪽 사진과 같이 기울어서 멈춰있게 된다, 다른 Animation들은 정상 작동
1번 문제 발견
: QuizCard가 생성됐을 때, 'Result Panel Correct'가 Active 상태로 나와 Front Panel보다 앞에 등장하여 Quiz Text도 가리고 Button도 안 눌리게 된 것
1번 문제 해결
: QuizCardController.cs의 SetQuiz() 함수에서 ShowQuizCardResult() 함수를 호출하지 않아서 Result Panel이 계속 SetActive 상태로 나온 것이었다.
--> ShowQuizCardResult( QuizCardResultType.None) 으로 호출하여 해결
// Quiz Card Result Panel
ShowQuizCardResult(QuizCardResultType.None);
2번 문제 발견
: 강사님께 여쭤보니 Incorrect Animation의 첫 프레임이 이미 회전한 상태여서 그렇다고 하신다.
2번 문제 해결
: Animation의 첫 프레임을 Rotation.Z를 0으로 변경하고 'Curves'를 건드려 Animation을 자연스럽게 수정하여 해결
>> 구현해야할 것 구상
Main 화면
- 남은 하트 수 표시
- 다음 Stage가 몇 Level인지 표시
- Play 버튼을 누르라고 물결 파동처럼 Animation
└ 상점
: 광고 연결
--> 광고를 다 봤는지 확인 여부(변수 추가)에 따라 하트를 더하기
--> 하트가 더해지는 Animation은 다른 곳에서 구현해야 한다?
└ Stage
: Level을 선택하여 게임을 시작할 때 몇 Level인지 잠깐 띄우는 팝업
Game 화면
: GameOver 시, 하트가 없다면 종료 버튼 외에 광고 보고 하트 3개 받기 버튼 추가
최종 코드
>> AdmobAdsManager.cs
: 내 AdUnitID는 삭제
using System;
using System.Collections;
using System.Collections.Generic;
using GoogleMobileAds.Api;
using UnityEngine;
using UnityEngine.SceneManagement;
public class AdmobAdsManager : Singleton<AdmobAdsManager>
{
#if UNITY_ANDROID
private string _bannerAdUnitId = "";
private string _interstitialAdUnitId = "";
private string _rewardedAdUnitId = "";
#elif UNITY_IOS
private string _bannerAdUnitId = "ca-app-pub-3940256099942544/2934735716"; // 테스트 광고 ID
private string _interstitialAdUnitId = "ca-app-pub-3940256099942544/4411468910"; // 테스트 광고 ID
private string _rewardedAdUnitId = "ca-app-pub-3940256099942544/1712485313"; // 테스트 광고 ID
#endif
private BannerView _bannerView;
private InterstitialAd _interstitialAd;
private RewardedAd _rewardedAd;
private void Start()
{
// SDK 초기화
MobileAds.Initialize(initStatus =>
{
// 배너 광고 표시 --> 광고 제거 구매 여부를 확인 후 표시할지 말지 결정해야 한다.
LoadBannerAd();
// 전면 광고 표시
LoadInterstitialAd();
// 보상형 광고 표시
LoadRewardedAd();
});
}
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
}
#region Banner Ads
public void CreateBannerView()
{
Debug.Log("Creating banner view");
if (_bannerView != null)
{
// Banner View 소멸
_bannerView.Destroy();
_bannerView = null;
}
_bannerView = new BannerView(_bannerAdUnitId, AdSize.Banner, AdPosition.Bottom);
}
public void LoadBannerAd()
{
if (_bannerView == null)
{
CreateBannerView();
}
var adRequest = new AdRequest();
_bannerView.LoadAd(adRequest);
RegisterBannerAdsEventHandler();
}
private void RegisterBannerAdsEventHandler()
{
// Raised when an ad is loaded into the banner view.
_bannerView.OnBannerAdLoaded += () =>
{
Debug.Log("Banner view loaded an ad with response : "
+ _bannerView.GetResponseInfo());
};
// Raised when an ad fails to load into the banner view.
_bannerView.OnBannerAdLoadFailed += (LoadAdError error) =>
{
Debug.LogError("Banner view failed to load an ad with error : "
+ error);
};
// Raised when the ad is estimated to have earned money.
_bannerView.OnAdPaid += (AdValue adValue) =>
{
Debug.Log(String.Format("Banner view paid {0} {1}.",
adValue.Value,
adValue.CurrencyCode));
};
// Raised when an impression is recorded for an ad.
_bannerView.OnAdImpressionRecorded += () =>
{
Debug.Log("Banner view recorded an impression.");
};
// Raised when a click is recorded for an ad.
_bannerView.OnAdClicked += () =>
{
Debug.Log("Banner view was clicked.");
};
// Raised when an ad opened full screen content.
_bannerView.OnAdFullScreenContentOpened += () =>
{
Debug.Log("Banner view full screen content opened.");
};
// Raised when the ad closed full screen content.
_bannerView.OnAdFullScreenContentClosed += () =>
{
Debug.Log("Banner view full screen content closed.");
};
}
#endregion
#region Interstitial Ads
/// <summary>
/// 전면 광고 준비(Load) Method
/// </summary>
public void LoadInterstitialAd()
{
if (_interstitialAd != null)
{
_interstitialAd.Destroy();
_interstitialAd = null;
}
Debug.Log("Loading the interstitial ad.");
var adRequest = new AdRequest();
InterstitialAd.Load(_interstitialAdUnitId, adRequest, (InterstitialAd ad, LoadAdError error) =>
{
if (error != null || ad == null)
{
Debug.LogError("interstitial ad failed to load an ad " + "with error : " + error);
return;
}
Debug.Log("Interstitial ad loaded with response : " + ad.GetResponseInfo());
_interstitialAd = ad;
RegisterInterstitialAdsEventHandlers(_interstitialAd);
});
}
/// <summary>
/// 전면 광고 표시 Method
/// </summary>
public void ShowInterstitialAd()
{
if (_interstitialAd != null && _interstitialAd.CanShowAd())
{
Debug.Log("Showing the interstitial ad.");
_interstitialAd.Show();
}
else
{
Debug.Log("Interstitial ad is not ready yet.");
}
}
/// <summary>
/// 전면 광고 이벤트 수신 Method
/// </summary>
/// <param name="interstitialAd"></param>
private void RegisterInterstitialAdsEventHandlers(InterstitialAd interstitialAd)
{
// Raised when the ad is estimated to have earned money.
interstitialAd.OnAdPaid += (AdValue adValue) =>
{
Debug.Log(String.Format("Interstitial ad paid {0} {1}.",
adValue.Value,
adValue.CurrencyCode));
};
// Raised when an impression is recorded for an ad.
interstitialAd.OnAdImpressionRecorded += () =>
{
Debug.Log("Interstitial ad recorded an impression.");
};
// Raised when a click is recorded for an ad.
interstitialAd.OnAdClicked += () =>
{
Debug.Log("Interstitial ad was clicked.");
};
// Raised when an ad opened full screen content.
interstitialAd.OnAdFullScreenContentOpened += () =>
{
Debug.Log("Interstitial ad full screen content opened.");
};
// Raised when the ad closed full screen content.
interstitialAd.OnAdFullScreenContentClosed += () =>
{
Debug.Log("Interstitial ad full screen content closed.");
// 전면 광고 닫히면 다시 로드
LoadInterstitialAd();
};
// Raised when the ad failed to open full screen content.
interstitialAd.OnAdFullScreenContentFailed += (AdError error) =>
{
Debug.LogError("Interstitial ad failed to open full screen content " +
"with error : " + error);
// 전면 광고 로드 실패 시, 다시 로드
LoadInterstitialAd();
};
}
#endregion
#region Rewarded Ads
public void LoadRewardedAd()
{
if (_rewardedAd != null)
{
_rewardedAd.Destroy();
_rewardedAd = null;
}
Debug.Log("Loading the rewarded ad.");
var adRequest = new AdRequest();
RewardedAd.Load(_rewardedAdUnitId, adRequest, (RewardedAd ad, LoadAdError error) =>
{
if (error != null || ad == null)
{
Debug.LogError("Rewarded ad failed to load an ad " + "with error : " + error);
return;
}
Debug.Log("Rewarded ad loaded with response : " + ad.GetResponseInfo());
_rewardedAd = ad;
RegisterRewardedAdEventHandlers(_rewardedAd);
});
}
public void ShowRewardedAd()
{
const string rewardMsg = "Rewarded ad rewarded the user. Type: {0}, Amount: {1}";
if (_rewardedAd != null && _rewardedAd.CanShowAd())
{
_rewardedAd.Show((Reward reward) =>
{
Debug.Log(String.Format(rewardMsg, reward.Type, reward.Amount));
});
}
}
private void RegisterRewardedAdEventHandlers(RewardedAd ad)
{
// Raised when the ad is estimated to have earned money.
ad.OnAdPaid += (AdValue adValue) =>
{
Debug.Log(String.Format("Rewarded ad paid {0} {1}.",
adValue.Value,
adValue.CurrencyCode));
};
// Raised when an impression is recorded for an ad.
ad.OnAdImpressionRecorded += () =>
{
Debug.Log("Rewarded ad recorded an impression.");
};
// Raised when a click is recorded for an ad.
ad.OnAdClicked += () =>
{
Debug.Log("Rewarded ad was clicked.");
};
// Raised when an ad opened full screen content.
ad.OnAdFullScreenContentOpened += () =>
{
Debug.Log("Rewarded ad full screen content opened.");
};
// Raised when the ad closed full screen content.
ad.OnAdFullScreenContentClosed += () =>
{
Debug.Log("Rewarded ad full screen content closed.");
// Reload the ad so that we can show another as soon as possible.
LoadRewardedAd();
};
// Raised when the ad failed to open full screen content.
ad.OnAdFullScreenContentFailed += (AdError error) =>
{
Debug.LogError("Rewarded ad failed to open full screen content " +
"with error : " + error);
// Reload the ad so that we can show another as soon as possible.
LoadRewardedAd();
};
}
#endregion
}
>> QuizCardController.cs
: 종료버튼 추가, 버그 수정
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public struct QuizData
{
public int index;
public string question;
public string description;
public int type;
public int answer;
public string firstOption; // 원래는 string[] Options로 했었다
public string secondOption;
public string thirdOption;
}
// QuizCard의 위치 상태를 정의할 클래스가 반드시 구현할 Method의 목록
public interface IQuizCardPositionState
{
void Transition(bool withAnimation, Action onComplete = null);
}
// QuizCard의 위치 상태 전이를 관리할 목적
public class QuizCardPositionStateContext
{
private IQuizCardPositionState _currentState;
public void SetState(IQuizCardPositionState state, bool withAnimation, Action onComplete = null)
{
if (_currentState == state) return;
_currentState = state;
_currentState.Transition(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>();
}
}
// QuizCard가 첫 번째 위치에 나타날 상태 클래스
public class QuizCardPositionStateFirst: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateFirst(QuizCardController quizCardController) : base(quizCardController) { }
public void Transition(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();
}
}
// QuizCard가 두 번째 위치에 나타날 상태 클래스
public class QuizCardPositionStateSecond: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateSecond(QuizCardController quizCardController) : base(quizCardController) { }
public void Transition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.2f : 0f;
_rectTransform.DOAnchorPos(new Vector2(0f, 160f), 0);
_rectTransform.DOScale(0.9f, animationDuration);
_canvasGroup.DOFade(0.7f, animationDuration).OnComplete(() => onComplete?.Invoke());
_rectTransform.SetAsFirstSibling();
}
}
// QuizCard가 사라질 상태를 처리할 상태 클래스
public class QuizCardPositionStateRemove: QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateRemove(QuizCardController quizCardController) : base(quizCardController) { }
public void Transition(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());
}
}
// QuizCard가 뒤집어지는 상태 클래스
public class QuizCardPositionStateFlip : QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateFlip(QuizCardController quizCardController) : base(quizCardController) { }
public void Transition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.3f : 0f;
_rectTransform.DORotate(new Vector3(0f, 90f, 0f), animationDuration / 2)
.OnComplete(() =>
{
_rectTransform.DORotate(new Vector3(0f, 0f, 0f), animationDuration / 2)
.OnComplete(() => onComplete?.Invoke());
});
}
}
public class QuizCardPositionStateFlipNormal : QuizCardPositionState, IQuizCardPositionState
{
public QuizCardPositionStateFlipNormal(QuizCardController quizCardController) : base(quizCardController) { }
public void Transition(bool withAnimation, Action onComplete = null)
{
var animationDuration = (withAnimation) ? 0.3f : 0f;
_rectTransform.DORotate(new Vector3(0f, 90f, 0f), animationDuration / 2)
.OnComplete(() =>
{
_rectTransform.DORotate(new Vector3(0f, 0f, 0f), animationDuration / 2)
.OnComplete(() => onComplete?.Invoke());
});
}
}
public class QuizCardController : MonoBehaviour
{
[SerializeField] private GameObject frontPanel;
[SerializeField] private GameObject correctBackPanel;
[SerializeField] private GameObject incorrectBackPanel;
// Front Panel
[SerializeField] private TMP_Text questionText; // 퀴즈
[SerializeField] private TMP_Text descriptionText; // 설명
[SerializeField] private Button[] optionButtons; // 보기 --> 타입을 TMP_Text로 해서 text를 직접 받아도 된다.
[SerializeField] private GameObject threeOptionButtons; // 퀴즈 타입에 따른 버튼
[SerializeField] private GameObject oxButtons; // 퀴즈 타입에 따른 버튼
// Incorrect Back Panel
//[SerializeField] private TMP_Text heartCountText;
// Timer
[SerializeField] private MobicsTimer timer;
// 애니메이션
[SerializeField] private GameObject quizCardResultPanel;
// Heart Panel
[SerializeField] private HeartPanelController heartPanelController;
public enum QuizCardPanelType { FrontPanel, CorrectBackPanel, IncorrectBackPanel }
private enum QuizCardResultType { None, Correct, Incorrect }
public delegate void QuizCardDelegate(int cardIndex);
private event QuizCardDelegate onCompleted;
private int _answer;
private int _quizCardIndex;
private Vector2 _correctBackPanelPosition;
private Vector2 _incorrectBackPanelPosition;
// Quiz Card 위치 상태
private IQuizCardPositionState _positionStateFirst;
private IQuizCardPositionState _positionStateSecond;
private IQuizCardPositionState _positionStateRemove;
private IQuizCardPositionState _positionStateFlip;
private IQuizCardPositionState _positionStateFlipNormal;
private QuizCardPositionStateContext _positionStateContext;
// 애니메이션
private Animator _animator;
private void Awake()
{
// 숨겨진 패널의 좌표 저장
_correctBackPanelPosition = correctBackPanel.GetComponent<RectTransform>().anchoredPosition;
_incorrectBackPanelPosition = incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition;
// 상태 관리를 위한 Context 객체 생성
_positionStateContext = new QuizCardPositionStateContext();
_positionStateFirst = new QuizCardPositionStateFirst(this);
_positionStateSecond = new QuizCardPositionStateSecond(this);
_positionStateRemove = new QuizCardPositionStateRemove(this);
_positionStateFlip = new QuizCardPositionStateFlip(this);
_positionStateFlipNormal = new QuizCardPositionStateFlipNormal(this);
_positionStateContext.SetState(_positionStateRemove, false); // 카드 위치 초기화
_animator = GetComponent<Animator>();
}
private void Start()
{
timer.OnTimeout = () =>
{
// TODO: 오답 연출
ShowQuizCardResult(QuizCardResultType.Incorrect);
//SetQuizCardPanelActive(QuizCardPanelType.IncorrectBackPanel);
};
}
#region 구조 개선 전 코드
// public void SetVisible(bool isVisible)
// {
// if (isVisible)
// {
// timer.InitTimer();
// timer.StartTimer();
// }
// else
// {
// timer.InitTimer();
// }
// }
#endregion
public enum QuizCardPositionType { First, Second, Remove }
/// <summary>
/// Quiz Card 위치를 지정하는 Method
/// </summary>
/// <param name="quizCardPositionType">Quiz Card 위치</param>
/// <param name="withAnimation">애니메이션 여부</param>
/// <param name="onComplete">위치 지정 후 실행할 동작</param>
public void SetQuizCardPosition(QuizCardPositionType quizCardPositionType,
bool withAnimation, Action onComplete = null)
{
switch (quizCardPositionType)
{
case QuizCardPositionType.First:
_positionStateContext.SetState(_positionStateFirst, withAnimation, () =>
{
timer.InitTimer();
timer.StartTimer();
onComplete?.Invoke();
});
break;
case QuizCardPositionType.Second:
_positionStateContext.SetState(_positionStateSecond, withAnimation, () =>
{
timer.InitTimer();
onComplete?.Invoke();
});
break;
case QuizCardPositionType.Remove:
_positionStateContext.SetState(_positionStateRemove, withAnimation, onComplete);
break;
}
}
public void SetQuiz(QuizData quizData, QuizCardDelegate onCompleted)
{
// 1. 퀴즈
// 2. 설명
// 3. 타입 (0: OX퀴즈, 1: 보기 3개 객관식)
// 4. 정답
// 5. 보기 (1, 2, 3)
// 퀴즈 카드 인덱스 할당
_quizCardIndex = quizData.index;
// front Panel 표시
SetQuizCardPanelActive(QuizCardPanelType.FrontPanel, false);
// 퀴즈 데이터 표현
questionText.text = quizData.question;
_answer = quizData.answer;
descriptionText.text = quizData.description;
if (quizData.type == 0) // 3지선다 퀴즈
{
threeOptionButtons.SetActive(true);
oxButtons.SetActive(false);
var firstButtonText = optionButtons[0].GetComponentInChildren<TMP_Text>();
firstButtonText.text = quizData.firstOption;
var secondButtonText = optionButtons[1].GetComponentInChildren<TMP_Text>();
secondButtonText.text = quizData.secondOption;
var thirdButtonText = optionButtons[2].GetComponentInChildren<TMP_Text>();
thirdButtonText.text = quizData.thirdOption;
}
else if (quizData.type == 1) // OX 퀴즈
{
threeOptionButtons.SetActive(false);
oxButtons.SetActive(true);
}
this.onCompleted = onCompleted;
heartPanelController.InitHeartCount(GameManager.Instance.heartCount);
// Quiz Card Result Panel
ShowQuizCardResult(QuizCardResultType.None);
}
/// <summary>
/// 퀴즈의 정답을 선택하기 위한 버튼
/// </summary>
/// <param name="buttonIndex"></param>
public void OnClickOptionButton(int buttonIndex)
{
// Timer 일시 정지
timer.PauseTimer();
if (buttonIndex == _answer) // 정답
{
Debug.Log("정답!");
// TODO: 정답 연출
ShowQuizCardResult(QuizCardResultType.Correct);
//SetQuizCardPanelActive(QuizCardPanelType.CorrectBackPanel);
}
else // 오답
{
Debug.Log("오답!");
// TODO: 오답 연출
ShowQuizCardResult(QuizCardResultType.Incorrect);
//SetQuizCardPanelActive(QuizCardPanelType.IncorrectBackPanel);
}
}
public void SetQuizCardPanelActive(QuizCardPanelType quizCardPanelType)
{
ShowQuizCardResult(QuizCardResultType.None);
SetQuizCardPanelActive(quizCardPanelType, true);
}
private void ShowQuizCardResult(QuizCardResultType quizCardResultType)
{
switch (quizCardResultType)
{
case QuizCardResultType.Correct:
quizCardResultPanel.SetActive(true);
_animator.SetTrigger("correct");
break;
case QuizCardResultType.Incorrect:
quizCardResultPanel.SetActive(true);
_animator.SetTrigger("incorrect");
break;
case QuizCardResultType.None:
quizCardResultPanel.SetActive(false);
break;
}
}
public void SetQuizCardPanelActive(QuizCardPanelType quizCardPanelType, bool withAnimation)
{
switch (quizCardPanelType)
{
case QuizCardPanelType.FrontPanel:
correctBackPanel.SetActive(false);
incorrectBackPanel.SetActive(false);
_positionStateContext.SetState(_positionStateFlipNormal, withAnimation, () =>
{
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, withAnimation, () =>
{
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, withAnimation, () =>
{
incorrectBackPanel.SetActive(true);
correctBackPanel.GetComponent<RectTransform>().anchoredPosition = _correctBackPanelPosition;
incorrectBackPanel.GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
});
break;
}
}
public void OnClickExitButton()
{
GameManager.Instance.QuitGame();
}
#region Correct Back Panel
/// <summary>
/// 다음 버튼 이벤트
/// </summary>
public void OnClickNextQuizButton()
{
onCompleted?.Invoke(_quizCardIndex);
}
#endregion
#region Incorrect Back Panel
/// <summary>
/// 다시 도전 버튼 이벤트
/// </summary>
public void OnClickRetryQuizButton()
{
if (GameManager.Instance.heartCount > 0)
{
GameManager.Instance.heartCount--;
heartPanelController.RemoveHeart(() =>
{
SetQuizCardPanelActive(QuizCardPanelType.FrontPanel);
// 타이머 초기화 및 시작
timer.InitTimer();
timer.StartTimer();
});
}
else
{
// 하트가 부족해서 Retry 불가
heartPanelController.EmptyHeart();
}
}
#endregion
}
>> GamePanelController.cs
: 함수명, 매개변수명 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GamePanelController : MonoBehaviour
{
private GameObject _firstQuizCardObject;
private GameObject _secondQuizCardObject;
private List<QuizData> _quizDataList;
// private int _lastGeneratedQuizIndex; // 구조 개선 전 코드
private int _lastStageIndex;
private Queue<QuizCardController> _quizCardQueue = new();
/// <summary>
/// 새로운 퀴즈 카드 추가하는 함수
/// </summary>
/// <param name="quizData">퀴즈 데이터</param>
/// <param name="isInit">초기화 여부</param>
public void AddQuizCardObject(QuizData? quizData, bool isInit = false) // Queue를 이용한 새로운 로직
{
QuizCardController tempQuizCardController = null; // 뺄 Quiz Card
// 1. First 영역의 카드 제거
void RemoveFirstQuizCard(Action onCompleted = null)
{
tempQuizCardController = _quizCardQueue.Dequeue();
tempQuizCardController.SetQuizCardPosition(QuizCardController.QuizCardPositionType.Remove,
true, onCompleted);
}
// 2. Second 영역의 카드를 First 영역으로 이동
void SecondQuizCardToFirst(Action onCompleted = null)
{
var firstQuizCardController = _quizCardQueue.Peek();
firstQuizCardController.SetQuizCardPosition(QuizCardController.QuizCardPositionType.First,
true, onCompleted);
}
// 3. 새로운 퀴즈 카드를 Second 영역에 생성
void AddNewQuizCard(Action onCompleted = 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,
true, onCompleted);
}
}
// 애니메이션 처리
if (_quizCardQueue.Count > 0)
{
if (isInit)
{
SecondQuizCardToFirst();
AddNewQuizCard();
}
else
{
RemoveFirstQuizCard(() =>
SecondQuizCardToFirst(() =>
AddNewQuizCard(() =>
{
if (tempQuizCardController != null)
ObjectPool.Instance.ReturnObject(tempQuizCardController.gameObject);
})));
}
}
else
{
AddNewQuizCard();
}
}
private void Start()
{
_lastStageIndex = UserInformations.LastStageIndex;
InitQuizCards(_lastStageIndex);
}
private void InitQuizCards(int stageIndex)
{
_quizDataList = QuizDataController.LoadQuizData(stageIndex);
AddQuizCardObject(_quizDataList[0], true);
AddQuizCardObject(_quizDataList[1], true);
#region 구조 개선 전 코드
// _firstQuizCardObject = ObjectPool.Instance.GetObject();
// _firstQuizCardObject.GetComponent<QuizCardController>()
// .SetQuiz(_quizDataList[0], 0, OnCompletedQuiz);
//
// _secondQuizCardObject = ObjectPool.Instance.GetObject();
// _secondQuizCardObject.GetComponent<QuizCardController>()
// .SetQuiz(_quizDataList[1], 1, OnCompletedQuiz);
//
// SetQuizCardPosition(_firstQuizCardObject, 0);
// SetQuizCardPosition(_secondQuizCardObject, 1);
// 마지막으로 생성된 Quiz Index
// _lastGeneratedQuizIndex = 1;
#endregion
}
private void OnCompletedQuiz(int cardIndex)
{
if (cardIndex < _quizDataList.Count - 2)
{
AddQuizCardObject(_quizDataList[cardIndex + 2]);
}
else
{
AddQuizCardObject(null);
if (cardIndex == _quizDataList.Count - 1)
{
// TODO: 스테이지 클리어 연출
_lastStageIndex++;
// TODO: 스테이지 클리어 연출 후, 새로운 스테이지 시작
if (_lastStageIndex < Constants.MAX_STAGE_COUNT) // 임시 코드
InitQuizCards(_lastStageIndex);
}
}
#region 구조 개선 전 코드
// if (cardIndex >= Constants.MAX_QUIZ_COUNT - 1)
// {
// if (_lastStageIndex >= Constants.MAX_STAGE_COUNT - 1)
// {
// // TODO: 올 클리어 연출
//
// GameManager.Instance.QuitGame();
// }
// else
// {
// // TODO: 스테이지 클리어 연출
// InitQuizCards(++_lastStageIndex);
// return;
// }
// }
// else
// {
// //ChangeQuizCard();
// if (_quizDataList.Count > cardIndex + 1)
// AddQuizCardObject(_quizDataList[cardIndex + 1]);
// else
// AddQuizCardObject(null);
// }
#endregion
}
#region 구조 개선 전 코드
// private void SetQuizCardPosition(GameObject quizCardObject, int index)
// {
// var quizCardTransform = quizCardObject.GetComponent<RectTransform>();
// if (index == 0)
// {
// quizCardTransform.anchoredPosition = new Vector2(0, 0);
// quizCardTransform.localScale = Vector3.one;
// quizCardTransform.SetAsLastSibling(); // 같은 depth에서 마지막으로 이동 --> 카드가 앞으로 배치됨
//
// quizCardObject.GetComponent<QuizCardController>().SetVisible(true);
// }
// else if (index == 1)
// {
// quizCardTransform.anchoredPosition = new Vector2(0, 160);
// quizCardTransform.localScale = Vector3.one * 0.9f;
// quizCardTransform.SetAsFirstSibling(); // 같은 depth에서 처음으로 이동
//
// quizCardObject.GetComponent<QuizCardController>().SetVisible(false);
// }
// }
// private void ChangeQuizCard()
// {
// if (_lastGeneratedQuizIndex >= Constants.MAX_QUIZ_COUNT) return;
//
// var temp = _firstQuizCardObject;
// _firstQuizCardObject = _secondQuizCardObject;
// _secondQuizCardObject = ObjectPool.Instance.GetObject();
//
// if (_lastGeneratedQuizIndex < _quizDataList.Count - 1)
// {
// _lastGeneratedQuizIndex++;
// _secondQuizCardObject.GetComponent<QuizCardController>()
// .SetQuiz(_quizDataList[_lastGeneratedQuizIndex], _lastGeneratedQuizIndex, OnCompletedQuiz);
// }
//
// SetQuizCardPosition(_firstQuizCardObject, 0);
// SetQuizCardPosition(_secondQuizCardObject, 1);
//
// ObjectPool.Instance.ReturnObject(temp);
// }
#endregion
}
C# 단기 교육 보강
16일차
장염 이슈로 추후 작성 예정...
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 66일차 (0) | 2025.03.05 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 65일차 (0) | 2025.03.05 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 63일차 (0) | 2025.02.26 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 62일차 (0) | 2025.02.25 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 61일차 (0) | 2025.02.25 |