最近在学习unity的物体移动,刚好某铁玩到了黄金时刻的玩法,想自己试一下实现出来;
废话不多说,先上效果图
玩法介绍
这种玩法一般都是有三列图案组成,转动停止后三列图案一致时即可得到奖励/胜利;
每个策划对这个转动效果的定义都会不一样,有喜欢缓慢停止的,通过缓慢停止塑造一种紧张感;有突然停止回弹的,塑造转动的爽感;视觉效果会影响玩家游戏时的心情,我个人更倾向于后者,更能突出视觉的爽感;
技术实现
思考
1、使用一个总控脚本控制n个Slot单元,给slot单元发出需要转动的位置,控制是否能spin,判断输赢等;
2、使用一个Slot单元控制slot单元转动,只管接收总控的目标值进行移动,不做额外的功能;
总控代码
SlotMachine3.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
public class SlotMachine3 : MonoBehaviour
{
public Slot3[] slots; // slot单元数组
public bool isSpin; // 判断当前是否在转动
public float[] slotResult; // Slot的结果
private int _stoppedSlots; // 记录停止的slot数量
private const string SpinMethodName = nameof(Spin);
public Dictionary<float, string> slotItems; // slot的位置数据
private Dictionary<string, int> _slotBet; // slot的倍率
// 录入数据
private void MapSlotItems()
{
slotItems = new Dictionary<float, string>
{
{ -5.25f, "Diamond" },
{ -3.75f, "Crown" },
{ -2.25f, "Watermelon" },
{ -0.75f, "Bar" },
{ 0.75f, "Seven" },
{ 2.25f, "Cherry" },
{ 3.75f, "Lemon" },
{ 5.25f, "Diamond" }
};
}
private void MapSlotBet()
{
_slotBet = new Dictionary<string, int>
{
{ "Seven", 500 },
{ "Bar", 150 },
{ "Crown", 100 },
{ "Diamond", 50 },
{ "Cherry", 25 },
{ "Watermelon", 10 },
{ "Lemon", 5 }
};
}
/// <summary>
/// 转动spin
/// </summary>
public void Spin()
{
// 如果当前在转动,则跳出函数
if (isSpin) return;
// 生成开奖结果,指导slot转动
GenerateResult();
isSpin = true;
foreach (var slot in slots)
{
// 转动所有slot单元
slot.StartCoroutine(SpinMethodName);
}
}
/// <summary>
/// 等待所有slot停止并检查结果
/// </summary>
public void WaitResults()
{
// 每当一个slot调用则证明停止
_stoppedSlots--;
// 当最后一个停止时,重置这个停止的监控
if (_stoppedSlots > 0) return;
_stoppedSlots = ResetStoppedSlotsCount();
// 调用结果对比方法
CheckResults();
}
/// <summary>
/// 校验数组元素是否一致
/// </summary>
/// <param name="resultNameList">需要校验的结果数组</param>
/// <returns></returns>
private static bool CheckResultIsOnlyOneItem(string[] resultNameList)
{
return resultNameList.Distinct().Count() == 1;
}
/// <summary>
/// 检查结果是否中奖
/// </summary>
private void CheckResults()
{
isSpin = false;
var resultNameList = new string[3];
var numCounter = 0;
foreach (var key in slotResult)
{
resultNameList[numCounter] = slotItems[key];
numCounter++;
}
// 检查数组是否满足元素唯一,唯一即中奖;
if (!CheckResultIsOnlyOneItem(resultNameList)) return;
var bet = _slotBet[resultNameList[0]];
// 这里用倍率代表金币吧
AddMoney(bet * 1);
}
private static void AddMoney(int money)
{
Debug.Log($"赢了 {money} 金币");
}
/// <summary>
/// 生成结果数组
/// </summary>
private void GenerateResult()
{
// 清空数组
slotResult = Array.Empty<float>();
// 随机生成结果
slotResult = slotItems.Keys.OrderBy(x => Random.value).Take(_stoppedSlots).ToArray();
for (var i = 0; i < slotResult.Length; i++)
{
slots[i].targetY = slotResult[i];
}
}
/// <summary>
/// 查看需要监控多少个slot单元
/// </summary>
/// <returns>slot单元数组长度</returns>
private int ResetStoppedSlotsCount()
{
return slots.Length;
}
private void Awake()
{
_stoppedSlots = ResetStoppedSlotsCount();
MapSlotItems();
MapSlotBet();
}
}
unity对象结构
SlotMachine3
Row
缓慢减速效果
效果:期望转出n圈后,平滑减速直到item转动到总控发出的指定位置
Slot2.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// TODO:减速移动到指定位置
public class Slot2 : MonoBehaviour
{
public float speed;
public float targetY;
private SlotMachine2 _sm2;
private const string EndSpinMethodName = nameof(EndSpin);
private const float SpeedDownRate = 0.85f;
private const float StartY = -5.25f;
private const float EndY = 5.25f;
public IEnumerator Spin()
{
speed = 15f + Random.Range(10f, 20f);
while (speed >= 10f)
{
transform.Translate(Vector2.up * Time.deltaTime * -speed);
if (transform.localPosition.y <= StartY)
{
// 出结果之后才开始减速
if (_sm2.slotResult.Length != 0) speed *= SpeedDownRate;
transform.localPosition = new Vector2(transform.localPosition.x, EndY);
}
yield return null;
}
// 调用到这里的时候其实已经是从最初开始,调用End就是移动到指定距离了
StartCoroutine(EndSpinMethodName);
yield return null;
}
public IEnumerator EndSpin()
{
// 如果是转到第一个的diamond,那就让他转动到最后一个,主要是增加转动带来的视觉效果
if (Mathf.Approximately(targetY, StartY)) targetY = EndY;
while (speed >= 5f)
{
var position = transform.localPosition;
speed -= SpeedDownRate * Time.deltaTime;
position = Vector2.MoveTowards(position, new Vector2(position.x, targetY), speed * Time.deltaTime);
transform.localPosition = position;
if (new Vector2(position.x, position.y) == new Vector2(position.x, targetY)) break;
yield return null;
}
speed = 0f;
_sm2.WaitResults();
yield return null;
}
private void RandomItem()
{
var randomY = _sm2.slotItems.Keys.OrderBy(x => Random.value).Take(1);
transform.position = new Vector3(transform.position.x, randomY.Average(), transform.position.z);
}
private void Start()
{
_sm2 = GetComponentInParent<SlotMachine2>();
RandomItem();
}
}
惯性回弹效果
效果:期望速度不变的情况下转动到总控发出的指定位置+偏移值后急停,然后回弹到指定位置;
Slots3.cs
using System.Collections;
using System.Linq;
using UnityEngine;
// TODO: 不减速,回弹效果
public class Slot3 : MonoBehaviour
{
public float targetY;
private SlotMachine3 _sm3;
private float _speed;
private const float OffsetY = 0.8f; // 回弹的偏移值
private const float BounceSpeed = 5f; // 回弹速度
private const int SlotRound = 5; // 转的圈数
private const string EndSpinMethodName = nameof(EndSpin); // 调用的方法名
private const float StartY = 5.25f;
private const float EndY = -5.25f;
public IEnumerator Spin()
{
var spinRound = SlotRound;
_speed = 15f + Random.Range(10f, 20f);
while (spinRound > 0)
{
transform.Translate(Vector2.up * Time.deltaTime * -_speed);
// 超出边界重置
if (transform.localPosition.y <= EndY)
{
// 出结果之后才开始减速
if (_sm3.slotResult.Length == 0) spinRound = SlotRound;
transform.localPosition = new Vector2(transform.localPosition.x, StartY);
spinRound--;
}
yield return null;
}
// 调用到这里的时候其实已经是从最初开始,调用End就是移动到指定距离了
StartCoroutine(EndSpinMethodName);
yield return null;
}
public IEnumerator EndSpin()
{
// 如果是转到第一个的diamond,那就让他转动到最后一个,主要是增加转动带来的视觉效果
if (Mathf.Approximately(targetY, StartY)) targetY = EndY;
Vector2 startPosition = transform.localPosition;
// 按照原来的速度移动到目标后跳出循环
while (transform.localPosition.y >= (targetY - OffsetY))
{
transform.Translate(Vector2.up * Time.deltaTime * -_speed);
yield return null;
}
// 回弹到指定位置后再回到目标位置
while (Vector2.Distance(transform.localPosition, new Vector2(startPosition.x, targetY)) > 0.01f)
{
transform.localPosition = Vector2.MoveTowards(transform.localPosition,
new Vector2(startPosition.x, targetY), BounceSpeed * Time.deltaTime);
yield return null;
}
_speed = 0f;
_sm3.WaitResults();
}
private void RandomItem()
{
var randomY = _sm3.slotItems.Keys.OrderBy(x => Random.value).Take(1);
transform.localPosition = new Vector3(transform.localPosition.x, randomY.Average(), transform.localPosition.z);
}
private void Start()
{
_sm3 = GetComponentInParent<SlotMachine3>();
RandomItem();
}
}
附录
美术资源(科学上网):SlotMachineArt.zip
图案坐标表
倍率表