【unity实战】实现一个简易点的带吸附的建造系统
·
最终效果

前言
在现代游戏开发中,建造系统已经成为许多游戏类型的核心玩法元素,从生存沙盒类游戏(如《我的世界》、《方舟:生存进化》)到策略建造游戏(如《模拟城市》、《异星工厂》),一个优秀的建造系统能极大提升玩家的游戏体验。
本文将带你实现一个Unity中的简易建造系统,主要特点包括:
-
物体预览功能
-
网格吸附对齐
-
旋转控制
-
材质切换反馈
关键代码解析
1、预览物体创建
private void CreatePreviewObject(Transform buildPrefab)
{
// 获取预制体尺寸信息
Mesh mesh = buildPrefab.GetComponentInChildren<MeshFilter>().sharedMesh;
objectSize = Vector3.Scale(mesh.bounds.size, buildPrefab.localScale);
// 清理旧预览
if (buildPreview) Destroy(buildPreview.gameObject);
// 实例化新预览
buildPreview = Instantiate(buildPrefab);
// 设置预览材质
var renderer = buildPreview.GetComponentInChildren<MeshRenderer>();
originalMaterial = renderer.sharedMaterial;
renderer.material = previewMaterial;
// 禁用碰撞体
buildPreview.GetComponentInChildren<Collider>().enabled = false;
}
这段代码完成了预览物体的初始化工作,其中:
-
通过Mesh信息计算物体实际尺寸
-
使用半透明材质区分预览状态
-
禁用碰撞体避免影响射线检测
2、网格吸附算法
private Vector3 CalculateSnappedPosition(Vector3 hitPoint)
{
float snappedRotation = Mathf.Round(transform.eulerAngles.y / 90f) * 90f;
float x, y, z;
// 根据旋转状态调整对齐轴
if (snappedRotation == 0f || snappedRotation == 180f)
{
x = Mathf.Round(hitPoint.x / objectSize.x) * objectSize.x;
z = Mathf.Round(hitPoint.z / objectSize.z) * objectSize.z;
}
else
{
x = Mathf.Round(hitPoint.x / objectSize.z) * objectSize.z;
z = Mathf.Round(hitPoint.z / objectSize.x) * objectSize.x;
}
// Y轴对齐并居中
y = Mathf.Round(hitPoint.y / objectSize.y) * objectSize.y + objectSize.y / 2f;
return new Vector3(x, y, z);
}
这个算法实现了:
-
根据当前旋转角度智能选择对齐轴
-
保持Y轴位置居中
-
确保物体完美对齐网格
最终代码
using UnityEngine;
public class BuildSystem : MonoBehaviour
{
[SerializeField] private float maxBuildDistance = 7f; // 最大建造距离
[SerializeField] private Transform floorPrefab; //地板预制体
[SerializeField] private Transform wallPrefab; //墙壁预制体
[SerializeField] private Material previewMaterial; // 预览状态材质
private Transform buildPreview; // 预览物体实例
private Material originalMaterial; // 原始材质
private Vector3 objectSize; // 物体尺寸
private RaycastHit hitInfo; // 射线检测信息
void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
CreatePreviewObject(floorPrefab);
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
CreatePreviewObject(wallPrefab);
}
if (buildPreview == null) return;
// 左键点击放置建筑
if (Input.GetMouseButtonDown(0))
{
FinalizeBuilding();
}
// R键旋转建筑预览
if (Input.GetKeyDown(KeyCode.R))
{
RotatePreview();
}
UpdatePreviewPosition();
}
/// <summary>
/// 创建预览物体
/// </summary>
private void CreatePreviewObject(Transform buildPrefab)
{
// 获取预制体的网格尺寸并计算实际大小
Mesh mesh = buildPrefab.GetComponentInChildren<MeshFilter>().sharedMesh;
objectSize = Vector3.Scale(mesh.bounds.size, buildPrefab.localScale);
if (buildPreview) Destroy(buildPreview.gameObject);
buildPreview = Instantiate(buildPrefab);
// 获取原始材质并应用预览材质
var renderer = buildPreview.GetComponentInChildren<MeshRenderer>();
originalMaterial = renderer.sharedMaterial;
renderer.material = previewMaterial;
// 禁用碰撞体
buildPreview.GetComponentInChildren<Collider>().enabled = false;
}
/// <summary>
/// 更新预览物体位置
/// </summary>
private void UpdatePreviewPosition()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo, maxBuildDistance))
{
// 计算网格对齐位置并应用
buildPreview.position = CalculateSnappedPosition(hitInfo.point);
buildPreview.rotation = transform.rotation;
}
}
/// <summary>
/// 计算网格对齐的位置(考虑旋转)
/// </summary>
private Vector3 CalculateSnappedPosition(Vector3 hitPoint)
{
// 获取当前旋转角度(0°、90°、180°、270°)
float snappedRotation = Mathf.Round(transform.eulerAngles.y / 90f) * 90f;
float x, y, z;
// 根据旋转角度调整对齐方式
if (snappedRotation == 0f || snappedRotation == 180f)
{
// 0°或180°时正常对齐X和Z轴
x = Mathf.Round(hitPoint.x / objectSize.x) * objectSize.x;
z = Mathf.Round(hitPoint.z / objectSize.z) * objectSize.z;
}
else
{
// 90°或270°时交换X和Z轴的对齐方式
x = Mathf.Round(hitPoint.x / objectSize.z) * objectSize.z;
z = Mathf.Round(hitPoint.z / objectSize.x) * objectSize.x;
}
// Y轴始终按正常方式对齐并居中
y = Mathf.Round(hitPoint.y / objectSize.y) * objectSize.y + objectSize.y / 2f;
return new Vector3(x, y, z);
}
/// <summary>
/// 确认建造,将预览物体转为实际建筑
/// </summary>
private void FinalizeBuilding()
{
// 启用碰撞体并恢复原始材质
buildPreview.GetComponentInChildren<Collider>().enabled = true;
buildPreview.GetComponentInChildren<MeshRenderer>().material = originalMaterial;
// 重置预览引用
buildPreview = null;
}
/// <summary>
/// 旋转预览物体
/// </summary>
private void RotatePreview()
{
transform.Rotate(new Vector3(0, 90, 0));
}
}
效果

专栏推荐
完结
好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

更多推荐
所有评论(0)