목차
※ 운영체제의 신기능을 유심하게 살펴볼 필요가 있다.
퀴즈 게임 만들기
25.02.20
지난 시간에 이은 활동
>> Game Scene 전체 구현하기 --> 게임 플레이 영상과 동일한 형태로 게임을 구현하기
- Button 이미지 적용
- 정답/오답 연출 구현
- 폰트 교체 --> 에스코어드림 폰트
- 스테이지 시작 구현
※ 아직 미구현 된 Game Scene
- 레벨(스테이지) 팝업
- 정답과 오답 시, 애니메이션
- 오답 시, 몇 문제 남았는지 표시
- 앞의 퀴즈 카드에 명암을 넣어서 입체적으로 표현
└ Button 이미지 적용
: 강사님과 함께 적용
※ Sprite로 버튼을 눌렀을 때 Image 설정하는 방법
※ 자주 쓰는 Color를 저장하는 방법
>> Sprite Editor
: Sprite가 늘어나도 안 깨지도록 설정
>> Width와 Height 계산
: 'FirstOptionButton'에 넣은 Source Image의 'Set Native Size'가 800x146이고 Button 사이의 간격을 20으로 둔다면 'Three Option Buttons'의 Height는 (146*3) + (20*2) = 478이 된다.
--> Options도 마찬가지로 변경
--> Options의 PosY랑 Spacing은 임의로 설정한 것
: O,X Button은 2개기 때문에 (478/2) = 229로 설정
>> Option Buttons 3개의 Text와 퀴즈 내용의 Text 색 변경
: 저장해둔 컬러 사용 --> (44, 55, 89, 255)
>> 이후 나머지 Button에도 적용하자
└ 폰트 교체
: 에스코어드림 폰트 --> 4(Regular), 5(Medium) 사용
https://s-core.co.kr/company/font/
에스코어
에스코어는 디지털 혁신을 위한 고급 프로페셔널 서비스를 제공합니다. 매니지먼트 컨설팅과 소프트웨어 테크놀로지 서비스 오퍼링을 살펴보세요.
s-core.co.kr
>> Font Asset 생성
32-126,44032-55203,12593-12643,8200-9900
>> Text에 적용
└ 정답/오답 연출 구현
: DOTween을 사용하지 않고 Unity에 있는 Animation 만으로 구현해봄
>> Front Panel의 자식으로 빈 게임 오브젝트로 'Result Panel' 생성
--> 원래는 Correct와 Incorrect를 따로 구현하려고 해서, Horizontal Layout Group 을 추가하고 Spacing을 200 줬었다
>> Result Panel의 자식으로 빈 게임 오브젝트로 'Result Panel Correct' 생성
: 원래는 Result Panel Correct와 Result Panel Incorrect로 분리해서 구현하려 했으나, 하나에 전부 구현하게 됨
--> 이미 Animation을 만든 후라서 이를 수정하면 만든 Animation이 전부 깨지게 되므로 놔둠 (아직 구현하지 않았다면 Result Panel Correct도 필요없으니 삭제하고 Result Panel에 만들자)
>> Result Panel Correct 구현
- Pang Image
- Circle Background Image
--> Set Native Size
- Circle Stroke Image
--> Set Native Size
- Point Image 3개
--> Set Native Size
- Marker Correct / Marker Incorrect
: Marker Correct의 이름이 Marker (Animation 깨짐 때문에 변경 못함)
- Text Correct / Text Incorrect
>> Quiz Card에 Animator 추가하여 Animation 구현
: Project에 Animator Controller를 생성하여 바인딩
>> Anim 생성 및 Animation 구현
: 'QuizCardCorrect' 라는 이름으로 Clip 생성
- 방법 1. 원하는 프레임에서 원하는 값으로 설정한 뒤, 'Add Key'로 추가
- 방법 2. 녹화 버튼을 눌러서 시작 프레임에서 원하는 값과 끝 프레임에서 원하는 값을 모두 설정하고 녹화 중지
>> Animation 설정
- Circle Background Image : [0 ~ 60프레임] Color의 Alpha 값을 0 ~ 1로 변경
- Circle Stroke Image : [0 ~ 60프레임] Fill Amount 값을 0 ~ 1로 변경
- Marker의 Stroke : [60 ~ 75프레임] Fill Amount 값을 0 ~ 1로 변경
- Marker의 Stroke (1) : [75 ~ 90프레임] Fill Amount 값을 0 ~ 1로 변경
- Pang Image : [0 ~ 30프레임] Color의 Alpha 값을 0 ~ 40으로 변경 / [0 ~ 170프레임] Rotation.Z 값을 0 ~ 360으로 변경
- Point Image 3개 : 셋 다 0프레임에서 Image의 Active를 false
└ Point Image : [100프레임] Image의 Active를 true
└ Point Image (1) : [110프레임] Image의 Active를 true
└ Point Image (2) : [90프레임] Image의 Active를 true
- Text : [45 ~ 60프레임] Color의 Alpha 값을 0 ~ 1로 변경, Scale.x와 Scale.y를 3 ~ 1로 변경
※ Animation의 지속 시간을 늘리기 위해 170프레임에서 'Add Key' 해주기
>> 완성된 모습
>> Incorrect Animation 만들기
: Animation Clip 추가 후, Correct로 만든 Animation 전부 복사 붙여넣기
>> Animation 설정
- Quiz Card : [0 ~ 18프레임] Rotation.Z 값을 10 ~ -10으로 변경 / [18 ~ 35프레임] Rotation.Z 값을 -10 ~ 0으로 변경
- Circle Background Image : [0 ~ 60프레임] Color의 Alpha 값을 0 ~ 1로 변경 --> Color를 저장해둔 분홍색으로 변경
- Circle Stroke Image : [0 ~ 60프레임] Fill Amount 값을 0 ~ 1로 변경
- Marker Incorrect의 Stroke : [60 ~ 75프레임] Fill Amount 값을 0 ~ 1로 변경
- Marker Incorrect의 Stroke (1) : [75 ~ 90프레임] Fill Amount 값을 0 ~ 1로 변경
- Point Image 3개 : 셋 다 0프레임에서 Image의 Active를 false
└ Point Image : [100프레임] Image의 Active를 true
└ Point Image (1) : [110프레임] Image의 Active를 true
└ Point Image (2) : [90프레임] Image의 Active를 true
- Text Incorrect : [45 ~ 60프레임] Color의 Alpha 값을 0 ~ 1로 변경, Scale.x와 Scale.y를 3 ~ 1로 변경
--> Correct 대비 변경 사항
: Quiz Card 추가, Circle Background Image의 Color 변경, Marker Incorrect의 Stroke로 변경
※ Quiz Card의 Rotation 변화를 Curves로 더 다채롭게 바꾸기
>> 완성된 모습
>> Animator 설정
: New State를 만들어서 사진과 같이 세팅
>> Parameter 생성
: Trigger 타입으로 'correct'와 'incorrect' 생성 후 Conditions에 각각 맞게 추가
--> (Idle -> QuizCardIncorrect)에는 incorrect 추가
>> 코드로 작성
: QuizCardController.cs에 Animation 부분 추가
>> Quiz Card Controller에 바인딩
>> Animation이 끝나고 함수 호출하도록
: 170프레임에 Event 추가
--> Incorrect에도 마찬가지로 적용
※ 이후 세세한 디테일은 스스로 해보자
: 현재 Flip이 되지않는 버그가 있음
Main Scene
>> Menu Button들의 Image 변경 및 자식의 Text 삭제
- Play Button
- Shop Button
- Stage Button
- Leaderboard Button
- Settings Button
--> 나머지 버튼들도 동일한 방식으로 추가
>> Menu Buttons의 위치 조정
>> Remove Ads Button 추가
>> Popup Panel 구현
1. 'Shop Panel' 생성 --> 저장해둔 색으로 변경
2. 'Shop Panel'의 자식으로 'Panel Background' 생성
: Panel을 생성하여 Top값 조정 및 Source Image를 'None'으로 바꾸고 Color의 Alpha값을 255로 수정
3. 'Shop Panel'의 자식으로 'Panel Top Image' 생성
: Image로 생성
※ Image Size가 안 맞다면, Image Type을 Sliced로 바꾸기 전에 Simple 상태에서 Set Native Size를 누르고 Sliced로 변경
--> Anchor는 Alt + Shift
>> 'modal_popup_bg'를 Sprite Editor로 수정
4. 'Shop Panel'의 자식으로 'Close Button' 생성
: Button으로 생성
--> Anchor는 Alt + Shift
5. 'Shop Panel'의 자식으로 'Title Text (TMP)' 생성
: Text로 생성 --> Color는 저장해 둔 색으로 변경
--> Anchor는 Alt + Shift
>> 지금까지 만든 모습
앞으로 구현해야하는 모바일 기능
- Admob --> 구글에서 제공해주는 SDK 이용
- GPGS(Google Play Game Services) : Leaderboard, 업적 등 --> 구글에서 제공해주는 SDK 이용
- In-App 결제 --> Unity에서 제공해주는 SDK 이용
--> Native 개발을 알면 전부 직접 만들 수 있고 Admob을 제외하면 직접 만들어 쓰는게 제일 좋지만, 어렵다.
최종 코드
>> 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 퀴즈
{
oxButtons.SetActive(true);
threeOptionButtons.SetActive(false);
}
this.onCompleted = onCompleted;
heartPanelController.InitHeartCount(GameManager.Instance.heartCount);
}
/// <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()
{
}
#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
}
C# 단기 교육 보강
12일차
Unity C# 프로그래밍 중급 문법
Event와 Unity Event
- Event : 외부 클래스에서 호출하지 못하도록 한다. --> 안정적이다.
※ 코드로만 작성해야한다.
- Unity Event
: Inspector 상에서 바인딩하여 사용 가능하다
- UnityEvent 추가 - AddListener(함수명);
- UnityEvent 삭제 - RemoveListener(함수명); --> 전체 삭제 : RemoveAllListener();
Delegate
: 함수를 참조하여 실행하는 대리자
- 장점
- 여러 함수를 엮어서(체인) 쓸 수 있다. --> 여러 명에서 협업할 때 각자 함수를 유연하게 넣었다 뺄 수 있다.
- 모듈화된 코드를 작성할 수 있다.
익명 함수와 Lambda
: 가상의 함수를 만들어서 사용하는 방법
>> 'Game Math' Scene에서 Turret 생성 Button에 대해 Delegate 활용
: BoardMatrix.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class BoardMatrix : MonoBehaviour
{
public GameObject tilePrefab;
public Vector2 boardSize = new Vector2(5, 5);
public int[,] tileArray;
public GameObject turretPrefab;
public GameObject[] turrets;
public Button[] buttons; // 버튼 5개 받기
private void Awake()
{
// 버튼 5개 세팅
buttons[0].onClick.AddListener(() => OnChangeTurret(0));
buttons[1].onClick.AddListener(() => OnChangeTurret(1));
buttons[2].onClick.AddListener(() => OnChangeTurret(2));
buttons[3].onClick.AddListener(() => OnChangeTurret(3));
buttons[4].onClick.AddListener(() => OnChangeTurret(4));
}
private void Start()
{
tileArray = new int[(int)boardSize.x, (int)boardSize.y];
for (int x = 0; x < boardSize.x; x++)
{
for (int z = 0; z < boardSize.y; z++)
{
GameObject tileObj = Instantiate(tilePrefab);
tileObj.transform.position = new Vector3(x, 0, z);
// tileArray[x, z] = 1;
}
}
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
int x = Mathf.RoundToInt(hit.collider.transform.position.x);
int z = Mathf.RoundToInt(hit.collider.transform.position.z);
if (tileArray[x, z] == 0)
{
GameObject turretObj = Instantiate(turretPrefab);
turretObj.transform.position = new Vector3(x, 0, z);
tileArray[x, z] = 1;
}
}
}
}
public void OnChangeTurret(int index)
{
turretPrefab = turrets[index];
}
}
Closure 이슈
: 외부 함수의 변수를 내부 함수가 참조할 때 발생
--> 캐싱
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 61일차 (0) | 2025.02.25 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 60일차 (0) | 2025.02.21 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 58일차 (0) | 2025.02.19 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 57일차 (0) | 2025.02.18 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 56일차 (0) | 2025.02.17 |