언어/유니티

6-2 룰렛 만들기(소스)

깡 딱 2023. 3. 18. 22:35
728x90

Roulette 이라는 코드 한개 만들어주고 

using System.Collections;
using UnityEngine.Events;
using UnityEngine;

public class Roulette : MonoBehaviour
{
	[SerializeField]
	private	Transform			piecePrefab;				// 룰렛에 표시되는 정보 프리팹
	[SerializeField]
	private	Transform			linePrefab;					// 정보들을 구분하는 선 프리팹
	[SerializeField]
	private	Transform			pieceParent;				// 정보들이 배치되는 부모 Transform
	[SerializeField]
	private	Transform			lineParent;					// 선들이 배치되는 부모 Transform
	[SerializeField]
	private	RoulettePieceData[]	roulettePieceData;			// 룰렛에 표시되는 정보 배열

	[SerializeField]
	private	int					spinDuration;				// 회전 시간
	[SerializeField]
	private	Transform			spinningRoulette;			// 실제 회전하는 회전판 Transfrom
	[SerializeField]
	private	AnimationCurve		spinningCurve;				// 회전 속도 제어를 위한 그래프

	private	float				pieceAngle;					// 정보 하나가 배치되는 각도
	private	float				halfPieceAngle;				// 정보 하나가 배치되는 각도의 절반 크기
	private	float				halfPieceAngleWithPaddings;	// 선의 굵기를 고려한 Padding이 포함된 절반 크기
	
	private	int					accumulatedWeight;			// 가중치 계산을 위한 변수
	private	bool				isSpinning = false;			// 현재 회전중인지
	private	int					selectedIndex = 0;			// 룰렛에서 선택된 아이템

	private void Awake()
	{
		pieceAngle					= 360 / roulettePieceData.Length;
		halfPieceAngle				= pieceAngle * 0.5f;
		halfPieceAngleWithPaddings	= halfPieceAngle - (halfPieceAngle * 0.25f);

		SpawnPiecesAndLines();
		CalculateWeightsAndIndices();

		// Debug..
		//Debug.Log($"Index : {GetRandomIndex()}");
	}

	private void SpawnPiecesAndLines()
	{
		for ( int i = 0; i < roulettePieceData.Length; ++ i )
		{
			Transform piece = Instantiate(piecePrefab, pieceParent.position, Quaternion.identity, pieceParent);
			// 생성한 룰렛 조각의 정보 설정 (아이콘, 설명)
			piece.GetComponent<RoulettePiece>().Setup(roulettePieceData[i]);
			// 생성한 룰렛 조각 회전
			piece.RotateAround(pieceParent.position, Vector3.back, (pieceAngle * i));

			Transform line = Instantiate(linePrefab, lineParent.position, Quaternion.identity, lineParent);
			// 생성한 선 회전 (룰렛 조각 사이를 구분하는 용도)
			line.RotateAround(lineParent.position, Vector3.back, (pieceAngle * i) + halfPieceAngle);
		}
	}

	private void CalculateWeightsAndIndices()
	{
		for ( int i = 0; i < roulettePieceData.Length; ++ i )
		{
			roulettePieceData[i].index = i;

			// 예외처리. 혹시라도 chance값이 0 이하이면 1로 설정
			if ( roulettePieceData[i].chance <= 0 )
			{
				roulettePieceData[i].chance = 1;
			}

			accumulatedWeight += roulettePieceData[i].chance;
			roulettePieceData[i].weight = accumulatedWeight;

			Debug.Log($"({roulettePieceData[i].index}){roulettePieceData[i].description}:{roulettePieceData[i].weight}");
		}
	}

	private int GetRandomIndex()
	{
		int weight = Random.Range(0, accumulatedWeight);

		for ( int i = 0; i < roulettePieceData.Length; ++ i )
		{
			if ( roulettePieceData[i].weight > weight )
			{
				return i;
			}
		}

		return 0;
	}

	public void Spin(UnityAction<RoulettePieceData> action=null)
	{
		if ( isSpinning == true ) return;

		// 룰렛의 결과 값 선택
		selectedIndex = GetRandomIndex();
		// 선택된 결과의 중심 각도
		float angle			= pieceAngle * selectedIndex;
		// 정확히 중심이 아닌 결과 값 범위 안의 임의의 각도 선택
		float leftOffset	= (angle - halfPieceAngleWithPaddings) % 360;
		float rightOffset	= (angle + halfPieceAngleWithPaddings) % 360;
		float randomAngle	= Random.Range(leftOffset, rightOffset);

		// 목표 각도(targetAngle) = 결과 각도 + 360 * 회전 시간 * 회전 속도
		int	  rotateSpeed	= 2;
		float targetAngle	= (randomAngle + 360 * spinDuration * rotateSpeed);

		Debug.Log($"SelectedIndex:{selectedIndex}, Angle:{angle}");
		Debug.Log($"left/right/random:{leftOffset}/{rightOffset}/{randomAngle}");
		Debug.Log($"targetAngle:{targetAngle}");

		isSpinning = true;
		StartCoroutine(OnSpin(targetAngle, action));
	}

	private IEnumerator OnSpin(float end, UnityAction<RoulettePieceData> action)
	{
		float current = 0;
		float percent = 0;

		while ( percent < 1 )
		{
			current += Time.deltaTime;
			percent = current / spinDuration;

			float z = Mathf.Lerp(0, end, spinningCurve.Evaluate(percent));
			spinningRoulette.rotation = Quaternion.Euler(0, 0, z);

			yield return null;
		}

		isSpinning = false;

		if ( action != null ) action.Invoke(roulettePieceData[selectedIndex]);
	}
}

 

스크립트 하나 집어넣고 

 

차례 대로 piece prefabs 넣어주고 다음 Line prefabs 넣어준다.

그리고 

 

남은 밑에 두개는 이거 넣어준다.

 

그리고 사진넣어주고 

밑에 5설정 후 spinningRoulette 넣어주고 

밑에 선들 두개 추가하고 위치

0 0 // 0.3,0.1  // 0.7,0.9  // 1 1

 

그리고 버튼하나 추가

버튼 위치 추가해주고

 

빈 오브젝트 하나 추가해주고 이름을 Demo로 바꿔주고 Demo 스크립트를 넣어준다.

using UnityEngine;
using UnityEngine.UI;

public class Demo : MonoBehaviour
{
	[SerializeField]
	private	Roulette	roulette;
	[SerializeField]
	private	Button		buttonSpin;

	private void Awake()
	{
		buttonSpin.onClick.AddListener(()=>
		{
			buttonSpin.interactable = false;
			roulette.Spin(EndOfSpin);
		});
	}

	private void EndOfSpin(RoulettePieceData selectedData)
	{
		buttonSpin.interactable = true;

		Debug.Log($"{selectedData.index}:{selectedData.description}");
	}
}

데모에 스크립트와 버튼을 연결시켜주고 

실행시키면 잘돌아간다.

728x90