[游戏开发]Unity随机网格中空位置_二叉树

news2024/11/24 7:27:16

[目录]

  • 0. 前言
  • 1. 简单随机
  • 2. 可用位置内随机
  • 3. 二叉树权重随机
    • (1)分区域随机
    • (2)设置权重均衡概率
    • (3)二叉树缓存权重
    • (4)利用二叉树随机
    • (5)优缺点
  • 4. 测试对比
  • 4. 结束咯

0. 前言

在做个小游戏,需要随机在网格的空位置上生成方块,在随机的时候,感觉简单随机的方式效率很低而且不稳定。就在想有没有比较快的方式能够随机到想要的位置。最后是用二叉树记录下权重并进行随机,很稳定而且效率还不错。所以写个文章记录一下,以供参考。

1. 简单随机

在一个固定的网格中随机一个空位置。比如说在一个20*20的棋盘上,上面有若干个棋子,现在要生成新的棋子在空的位置上。最简单的方式就是在矩形内随机一个坐标,然后判断该位置是否有棋子,没有的话再随机一次,如下。

public Vector2Int RandomPos()
{
    int x, y;
    do
    {
        x = Random.Range(0, bound.width);
        y = Random.Range(0, bound.height);
    }
    while (flagMap[x, y]);
    return new Vector2Int(x, y);
}

这样简单随机容易在空位比较少的时候随机很久才能随机到空位,比如只剩下一个位置,那20*20的格子,就是1/400的概率随机到,那就需要重复很多次了。而且有安全问题,比如现在棋子都下满了没位置生成时调用就会无限循环。

稍微改进方法的是,加一个外额随机次数限制,比如说最多随机个1000次,如果没有,就当没有空位。解决死循环、随机太多次的问题,会跟推荐一点。总的来说:

优点:编写简单,运行较快,特别是地图大,空位多的时候随机非常快。
缺点:空位少时,运行时间长,容易漏掉某个位置随机不到,不稳定

2. 可用位置内随机

除了直接随机坐标的方式,还有另一种简单方法,就是预先计算一下可用位置,然后在可用位置内随机。

public Vector2Int RandomPos()
{
    int w = flagMap.GetLength(0);
    int h = flagMap.GetLength(1);

    // 计算所有可能
    tempPos.Clear();
    for (int i = 0; i < w; i++)
    {
        for (int j = 0; j < h; j++)
        {
            if (!flagMap[i, j])
            {
                tempPos.Add(new Vector2Int(i, j));
            }
        }
    }

    // 随机一个
    if (tempPos.Count <= 0)
    {
        return Vector2Int.zero;
    }
    else
    {
        int index = Random.Range(0, tempPos.Count);
        return tempPos[index];
    }
}

这种方法就是非常稳定,如果有空位毕竟可以随机到,而且不会有脸黑多次随机的问题。带来的额外问题就是,计算并记录所有位置需要额外的开销。

稍微改进方法是,在放置或者移除棋子的时候预先保存可用位置计数n,[0,n)之间随机出m,然后再依次找到第m个空位,即为随机位置。这样就可以减少检查和缓存的数据量。那么,这种方法的话:

优点:稳定,不会遗漏空位。地图小的时候,而且对空位要求严格的时候还是挺好用的。
缺点:效率低,特别是地图大的话,要计算一下所有的位置就比较麻烦了。

3. 二叉树权重随机

终于到了今天要讲的这个方法了,是按照分区域随机的思路想出来的。

(1)分区域随机

比如说我们可以先将网格分成均分成4个部分,然后随机一个位置,这个小区域也可以再划分为4个区域,直到剩下4个位置,就可以在这4个位置中随机一个,如果没有空位了。则重新跳回上一层区域再随机其他位置,如此重复。如下图(8*8)可供参考。
在这里插入图片描述
分层的最主要目的还是为了,在保证能够随机出空位的前提下,减少每次计算的量,不用每次都将地图从头开始判断是否可用。

(2)设置权重均衡概率

那么这个时候,聪明的小伙子就会发现这个方法的问题,概率不随机。比如说,如下图(4*4),我们在随机到左上区域的概率是1/4,但里面只有两个空位,那么这个时候这两个空位就会享受这1/8的概率。而右下的4空位分别只有1/16的概率。
在这里插入图片描述
解决问题的方法,是计算并缓存一下权重,比如说左上只有两个空位,随机时享有2的权重,右下享有4的权重。如何比较好的计算并保存这个权重呢?刚刚随机区域的时候,有没有感觉很像深度优先搜索。这也让我联想到树结构,就打算用二叉树来做这个权重的缓存,而且二叉树有个好处就是非此即彼,每次只要判断一下做或者右即可。

(3)二叉树缓存权重

我们先考虑将这个数组降到1维,因为一维的话就只有左右之分, 更符合二叉树。我们编个号,可以随机的位置权重为1,不可以随机的位置权重为0。如下图。
在这里插入图片描述
那么我们就就可以得到如下的二叉树,蓝色为权重。
在这里插入图片描述

这种情况下,需要缓存的数据也不会太多,毕竟是取对数的,越往上消减得越快,缓存的数据在格子数量的2倍以内。而且为了存储和索引更加简单,我们用可以数组(记为weight)存一下这个二叉树。刚刚举的例子是一个16格的网格,意外(故意)刚好是2的指数倍,如果是3*3共9格,那就需要把后面的10-15权重置零就可以了。

另外是修改权重也会比较简单,每次放置或者移除棋子时,往上更改权重即可,如下。

public void SetEnable(int x, int y, bool enable)
{
    int index = Pos2Index(x, y);
    if (index < 0 &&
        index >= weight[layerCount - 1].Length)
    {
        throw new System.Exception("x,y is out of Length");
    }
    int value = enable ? 1 : 0;
    SetWeight(index, value);
}

private void SetWeight(int index, int value)
{
    int changeValue = value - weight[layerCount - 1][index];
    if (changeValue != 0)
    {
        for (int layer = layerCount - 1; layer >= 0; layer--)
        {
            weight[layer][index] += changeValue;
            index = index >> 1; // =index /2;
            index = index > 0 ? index : 0;
        }
    }
}

private int Pos2Index(int x, int y)
{
    int i = x - gridBound.x;
    int j = y - gridBound.y;
    return i * gridBound.height + j;
}

其中,SetEnable 用于设置改位置是否可以被随机到,SetWeight 用于设置权重,Pos2Index用于转换坐标到一维数组的坐标。

(4)利用二叉树随机

随机的时候。首先是[0,13)内随机一个数,假设是9好了,大于或等于5,所以可以判断是右边部分。对于下一个节点判断是,9 - 5=4,大于或等于4,所以是判断为左边。依次不断进行到网格最底部就可以了,还是挺好理解的。那么代码如下。

public Vector2Int RandomPos()
{
    if (IsEmpty)
    {
        return Vector2Int.zero;
    }
    else
    {
        int index = 0;
        int value = Random.Range(0, weight[0][0]);
        for (int layer = 1; layer < layerCount; layer++)
        {
            if (value >= weight[layer][index])
            {
                value -= weight[layer][index];
                index = (index << 1) + 1;// i*2+1
            }
            else
            {
                index = index << 1;
            }
        }
        return Index2Pos(index);
    }
}

好咯,那么到现在,我们就可以又快又稳定的随机到这个点了。

(5)优缺点

优点:随机稳定,效率高。如果有空位一定能够随机到
缺点:额外内存开销,设置可否随机状态时有额外计算开销。

不适合地图比较大的情况,不过通常要严格随机位置的,应该不会区域太大,围棋棋盘也就19*19,看起来也挺密密麻麻的了。

4. 测试对比

简单测试一下,这个【3.二叉树权重随机】,和【1. 简单随机】比较一下。
在20*20的大小内,有一半已经有棋子的情况下,随机200次的时间开销。

RectInt rect = new RectInt(0, 0, 20, 20);
int repeatTime = 1000;
int setPosCount = 200;
int randomCount = 200;

RandomGrid grid = new RandomGrid(rect);
for (int j = 0; j < setPosCount; j++)
{
    Vector2Int pos = grid.RandomPos();
    grid.SetEnable(pos, false);
}
long time = System.DateTime.Now.Ticks;
for (int i = 0; i < repeatTime; i++)
{
    for (int j = 0; j < randomCount; j++)
    {
        grid.RandomPos();
    }
}
DebugU.Log("time1:" + (System.DateTime.Now.Ticks - time));

RandomGrid2 grid2 = new RandomGrid2(rect);
for (int j = 0; j < setPosCount; j++)
{
    Vector2Int pos = grid2.RandomPos();
    grid2.SetEnable(pos, false);
}
time = System.DateTime.Now.Ticks;
for (int i = 0; i < repeatTime; i++)
{
    for (int j = 0; j < randomCount; j++)
    {
        grid2.RandomPos();
    }
}
DebugU.Log("time2:" + (System.DateTime.Now.Ticks - time));
time1:179407
time2:259427

time1为二叉树权重随机的时间,time1比time2要快挺多,说明二叉树权重随机的方法效率还不错的,下面是网格内有不同数空位情况下,两种花费时间。

// 380个空位 / 95% 为空位 
time1:149498
time2:89699
// 300个空位 / 75% 为空位
time1:179401
time2:149499
// 200个空位 / 50% 为空位
time1:179407
time2:259427
// 100个空位 / 25% 为空位
time1:129564
time2:358803
// 20个空位 / 5% 为空位 
time1:129571
time2:2178037

比较一下,也可以发现二叉树权重随机会更加稳定一点,而且效率也还不错。其实最重要的是能够确保,有空位的时候能够随机到该位置。

4. 结束咯

在实际用二叉树权重随机的方法的时候,有个问题,比如我想要随机出一个3*3的小空位,而不是单独一个点,那么这个时候就没办法直接随机了。另外,比如需要限定点在第一第二行内,这种带范围的随机,也不好处理。

那么这个时候,我是用了一个临时权重TempWeight,原本的权重Weight,需要加上这个值才是最后的权重值。那么在随机的点不是3*3的空位(或者其他条件)时,在该位置TempWeight置为-1,那么和Weight相加为0,即权重为0,不会再随机到。然后我们再重复操作,直到找到符合要求的点。完成之后就可以重置一下TempWeight,不会影响下次操作。当然,如果下次随机的条件也是相同的,那还是可以先暂时保留TempWeight,以提高随机小卢。

ok,那就结束咯。

突发奇想的一个方法,希望能够各位有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/667410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

简要介绍 | Backbone与Baseline的区别

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对Backbone和Baseline进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 Backbone与Baseline的区别&#xff1a;从神经网络到性能基准 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来…

大厂测试员是如何编写测试用例呢?

一、测试用例是软件测试的核心 软件测试的重要性是毋庸置疑的。但如何以最少的人力、资源投入&#xff0c;在最短的时间内完成测试&#xff0c;发现软件系统的缺陷&#xff0c;保证软件的优良品质&#xff0c;则是软件公司探索和追求的目标。每个软件产品或软件开发项目都需要…

【活动总结】0617COC深圳社区首场线下AI技术沙龙活动最强总结

文章目录 活动的发起活动的宣传活动的进行活动的收尾活动的总结活动的致谢更多展望友情链接 就在2023年6月17日&#xff0c;CSDN COC 深圳城市开发者社区&#xff0c;在深圳大学组织了一次以【智能未来 —— 人工智能与城乡规划的交叉对话】为主题的线下沙龙活动&#xff0c;活…

基于spring boot的JsonSerializer 业务内容国际化

说起国际化&#xff0c;真的是老生常谈了。后端有各种i18n的依赖组件&#xff0c;springboot本身也支持i18n的设置&#xff0c;前端vue也有i18n的设置&#xff0c;这些常规操作就不提了&#xff0c;大家可以去搜索其他博客&#xff0c;写的都很详细。 本篇博客主要写的是业务内…

5大技巧,实现视频号预约直播人数暴涨!

两个体量相当的视频号&#xff0c;为什么别人的直播间人数过万&#xff0c;而自己的直播间却寥寥无几&#xff1f;这其中有一个非常重要的原因&#xff0c;就是预约直播的工作没有做好。 通常情况下&#xff0c;视频号直播预约人数和最终场观呈现1:10的比例&#xff0c;换言之…

聊聊Redis中的跳跃表

Redis 大家项目中应该都用过&#xff0c;哪怕没有分布式锁、幂等校验的一些逻辑使用场景&#xff0c;缓存数据这个大家肯定都用过吧&#xff1f;最简单的key-value格式&#xff0c;直接存储String类型。 当然&#xff0c;针对越来越复杂的业务场景&#xff0c;后续也可能用到li…

合宙Air724UG Cat.1模块硬件设计指南--数字语音接口

数字语音接口 简介 数字音频接口DAI&#xff0c;即Digital Audio Interfaces&#xff0c;表示在板级或板间传输数字音频信号的方式。相比于模拟接口&#xff0c;数字音频接口抗干扰能力更强&#xff0c;硬件设计简单&#xff0c;DAI在音频电路设计中得到越来越广泛的应用。 特…

【学习日记2023.6.20】之 分布式事务_CAP定理_BASE理论_微服务集成Seata_Seata的四种事务模式_高可用架构模型

文章目录 1. 分布式事务问题1.1 本地事务1.2 分布式事务1.3 演示分布式事务问题 2. 理论基础2.1 CAP定理2.1.1 一致性2.1.2 可用性2.1.3 分区容错2.1.4 矛盾 2.2 BASE理论2.3 解决分布式事务的思路 3. 初识Seata3.1 Seata的架构3.2 部署TC服务3.2.1 下载3.2.2 解压3.2.3 修改配…

数据库系统概述——第七章 数据库设计(知识点复习+练习题)

&#x1f31f;博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 &#x1f99a;专栏&am…

p7付费课程笔记:jvm基础知识、字节码、类加载器

编程语言 演化&#xff1a; 机器语言->编程语言->高级语言&#xff08;java&#xff0c;c,Go,Rust等&#xff09; 面向过程–面向对象-面向函数 java是一种面向对象、静态类型、编译执行&#xff0c;有VM&#xff08;虚拟机&#xff09;/GC和运行时、跨平台的高级语言…

第二章 视觉感知与视觉通道(复习)

大纲 视觉感知 认知 视觉通道 色彩* 可视化致力于外部认知&#xff0c;也就是说&#xff0c;怎样利用大脑以外的资源来增强大脑本身的认知能力。 感知是指客观事物通过人的感觉器官在人脑中形成的直接反映 感觉器官&#xff1a;眼、耳、口、鼻、神经末梢 视觉感知就是客观事物通…

世界史上五个横跨亚欧非三大洲的超强帝国

古代地中海和西亚地区文明出现的很早&#xff0c;经济文化社会都比较先进&#xff0c;其中古埃及早在四千多年前就建立了庞大的帝国&#xff0c;给世人留下了不朽的金字塔&#xff1b;两河流域、希腊半岛也很早就出现了城邦制的国家&#xff0c;也创造了灿烂的文明。同时&#…

架构设计我们要注意什么?

这几天我正在做一个新项目的架构设计&#xff0c;关于动态流程引擎平台的搭建&#xff0c;涉及到了系统架构的设计&#xff0c;里面涉及了方方面面&#xff0c;所以就想着结合自己的实际经验&#xff0c;遇到的问题&#xff0c;以及自己的理解&#xff0c;为大家做一个简单的分…

损失函数:IoU、GIoU、DIoU、CIoU、EIoU、alpha IoU、SIoU、WIoU超详细精讲及Pytorch实现

前言 损失函数是用来评价模型的预测值和真实值不一样的程度&#xff0c;损失函数越小&#xff0c;通常模型的性能越好。不同的模型用的损失函数一般也不一样。 损失函数的使用主要是在模型的训练阶段&#xff0c;如果我们想让预测值无限接近于真实值&#xff0c;就需要将损…

献给蓝初小白系列(二)——Liunx应急响应

1、Linux被入侵的症状​​ ​​https://blog.csdn.net/weixin_52351575/article/details/131221720​​ 2、Linux应急措施 顺序是&#xff1a;隔离主机--->阻断通信--->清除病毒--->可疑用户--->启动项和服务--->文件与后门--->杀毒、重装系统、恢复数据 …

python代码加密方案

为何要对代码加密&#xff1f; python的解释特性是将py编译为独有的二进制编码pyc 文件&#xff0c;然后对pyc中的指令进行解释执行&#xff0c;但是pyc的反编译却非常简单&#xff0c;可直接反编译为源码&#xff0c;当需要将产品发布到外部环境的时候&#xff0c;源码的保护尤…

Guitar Pro是什么软件 Guitar Pro有什么用

相信玩吉他的朋友多多少少都听说过Guitar Pro这款软件&#xff0c;那大家知道Guitar Pro是什么软件&#xff1f;Guitar Pro有什么用呢&#xff1f;今天小编就和大家分享一下关于Guitar Pro这款吉他软件的相关内容。 一、Guitar Pro是什么软件 简单说Guitar Pro是一款吉他谱软…

Vue实现元素沿着坐标数组移动,超出窗口视图时页面跟随元素滚动

一、实现元素沿着坐标数组移动 现在想要实现船沿着下图中的每个河岸移动。 实现思路&#xff1a; 1、将所有河岸的位置以 [{x: 1, y: 2}, {x: 4, y: 4}, …] 的形式保存在数组中。 data() {return {coordinateArr: [{ x: 54, y: 16 }, { x: 15, y: 31 }, { x: 51, y: 69 }…

leetcode77. 组合(回溯算法-java)

组合 leetcode77. 组合题目描述解题思路代码演示 递归专题 leetcode77. 组合 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/combinations 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个…

量化交易:止盈策略与回测

我们买基金或股票的时候通常用最简单的策略进行决策&#xff1a;低买高卖&#xff0c;跌的多了就加仓拉低持有成本&#xff0c;达到收益率就卖出。 那么如何用代码表示这个策略呢&#xff1f;首先定义交易信号则是&#xff1a;0.5%时买入&#xff0c;目标止盈线是1.5%&#xf…