Unity开发中常用的洗牌算法

在Unity游戏开发中,洗牌算法(Shuffle Algorithm)是一种常用的技术,主要用于随机化数组或列表元素的顺序。这在卡牌游戏、随机道具生成、敌人出场顺序等场景中非常有用。

基本洗牌算法

1. Fisher-Yates洗牌算法

这是最经典且高效的洗牌算法,时间复杂度为O(n)。

cs 复制代码
// Fisher-Yates洗牌算法
public static void Shuffle<T>(T[] array)
{
    int n = array.Length;
    for (int i = 0; i < n; i++)
    {
        // 在剩余元素中随机选择一个
        int r = i + Random.Range(0, n - i);
        // 交换当前元素和随机选择的元素
        T temp = array[r];
        array[r] = array[i];
        array[i] = temp;
    }
}

Unity特定实现

1. 使用Unity的Random类

cs 复制代码
using UnityEngine;
using System.Collections.Generic;

public class Shuffler : MonoBehaviour
{
    public static void ShuffleArray<T>(T[] array)
    {
        for (int i = array.Length - 1; i > 0; i--)
        {
            int j = Random.Range(0, i + 1);
            T temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
}

2. 洗牌并返回新列表

cs 复制代码
public static List<T> ShuffleList<T>(List<T> inputList)
{
    List<T> shuffledList = new List<T>(inputList);
    int n = shuffledList.Count;
    while (n > 1)
    {
        n--;
        int k = Random.Range(0, n + 1);
        T value = shuffledList[k];
        shuffledList[k] = shuffledList[n];
        shuffledList[n] = value;
    }
    return shuffledList;
}

高级应用

1. 可重复的随机洗牌(使用种子)

cs 复制代码
public static void ShuffleWithSeed<T>(T[] array, int seed)
{
    System.Random rng = new System.Random(seed);
    int n = array.Length;
    while (n > 1)
    {
        n--;
        int k = rng.Next(n + 1);
        T value = array[k];
        array[k] = array[n];
        array[n] = value;
    }
}

2. 权重洗牌算法

当元素有不同的出现概率时:

cs 复制代码
public static T WeightedShuffle<T>(List<T> elements, List<float> weights)
{
    if (elements.Count != weights.Count)
    {
        Debug.LogError("元素和权重数量不匹配");
        return default(T);
    }

    float totalWeight = 0f;
    foreach (float weight in weights)
    {
        totalWeight += weight;
    }

    float randomValue = Random.Range(0f, totalWeight);
    float weightSum = 0f;

    for (int i = 0; i < elements.Count; i++)
    {
        weightSum += weights[i];
        if (randomValue <= weightSum)
        {
            return elements[i];
        }
    }

    return elements[elements.Count - 1];
}
cs 复制代码
public class Deck : MonoBehaviour
{
    public List<Card> cards = new List<Card>();
    
    public void ShuffleDeck()
    {
        for (int i = 0; i < cards.Count; i++)
        {
            int randomIndex = Random.Range(i, cards.Count);
            Card temp = cards[i];
            cards[i] = cards[randomIndex];
            cards[randomIndex] = temp;
        }
    }
    
    public Card DrawCard()
    {
        if (cards.Count == 0) return null;
        
        Card drawnCard = cards[0];
        cards.RemoveAt(0);
        return drawnCard;
    }
}

在游戏开发(特别是卡牌游戏如炉石传说)和计算机科学中,随机性分为真随机(True Random)​伪随机(Pseudo-Random)​两种类型,它们有着本质的区别和应用场景。

真随机(True Random)

基本概念

真随机是指完全不可预测、无模式的随机性,通常来源于物理世界的随机现象。

特点

  • 不可预测性:无法通过任何方法预测下一个结果
  • 无周期性:不会出现重复的模式
  • 来源:通常来自物理现象(如大气噪声、放射性衰变等)

实现方式

cs 复制代码
// 使用系统提供的真随机数生成器(基于硬件熵源)
using System.Security.Cryptography;

public static int TrueRandom(int min, int max)
{
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        byte[] randomNumber = new byte[4];
        rng.GetBytes(randomNumber);
        int value = BitConverter.ToInt32(randomNumber, 0);
        return Math.Abs(value % (max - min)) + min;
    }
}

应用场景

  • 加密安全场景(如生成加密密钥)
  • 高安全性赌博机
  • 科学实验中的随机抽样

伪随机(Pseudo-Random)

基本概念

伪随机是通过确定性算法生成的看似随机的数列,实际上是完全可预测的。

特点

  • 可重现性:使用相同种子会产生相同的随机序列
  • 周期性:经过足够长的序列后会重复
  • 高效性:计算速度快,资源消耗低

实现方式

cs 复制代码
// Unity/C#中的伪随机实现
public static int PseudoRandom(int min, int max)
{
    // System.Random或UnityEngine.Random都是伪随机
    return UnityEngine.Random.Range(min, max); 
    
    // 或者使用特定种子
    System.Random seededRandom = new System.Random(12345);
    return seededRandom.Next(min, max);
}

高级主题:伪随机的"公平性"调整

游戏开发者常对伪随机进行修改以改善玩家体验:

cs 复制代码
// "伪伪随机" - 防止连续倒霉的情况
public class FairRandom
{
    private Dictionary<string, int> outcomeCount = new Dictionary<string, int>();
    
    public bool WeightedRandom(string eventId, float baseProbability)
    {
        if (!outcomeCount.ContainsKey(eventId))
        {
            outcomeCount[eventId] = 0;
        }
        
        // 根据历史次数动态调整概率
        float adjustedProb = baseProbability * (outcomeCount[eventId] + 1);
        bool result = UnityEngine.Random.value < adjustedProb;
        
        if (result)
        {
            outcomeCount[eventId] = 0;
        }
        else
        {
            outcomeCount[eventId]++;
        }
        
        return result;
    }
}

真随机与伪随机的视觉表现差异

在Unity中,两种随机在视觉效果上可能表现出不同模式:

cs 复制代码
// 伪随机点分布(可能可见模式)
void OnDrawGizmos()
{
    System.Random prng = new System.Random(123);
    for (int i = 0; i < 1000; i++)
    {
        float x = (float)prng.NextDouble();
        float y = (float)prng.NextDouble();
        Gizmos.DrawSphere(new Vector3(x, y, 0), 0.01f);
    }
}

// 真随机点分布(更均匀)
void OnDrawGizmos()
{
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        for (int i = 0; i < 1000; i++)
        {
            byte[] bytes = new byte[8];
            rng.GetBytes(bytes);
            double x = BitConverter.ToDouble(bytes, 0) % 1.0;
            double y = BitConverter.ToDouble(bytes, 4) % 1.0;
            Gizmos.DrawSphere(new Vector3((float)x, (float)y, 0), 0.01f);
        }
    }
}

总结

  1. 游戏开发中90%的情况使用伪随机:性能好、可重现、易调试
  2. 真随机用于安全关键领域:如加密、真实赌博游戏
  3. 炉石等在线游戏:使用服务器控制的伪随机保证公平性和同步性
  4. 好的随机设计:不仅要考虑算法,还要考虑玩家心理感受

理解这两种随机性的区别和实现方式,可以帮助开发者根据具体需求做出更合适的选择。

相关推荐
Owen_Q4 分钟前
Leetcode百题斩-二分搜索
算法·leetcode·职场和发展
矢志航天的阿洪25 分钟前
蒙特卡洛树搜索方法实践
算法
UnderTheTime1 小时前
2025 XYD Summer Camp 7.10 筛法
算法
zstar-_1 小时前
Claude code在Windows上的配置流程
笔记·算法·leetcode
圆头猫爹1 小时前
第34次CCF-CSP认证第4题,货物调度
c++·算法·动态规划
27669582921 小时前
tiktok 弹幕 逆向分析
java·python·tiktok·tiktok弹幕·tiktok弹幕逆向分析·a-bogus·x-gnarly
秋说1 小时前
【PTA数据结构 | C语言版】出栈序列的合法性
c语言·数据结构·算法
用户40315986396631 小时前
多窗口事件分发系统
java·算法
用户40315986396632 小时前
ARP 缓存与报文转发模拟
java·算法
小林ixn2 小时前
大一新手小白跟黑马学习的第一个图形化项目:拼图小游戏(java)
java