温州企业建站系统,深圳住 建设局网站,网页设计模板素材旅游,wordpress upload 加密Unity即时战略/塔防项目实战#xff08;一#xff09;—— 构造网格建造系统
效果展示 Unity RTS游戏网格建造系统实现原理
地形和格子划分#xff0c;建造系统BuildManager构建
地形最终需要划分成一个一个的小方格#xff0c;首先定义一下小方格#xff1a;
private…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 sxw; 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