当前位置: 首页 > news >正文

Unity即时战略/塔防项目实战(一)——构造网格建造系统

Unity即时战略/塔防项目实战(一)—— 构造网格建造系统

效果展示

Unity RTS游戏网格建造系统

实现原理

地形和格子划分,建造系统BuildManager构建

地形最终需要划分成一个一个的小方格,首先定义一下小方格:

private struct MapCellNode
{public float height;		// 格子的中心高度public float steepness;		// 格子的梯度public Building current;	// 格子中存储的建筑
}

将地图分成m*n的小个子,用一个二维数组容纳这些格子,并对这些格子进行初始化:

// 盛放格子的容器
private static MapCellNode[,] mapCells;// 初始化格子,并计算每个格子的高度和坡度
private void InitMapCells()
{var terrainData = _terrain.terrainData;int gridWidth = (int)(terrainData.bounds.size.x / cellSize.x);int gridHeight = (int)(terrainData.bounds.size.z / cellSize.y);mapCells = new MapCellNode[gridWidth, gridHeight];for (int i = 0; i < gridWidth; ++i){for (int j = 0; j < gridHeight; ++j){mapCells[i, j].current = null;var center = GetCellLocalPosition(i, j);mapCells[i, j].height = center.y;var steepness = terrainData.GetSteepness(center.x / terrainData.size.x, center.z/terrainData.size.z);mapCells[i, j].steepness = steepness;}}
}

定义建造系统的一些API方便在其他地方使用:

// 根据格子索引,获取格子中心点的本地坐标
public static Vector3 GetCellLocalPosition(int w, int h)
{Vector3 withoutHeight = new(w * cellSize.x + cellSize.x * 0.5f, 0, h * cellSize.y + cellSize.y * 0.5f);return GetTerrainPosByLocal(withoutHeight);
}// 根据格子索引,获取格子中心点的世界坐标
public static Vector3 GetCellWorldPosition(int w, int h)
{return Instance.transform.TransformPoint(GetCellLocalPosition(w, h));
}// 计算地图上的本地坐标点,所属网格的索引
public static (int, int) GetCellIndexByLocalPosition(Vector3 local)
{return ((int)(local.x / Instance._cellSize.x), (int)(local.z / Instance._cellSize.y));
}// 计算地图上的世界坐标点,所属网格的索引
public static (int, int) GetCellIndexByWorldPosition(Vector3 world)
{return GetCellIndexByLocalPosition(Instance.transform.InverseTransformPoint(world));
}// 根据给定的格子区域(起始格子索引、宽度和高度),计算区域内所有格子的平均高度
public static float GetGridAverageHeight(int sx, int sy, int w, int h)
{float height = 0;int count = 0;for (int x = sx; x < sx+w; ++x){if( x < 0 || x >= gridSize.x)continue;for (int y = sy; y < sy + h; ++y){if( y < 0 || y >= gridSize.y)continue;height += mapCells[x, y].height;++count;}}if (count > 0)return height / count;return 0;
}
PreBuilding 和“开始建造”

由于一次只能建造一个建筑,因此,当开始建造时,首先持有待建造的物体,用current来保存待建造的物体。

// 开始建造,根据id查询待建物,并持有它。
public static void TakeBuilding(string id)
{if (!Instance.preBuildings.TryGetValue(id, out PreBuilding pb))return;BeginBuild(pb);
}// 准备建造指定的建筑物
private static void BeginBuild(PreBuilding pb)
{// 让待建物准备建造(重置待建物的材质参数等)pb.BeginBuild();currentBuilding = pb;// 在待建物周围绘制方格线Instance.buildLineDrawer.gameObject.SetActive(true);Transform trans = Instance.buildLineDrawer.transform;trans.SetParent(currentBuilding.transform);trans.localPosition = projectorOffset - currentBuilding.AlignToCellOffset();// 如果待建物是具有攻击范围或影响范围的,则显示范围指示器并设置半径为待建物的影响范围if (currentBuilding.canAttack){Instance.attackCircel.gameObject.SetActive(true);Instance.attackCircel.SetRadius(currentBuilding.AttackRadius);trans = Instance.attackCircel.transform;trans.SetParent(currentBuilding.transform);trans.localPosition = projectorOffset;}
}

然后就是建造检测逻辑:

private void Update()
{// 不在建造状态就返回if (currentBuilding is null || currentBuilding.IsBuilding){
#if DEBUG_MODDisplayDebugInfo();
#endifreturn;}// 按下右键就取消建造if (Input.GetMouseButtonDown(1)){CancelBuild();return;}// 不在UI上才建造if (EventSystem.current.IsPointerOverGameObject()){if (!Cursor.visible)Cursor.visible = true;return;}// 获取建造点if (!Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out RaycastHit hit, 100f,groundLayer.value)){if (!Cursor.visible)Cursor.visible = true;return;}if (Cursor.visible)Cursor.visible = false;// 按下R键就旋转待建物(换个朝向)if (Input.GetKeyDown(KeyCode.R))currentBuilding.NextRotation();// 获取地图格子索引var (x, y) = GetCellIndexByWorldPosition(hit.point);// 尝试放入待建物,如无法放置返回falseif (currentBuilding.CheckBuildingIndexPosOnGrid(x, y)){// 按下左键,准备结束建造if (Input.GetMouseButtonDown(0)){PrepareEndBuild();}}
}

检测能否放置在当前位置的方法如下:

public bool CheckBuildingIndexPosOnGrid(int x, int y)
{bool canBuild = true;// 根据朝向计算当前占用格子的宽度和高度// 比如:一个建筑物南北朝向放置时占用3*2个格子,但是东西朝向放置时将占用2*3个格子。var (w, h) = GetRealSizeWithDir();int dx = (w - 1) / 2;int dy = (h - 1) / 2;int sx = x - dx;int sy = y - dy;// 获取所占格子的平均地形高度float aheight = BuildManager.GetGridAverageHeight(sx, sy, w, h);string info = "超出范围";for (int px = sx; canBuild && px < sx + w; ++ px){// 判定x方向是否超出地图边界if (px < 0 || px >= BuildManager.gridSize.x){canBuild = false;break;}for (int py = sy; py < sy + h; ++py){// 判定z方向是否超出地图边界if (py < 0 || py >= BuildManager.gridSize.y){canBuild = false;break;}// 判定所占用的格子上是否已经存在其他建筑if (BuildManager.GetBuildingWithCell(px, py) is not null){canBuild = false;info = "已存在其他建筑";break;}// 判定格子地形高度与平均高度是否相差太多if (Mathf.Abs(aheight - BuildManager.GetCellHeight(px, py)) > 0.2f){canBuild = false;info = "地形不平";break;}// 判定格子坡度是否太陡if (BuildManager.GetCellStepness(px, py) > 3f){canBuild = false;info = "坡度太陡";break;}}}// 根据格子索引,获取世界坐标,并将其对齐到网格// AlignToCellOffset意义为:假设待建物体的中心点在物体的几何中心,那么,如果所占格子尺寸为奇数,// 则建筑是对称的,偏移为0;如果所占格子尺寸为偶数,则该建筑不是对称的,需要偏移半个单元格。var pos = BuildManager.GetCellWorldPosition(x, y) + AlignToCellOffset();// 如果能够在此处建造,则设置索引,并设置待建物材质为“绿色”,否则设置为“红色”。if (canBuild){_indexPos.x = sx;_indexPos.y = sy;_indexPos.width = w;_indexPos.height = h;preMaterial.SetColor(CommDefine.PrebuildColor, BuildManager.preBuildNormalColor);                }else{preMaterial.SetColor(CommDefine.PrebuildColor, BuildManager.preBuildBadColor);InfoTips.Display(info, pos, 1.2f );}// 设置待建物的世界坐标transform.position = pos;return canBuild;
}

当按下鼠标,确定在此处建造时:

// 准备完成建造
private static void PrepareEndBuild()
{// 恢复鼠标显示if (!Cursor.visible)Cursor.visible = true;// 关闭网格显示、关闭范围指示,开始播放建造动画currentBuilding.EndBuild();Instance.buildLineDrawer.gameObject.SetActive(false);if(currentBuilding.canAttack)Instance.attackCircel.gameObject.SetActive(false);
}// 建造完成(建造动画播放完成)
private static void FinishBuild()
{// 实例化真正要建造的物体Building bd = currentBuilding.CreateBuilding();// 将建筑保存到网格中SaveCurrentBuilding(bd);// 置空currentcurrentBuilding = null;
}
网格及范围指示的绘制

因为地形是不平的,要在不平整的地面上完美的绘制网格和范围指示器,那用到了投影(贴花),然后投影材质使用了自己写的shader,很简单:

  • 网格的Shader:
fixed4 frag(const v2f i) : SV_Target
{const float temp_output_2_0_g3 = 1 - _Width;const float2 appendResult10_g4 = float2(temp_output_2_0_g3, temp_output_2_0_g3);const float2 temp_output_11_0_g4 = abs(frac(i.uv0 * _ScaleOffset.xy + _ScaleOffset.zw) * 2.0 + -1.0) -appendResult10_g4;const float2 break16_g4 = 1.0 - temp_output_11_0_g4 / fwidth(temp_output_11_0_g4);float4 res = 1 - saturate(min(break16_g4.x, break16_g4.y)).xxxx;const float len = length(i.uv0 - float2(0.5,0.5));res *= step(len, 0.5);res *= smoothstep( 1-len, _min, _max);return res * _Color;
}
  • 范围指示的Shader:
fixed4 frag(const v2f i) : SV_Target
{const float radius = _Radius * 0.5;const float width = _Width * 0.5;const float len = length(i.uv0 - float2(0.5,0.5));float4 res = step(len, radius);const float4 inner = step(len, radius - width);res -= inner;res *= _Color;return res;
}
建造过程动画

由于缺乏美术资源,建造过程通过一个融合动画来展示建造过程,融合用ASE插件做的Shader:
在这里插入图片描述

相关文章:

Unity即时战略/塔防项目实战(一)——构造网格建造系统

Unity即时战略/塔防项目实战&#xff08;一&#xff09;—— 构造网格建造系统 效果展示 Unity RTS游戏网格建造系统实现原理 地形和格子划分&#xff0c;建造系统BuildManager构建 地形最终需要划分成一个一个的小方格&#xff0c;首先定义一下小方格&#xff1a; private…...

【ZOJ 1095】Humble Numbers 题解(动态规划)

一个素数只有2&#xff0c;3&#xff0c;5或7的数被称为谦逊数。序列1、2、3、4、5、6、7、8、9、10、12、14、15、16、18、20、21、24、25、27。。。显示了前20个不起眼的数字。 编写一个程序来查找并打印此序列中的第n个元素。 输入规范 输入由一个或多个测试用例组成。每个…...

百科媒体背书,什么媒体的收录可以修改百科?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好 大家都知道百科在百度搜索引擎中有很高的权重&#xff0c;排名非常靠前&#xff0c;任何机构&#xff0c;个人&#xff0c;或者企业做网络宣传百科是必不可少的&#xff0c;虽然任何人都可以注册并编辑其内容。但是&#x…...

USB鼠标实现——HID 报告的返回(八)

文章目录HID 报告的返回仓库地址USB 鼠标阅读顺序报告返回HID 报告的返回 仓库地址 仓库地址 USB 鼠标阅读顺序 枚举过程USB鼠标实现——设备描述符&#xff08;一&#xff09;USB鼠标实现——设置地址&#xff08;二&#xff09;USB鼠标实现——配置描述符集合&#xff08…...

DOPE PEG Maleimide,DOPE-PEG-Mal,二油酰磷脂酰乙醇胺PEG马来酰亚胺

文章关键词&#xff1a;高分子PEG&#xff0c;DOPE&#xff0c;聚乙二醇化修饰试剂基团反应特点&#xff1a; DOPE PEG Maleimide是一种由 DOPE 和马来酰亚胺基团组成的 PEG 化合物。基础产品数据&#xff1a; CAS号&#xff1a;N/A 中文名&#xff1a;1,2-二油酰-SN-甘油-3-磷…...

python-课后作业-2

1.Python 3.x的range()函数返回一个&#xff1a;可迭代的序列对象 注意&#xff1a; Python 3.x的range()函数返回一个可迭代的序列对象&#xff0c;其中包含指定范围内的整数。range()函数的语法如下&#xff1a; range([start], stop[, step]) 其中&#xff0c;start表示序…...

redis 六. list应用场景及底层分析

List 类型一. 简单命令示例二. java 操作示例三. 使用场景四. 底层分析一. 简单命令示例 1.首先简单说明: List是一个双端链表的结构,内容是2的32次方减1个元素,大概40多亿,主要功能有push/pop等,一般用在栈,队列,消息队列等场景 2.简单命令 //1.向列表左边添加元素 LPUSH ke…...

成语填字接龙隐私政策

1. 适用范围 (a) 在您注册本应用帐号时&#xff0c;您根据本应用要求提供的个人注册信息&#xff1b; (b) 在您使用本应用网络服务&#xff0c;或访问本应用平台网页时&#xff0c;本应用自动接收并记录的您的浏览器和计算机上的信息&#xff0c;包括但不限于您的IP地址、浏览…...

导出LKD3588开发板的根文件系统

序:将RK3588上的整个根文件系统的文件通过ssh拷贝到PC系统(虚拟机) 工具:RK3588上的ubuntu系统需要安装:ssh, rsync。 PC电脑(虚拟机)上安装:ssh, rsync。 安装ssh 和rsync不做介绍,百度里面全是,也很简单需要设置开发板root权限的密码,因为后面同步文件的时候会用到…...

【统计模型】某地区土壤所含可给态磷回归分析

目录 某地区土壤所含可给态磷回归分析 一、研究目的 二、数据来源和相关说明 三、描述性分析 3.1 样本描述 3.2 数据可视化 四、数据建模 4.1 回归模型A 4.2 回归模型B 4.3 回归模型B模型诊断 4.4 回归模型C 五、结论及建议 5.1 结论 5.2 建议 六、代码 某地区土…...

redis 十. 线程基础

目录一. redis 基础复习与了解redis6二. redis 线程问题总结一. redis 基础复习与了解redis6 redis官网, redis中文网站, redis命令参考网站此处以redis6.0.8或以上版本为例(查看自己redis版本命令"redis- server -v")按照redis6以上版本测试使用时,redis.conf下需要…...

NQA简介

NQA简介定义目的NQA原理描述使用DHCP进行测试DNS测试NQA的联动机制NQA的应用场景定义 网络质量分析NQA&#xff08;Network Quality Analysis&#xff09;是一种实时的网络性能侦探和统计技术&#xff0c;可以对响应时间、网络抖动、丢包率等网络信息进行统计。NQA能够实时监视…...

[python]上下文管理contextlib模块与with语句

文章目录with语句自定义对象支持withcontextlib模块closing自动关闭suppress回避错误ExitStack清理Python 中的 with 语句用于清理工作&#xff0c;封装了 try…except…finally编码范式&#xff0c;提高了易用性。with语句 with语句有助于简化资源管理&#xff1a; # 离开作…...

STM32之TIM编码器接口

编码器简介&#xff1a; 例子讲解&#xff1a;正交编码器有两个输出&#xff0c;一个A相&#xff0c;一个B相&#xff0c;AB接口输出正交信号。然后接入STM32的定时器的编码器接口&#xff0c;编码器接口自动控制定时器时基单元中的CNT计数器进行自增或自减&#xff0c;比如初始…...

b站第一,Python自动化测试实战详细教学,3天教你学会自动化测试

目录 简介 Python自动化测试概述 Python自动化测试目标 Python自动化测试流程 1. 测试计划和设计 2. 测试脚本开发 3. 测试执行和管理 4. 测试维护和优化 Python自动化测试最佳实践 Python自动化测试工具和框架 结论 简介 自动化测试是软件开发过程中一个必不可少的…...

刷题记录:P8804 [蓝桥杯 2022 国 B] 故障 条件概率

传送门:洛谷 题目描述: 题目较长,此处省略 输入: 3 5 30 20 50 0 50 33 25 0 30 0 35 0 0 0 0 0 25 60 1 3 输出: 2 56.89 1 43.11 3 0.00读完题目,我们会发现其实题目给了我们两个事件,并且这两个事件是相互关联的.因此不难想到使用条件概率 我们将故障原因看做事件AAA,结合…...

【算法】常用的基础数论

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;或许会很慢&#xff0c;但是不可以停下&#x1f43e; 文章目录1.GCD&LCM2.判断素数(质数)3.分解质因子1.GCD&LCM 最大公约数&最小共倍数 欧几里得算法——高效 //最大公约数 int gcd(int x,i…...

云原生场景下的容器网络隔离技术

云原生场景下的容器网络隔离技术 一、研究背景 随着云计算时代的到来&#xff0c;尤其是容器化技术的飞速发展&#xff0c;云原生作为云计算的未来阶段&#xff0c;其安全势必成为云安全的主要战场。从目前的云原生环境来看&#xff0c;云原生网络安全问题层出不穷&#xff0…...

用python绘制有向图

目录 添加边权重的有向图思路介绍代码实现效果图设置不同的样式节点和边的有向图思路介绍代码实现效果图下面的Python代码用于绘制有向图,其中使用了 networkx和 matplotlib.pyplot等库。 添加边权重的有向图 思路介绍 首先,创建了一个空的有向图像对象G,并添加了4个节点…...

Spring MongoDB 开发教程(一)—官方原版

MongoDB支持包含一系列功能&#xff1a;Spring配置支持基于Java的configuration类或Mongo驱动程序实例和副本集的XML命名空间。MongoTemplate帮助类&#xff0c;在执行常见的Mongo操作时提高生产力。包括文档和POJO之间的集成对象映射。将异常转换为Spring的可移植数据访问异常…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...