博客主页:https://xiaoy.blog.csdn.net
本文由 呆呆敲代码的小Y 原创
学习专栏推荐:Unity系统学习专栏
游戏制作专栏推荐:游戏制作
Unity实战100例专栏推荐:Unity 实战100例 教程
欢迎点赞 收藏 ⭐留言 如有错误敬请指正!
未来很长,值得我们全力奔赴更美好的生活✨
------------------❤️分割线❤️-------------------------
概率
在游戏中可以说是最玄学的东西了,只要涉及到游戏,基本上就跟概率是离不开关系的。WeightedRandomization
,功能比较单一,非常轻量,专门用来求概率的时候使用。对于游戏概率的介绍,之前有一篇文章讲过 游戏概率常用模型算法整理,感兴趣的小伙伴可以去看看哦
WeightedRandomization:简单加权随机化算法的实现
加权随机化 是当您想要呈现多个值时,它们之间的几率不同。 例如,考虑值 A、B 和 C。如果您决定需要这 3 个值之一,但您希望 A 出现 20% 的时间,B 40% 和 C 60%,那将是加权随机化。 每个值的几率可能不同,并且增加到 100%。 这些类将为您提供定义和实现您自己的加权随机化的工具。 我自己使用它来为 RPG 中的敌人类型创建模板,并根据模板定义的权重为统计数据分配点数。
简单地使用值类型作为通用参数初始化一个 WeightedRandomizer 实例。 使用您想要的值和您希望该值出现的几率调用 AddWeight。 对于您添加的每个值,几率可以是您想要的 0 到 1 之间的任何值,但在您尝试获取值之前,提供的所有权重的总和必须加起来为 1,以便保证有一个值出现背部。 添加所有权重后,使用 GetNext 方法获取下一个值。
插件本身由简单的几个脚本构成,在上面新加了一个可以配置权重的功能(初始版本只能配置概率,且概率和需为1)
核心方法如下:
AddOrUpdateWeight()
:负责将概率及概率对象添加进概率池子中。AddOrUpdateWeightInt
:负责将权重及权重对象添加进概率池子中。GetNext()
:从概率池子根据概率返回对应的对象。将插件的 .unitypackage 包导入 项目中。
1.首先针对不同的泛型对象配置好对应的概率(使用列表或者字典配置),或者直接在代码中添加对象及概率都可以。
//根据概率获取的泛型对象。可以是多种类型,如int/float/GameObject/Component/Script/等等
private WeightedRandomizer<GameObject> CurrentObjRandomizer;
private void Start()
{
//实例化插件
CurrentObjRandomizer = new WeightedRandomizer<GameObject>();
}
3.遍历配置的概率及概率对象,将其添加到WeightedRandomizer中。
此处也可以直接使用 AddOrUpdateWeight
代码添加概率及概率对象,省去第一步在Unity的监视器面板配置概率的步骤。
不过第一步的好处是可以在面板中可视化修改概率及概率对应的对象,体验更友好一些。
//将配置的概率添加到WeightedRandomizer中
foreach (var w in RandomizerList)
{
CurrentObjRandomizer.AddOrUpdateWeight(w.Go, w.Range);
}
4.然后在代码中需要使用这个概率的时候调用API:WeightedRandomizer.GetNext()
即可从配置的对象中根据概率抽取并返回该对象。
GameObject item = CurrentObjRandomizer.GetNext();
Debug.Log("根据概率获取的对象:"+item);
优点:可以直观明了的看到各个对象的概率,简单直观。
缺点:配置的各个概率对象 它们的概率和必须为1,也就是说我们想改动某个对象的获取概率时必须要同时改动另外的概率,否则概率和就不为1了。
若是想改为配置权重,则只需要将上述第三步的 AddOrUpdateWeight()
方法改为 AddOrUpdateWeightInt()
方法就好啦。
//将配置的权重添加到WeightedRandomizer中
foreach (var w in RandomizerList)
{
CurrentObjRandomizer.AddOrUpdateWeightInt(w.Go, w.Range);
}
然后在面板中配置格子对象的权重,记得将Value改为int属性即可。
优点是不需要在考虑概率和是否为1的限制,配置权重时可以根据实际情况随心所欲,更改某个权重时,无需同步修改其他权重就可生效。
下面具体演示了插件的使用方法,一种是使用ScriptableObject保存我们的概率及概率对象。另一种是直接在类中配置,直接调用。
使用ScriptableObject的好处是我们可以在任何在有需要使用到此概率获取的时候拿到概率对应的SO,直接使用SO的数据获取即可,SO就相当于一个保存数据的载体。
插件下载地址:https://download.csdn.net/download/zhangay1998/87426511
插件使用示例脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using WeightedRandomization;
#region 使用方法二:用ScriptableObject配置概率相关信息,使用该概率时进行获取
[SerializeField, CreateAssetMenu(fileName = "Weighted", menuName = "Model/WeightedSO"),]
public class WeightedSO : ScriptableObject
{
public enum WeihtedTestEnum
{
None,
Good,
Bad,
General,
}
[System.Serializable]
public struct GameObjectRandomizerData
{
public WeihtedTestEnum Go;
public float Range;
}
[Header("添加获取的对象及概率")]
public List<GameObjectRandomizerData> RandomizerList = new List<GameObjectRandomizerData>();
}
#endregion
public class WeightedRandom : MonoBehaviour
{
#region 使用方法一:直接在脚本中配置相关概率
[System.Serializable]
public struct GameObjectRandomizerData
{
public GameObject Go;
public float Range;
}
//根据概率获取的泛型对象。可以是多种类型,如int/float/GameObject/Component/Script/等等
private WeightedRandomizer<GameObject> CurrentObjRandomizer;
[Header("添加获取的对象及概率")]
public List<GameObjectRandomizerData> RandomizerList = new List<GameObjectRandomizerData>();
#endregion
public Button ClickBtn;
private void Start()
{
//1.实例化插件
CurrentObjRandomizer = new WeightedRandomizer<GameObject>();
//2.将配置的概率添加到WeightedRandomizer中
foreach (var w in RandomizerList)
{
CurrentObjRandomizer.AddOrUpdateWeight(w.Go, w.Range);
}
//Button按钮点击测试获取对象概率
ClickBtn.onClick.AddListener(()=>
{
//3.调用GetNext()从配置的概率中获取对象
GameObject item = CurrentObjRandomizer.GetNext();
Debug.Log("获取对象:" + item);
});
}
}
插件核心脚本如下:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace WeightedRandomization
{
public class WeightedRandomizer<T>
{
private bool adjusted;
private float tempWeightIntSum;
private List<WeightedChance<T>> weights;
private List<int> tempWeightIntRecordList;
private List<float> tempWeightRecordList;
public IRandomizationProvider Provider { get; set; }
public WeightedRandomizer()
{
this.weights = new List<WeightedChance<T>>();
tempWeightIntRecordList = new List<int>();
tempWeightRecordList = new List<float>();
}
public void AddOrUpdateWeight(T value, float weight)
{
if(weight == 0)
throw new ArgumentException("weighted value cannot have a 0% chance.");
WeightedChance<T> existing = this.weights.FirstOrDefault(x => Object.Equals(x.Value, value));
if (existing == null)
this.weights.Add(new WeightedChance<T> { Value = value, Weight = weight });
else
existing.Weight = weight;
this.adjusted = false;
}
public void AddOrUpdateWeightInt(T value, int weightInt)
{
if (weightInt == 0)
throw new ArgumentException("weighted value cannot have a 0% chance.");
WeightedChance<T> existing = this.weights.FirstOrDefault(x => Object.Equals(x.Value, value));
if (existing == null)
this.weights.Add(new WeightedChance<T> { Value = value, WeightInt = weightInt });
else
existing.WeightInt = weightInt;
this.adjusted = false;
tempWeightIntSum = 0;
tempWeightIntRecordList.Clear();
tempWeightRecordList.Clear();
for (int i = 0; i < this.weights.Count; i++)
{
tempWeightIntRecordList.Add(weights[i].WeightInt);
tempWeightIntSum += weights[i].WeightInt;
}
for (int i = 0; i < tempWeightIntRecordList.Count; i++)
{
tempWeightRecordList.Add(tempWeightIntRecordList[i] / tempWeightIntSum);
}
for (int i = 0; i < this.weights.Count; i++)
{
weights[i].Weight = tempWeightRecordList[i];
}
AddOrUpdateWeight(value, weights[weights.Count-1].Weight);
}
public void RemoveWeight(T value)
{
WeightedChance<T> existing = this.weights.FirstOrDefault(x => Object.Equals(x.Value, value));
if (existing != null)
{
this.weights.Remove(existing);
this.adjusted = false;
}
}
public void ClearWeights()
{
this.weights.Clear();
this.adjusted = false;
}
///
/// Determines the adjusted weights for all items in the collection. This will be called automatically if GetNext is called after there are changes to the weights collection.
///
public void CalculateAdjustedWeights()
{
var sorted = this.weights.OrderBy(x => x.Weight).ToList();
decimal weightSum = 0;
for (int i = 0; i < sorted.Count(); i++)
{
weightSum += (decimal)sorted[i].Weight;
if (i == 0)
sorted[i].AdjustedWeight = sorted[i].Weight;
else
sorted[i].AdjustedWeight = sorted[i].Weight + sorted[i - 1].AdjustedWeight;
}
if (weightSum != 1.0m)
throw new InvalidOperationException("The weights of all items must add up to 1.0 ");
this.weights = this.weights.OrderBy(x => x.AdjustedWeight).ToList();
this.adjusted = true;
}
///
/// Return a value based on the weights provided.
///
///
public T GetNext()
{
if (this.Provider == null)
this.Provider = UnityRandomizationProvider.Default;
if (!adjusted)
this.CalculateAdjustedWeights();
double d = this.Provider.NextRandomValue();
var item = this.weights.FirstOrDefault(x => d <= x.AdjustedWeight);
return item.Value;
}
}
}
概率插件WeightedRandomization
是一种最直接简单的方法从我们配置的概率中获取概率对象。