欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Unity 实现一个可拓展的简单物品交互系统

Unity 实现一个可拓展的简单物品交互系统

2025/3/14 15:38:11 来源:https://blog.csdn.net/2301_77947509/article/details/146162949  浏览:    关键词:Unity 实现一个可拓展的简单物品交互系统

         脚本由本人亲手所写,部分内容用ai的辅助 比如剔除无用组件,所以可以很容易看出来这个所谓的交互系统的稚嫩与不足

        不过经我验证,可以做到只需继承基类就可以写出新的交互逻辑

        比如:

        取走和放置物品

        钥匙门,普通门和互锁门的实现

        以及最最基本的信息显示

视频演示: 

Unity花了一个多小时做了一个可拓展的简单物品交互系统

 

        因为过于简单 思路也比较顺畅所以我就提供一下放置和捡起物品的实现思路 :

        下面是参数说明,我用ai来解释吧 对于这种事情 还是ai做的比较好

1. InteractableObjectBase 类

        这个类是一个基类,用于定义可交互对象的基本属性和行为

using TMPro;
using UnityEngine;
using UnityEngine.UI;
[System.Serializable]
public class InteractableObjectBase : MonoBehaviour
{[Header("交互设置")] [Tooltip("显示名称")]public string objectInfo;[Tooltip("可交互状态")]public bool isInteractable = true;[Tooltip("有效交互距离")][Range(0, 5)] public float checkDistance = 3f;[Tooltip("是否通过配置表设置文字")]public bool useConfig = false;public TextMeshProUGUI info;public Transform player;protected virtual void Start() {info = transform.Find("Canvas").GetComponentInChildren<TextMeshProUGUI>();player = FindFirstObjectByType<InteractableSystem>().transform;}public virtual void ShowInfo() {info.rectTransform.LookAt(player);info.text = objectInfo;info.alpha = 1;}public virtual void HideInfo() {info.alpha = 0;}public void GetInfoResouce() {if (useConfig == false){objectInfo = this.gameObject.name;}else {//请在这里填写内容}}public virtual void Interact() { } #if UNITY_EDITOR#endif
}
属性
  • objectInfo:可交互对象的显示名称
  • isInteractable:表示该对象是否可交互,默认为 true
  • checkDistance:有效交互距离,范围在 0 到 5 之间,默认为 3
  • useConfig:是否通过配置表设置文字信息
  • info:用于显示对象信息的 TextMeshProUGUI 组件
  • player:玩家的 Transform 组件
方法
  • Start():在对象开始时调用,初始化 info 和 player
  • ShowInfo():显示对象信息,将信息文本框朝向玩家,并设置文本内容和透明度
  • HideInfo():隐藏对象信息,将信息文本框的透明度设置为 0
  • GetInfoResouce():根据 useConfig 的值设置 objectInfo
  • Interact():虚方法,用于定义交互行为,具体实现由子类完成

2. InteractPickAndSet 类

这个类继承自 InteractableObjectBase,用于实现物品的捡起和放置功能

using TMPro;
using UnityEngine;public class InteractPickAndSet : InteractableObjectBase
{[Header("是否可以捡起")]public bool canPickup;[Header("手持设置")][SerializeField] private Vector3 holdScale = new Vector3(0.3f, 0.3f, 0.3f);[Header("拿起后的位置和缩放大小")][SerializeField] private Transform handPos;[SerializeField] private Vector3 scale = new Vector3(0.3f, 0.3f, 0.3f);public bool isHolding;[Header("放置设置")][SerializeField] private LayerMask placementMask;  // 可放置表面层级(在Inspector中设置)[SerializeField] private Material previewMaterial; // 半透明预览材质[SerializeField] private float maxPlaceDistance = 3f;private GameObject go;private Vector3 originalScale;private Transform originalParent;private Collider objCollider;private float originalY; protected override void Start(){base.Start();originalScale = transform.localScale;originalParent = transform.parent;if (handPos == null)handPos = player.transform.Find("Hand");objCollider = GetComponentInChildren<Collider>();}public override void Interact(){if (!isHolding)PickUp();elseTryPlace();}private void PickUp(){if (!canPickup || handPos.childCount > 0) return;// 保存原始状态originalParent = transform.parent;originalScale = transform.localScale;originalY = transform.position.y; // 保存原始的y坐标// 移动到手上transform.SetParent(handPos);transform.localPosition = Vector3.zero;transform.localRotation = Quaternion.identity;transform.localScale = holdScale;isHolding = true;objCollider.enabled = false;}private void TryPlace(){if (go == null) return;// 执行放置transform.SetParent(originalParent);Vector3 newPosition = go.transform.position;newPosition.y = originalY; transform.position = newPosition;transform.rotation = go.transform.rotation;transform.localScale = originalScale;objCollider.enabled = true;Destroy(go);isHolding = false;}void Update(){if (!isHolding) return;UpdateSetPreview();if (Input.GetKeyDown(KeyCode.Q)){CancelHold();}}private void UpdateSetPreview(){Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));if (Physics.Raycast(ray, out RaycastHit hit, maxPlaceDistance, placementMask)){if (go == null){CreatePreview();}go.transform.position = hit.point;go.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);}else{DestroyHoldObj();}}private void CreatePreview(){go = Instantiate(gameObject);go.transform.SetParent(null);// 恢复原始缩放go.transform.localScale = originalScale;// 禁用所有不需要的组件foreach (var comp in go.GetComponents<Component>()){if (comp is Transform || comp is MeshFilter || comp is MeshRenderer)continue;Destroy(comp);}// 设置半透明材质var renderer = go.GetComponentInChildren<Renderer>();renderer.material = previewMaterial;objCollider.enabled = false;}private void DestroyHoldObj(){if (go != null){Destroy(go);go = null;}}private void CancelHold(){transform.SetParent(originalParent);Vector3 newPosition = Vector3.zero;newPosition.y = originalY; transform.localPosition = newPosition;transform.localScale = originalScale;objCollider.enabled = true;DestroyHoldObj();isHolding = false;}public override void ShowInfo(){if (!isHolding) base.ShowInfo();}void OnDestroy(){DestroyHoldObj();}
}
属性
  • canPickup:表示该物品是否可以被捡起
  • holdScale:物品被拿起后的缩放大小
  • handPos:物品被拿起后放置的位置
  • scale:物品的缩放大小
  • isHolding:表示物品是否正在被持有
  • placementMask:可放置表面的层级
  • previewMaterial:半透明预览材质
  • maxPlaceDistance:最大放置距离
  • go:预览对象。
  • originalScale:物品的原始缩放大小
  • originalParent:物品的原始父对象
  • objCollider:物品的碰撞器
  • originalY:物品的原始 Y 坐标
方法
  • Start():在对象开始时调用,初始化 originalScale originalParenthandPos 和 objCollider
  • Interact():重写父类的 Interact 方法,根据 isHolding 的值决定是捡起还是放置物品。
  • PickUp():捡起物品,将物品移动到手上,并保存原始状态
  • TryPlace():尝试放置物品,将物品放置到预览对象的位置,并恢复原始状态
  • Update():在每一帧调用,更新放置预览,并处理取消持有操作
  • UpdateSetPreview():更新放置预览,根据射线检测结果创建或销毁预览对象
  • CreatePreview():创建预览对象,设置其缩放大小和材质
  • DestroyHoldObj():销毁预览对象
  • CancelHold():取消持有物品,将物品放回原始位置,并恢复原始状态
  • ShowInfo():重写父类的 ShowInfo 方法,只有在物品未被持有时才显示信息
  • OnDestroy():在对象销毁时调用,销毁预览对象

3. InteractableSystem 类

        这个类用于管理可交互对象的检测和交互

using UnityEngine;
using TMPro;public class InteractableSystem : MonoBehaviour
{#region Debug查看// 当前持有的可交互物体[SerializeField] private InteractableObjectBase currentObj;// 上一个持有的可交互物体[SerializeField] private InteractableObjectBase lastObj;[SerializeField] private InteractableObjectBase heldObject;#endregion[SerializeField] private UICursor cursor;[SerializeField] private Camera playerCamera;[Header("检测设置")][Tooltip("检测距离(米)")]public float playerCheckDistance = 5f;[Tooltip("射线半径")][Range(0, 0.5f)] public float sphereRadius = 0.1f;[Header("性能优化")][Tooltip("检测频率(秒)")]public float checkInterval = 0.1f;[Tooltip("启用检测距离优化")]public bool useSqrDistance = true;private float lastCheckTime;[Header("交互层级")]private LayerMask interactableLayer;public void Start(){interactableLayer = LayerMask.GetMask("Interactable");}void Update(){if (Time.time - lastCheckTime >= checkInterval){UpdateInteraction();lastCheckTime = Time.time;}UpdateUI();}/// <summary>/// 更新检测 本质就是球形检测/// </summary>private void UpdateInteraction(){currentObj = null;Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));if (Physics.SphereCast(ray, sphereRadius, out RaycastHit hit, playerCheckDistance, interactableLayer)){// 从碰撞器所在物体的父级获取 InteractableObject 组件InteractableObjectBase obj = hit.collider.GetComponentInParent<InteractableObjectBase>();if (obj != null && obj.isInteractable){if (CheckDistanceValid(hit.point, obj.checkDistance)){currentObj = obj;}}        }}/// <summary>/// 优化距离 /// </summary>/// <param name="targetPos"></param>/// <param name="checkDistance"></param>/// <returns></returns>private bool CheckDistanceValid(Vector3 targetPos, float checkDistance){if (useSqrDistance){float sqrDist = (transform.position - targetPos).sqrMagnitude;return sqrDist <= checkDistance * checkDistance;}return Vector3.Distance(transform.position, targetPos) <= checkDistance;}private void UpdateUI(){#region 这里添加检测交互按键if (Input.GetKeyDown(KeyCode.E)){InteractableObjectBase targetObj = heldObject != null ? heldObject : currentObj;if (targetObj != null){targetObj.Interact(); // 触发交互行为if (targetObj is InteractPickAndSet pickAndSet){if (pickAndSet.isHolding){heldObject = targetObj; // 拿起物品}else{heldObject = null; // 放置物品}}}}#endregionif (currentObj != null){Vector3 screenPos = playerCamera.WorldToScreenPoint(currentObj.transform.position);if (screenPos.z > 0)//不重叠显示{                currentObj.ShowInfo();cursor.SetHighlight(true);// 如果当前物体和上一个物体不同,隐藏上一个物体的信息if (lastObj != null && lastObj != currentObj){lastObj.HideInfo();}lastObj = currentObj;return;}}// 如果当前没有检测到可交互物体,隐藏上一个物体的信息if (lastObj != null){lastObj.HideInfo();lastObj = null;}cursor.SetHighlight(false);}
}
属性
  • currentObj:当前检测到的可交互对象
  • lastObj:上一个检测到的可交互对象
  • heldObject:当前持有的可交互对象
  • cursor:UI 光标
  • playerCamera:玩家的相机
  • playerCheckDistance:检测距离
  • sphereRadius:射线半径
  • checkInterval:检测频率
  • useSqrDistance:是否启用检测距离优化
  • lastCheckTime:上次检测的时间
  • interactableLayer:可交互对象的层级
方法
  • Start():在对象开始时调用,初始化 interactableLayer
  • Update():在每一帧调用,根据检测频率更新交互检测和 UI
  • UpdateInteraction():更新交互检测,使用球形射线检测可交互对象
  • CheckDistanceValid():检查距离是否有效,根据 useSqrDistance 的值选择不同的计算方法
  • UpdateUI():更新 UI,处理交互按键事件,显示或隐藏可交互对象的信息,并设置光标的高亮状态

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词