Unity实用插件篇 ✨ | 游戏中的求概率插件WeightedRandomization加权随机化算法
创始人
2024-06-03 21:09:55
0

请添加图片描述

  • 🎬 博客主页:https://xiaoy.blog.csdn.net

  • 🎥 本文由 呆呆敲代码的小Y 原创 🙉

  • 🎄 学习专栏推荐:Unity系统学习专栏

  • 🌲 游戏制作专栏推荐:游戏制作

  • 🌲Unity实战100例专栏推荐:Unity 实战100例 教程

  • 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

  • 📆 未来很长,值得我们全力奔赴更美好的生活✨

  • ------------------❤️分割线❤️-------------------------


        • 📢 前言
    • 🎬 Unity实用插件篇| 游戏中的求概率插件WeightedRandomization
      • 一、概率插件介绍
      • 二、使用方法介绍
      • 三、插件核心代码
    • 总结

请添加图片描述


📢 前言

  • 概率 在游戏中可以说是最玄学的东西了,只要涉及到游戏,基本上就跟概率是离不开关系的。
  • 例如游戏中抽卡、开宝箱、抽奖等等玩法,说到底就是使用 概率 在操控。
  • 比如原神中的祈愿,十连出4星,90发小保底,180发大保底都是在原有概率的基础上增加了一些可控的因素让玩家欲罢不能。
  • 游戏中对概率的定义方法也有很多种,主要看开发者想怎样操控这个概率从而使用不同的方法。
  • 本文就来介绍一个插件WeightedRandomization,功能比较单一,非常轻量,专门用来求概率的时候使用。

对于游戏概率的介绍,之前有一篇文章讲过 游戏概率常用模型算法整理,感兴趣的小伙伴可以去看看哦


🎬 Unity实用插件篇| 游戏中的求概率插件WeightedRandomization

在这里插入图片描述

一、概率插件介绍

WeightedRandomization:简单加权随机化算法的实现

   加权随机化 是当您想要呈现多个值时,它们之间的几率不同。 例如,考虑值 A、B 和 C。如果您决定需要这 3 个值之一,但您希望 A 出现 20% 的时间,B 40% 和 C 60%,那将是加权随机化。 每个值的几率可能不同,并且增加到 100%。 这些类将为您提供定义和实现您自己的加权随机化的工具。 我自己使用它来为 RPG 中的敌人类型创建模板,并根据模板定义的权重为统计数据分配点数。

  简单地使用值类型作为通用参数初始化一个 WeightedRandomizer 实例。 使用您想要的值和您希望该值出现的几率调用 AddWeight。 对于您添加的每个值,几率可以是您想要的 0 到 1 之间的任何值,但在您尝试获取值之前,提供的所有权重的总和必须加起来为 1,以便保证有一个值出现背部。 添加所有权重后,使用 GetNext 方法获取下一个值。

插件本身由简单的几个脚本构成,在上面新加了一个可以配置权重的功能(初始版本只能配置概率,且概率和需为1)

在这里插入图片描述

核心方法如下:

  1. AddOrUpdateWeight():负责将概率及概率对象添加进概率池子中。
  2. AddOrUpdateWeightInt:负责将权重及权重对象添加进概率池子中。
  3. GetNext():从概率池子根据概率返回对应的对象。

二、使用方法介绍

将插件的 .unitypackage 包导入 项目中。

1.首先针对不同的泛型对象配置好对应的概率(使用列表或者字典配置),或者直接在代码中添加对象及概率都可以。

字典结构:
在这里插入图片描述

列表+结构体:
在这里插入图片描述
2.在程序运行时实例化插件,

   //根据概率获取的泛型对象。可以是多种类型,如int/float/GameObject/Component/Script/等等private WeightedRandomizer CurrentObjRandomizer;private void Start(){//实例化插件CurrentObjRandomizer = new WeightedRandomizer();}

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 RandomizerList = new List();
}
#endregionpublic class WeightedRandom : MonoBehaviour
{#region 使用方法一:直接在脚本中配置相关概率[System.Serializable]public struct GameObjectRandomizerData{public GameObject Go;public float Range;}//根据概率获取的泛型对象。可以是多种类型,如int/float/GameObject/Component/Script/等等private WeightedRandomizer CurrentObjRandomizer;[Header("添加获取的对象及概率")]public List RandomizerList = new List();#endregionpublic Button ClickBtn;private void Start(){//1.实例化插件CurrentObjRandomizer = new WeightedRandomizer();//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{private bool adjusted;private float tempWeightIntSum;private List> weights;private List tempWeightIntRecordList;private List tempWeightRecordList;public IRandomizationProvider Provider { get; set; }public WeightedRandomizer(){this.weights = new List>();tempWeightIntRecordList = new List();tempWeightRecordList = new List();}public void AddOrUpdateWeight(T value, float weight){if(weight == 0)throw new ArgumentException("weighted value cannot have a 0% chance.");WeightedChance existing = this.weights.FirstOrDefault(x => Object.Equals(x.Value, value));if (existing == null)            this.weights.Add(new WeightedChance { 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 existing = this.weights.FirstOrDefault(x => Object.Equals(x.Value, value));if (existing == null)this.weights.Add(new WeightedChance { Value = value, WeightInt = weightInt });elseexisting.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 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;elsesorted[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 是一种最直接简单的方法从我们配置的概率中获取概率对象。
  • 插件非常轻量,只有几个脚本构成,在用到概率的时候直接将插件资源包拖入项目中即可一键调用。
  • 后面有机会也想尝试几种复杂的游戏概率模型用来学习使用。
  • 如果有小伙伴有概率模型这方面的独特见解和想法,也欢迎在评论区沟通交流哦~

相关内容

热门资讯

二年级作文不少于200个字【... 二年级作文不少于600个字 篇一我的暑假生活暑假终于来了,我迫不及待地迎接了这个长假。在这个暑假里,...
二年级暑假趣事作文捉老鼠(推... 二年级暑假趣事作文捉老鼠 篇一暑假快到了,我和弟弟决定在家里玩捉老鼠的游戏。我们找来了一些小道具,准...
小学二年级避暑山庄旅游作文(... 小学二年级避暑山庄旅游作文 篇一我和家人去了一个非常有趣的地方——避暑山庄。这个地方真的很美,有很多...
二年级作文游庐山【精简6篇】 二年级作文游庐山 篇一我和爸爸妈妈一起去了庐山。庐山是中国著名的山岳风景区,被誉为“江南第一山”。我...
二年级下册看图写话春天来了作... 二年级下册看图写话春天来了作文 篇一春天来了春天来了,大地变得生机勃勃。图中的小朋友们正在户外玩耍,...
二年级打雪仗作文指导【通用6... 二年级打雪仗作文指导 篇一打雪仗是冬天最有趣的活动之一,对于二年级的小朋友来说,更是一种享受。下面是...
二年级美丽的早晨作文(实用6... 二年级美丽的早晨作文 篇一美丽的早晨早晨的阳光透过窗户洒进来,房间里弥漫着一股清新的味道。我慢慢睁开...
小学二年级海边旅游作文200... 小学二年级海边旅游作文200字作文 篇一我和家人去海边旅游了,真是一个美好的经历!早上,我们一大早就...
二年级【优秀6篇】 二年级 篇一:我的暑假生活暑假终于来了,我迫不及待地开始了我的暑假生活。在这个悠长的假期里,我过得非...
赏荷花二年级作文【通用6篇】 赏荷花二年级作文 篇一欣赏荷花的美丽今天,我和爸爸妈妈一起去公园赏荷花。公园里有一个大大的荷花池,里...
舞蹈汇演作文二年级【精彩6篇... 舞蹈汇演作文二年级 篇一舞蹈汇演是一场精彩绝伦的表演,让我感受到了舞蹈的魅力和美妙。我在二年级的时候...
二年级作文我家的厨师(优质6... 二年级作文我家的厨师 篇一我家的厨师是我妈妈。她是一个非常厉害的厨师,每天都能给我们做出美味可口的饭...
童年趣事作文:枕头大战【精选... 童年趣事作文:枕头大战 篇一小时候的我总是充满了无尽的精力和好奇心,每天都在探索世界的各个角落。而最...
家乡的菊花作文二年级(经典6... 家乡的菊花作文二年级 篇一家乡的菊花我家乡是一个美丽的小镇,四季如春,花草繁盛。其中,最引人注目的要...
二年级小作文28篇【精简3篇... 二年级小作文28篇 篇一我最喜欢的动物我最喜欢的动物是猫。猫咪有软软的毛,尤其是它们的小脸上,摸起来...
二年级写我的家乡【优选6篇】 二年级写我的家乡 篇一我的家乡是一个美丽的小城镇。它位于一个宽广的山谷之中,四周环绕着青翠欲滴的群山...
春雨小学二年级作文300字【... 春雨小学二年级作文300字 篇一:我的偶像我的偶像是我的爸爸。他是一个非常勤劳、聪明和有责任心的人。...
秋节作文【实用6篇】 秋节作文 篇一:传统与现代的秋节庆祝方式秋节是中国传统的重要节日之一,也是我最喜欢的节日之一。在这个...
鱼儿的作文二年级(优秀6篇) 鱼儿的作文二年级 篇一我喜欢鱼儿我是一条小小鱼儿,生活在清澈见底的小溪里。我有一个漂亮的鱼尾巴,闪闪...
二年级语文下册四字词语知识(... 二年级语文下册四字词语知识 篇一在二年级的语文下册中,学生将会接触到一些四字词语的知识。这些四字词语...