【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中

news/2024/11/18 9:24:51/文章来源:https://www.cnblogs.com/sitarblogs/p/18551687

需求说明

结合前篇,仓库管理 和 获取鼠标点击的世界坐标位置 两篇内容,已经实现了:

  • 鼠标或键盘控制玩家移动;

  • 玩家触碰物体后,将物体放入仓库;

  • 鼠标点击仓库栏中的物体,任意放在空间中的功能。

接下来想要实现:

  • 鼠标点击仓库栏的物体之后,物体会跟随鼠标移动;

  • 键盘控制待放置的物体旋转;

  • 当鼠标点击空间中的位置时,物体被放置在该位置。

实现该部分的内容主要需要技术点

  • 使用ScriptableObject类,定义资源;

  • 鼠标跟随3D物体;

  • 利用层级,使不同层级的物体不发生触碰;

成果展示

Scene部分

利用层级,使不同层级的物体不发生触碰

避免鼠标跟随的待放置物体与玩家发生碰撞关系,进行如下设置:

新建层级(Layer), 11层 为 Ghost, 任意层位 Player

BuildingGhost物体的层级设为 Ghost,将 玩家的层级 设为 Player

然后在窗口 Edit -> project Setting -> Physics 将 层级 GhostPlayer 的关系取消勾选。

利用Raycast,确定鼠标点击的世界坐标位置

由于该方法是获取鼠标点击3D物体位置,如果想要让仓库栏的物体放在指定位置,则需要创建Plane地板物体,用于定位鼠标点击的世界坐标位置,然后将物体放在地板上。

利用鼠标的世界坐标位置,让物体跟随鼠标移动

创建空物体 BuildingGhost, 并绑定脚本 BuildingGhost.cs

新建物体实例,然后不断获取鼠标位置,让物体跟随鼠标移动。

跟随鼠标移动的物体层级都设为 11层 Ghost

处理预制体,并拖拽若干预制体到场景中

准备了三种可以放置的物体。

为了让物体能够放置在地板上,而非悬空或穿模,需要让物体的预制体中心Y轴与地板表面重合。

同时下一章节将设计网格系统,帮助更多物体的放置,并且使所有物体的状态都数据化。因此需要模板化处理可放置物体的预制体,这里由于网格系统尚未开发,默认单元格宽度为1*1。

物体所占有网格的面积;

物体的边角位置,将该边角作为预制体的坐标中心;

边角Anchor与面积Area部分在物体准备放置时显示帮助定位,物体放置之后隐藏;

模型相关的内容单独放在一个空物体下。

ScriptableObject配置可放置物体类型

设计ScriptableObject类,用于配置可放置的物体,包含其名称、预制体和单元格大小等;

给予可放置物体可旋转的属性;

其中,物体旋转之后 物体的中心坐标和世界坐标会发生偏差,为了物体跟随鼠标的视觉效果保持始终鼠标在物体的左下方,物体的位置在旋转后需要一定的偏移。

为所有可放置的物体新建为ScriptableObject类实例,并进行配置。

这里说明:配置ScriptableObject类实例,并非一定同本文一样在Asset中进行配置,也可以直接在脚本中 New。

存放所有可放置物体类型,并管理物体的选择、旋转、放置和取消

创建空物体 PlaceObjectBuilding, 并绑定脚本 PlaceObjectBuilding.cs

将所有配置好的ScriptableObject实例放入脚本中,用于后期的选择、旋转、放置和取消。

脚本部分

ScriptableObject类设计

[CreateAssetMenu()]
public class PlacedObjectTypeSO : ScriptableObject
{//改变方向public static Dir GetNextDir(Dir dir){switch (dir){default:case Dir.Down: return Dir.Left;case Dir.Left: return Dir.Up;case Dir.Up: return Dir.Right;case Dir.Right: return Dir.Down;}}public enum Dir{Down,Left,Up,Right,}public string nameString;public Transform prefab;public int width;public int height;public int GetRotationAngle(Dir dir){switch (dir){default:case Dir.Down: return 0;case Dir.Left: return 90;case Dir.Up: return 180;case Dir.Right: return 270;}}public Vector2Int GetRotationOffset(Dir dir){switch (dir){default:case Dir.Down: return new Vector2Int(0, 0);case Dir.Left: return new Vector2Int(0, width);case Dir.Up: return new Vector2Int(width, height);case Dir.Right: return new Vector2Int(height, 0);}}
}

管理放置物体类的设计

这里同时也会用于管理仓库。

public class PlaceObjectBuilding : MonoBehaviour
{public static PlaceObjectBuilding Instance;Inventory inventory;[SerializeField] UI_Inventory ui_inventory;[SerializeField] List<PlacedObjectTypeSO> placedObjectTypeSOList;PlacedObjectTypeSO selectedPlacedObjectTypeSO;public event EventHandler OnSelectedChanged;private Dir dir = Dir.Down;private void Awake(){Instance = this;inventory = new Inventory(new List<Goods>(), (goods) =>{inventory.DeleteGoods(goods.GetGoodsName());selectedPlacedObjectTypeSO = placedObjectTypeSOList.Find(_ => _.nameString == goods.GetGoodsName().ToString());RefreshSelectedObjectType();});ui_inventory.Init(inventory);}private void Update(){if (selectedPlacedObjectTypeSO != null){//鼠标左击 放置物体if (Input.GetMouseButtonDown(0)){Vector3 placePosition = Mouse3D.GetMouseWorldPosition();placePosition.y = 0;Transform placeObjectTransform = Instantiate(selectedPlacedObjectTypeSO.prefab,placePosition,Quaternion.Euler(0, selectedPlacedObjectTypeSO.GetRotationAngle(dir), 0));placeObjectTransform.SetParent(transform.parent);DeselectObjectType();}//键盘0 取消选择的物体,并放回仓库if (Input.GetKeyDown(KeyCode.Alpha0)){GoodsName goodsName = (GoodsName)Enum.Parse(typeof(GoodsName), selectedPlacedObjectTypeSO.nameString);inventory.AddGoods(goodsName);DeselectObjectType();}//键盘R 选择待放置的物体if (Input.GetKeyDown(KeyCode.R)){dir = GetNextDir(dir);}}}public void AddGoods(GoodsName goodsName) {inventory.AddGoods(goodsName);}//取消选择物体private void DeselectObjectType(){selectedPlacedObjectTypeSO = null;RefreshSelectedObjectType();}//选中的物体发生了变化,会触发鼠标跟随的物体也发生改变private void RefreshSelectedObjectType(){OnSelectedChanged?.Invoke(this, EventArgs.Empty);}public PlacedObjectTypeSO GetPlacedObjectTypeSO(){return selectedPlacedObjectTypeSO;}//获取鼠标的位置public Vector3 GetMouseWorldSnappedPosition(){Vector3 mousePosition = Mouse3D.GetMouseWorldPosition();if (selectedPlacedObjectTypeSO != null){//旋转后,物体的位置会发生偏移Vector2Int rotationOffset = selectedPlacedObjectTypeSO.GetRotationOffset(dir);Vector3 placedObjectWorldPosition = mousePosition + new Vector3(rotationOffset.x, 0, rotationOffset.y);return placedObjectWorldPosition;}else{return mousePosition;}}//物体旋转了,放置的物体也需要旋转public Quaternion GetPlacedObjectRotation(){if (selectedPlacedObjectTypeSO != null){return Quaternion.Euler(0, selectedPlacedObjectTypeSO.GetRotationAngle(dir), 0);}else{return Quaternion.identity;}}
}

物体跟随鼠标移动

public class BuildingGhost : MonoBehaviour
{private Transform profab;private PlacedObjectTypeSO placedObjectTypeSO;private void Start(){RefreshVisual();PlaceObjectBuilding.Instance.OnSelectedChanged += Instance_OnSelectedChanged;}private void Instance_OnSelectedChanged(object sender, System.EventArgs e){RefreshVisual();}//不断更新位置private void LateUpdate(){if (placedObjectTypeSO == null) return;Vector3 targetPosition = PlaceObjectBuilding.Instance.GetMouseWorldSnappedPosition();targetPosition.y = 0.3f;transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * 15f);transform.rotation = Quaternion.Lerp(transform.rotation, PlaceObjectBuilding.Instance.GetPlacedObjectRotation(), Time.deltaTime * 15f);}//更新跟随的物体实例private void RefreshVisual(){if (profab != null){Destroy(profab.gameObject);profab = null;}placedObjectTypeSO = PlaceObjectBuilding.Instance.GetPlacedObjectTypeSO();if (placedObjectTypeSO != null){profab = Instantiate(placedObjectTypeSO.prefab, Vector3.zero, Quaternion.identity);DisplayAreaAnchor(profab.Find("locate"), true);profab.parent = transform;profab.localPosition = Vector3.zero;profab.localEulerAngles = Vector3.zero;SetLayerRecursive(profab.gameObject, 11);}}//隐藏定位辅助部分private void DisplayAreaAnchor(Transform locate, bool isVisible){locate.gameObject.SetActive(isVisible);}//设置层级private void SetLayerRecursive(GameObject targetGameObject, int layer){targetGameObject.layer = layer;foreach (Transform child in targetGameObject.transform){SetLayerRecursive(child.gameObject, layer);}}
}

玩家的控制

大多数内容与前篇相同,但是将仓库类inventory操作的部分放到了搭建脚本PlaceObjectBuilding中。

public class Player : MonoBehaviour
{[SerializeField] private float moveSpeed = 20f;private void OnTriggerEnter(Collider c){Transform cProfab = c.transform.parent.parent;if (Enum.TryParse(cProfab.tag, true, out GoodsName goodsName)){//PlaceObjectBuilding单例中操作仓库PlaceObjectBuilding.Instance.AddGoods(goodsName);Destroy(cProfab.gameObject);}}private void Update(){Vector3 inputDir = new Vector3(0, 0, 0);if (Input.GetKey(KeyCode.UpArrow)) inputDir.z += +1f;if (Input.GetKey(KeyCode.DownArrow)) inputDir.z += -1f;if (Input.GetKey(KeyCode.LeftArrow)) inputDir.x += -1f;if (Input.GetKey(KeyCode.RightArrow)) inputDir.x += +1f;Vector3 moveDir = transform.forward * inputDir.z + transform.right * inputDir.x;transform.position += moveDir * moveSpeed * Time.deltaTime;if (Input.GetMouseButtonUp(1)){Debug.Log(Mouse3D.GetMouseWorldPosition());Transform t = Mouse3D.GetClickedTransform();if (t != null){Vector3 targetPosition = t.position;targetPosition.y = transform.position.y;transform.DOMove(targetPosition, 2);}else{Debug.Log("null");}}}
}

其他的,仓库类、物品类没有发生改变,修改的只有物品的种类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/836015.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes v1.16.3版本开启 Job ttlSecondsAfterFinished 自动清理机制

前言 Kubernetes v1.23 之前,Job 在处于 Completed 后,默认是不会被清理的。 完成的 Job 通常不需要留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。 Kubernetes v1.23 之后, TTL 控制器所提供的 TTL 机制。 通过设置 Job 的 .spec.ttlSecondsAfterFini…

当然不是草台班子-冲刺日志2

作业所属课程 软件工程2024作业要求 2024秋软工实践团队作业-第三次( Alpha冲刺)作业目标 alpha冲刺完成项目核心功能团队名称 当然不是草台班子团队成员学号 姓名102201427 侯丽珂102201426 郑嘉祺102201241 戴康怡102201218 肖晗涵112200328 谢李东292300304 陈鹭102201242…

当然不是草台班子-冲刺日志3

作业所属课程 软件工程2024作业要求 2024秋软工实践团队作业-第三次( Alpha冲刺)作业目标 alpha冲刺完成项目核心功能团队名称 当然不是草台班子团队成员学号 姓名102201427 侯丽珂102201426 郑嘉祺102201241 戴康怡102201218 肖晗涵112200328 谢李东292300304 陈鹭102201242…

制作网站修改公司,如何在制作网站过程中修改公司信息

在制作网站的过程中,修改公司信息可以确保网站的准确性和专业性。以下是具体步骤:登录后台:打开浏览器,输入网站的后台地址,使用管理员账号登录。进入公司信息管理:在后台左侧菜单栏中选择“内容” -> “公司信息”。 找到需要修改的信息,点击“编辑”。修改公司信息…

网站是用修改access数据库文件格式,如何在Access数据库中修改网站数据

Access数据库是一种常用的桌面数据库,常用于小型网站的数据管理。以下是具体步骤:打开Access数据库:打开Microsoft Access应用程序。 导入或打开现有的Access数据库文件(.mdb或.accdb)。导航到数据表:在Access主界面中,选择“表”视图。 找到需要修改的数据表,双击打开…

网站首页界面怎么修改,如何在网站后台或代码编辑器中修改首页界面

修改首页界面可以提升网站的视觉效果和用户体验。以下是修改首页界面的步骤:登录网站后台:打开浏览器,输入网站的后台地址,例如 http://yourdomain.com/admin。 输入管理员账号和密码,点击“登录”。进入模板管理:登录后,点击顶部菜单栏中的“模板”或“主题”。 选择“…

vivo 游戏中心包体积优化方案与实践

介绍APP包体积优化的必要性,游戏中心App在实际优化过程中的有效措施,包括一些优化建议以及优化思路。作者:来自 vivo 互联网大前端团队- Ke Jie介绍 App 包体积优化的必要性,游戏中心 App 在实际优化过程中的有效措施,包括一些优化建议以及优化思路。 一、包体积优化的必要…

# 学期(如2024-2025-1) 学号(如:20241402) 《计算机基础与程序设计》第9周学习总结

学期(如2024-2025-1) 学号(如:20241402) 《计算机基础与程序设计》第9周学习总结 作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个…

11.12实验15:职责链模式

[实验任务一]:财务审批 某物资管理系统中物资采购需要分级审批,主任可以审批1万元及以下的采购单,部门经理可以审批5万元及以下的采购单,副总经理可以审批10万元及以下的采购单,总经理可以审批20万元及以下的采购单,20万元以上的采购单需要开职工大会确定。现用职责链模式…

11.13实验16:命令模式

[实验任务一]:多次撤销和重复的命令模式 某系统需要提供一个命令集合(注:可以使用链表,栈等集合对象实现),用于存储一系列命令对象,并通过该命令集合实现多次undo()和redo()操作,可以使用加法运算来模拟实现。 实验要求: 1. 提交类图;2. 提交源代码; package rjsj.n…

Android Studio 2023搭建Flutter开发环境

1、安装Plugins Flutter,搜索出来,就点击Install。安完之后重启Android Studio。 2、再到Plugins查看Installed ,是否安装成功了Flutter和Dart。3、安装Flutter SDK,下载地址:https://docs.flutter.dev/get-started/install/windows/mobile4、配置sdk 的环境变量PATH5、cm…

11.5实验10:组合模式

[实验任务一]:组合模式 用透明组合模式实现教材中的“文件夹浏览”这个例子。 实验要求: 1.文件的执行不需真正实现,只需简单提示即可; 2.提交源代码; 3.注意编程规范。public abstract class AbstractFile {public abstract void add(AbstractFile ele);public abstract …