오늘은 모두의 마블 주사위 던지기 시스템을 만들어봤다. 2학기 엔진 응용 팀 프로젝트가 주사위 주제의 게임이라서 만들어 보았다.
1. 원 설정하기
일단, 삼각함수를 이용해서 구현하므로 호의 바깥 부분 모양의 게이지가 필요하다. 직접 포토샵에서 원을 잘라서 만들었다.
Unity에서 Canvas->Image를 클릭하고 해당 Sprite를 넣어준다. Preserve Aspect도 활성화해 이미지의 비율이 깨지지 않도록 한다. RayCast Target 또한 필요하지 않을 것 같으므로 비활성화해준다.
여기서 중요한 게, Pivot을 Center 말고 게이지 중간의 아래쪽으로 선택해야 한다. 이는 나중에 중심점과의 길이를 구하기 위해서이다.
Pivot으로 설정해서 파란 원형 테두리인 Pivot을 옮기자. Pivot을 옮기는 이유는 나중에 center와의 반지름과 각도를 한번에 구할 것이기 때문이다.
그리고 방금 만든 게이지의 자식으로 원형 이미지를 하나 생성해줄 것이다.
원형 이미지는 빨간색의 반투명하게 한다. 원형 이미지의 크기 값과 위치를 조정해 게이지 아래 부분과 원형이 맞물리도록 해야 한다.
이렇게 하는 이유는 원의 중심을 구하기 위해서이다.
이렇게 생각하면 쉽다. 저 게이지는 호의 바깥 부분이므로 호를 포함하는 원을 그려준다. 그렇게 되면 호의 중심은 방금 생성한 원의 중심이 되는 것이다!
크기를 맞추어줬다면 Image 컴포넌트는 꺼준다. 빨간색 불투명한 원을 볼 필요는 없으니까 말이다.
2. 눈금 그리기
이제 삼각함수를 이요해서 눈금을 그려주겠다. 눈금을 그려주기 위해 할 작업이 많다...ㅎㅎ
일단 최대 각도를 구해야 한다. 무슨 뜻이냐면...
이 노란 각도를 구해야하는 것이다. 각도를 구하는 것은 쉽다! 왜냐하면 아까 오른쪽 끝 부분에 게이지의 pivot을 설정해주어서 Atan2 함수로 중심과의 각을 구하면 되기 때문이다!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiceGague : MonoBehaviour
{
private readonly int GRADE = 12;
[SerializeField] private Transform center; // 원의 중심
[SerializeField] private GameObject graduation; // 눈금 프리팹
public float radius; // 원의 반지름
private float angle; // 똑딱거릴 최대 각도
private void Start()
{
radius = Vector2.Distance(transform.position, center.position);
Vector3 direction = transform.position - center.position;
angle = 90f - Mathf.Atan2(direction.y, direction.x);
}
}
이렇게 radius와 angle을 쉽게 구해줄 수 있다.
각도를 구할 때 90을 뺀 이유는 Atan2로 각도를 구하면 우리가 구하고자하는 부분이 아닌, 바깥쪽 부분이 구해지기 때문이다. 따라서 90도를 빼면 우리가 구하고자하는 값이 나온다.
스크립트는 게이지 오브젝트에 붙여주어야 한다! 그럼 눈금으로 쓰일 오브젝트를 만들고 오자.
게이지의 높이에 딱 맞게 만들어준다. 취향껏 가로 세로를 그려주고 Active를 비활성화해줄 것이다. 프리팹으로 넣어도 된다.
다 완성한 코드라서 인스펙터에서는 이렇게 뜨지만, 지금 상태로는 Center는 게이지의 자식 (아까 만들어주었던 빨간 원)을, Graduation에는 방금 만든 눈금을 넣어준다.
그러면 눈금 생성 코드를 작성해보자.
private void Start()
{
radius = Vector2.Distance(transform.position, center.position);
Vector3 direction = transform.position - center.position;
angle = 90f - Mathf.Atan2(direction.y, direction.x);
// 눈금 생성
for (int i = 1; i < GRADE; i++)
{
GameObject newObj = Instantiate(graduation, transform);
newObj.transform.position = GetPos(-angle + (angle * 2 / GRADE) * i);
newObj.transform.up = GetDirection(-angle + (angle * 2 / GRADE) * i));
newObj.SetActive(true);
}
}
반복문으로 추가해주었다. 12개의 공간으로 나누어져있다면, 눈금은 11개이기 때문에 GRADE-1번 눈금이 생성된다.
-curAngle, 즉 게이지의 가장 왼쪽부터 시작한다.
전체 angle(angle*2)를 GRADE로 나눈 값에 i를 순차적으로 곱해준다.
만약 angle이 40이고 GRADE가 4라고 친다면...
case 1
angle = -40 + 80/4 * 1 = -20
case 2
angle = -40 + 80/4 * 2 = 0
case 3
angle = -40 + 80/4 * 3 = 20
세 각도가 모두 구해지기 때문이다. 쉽게 말해 -curAngle부터 시작히 angle*2/GRADE를 순차적으로 곱한 값을 더하는 것.
실행해보면 잘 나오는 것을 볼 수 있다.
3. 움직이기
움직이기는 지금까지 구한 값을 바탕으로 간단하게 구현할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiceGague : MonoBehaviour
{
private readonly int GRADE = 12;
[SerializeField] private Transform center; // 원의 중심
[SerializeField] private float speed = 3f; // 똑딱 스피드
[SerializeField] private Transform visual; // 똑딱거리는 오브젝트
[SerializeField] private GameObject graduation; // 눈금 프리팹
private bool isLeftMove; // 현재 왼쪽으로 가고있는지 아닌지
private float curAngle; // 현재 각도
public float radius; // 원의 반지름
private float angle; // 똑딱거릴 최대 각도
}
몇 개의 변수들을 더 추가해준다.
일단 똑딱거릴 오브젝트를 만들어보자.
간단하게 Capsule 스프라이트로 만들어주었다. 이때 중요한 게 Pivot을 Capsule의 아래로 만들어주어야 한다! 그래야 돌아가는 기본 구조를 게이지의 아래쪽으로 만들어주었기 때문이다.
게이지의 Trasnform에 캡슐을 집어넣자!
이제 코드를 써보자.
// 현재 angle의 방향
private Vector3 GetDirection(float angle)
{
Vector3 direction = Vector3.zero;
direction.x = Mathf.Sin(angle * Mathf.Deg2Rad);
direction.y = Mathf.Cos(angle * Mathf.Deg2Rad);
return direction;
}
일단 돌아갈 때 -angle부터 angle까지 Time.deltaTime을 이용해서 증가와 감소를 번갈아 시킬 것이다. 그러므로 현재 각도로 방향을 구하게 할 것이다.
삼각함수를 이용해서 구했다. angle의 라디안 값의 sin을 x, cos을 y로하는 방향 벡터를 만들어주면 된다.
그러면 angle로 똑딱거릴 위치도 구할 수 있지 않을까?
// 현재 angle의 위치
private Vector3 GetPos(float angle)
{
return center.position + GetDirection(angle) * radius;
}
방향이 있으므로 생각보다 쉽게 구할 수 있다!
이제 -angle부터 angle까지 번갈아가면서 감소, 증가를 하는 똑딱이도 구현할 수 있다.
private void Update()
{
curAngle += Time.deltaTime * speed;
// 각 끝에 도달했을 때 방향을 바꾼다
if (curAngle > angle || curAngle < -angle)
{
ChangeDirection(!isLeftMove);
}
visual.position = GetPos(curAngle);
visual.up = GetDirection(curAngle);
}
현재 각도가 원래 각도보다 커졌거나, -원래 각도보다 작아졌을 때 방향을 바꾸는 함수를 호출한다.
visual은 똑딱이 오브젝트인데, 방금 만들어준 curAngle일 때 GetPos를 호출한 걸 위치로 써주면 된다. visual은 up vector는 curAngle일 때의 방향과 같으므로, 함수를 대입해준다.
방향을 바꾸어보자.
// 현재 방향을 바꾸는 함수
private void ChangeDirection(bool isLeftMove)
{
this.isLeftMove = isLeftMove;
if (isLeftMove)
curAngle = angle;
else
curAngle = -angle;
speed *= -1;
}
IsLeftMove, 왼쪽으로 갈 것이라면 현재 angle이 양수 각도여야 하고, 아니라면 음수 각도부터 다시 시작해야 한다. 이 작업을 해주어야 빠른 speed에서도 안정적으로 작동이 된다.
또한 스피드도 -1을 곱해주어서 방향을 바꿔준다.
왼쪽부터 시작하려면, Start에서 curAngle을 -angle로 바꿔주는 작업을 해야 한다!
4. 단계 판단
이제 Roll 버튼과 몇 단계인지 판단하는 함수를 만들어볼 것이다!
일단 curAngle을 바탕으로 몇 단계인지 판단하는 함수를 만들어보자.
// 등급(단계) 판단 함수
private void CaculateGrade()
{
float rate = (curAngle + angle) / (angle * 2f);
// -angle부터 angle까지 curAngle의 비율
// 1~GRADE까지 단계가 나온다
// 0부터 시작하므로 1을 더해줌
int grade = (int)(rate / (1 / (float)GRADE)) + 1;
Debug.Log($"{grade}단계");
}
함수 자체는 크게 어렵지 않다. rate는 -angle~angle까지 중에 curAngle의 비율을 나타낸 것이다.
아래 그림을 보면 rate를 구하는 코드에 대한 이해도가 올라간다.
grade를 구하는 것 또한 어렵지 않다. 1/GRADE가 rate에 몇 번 들어갈 수 있는지로 생각하면 쉽다. 0번 들어갈 수 있으므로 마지막에 1을 더해주면 된다.
또, 버튼을 누르면 똑딱거리는 게 멈춰야 하므로 플레이 할 수 있음을 나타내는 bool 변수를 만들어준다.
private bool isPlaying = true;
public bool IsPlaying
{
get => isPlaying;
set
{
if (value)
{
// 초기화하며 시작
curAngle = -angle;
}
else
{
CaculateGrade();
}
isPlaying = value;
}
}
IsPlaying을 false로 바꿨을 경우 등급을 계산해주고, 아니라면 curAngle을 -angle로 초기화하여 왼쪽부터 시작하도록 해준다.
버튼을 만든다! 버튼에 붙일 스크립트도 간단하게 만들어준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RollButton : MonoBehaviour
{
private void Start()
{
DiceGague diceGague = FindObjectOfType<DiceGague>();
Button button = GetComponent<Button>();
button.onClick.AddListener(() => diceGague.IsPlaying = false);
}
}
DiceGague를 찾아 누를시 IsPlaying을 false로 만들어주는 스크립트이다.
버튼 오브젝트에 붙여준다.
이제 플레이를 해보면...
2단계가 잘 뜨는 것을 볼 수 있다!!
전체 코드
DiceGague
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiceGague : MonoBehaviour
{
private readonly int GRADE = 12;
[SerializeField] private Transform center; // 원의 중심
[SerializeField] private float speed = 3f; // 똑딱 스피드
[SerializeField] private Transform visual; // 똑딱거리는 오브젝트
[SerializeField] private GameObject graduation; // 눈금 프리팹
private bool isLeftMove; // 현재 왼쪽으로 가고있는지 아닌지
private float curAngle; // 현재 각도
public float radius; // 원의 반지름
private float angle; // 똑딱거릴 최대 각도
private bool isPlaying = true;
public bool IsPlaying
{
get => isPlaying;
set
{
if (value)
{
// 초기화하며 시작
curAngle = -angle;
}
else
{
CaculateGrade();
}
isPlaying = value;
}
}
private void Start()
{
radius = Vector2.Distance(transform.position, center.position);
Vector3 direction = transform.position - center.position;
angle = 90f - Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
// 눈금 생성
for (int i = 1; i < GRADE; i++)
{
GameObject newObj = Instantiate(graduation, transform);
newObj.transform.position = GetPos(-angle + (angle * 2 / GRADE) * i);
newObj.transform.up = GetDirection(-angle + (angle * 2 / GRADE) * i);
newObj.SetActive(true);
}
IsPlaying = true;
}
private void Update()
{
if (IsPlaying)
{
curAngle += Time.deltaTime * speed;
// 각 끝에 도달했을 때 방향을 바꾼다
if (curAngle > angle || curAngle < -angle)
{
ChangeDirection(!isLeftMove);
}
visual.position = GetPos(curAngle);
visual.up = GetDirection(curAngle);
}
}
// 현재 방향을 바꾸는 함수
private void ChangeDirection(bool isLeftMove)
{
this.isLeftMove = isLeftMove;
if (isLeftMove)
curAngle = angle;
else
curAngle = -angle;
speed *= -1;
}
// 현재 angle의 방향
private Vector3 GetDirection(float angle)
{
Vector3 direction = Vector3.zero;
direction.x = Mathf.Sin(angle * Mathf.Deg2Rad);
direction.y = Mathf.Cos(angle * Mathf.Deg2Rad);
return direction;
}
// 현재 angle의 위치
private Vector3 GetPos(float angle)
{
return center.position + GetDirection(angle) * radius;
}
// 등급(단계) 판단 함수
private void CaculateGrade()
{
float rate = (curAngle + angle) / (angle * 2f);
// -angle부터 angle까지 curAngle의 비율
// 1~GRADE까지 단계가 나온다
// 0부터 시작하므로 1을 더해줌
int grade = (int)(rate / (1 / (float)GRADE)) + 1;
Debug.Log($"{grade}단계");
}
}
RollButton
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RollButton : MonoBehaviour
{
private void Start()
{
DiceGague diceGague = FindObjectOfType<DiceGague>();
Button button = GetComponent<Button>();
button.onClick.AddListener(() => diceGague.IsPlaying = false);
}
}
이렇게 주사위 던지기 게이지를 만들어 보았다.
만들면서 삼각함수에 대한 개념과 기초 수학 능력이 향상된 것 같다. 이걸로 재밌는 게임을 만들어봐야지!
'Unity' 카테고리의 다른 글
[Unity][최적화] 졸업작품 빌드 용량 줄이기 (0) | 2023.05.21 |
---|---|
[Unity] Scriptable Object를 JSON으로 저장할 때의 문제점 (0) | 2022.12.26 |
[Unity][C#] 유니티 - 스프레드 시트 연동하기 (1) | 2022.11.05 |