本文介绍: 手搓一个网格放置功能,及装修建造种植功能但是它有一些缺点,比如网格自己绘制的,使用起来可能比较麻烦,所有这里分享另一种更加简单方法。就是使用tilemap可以省略自己绘制复杂网格时间,但是缺点可能就是玩家无法在游戏界面看到网格的具体位置,当然,实现功能千千万万,选择自己喜欢的就行。

最终效果

在这里插入图片描述

前言

其实3d物品建造装修系统之前就已经做过了,感兴趣可以看看手搓一个网格放置功能,及装修建造种植功能

但是它有一些缺点,比如网格是自己绘制的,使用起来可能比较麻烦,所有这里分享另一种更加简单方法。就是使用tilemap可以省略自己绘制复杂网格的时间,但是缺点可能就是玩家无法在游戏界面看到网格的具体位置,当然,实现功能千千万万,选择自己喜欢的就行。

绘制开始场景

在这里插入图片描述
平台放置tilemap,并配置对应参数
在这里插入图片描述
在这里插入图片描述

简单绘制,效果
在这里插入图片描述

素材

可以寻找下载你喜欢的模型导入项目

这里推荐地址
https://sketchfab.com/Cytiene/collections/greatdownloadable-models-6304c532e52649f59de0de234edcb91f
在这里插入图片描述

开始

新增放置对象脚本PlaceableObject ,暂时什么都不做

public class PlaceableObject : MonoBehaviour { }

所有模型物品都挂载脚本,并给模型添加碰撞
在这里插入图片描述

新增BuildingSystem定义一个建筑系统脚本

public class BuildingSystem : MonoBehaviour
{
    public static BuildingSystem current;

    public GridLayout gridLayout;
    private Grid grid;
    [SerializeField] private Tilemap mainTilemap; // 地图的Tilemap组件
    [SerializeField] private TileBase whiteTile; // 白色方块的TileBase
    public GameObject prefab1; // 预制体1
    public GameObject prefab2; // 预制体2
    private PlaceableObject objectToPlace; // 当前放置对象

    private void Awake()
    {
        current = this;
        grid = gridLayout.gameObject.GetComponent<Grid>(); // 获取网格组件
    }

	//测试切换不同模型物品
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            InitializeWithObject(prefab1);
        }
        else if (Input.GetKeyDown(KeyCode.B))
        {
            InitializeWithObject(prefab2);
        }
    }


    // 工具方法:将鼠标位置转换为世界坐标系下的位置
    public static Vector3 GetMouseWorldPosition()
    {
        // 从相机发出一条射线,将鼠标位置转换为世界坐标系下的位置
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        // 如果射线碰撞到物体,则返回碰撞点的世界坐标
        if (Physics.Raycast(ray, out RaycastHit raycastHit))
        {
            return raycastHit.point;
        }
        else // 否则返回向量
        {
            return Vector3.zero;
        }
    }


    // 将坐标对齐网格
    public Vector3 SnapCoordinateToGrid(Vector3 position)
    {
        Vector3Int cellPos = gridLayout.WorldToCell(position); // 将世界坐标转换为网格单元坐标
        position = grid.GetCellCenterWorld(cellPos); // 获取网格单元中心点的世界坐标
        return position;
    }

    //初始化放置物体
    public void InitializeWithObject(GameObject prefab)
    {
        // 将物体初始位置设为网格对齐的原点
        Vector3 position = SnapCoordinateToGrid(Vector3.zero);

        // 在初始位置实例化物体
        GameObject obj = Instantiate(prefab, position, Quaternion.identity);

        // 获取PlaceableObject组件添加ObjectDrag组件
        objectToPlace = obj.GetComponent<PlaceableObject>(); // 获取放置物体组件
        obj.AddComponent<ObjectDrag>(); // 添加拖拽组件
    }
}

新增ObjectDrag定义一个物体拖拽脚本,注意物品移动要有碰撞体,不然拖拽不会生效

public class ObjectDrag : MonoBehaviour
{
    private Vector3 offset; // 鼠标按下时物体和鼠标之间的偏移量

    // 当鼠标按下时记录偏移
    private void OnMouseDown()
    {
        offset = transform.position - BuildingSystem.GetMouseWorldPosition();
    }

    // 当鼠标拖动移动物体并对齐到网格上
    private void OnMouseDrag()
    {
        Vector3 pos = BuildingSystem.GetMouseWorldPosition() + offset;
        transform.position = BuildingSystem.current.SnapCoordinateToGrid(pos);
    }
}

挂载脚本配置
在这里插入图片描述
效果,按AB生成不同的物品,点击物品可以进行拖拽
在这里插入图片描述
当然,你也可以修改ObjectDrag,直接使用Update方法,让物品一直跟随鼠标移动

// 每帧更新建筑物的位置
private void Update()
{
    Vector3 pos = BuildingSystem.GetMouseWorldPosition() + offset;
    transform.position = BuildingSystem.current.SnapCoordinateToGrid(pos);
}

放置

修改PlaceableObject

public class PlaceableObject : MonoBehaviour
{
    // 是否已经放置
    public bool Placed { get; private set; }

    // 物体占据的格子
    public Vector3Int Size { get; private set; }

    // 物体碰撞器的四个顶点本地坐标系
    private Vector3[] Vertices;

    private void Start()
    {
        // 获取物体碰撞器的四个顶点
        GetColliderVertexPositionsLocal();
        // 计算物体占据的格子
        CalculateSizeInCells();
    }

    // 获取物体碰撞器的四个顶点
    private void GetColliderVertexPositionsLocal()
    {
        BoxCollider b = gameObject.GetComponent<BoxCollider>();
        Vertices = new Vector3[4];
        Vertices[0] = b.center + new Vector3(-b.size.x, -b.size.y, -b.size.z) * 0.5f;
        Vertices[1] = b.center + new Vector3(b.size.x, -b.size.y, -b.size.z) * 0.5f;
        Vertices[2] = b.center + new Vector3(b.size.x, -b.size.y, b.size.z) * 0.5f;
        Vertices[3] = b.center + new Vector3(-b.size.x, -b.size.y, b.size.z) * 0.5f;
    }

    // 计算物体占据的格子
    private void CalculateSizeInCells()
    {
        Vector3Int[] vertices = new Vector3Int[Vertices.Length];
        for (int i = 0; i < vertices.Length; i++)
        {
            // 将物体顶点本地坐标系转换到世界坐标系
            Vector3 worldPos = transform.TransformPoint(Vertices[i]);
            // 将世界坐标系中的位置转换格子坐标系中的位置
            vertices[i] = BuildingSystem.current.gridLayout.WorldToCell(worldPos);
        }
        // 计算物体占据的格子
        Size = new Vector3Int(
            Mathf.Abs(vertices[0].x - vertices[1].x),
            Mathf.Abs(vertices[0].y - vertices[3].y),
            1
        );
    }

    // 获取物体的起始位置(左下角格子位置)
    public Vector3 GetStartPosition()
    {
        return transform.TransformPoint(Vertices[0]);
    }

    // 放置物体
    public virtual void Place()
    {
        // 删除物体拖拽组件
        ObjectDrag drag = gameObject.GetComponent<ObjectDrag>();
        Destroy(drag);

        // 标记物体已经放置
        Placed = true;

        // TODO:触发放置事件
    }
}

修改BuildingSystem

private void Update()
{
    //。。。

    //放置测试
    if (Input.GetKeyDown(KeyCode.Space))
    {
        if (CanBePlaced(objectToPlace)) // 检查物体是否可以放置
        {
            objectToPlace.Place(); // 放置物体
            Vector3Int start = gridLayout.WorldToCell(objectToPlace.GetStartPosition()); // 将世界坐标转换格子坐标
            TakeArea(start, objectToPlace.Size); // 将物体所占据的区域填充为白色瓦片
        }
        else
        {
            Destroy(objectToPlace.gameObject); // 物体无法放置,销毁物体
        }
    }
    else if (Input.GetKeyDown(KeyCode.Escape))
    {
        Destroy(objectToPlace.gameObject); // 按下 Esc 键,销毁物体
    }
}

//获取一个区域内的瓦片信息数组
private static TileBase[] GetTilesBlock(BoundsInt area, Tilemap tilemap)
{
    TileBase[] array = new TileBase[area.size.x * area.size.y * area.size.z];
    int counter = 0;
    foreach (var v in area.allPositionsWithin)
    {
        Vector3Int pos = new Vector3Int(v.x, v.y, 0);
        array[counter] = tilemap.GetTile(pos); // 获取指定位置上的瓦片
        counter++;
    }
    return array;
}

//检查物体是否可以放置在指定位
private bool CanBePlaced(PlaceableObject placeableObject)
{
    BoundsInt area = new BoundsInt();
    area.position = gridLayout.WorldToCell(placeableObject.GetStartPosition()); // 将世界坐标转换为格子坐标
    area.size = placeableObject.Size; // 获取物体所占据的格子大小
    TileBase[] baseArray = GetTilesBlock(area, mainTilemap); // 获取该区域内的瓦片数组
    foreach (var b in baseArray)
    {
        if (b == whiteTile) // 如果有白色瓦片,表示物体无法放置
        {
            return false;
        }
    }
    return true; // 没有白色瓦片,可以放置物体
}

//在指定区域填充为白色瓦片
public void TakeArea(Vector3Int start, Vector3Int size)
{
    mainTilemap.BoxFill(start, whiteTile, start.x, start.y, start.x + size.x, start.y + size.y); // 将指定区域填充为白色瓦片
}

效果,物体重叠会直接销毁物品
在这里插入图片描述

旋转物体

修改PlaceableObject

//旋转
public void Rotate()
{
    transform.Rotate(eulers: new Vector3(0, 90, 0)); // 绕 Y 轴顺时针旋转 90 度

    // 交换长宽并限制高度为 1
    Size = new Vector3Int(Size.y, Size.x, 1);

    // 旋转点数
    Vector3[] vertices = new Vector3[Vertices.Length];
    for (int i = 0; i < vertices.Length; i++)
    {
        vertices[i] = Vertices[(i + 1) % Vertices.Length]; // 将顶点数顺时针旋转
    }
    Vertices = vertices; // 更新点数
}

修改BuildingSystem调用

private void Update()
{ 
	//。。。

	//按回车旋转物体
	if (Input.GetKeyDown(KeyCode.Return))
    {
        objectToPlace.Rotate();
    }
}

效果
在这里插入图片描述

扩展优化

1. 绘制地图边界,确保放置物品在指定区域内工作

建筑区域周围绘制瓷砖边框,这将增加图块地图边界效果,并确保放置物品在指定区域内工作
在这里插入图片描述

2. 让模型所占面积大小更加准确

现在TileMap每格网格比较大,为了让模型所占面积大小更加准确,可以适当缩小Grid的比例
在这里插入图片描述

效果
在这里插入图片描述

3. 隐藏白色瓦片指示区域

实际使用我们肯定不想看到白色瓦片所显示的指示区域,我们可以关闭Tilemap Renderer,或者修改TileMap颜色透明的为0
在这里插入图片描述
效果
在这里插入图片描述

最终效果

在这里插入图片描述

其他

后续其他内容我就不继续完善了,留给大家自己去发挥,比如

源码

https://gitcode.net/unity1/3dplacesystem
在这里插入图片描述

参考

视频https://www.youtube.com/watch?v=rKp9fWvmIww&amp;t=567s

完结

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

好了,我是向宇https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

原文地址:https://blog.csdn.net/qq_36303853/article/details/134595989

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_24354.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注