最终效果

在这里插入图片描述

前言

在现代游戏开发中,建造系统已经成为许多游戏类型的核心玩法元素,从生存沙盒类游戏(如《我的世界》、《方舟:生存进化》)到策略建造游戏(如《模拟城市》、《异星工厂》),一个优秀的建造系统能极大提升玩家的游戏体验。

本文将带你实现一个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));
    }
}

效果
在这里插入图片描述


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】
【unity游戏开发——编辑器扩展】
【unity游戏开发——热更新】
【unity游戏开发——网络】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐