这样吧,我重新换一种方式问你,我接下来要给你五个代码,让你更加了解我的项目,代码1:using UnityEngine;
using UnityEngine.UI;
using Vuforia;
using TMPro;
using System.Collections.Generic;
using UnityEngine.EventSystems;
[System.Serializable]
public class VaseOption
{
public Button button; // 按钮
public GameObject prefab; // 花瓶预制体
}
public class ARFlowerManager : MonoBehaviour
{
[Header("Vuforia")]
public PlaneFinderBehaviour planeFinder;
public GameObject groundPlaneStage;
[Header("Vase Prefabs")]
public List<VaseOption> vaseOptions = new List<VaseOption>();
[Header("Additional Selection Buttons")]
[Tooltip("额外需要随选择按钮一起切换的 UI(例如花朵列表按钮)。")]
public List<Button> extraSelectionButtons = new List<Button>();
[Header("UI Elements")]
[Tooltip("销毁当前选中花瓶的按钮(可选)。")]
public Button deleteButton;
public TextMeshProUGUI hintText;
[Header("Canvas Control (Optional)")]
[Tooltip("如果设置,首次放置花瓶后会保持激活;否则忽略。")]
public Canvas mainCanvas;
[Header("Touch Controls")]
[Tooltip("平面拖拽时的水平旋转灵敏度(度/像素)。")]
public float rotationSensitivity = 0.15f;
[Tooltip("双指缩放的灵敏度。数值越大,缩放越敏感。")]
public float pinchScaleSensitivity = 0.0025f;
[Tooltip("可被拖拽/选中的层。")]
public LayerMask draggableLayers = ~0;
[Tooltip("屏幕点击拾取模型的最大射线距离。")]
public float maxRaycastDistance = 8f;
private readonly List<VaseManipulator> activeVases = new List<VaseManipulator>();
private GameObject selectedPrefab;
private Button lastSelectedButton;
private Camera mainCamera;
private VaseManipulator activeManipulator;
// 拖拽状态
private Plane dragPlane;
private Vector3 dragOffset;
private bool isDragging;
private Vector2 lastTouchPosition;
// 双指缩放
private bool isPinching;
private float lastPinchDistance;
void Start()
{
mainCamera = Camera.main;
if (hintText != null)
{
hintText.raycastTarget = false;
}
foreach (var opt in vaseOptions)
{
if (opt.button == null) continue;
VaseOption captured = opt;
opt.button.onClick.AddListener(() => OnVaseButtonClicked(captured));
}
if (deleteButton != null)
{
deleteButton.onClick.AddListener(DeleteActiveVase);
deleteButton.gameObject.SetActive(true);
}
if (planeFinder != null)
{
planeFinder.OnInteractiveHitTest.AddListener(OnPlaneHit);
}
UpdateSelectionButtons(true);
UpdateHintText();
}
void Update()
{
HandleTouches();
}
void OnDestroy()
{
if (planeFinder != null)
{
planeFinder.OnInteractiveHitTest.RemoveListener(OnPlaneHit);
}
}
void OnVaseButtonClicked(VaseOption option)
{
if (selectedPrefab == option.prefab)
{
ResetButtonColor(lastSelectedButton);
selectedPrefab = null;
lastSelectedButton = null;
}
else
{
if (lastSelectedButton != null)
{
ResetButtonColor(lastSelectedButton);
}
selectedPrefab = option.prefab;
lastSelectedButton = option.button;
SetButtonSelectedColor(option.button);
}
UpdateHintText();
}
void HandleTouches()
{
if (Input.touchCount == 0)
{
StopDrag();
StopPinch();
return;
}
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (IsTouchOverUI(touch.fingerId))
{
StopDrag();
return;
}
switch (touch.phase)
{
case TouchPhase.Began:
TryBeginDrag(touch);
break;
case TouchPhase.Moved:
case TouchPhase.Stationary:
if (isDragging && activeManipulator != null)
{
UpdateDrag(touch);
}
break;
case TouchPhase.Ended:
case TouchPhase.Canceled:
StopDrag();
break;
}
}
else // 多点触控(>=2)
{
Touch t0 = Input.GetTouch(0);
Touch t1 = Input.GetTouch(1);
if (IsTouchOverUI(t0.fingerId) || IsTouchOverUI(t1.fingerId))
{
StopPinch();
return;
}
if (!isPinching)
{
BeginPinch(t0, t1);
}
else
{
UpdatePinch(t0, t1);
}
}
}
void TryBeginDrag(Touch touch)
{
VaseManipulator manipulator = TryGetManipulatorUnderTouch(touch.position);
if (manipulator == null)
{
StopDrag();
return;
}
SelectManipulator(manipulator);
dragPlane = new Plane(Vector3.up, manipulator.transform.position);
if (TryGetPointOnDragPlane(touch.position, out Vector3 hitPoint))
{
dragOffset = manipulator.transform.position - hitPoint;
}
else
{
dragOffset = Vector3.zero;
}
lastTouchPosition = touch.position;
isDragging = true;
}
void UpdateDrag(Touch touch)
{
lastTouchPosition = touch.position;
if (TryGetPointOnDragPlane(touch.position, out Vector3 hitPoint))
{
Vector3 target = hitPoint + dragOffset;
activeManipulator.MoveTo(target);
}
float rotateDelta = touch.deltaPosition.x * rotationSensitivity;
if (Mathf.Abs(rotateDelta) > Mathf.Epsilon)
{
activeManipulator.RotateBy(rotateDelta);
}
}
void StopDrag()
{
isDragging = false;
}
void BeginPinch(Touch t0, Touch t1)
{
if (activeManipulator == null)
{
activeManipulator = TryGetManipulatorUnderTouch(t0.position) ??
TryGetManipulatorUnderTouch(t1.position);
}
if (activeManipulator == null)
{
return;
}
isPinching = true;
lastPinchDistance = Vector2.Distance(t0.position, t1.position);
}
void UpdatePinch(Touch t0, Touch t1)
{
if (activeManipulator == null)
{
StopPinch();
return;
}
float currentDistance = Vector2.Distance(t0.position, t1.position);
if (lastPinchDistance <= Mathf.Epsilon)
{
lastPinchDistance = currentDistance;
return;
}
float delta = currentDistance - lastPinchDistance;
float scaleFactor = 1f + delta * pinchScaleSensitivity;
activeManipulator.ScaleByFactor(scaleFactor);
lastPinchDistance = currentDistance;
}
void StopPinch()
{
isPinching = false;
lastPinchDistance = 0f;
}
VaseManipulator TryGetManipulatorUnderTouch(Vector2 screenPos)
{
if (mainCamera == null)
{
mainCamera = Camera.main;
}
if (mainCamera == null)
{
return null;
}
Ray ray = mainCamera.ScreenPointToRay(screenPos);
if (Physics.Raycast(ray, out RaycastHit hit, maxRaycastDistance, draggableLayers, QueryTriggerInteraction.Ignore))
{
return hit.collider.GetComponentInParent<VaseManipulator>();
}
return null;
}
bool TryGetPointOnDragPlane(Vector2 screenPos, out Vector3 point)
{
point = Vector3.zero;
if (mainCamera == null)
{
mainCamera = Camera.main;
}
if (mainCamera == null) return false;
Ray ray = mainCamera.ScreenPointToRay(screenPos);
if (dragPlane.Raycast(ray, out float enter))
{
point = ray.GetPoint(enter);
return true;
}
return false;
}
void SelectManipulator(VaseManipulator manipulator)
{
activeManipulator = manipulator;
UpdateHintText();
}
void DeleteActiveVase()
{
if (activeManipulator == null) return;
activeVases.Remove(activeManipulator);
Destroy(activeManipulator.gameObject);
activeManipulator = null;
UpdateHintText();
}
void UpdateSelectionButtons(bool active)
{
foreach (var option in vaseOptions)
{
if (option.button != null)
{
option.button.gameObject.SetActive(active);
}
}
foreach (var extra in extraSelectionButtons)
{
if (extra != null)
{
extra.gameObject.SetActive(active);
}
}
}
void UpdateHintText()
{
if (hintText == null) return;
if (activeVases.Count == 0)
{
if (selectedPrefab == null)
{
hintText.text = "选择一个花瓶,然后点击识别到的平面进行放置。";
}
else
{
hintText.text = "在平面上轻触即可放置花瓶。";
}
return;
}
if (activeManipulator != null)
{
hintText.text = "单指拖拽移动,左右滑动可旋转,双指收放可缩放当前花瓶。";
}
else
{
hintText.text = "点选任意花瓶进入手势编辑模式。";
}
}
bool IsTouchOverUI(int fingerId)
{
if (EventSystem.current == null) return false;
return EventSystem.current.IsPointerOverGameObject(fingerId);
}
void OnPlaneHit(HitTestResult result)
{
if (selectedPrefab == null)
{
UpdateHintText();
return;
}
GameObject vase = Instantiate(
selectedPrefab,
result.Position,
result.Rotation,
groundPlaneStage != null ? groundPlaneStage.transform : null);
vase.name = selectedPrefab.name + "_Instance";
var manipulator = vase.GetComponent<VaseManipulator>();
if (manipulator == null)
{
manipulator = vase.AddComponent<VaseManipulator>();
}
manipulator.manager = this;
activeVases.Add(manipulator);
SelectManipulator(manipulator);
ResetButtonColor(lastSelectedButton);
selectedPrefab = null;
lastSelectedButton = null;
if (mainCanvas != null && !mainCanvas.gameObject.activeSelf)
{
mainCanvas.gameObject.SetActive(true);
}
UpdateHintText();
}
void ResetButtonColor(Button b)
{
if (b == null) return;
var colors = b.colors;
colors.normalColor = Color.white;
b.colors = colors;
}
void SetButtonSelectedColor(Button b)
{
if (b == null) return;
var colors = b.colors;
colors.normalColor = new Color(0.6f, 1f, 0.6f);
b.colors = colors;
}
}
代码2:using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// 可长按的按钮组件,用于检测按钮是否被按住。
/// 仍然保留以兼容旧的按钮交互,但最新版本已改为手势交互。
/// </summary>
[System.Obsolete("HoldableButton 仅用于兼容旧的按钮编辑方式,新的多指手势无需再挂载该组件。")]
public class HoldableButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
private bool isPressed = false;
/// <summary>
/// 按钮是否被按住
/// </summary>
public bool IsPressed
{
get { return isPressed; }
}
public void OnPointerDown(PointerEventData eventData)
{
isPressed = true;
}
public void OnPointerUp(PointerEventData eventData)
{
isPressed = false;
}
public void OnPointerExit(PointerEventData eventData)
{
// 当手指/鼠标移出按钮区域时,也停止按住状态
isPressed = false;
}
void OnDisable()
{
// 当按钮被禁用时,重置状态
isPressed = false;
}
}
代码3:using UnityEngine;
public enum EditAction
{
RotateLeft, // 向左旋转
RotateRight, // 向右旋转
ScaleUp, // 放大
ScaleDown, // 缩小
MoveForward, // 前移
MoveBackward, // 后移
MoveLeft, // 左移
MoveRight, // 右移
MoveUp, // 上升
MoveDown // 下降
}
public class VaseManipulator : MonoBehaviour
{
[Header("Edit Parameters - Continuous Mode")]
[Tooltip("旋转速度(度/秒),建议值:30-60")]
public float rotationSpeed = 45f; // 每秒旋转的角度(度/秒)
[Tooltip("缩放速度(单位/秒),建议值:0.5-1.5")]
public float scaleSpeed = 1.0f; // 每秒缩放的步长
[Tooltip("移动速度(米/秒),建议值:0.2-0.5")]
public float moveSpeed = 0.3f; // 每秒移动的距离(米/秒)
[Header("Legacy Parameters (for single click mode)")]
[Tooltip("单次旋转角度(已废弃,使用rotationSpeed代替)")]
public float rotationStep = 15f;
[Tooltip("单次缩放步长(已废弃,使用scaleSpeed代替)")]
public float scaleStep = 0.1f;
[Tooltip("单次移动距离(已废弃,使用moveSpeed代替)")]
public float moveStep = 0.05f;
public ARFlowerManager manager;
private Vector3 originalScale;
[Header("Scale Range")]
[Tooltip("是否限制缩放范围。如果关闭,将允许任意放大(>0)。")]
public bool limitScaleRange = false;
[Tooltip("最小缩放倍数(相对于初始体积)。")]
[Min(0.01f)] public float minScaleFactor = 0.1f;
[Tooltip("最大缩放倍数(相对于初始体积)。")]
[Min(0.01f)] public float maxScaleFactor = 10f;
void Start()
{
originalScale = transform.localScale;
}
/// <summary>
/// 直接把物体移动到目标位置(用于拖拽操作)。
/// </summary>
public void MoveTo(Vector3 targetPosition)
{
transform.position = targetPosition;
}
/// <summary>
/// 绕世界Y轴增量旋转。
/// </summary>
public void RotateBy(float deltaDegrees)
{
if (Mathf.Approximately(deltaDegrees, 0f)) return;
transform.Rotate(0f, deltaDegrees, 0f, Space.World);
}
/// <summary>
/// 通过倍率缩放模型,内部会自动套用缩放限制。
/// </summary>
public void ScaleByFactor(float factor)
{
if (factor <= 0f || Mathf.Approximately(factor, 1f))
{
return;
}
Vector3 targetScale = transform.localScale * factor;
ApplyScale(targetScale);
}
/// <summary>
/// 连续执行操作(用于长按模式)
/// </summary>
public void PerformContinuousAction(EditAction action)
{
float deltaTime = Time.deltaTime;
switch (action)
{
case EditAction.RotateLeft:
RotateLeftContinuous(deltaTime);
break;
case EditAction.RotateRight:
RotateRightContinuous(deltaTime);
break;
case EditAction.ScaleUp:
ScaleUpContinuous(deltaTime);
break;
case EditAction.ScaleDown:
ScaleDownContinuous(deltaTime);
break;
case EditAction.MoveForward:
MoveForwardContinuous(deltaTime);
break;
case EditAction.MoveBackward:
MoveBackwardContinuous(deltaTime);
break;
case EditAction.MoveLeft:
MoveLeftContinuous(deltaTime);
break;
case EditAction.MoveRight:
MoveRightContinuous(deltaTime);
break;
case EditAction.MoveUp:
MoveUpContinuous(deltaTime);
break;
case EditAction.MoveDown:
MoveDownContinuous(deltaTime);
break;
}
}
/// <summary>
/// 单次执行操作(保留用于兼容性)
/// </summary>
public void PerformAction(EditAction action)
{
switch (action)
{
case EditAction.RotateLeft:
RotateLeft();
break;
case EditAction.RotateRight:
RotateRight();
break;
case EditAction.ScaleUp:
ScaleUp();
break;
case EditAction.ScaleDown:
ScaleDown();
break;
case EditAction.MoveForward:
MoveForward();
break;
case EditAction.MoveBackward:
MoveBackward();
break;
case EditAction.MoveLeft:
MoveLeft();
break;
case EditAction.MoveRight:
MoveRight();
break;
case EditAction.MoveUp:
MoveUp();
break;
case EditAction.MoveDown:
MoveDown();
break;
}
}
// ========== 连续操作方法(长按模式) ==========
void RotateLeftContinuous(float deltaTime)
{
// 绕Y轴逆时针旋转(向左)
float rotationAmount = -rotationSpeed * deltaTime;
transform.Rotate(0, rotationAmount, 0, Space.World);
}
void RotateRightContinuous(float deltaTime)
{
// 绕Y轴顺时针旋转(向右)
float rotationAmount = rotationSpeed * deltaTime;
transform.Rotate(0, rotationAmount, 0, Space.World);
}
void ScaleUpContinuous(float deltaTime)
{
float scaleAmount = scaleSpeed * deltaTime;
Vector3 newScale = transform.localScale + Vector3.one * scaleAmount;
ApplyScale(newScale);
}
void ScaleDownContinuous(float deltaTime)
{
float scaleAmount = scaleSpeed * deltaTime;
Vector3 newScale = transform.localScale - Vector3.one * scaleAmount;
ApplyScale(newScale);
}
void MoveForwardContinuous(float deltaTime)
{
// 在平面上向前移动(相对于摄像机的Z轴正方向)
Vector3 forward = Camera.main.transform.forward;
forward.y = 0; // 保持Y轴不变,只在平面上移动
forward.Normalize();
transform.position += forward * moveSpeed * deltaTime;
}
void MoveBackwardContinuous(float deltaTime)
{
// 在平面上向后移动
Vector3 backward = -Camera.main.transform.forward;
backward.y = 0;
backward.Normalize();
transform.position += backward * moveSpeed * deltaTime;
}
void MoveLeftContinuous(float deltaTime)
{
// 在平面上向左移动(相对于摄像机的X轴负方向)
Vector3 left = -Camera.main.transform.right;
left.y = 0;
left.Normalize();
transform.position += left * moveSpeed * deltaTime;
}
void MoveRightContinuous(float deltaTime)
{
// 在平面上向右移动(相对于摄像机的X轴正方向)
Vector3 right = Camera.main.transform.right;
right.y = 0;
right.Normalize();
transform.position += right * moveSpeed * deltaTime;
}
void MoveUpContinuous(float deltaTime)
{
transform.position += Vector3.up * moveSpeed * deltaTime;
}
void MoveDownContinuous(float deltaTime)
{
transform.position += Vector3.down * moveSpeed * deltaTime;
}
// ========== 单次操作方法(保留用于兼容性) ==========
void RotateLeft()
{
// 绕Y轴逆时针旋转(向左)
transform.Rotate(0, -rotationStep, 0, Space.World);
}
void RotateRight()
{
// 绕Y轴顺时针旋转(向右)
transform.Rotate(0, rotationStep, 0, Space.World);
}
void ScaleUp()
{
Vector3 newScale = transform.localScale + Vector3.one * scaleStep;
ApplyScale(newScale);
}
void ScaleDown()
{
Vector3 newScale = transform.localScale - Vector3.one * scaleStep;
ApplyScale(newScale);
}
void MoveForward()
{
// 在平面上向前移动(相对于摄像机的Z轴正方向)
Vector3 forward = Camera.main.transform.forward;
forward.y = 0; // 保持Y轴不变,只在平面上移动
forward.Normalize();
transform.position += forward * moveStep;
}
void MoveBackward()
{
// 在平面上向后移动
Vector3 backward = -Camera.main.transform.forward;
backward.y = 0;
backward.Normalize();
transform.position += backward * moveStep;
}
void MoveLeft()
{
// 在平面上向左移动(相对于摄像机的X轴负方向)
Vector3 left = -Camera.main.transform.right;
left.y = 0;
left.Normalize();
transform.position += left * moveStep;
}
void MoveRight()
{
// 在平面上向右移动(相对于摄像机的X轴正方向)
Vector3 right = Camera.main.transform.right;
right.y = 0;
right.Normalize();
transform.position += right * moveStep;
}
void MoveUp()
{
transform.position += Vector3.up * moveStep;
}
void MoveDown()
{
transform.position += Vector3.down * moveStep;
}
void ApplyScale(Vector3 targetScale)
{
const float epsilon = 0.01f;
float scaleFactor = targetScale.x / originalScale.x;
if (scaleFactor <= epsilon)
{
return;
}
if (!limitScaleRange || (scaleFactor >= minScaleFactor && scaleFactor <= maxScaleFactor))
{
transform.localScale = targetScale;
}
}
}
代码4:using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace ARFlower
{
/// <summary>
/// 控制底部抽屉式列表的开关,并把状态同步给 Animator。
/// 实际的展开/收起动画完全由 Animator 中的状态机和动画片段控制。
/// </summary>
public class DrawerPanelController : MonoBehaviour
{
[Header("Required References")]
[SerializeField] private RectTransform drawerPanel;
[SerializeField] private Button toggleButton;
[Header("Animator Settings")]
[Tooltip("控制抽屉动画的 Animator 组件。")]
[SerializeField] private Animator drawerAnimator;
[Tooltip("Animator 中用于表示“抽屉是否打开”的 Bool 参数名称。")]
[SerializeField] private string animatorOpenBoolParam = "IsOpen";
[Tooltip("是否在启用时就默认打开抽屉。")]
[SerializeField] private bool startOpened = false;
[Tooltip("在抽屉被隐藏时是否自动隐藏主按钮(防止阶段切换时残留)。")]
[SerializeField] private bool hideToggleWhenDrawerHidden = true;
[Header("Debugging")]
[Tooltip("打开后会在控制台输出按钮绑定与开关调用的详细日志。")]
[SerializeField] private bool enableDebugLogs = false;
private bool isOpen;
private bool runtimeListenerAdded;
private bool? lastAnimatorBoolValue;
private int lastToggleFrame = -1;
public bool IsOpen => isOpen;
/// <summary>
/// 当抽屉开关状态改变时触发,参数表示当前是否开启。
/// </summary>
public event Action<bool> DrawerStateChanged;
private void Awake()
{
if (!drawerPanel)
{
drawerPanel = GetComponent<RectTransform>();
}
if (!drawerAnimator && drawerPanel)
{
drawerAnimator = drawerPanel.GetComponent<Animator>();
}
isOpen = startOpened;
ApplyInstantState(isOpen);
}
private void Start()
{
CacheDrawerActiveState();
}
private void OnEnable()
{
LogDebug(
$"OnEnable. toggleButton={(toggleButton ? toggleButton.name : "null")}, drawerAnimator={(drawerAnimator ? drawerAnimator.name : "null")}");
if (toggleButton != null && !HasPersistentDrawerBinding())
{
toggleButton.onClick.AddListener(ToggleDrawer);
runtimeListenerAdded = true;
LogDebug("Added runtime ToggleDrawer listener (no persistent binding detected).");
}
else if (toggleButton != null)
{
LogDebug("Detected persistent binding in OnClick(). Skipping runtime listener.");
}
}
private void OnDisable()
{
if (toggleButton != null && runtimeListenerAdded)
{
toggleButton.onClick.RemoveListener(ToggleDrawer);
runtimeListenerAdded = false;
LogDebug("Removed runtime ToggleDrawer listener in OnDisable.");
}
if (hideToggleWhenDrawerHidden)
{
SetToggleVisibility(false);
}
}
public void ToggleDrawer()
{
if (lastToggleFrame == Time.frameCount)
{
LogDebug("ToggleDrawer ignored: another toggle already processed this frame.");
return;
}
lastToggleFrame = Time.frameCount;
LogDebug($"ToggleDrawer invoked. isOpen(before)={isOpen}");
if (isOpen)
{
CloseDrawer();
}
else
{
OpenDrawer();
}
}
public void OpenDrawer()
{
LogDebug("OpenDrawer requested.");
SetDrawerState(true);
}
public void CloseDrawer()
{
LogDebug("CloseDrawer requested.");
SetDrawerState(false);
}
private void SetDrawerState(bool open)
{
LogDebug($"SetDrawerState called. targetState={(open ? "Open" : "Closed")}, currentState={(isOpen ? "Open" : "Closed")}");
if (isOpen == open)
{
LogDebug("State unchanged. Skip animator update.");
return;
}
isOpen = open;
LogDebug($"State updated. isOpen(now)={isOpen}");
UpdateAnimator();
DrawerStateChanged?.Invoke(isOpen);
}
private void ApplyInstantState(bool open)
{
if (drawerPanel == null)
{
return;
}
LogDebug($"ApplyInstantState: force set to {(open ? "Open" : "Closed")} without animation.");
isOpen = open;
UpdateAnimator();
}
private bool lastDrawerActive = true;
private void Update()
{
MonitorAnimatorBoolDebug();
}
private void LateUpdate()
{
CacheDrawerActiveState();
}
private void CacheDrawerActiveState()
{
if (!hideToggleWhenDrawerHidden || toggleButton == null || drawerPanel == null)
{
return;
}
bool active = drawerPanel.gameObject.activeInHierarchy;
if (active != lastDrawerActive)
{
SetToggleVisibility(active);
lastDrawerActive = active;
}
}
private void SetToggleVisibility(bool visible)
{
if (toggleButton == null) return;
var targetObject = toggleButton.gameObject;
if (targetObject.activeSelf != visible)
{
targetObject.SetActive(visible);
}
}
/// <summary>
/// 把当前 isOpen 状态同步给 Animator。
/// </summary>
private void UpdateAnimator()
{
if (drawerAnimator == null)
{
LogDebug("UpdateAnimator skipped: drawerAnimator missing.");
return;
}
if (string.IsNullOrEmpty(animatorOpenBoolParam))
{
LogDebug("Animator bool parameter name is empty, skip updating.");
return;
}
drawerAnimator.SetBool(animatorOpenBoolParam, isOpen);
lastAnimatorBoolValue = isOpen;
LogDebug($"Animator parameter set: {animatorOpenBoolParam}={(isOpen ? "True" : "False")}. Triggered {(isOpen ? "Open" : "Close")} animation.");
}
/// <summary>
/// 当在 Animator 窗口里手动勾选 Bool 参数时,输出调试日志方便排查。
/// </summary>
private void MonitorAnimatorBoolDebug()
{
if (!enableDebugLogs)
{
return;
}
if (drawerAnimator == null || string.IsNullOrEmpty(animatorOpenBoolParam))
{
return;
}
bool current = drawerAnimator.GetBool(animatorOpenBoolParam);
if (!lastAnimatorBoolValue.HasValue)
{
lastAnimatorBoolValue = current;
LogDebug(
$"Animator bool monitor initialized. {animatorOpenBoolParam}={(current ? "True" : "False")}, drawer state={isOpen}.");
return;
}
if (lastAnimatorBoolValue.Value != current)
{
LogDebug(
$"Animator bool changed manually to {(current ? "True" : "False")}. Drawer internal state={isOpen}. (Param: {animatorOpenBoolParam})");
lastAnimatorBoolValue = current;
}
}
/// <summary>
/// 检测 Button 的 OnClick() 是否已经在 Inspector 中绑定过当前组件的任意抽屉方法。
/// 如果已有,就无需再在代码里重复绑定,避免按钮点击两次。
/// </summary>
private bool HasPersistentDrawerBinding()
{
if (toggleButton == null)
{
return false;
}
var onClick = toggleButton.onClick;
int persistentCount = onClick.GetPersistentEventCount();
LogDebug($"Inspecting persistent bindings. Count={persistentCount}");
for (int i = 0; i < persistentCount; i++)
{
if (onClick.GetPersistentTarget(i) != this)
{
LogDebug($"Persistent binding #{i} target={onClick.GetPersistentTarget(i)?.name ?? "null"} (ignored).");
continue;
}
string method = onClick.GetPersistentMethodName(i);
if (method == nameof(ToggleDrawer) ||
method == nameof(OpenDrawer) ||
method == nameof(CloseDrawer))
{
LogDebug($"Persistent binding #{i} matches method {method}.");
return true;
}
else
{
LogDebug($"Persistent binding #{i} method {method} does not match.");
}
}
return false;
}
private void LogDebug(string message)
{
if (!enableDebugLogs)
{
return;
}
Debug.Log($"[DrawerPanelController] {name}: {message}", this);
}
}
}
代码5:using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using Vuforia;
namespace ARFlower
{
/// <summary>
/// Binds Vuforia image targets to hidden canvas roots. When a target is tracked,
/// the corresponding canvas is activated so the player can place flowers using
/// the existing vase workflow.
/// </summary>
[DisallowMultipleComponent]
public class ImageTargetCanvasController : MonoBehaviour
{
[Serializable]
public class TargetCanvasBinding
{
[Tooltip("Image Target (ObserverBehaviour) generated by Vuforia for this card.")]
public ObserverBehaviour observer;
[Tooltip("Canvas root that should become active when the target is detected.")]
public GameObject canvasRoot;
[Tooltip("If enabled, the canvas stays active even after tracking is lost.")]
public bool keepActiveAfterRecognition = true;
[Tooltip("Optional hint text to push to the shared hint label when this target is active.")]
[TextArea] public string hintMessage;
}
[Header("Bindings")]
[SerializeField] private TargetCanvasBinding[] bindings = Array.Empty<TargetCanvasBinding>();
[Header("Tracking Lost Behaviour")]
[Tooltip("If false, canvases remain active forever after first detection. Enable to automatically hide them when the card is lost.")]
[SerializeField] private bool deactivateOnTrackingLost = false;
[Tooltip("Delay before hiding a canvas after tracking is lost (only when deactivateOnTrackingLost is true).")]
[SerializeField, Min(0f)] private float lostTrackingDelay = 0.35f;
[Header("Shared Hint Override (Optional)")]
[SerializeField] private TMP_Text sharedHint;
[SerializeField, TextArea] private string fallbackHintMessage;
private readonly Dictionary<ObserverBehaviour, TargetCanvasBinding> _lookup = new();
private string _cachedInitialHint;
/// <summary>
/// 当某个Canvas被激活时回调,参数为该Canvas根节点。
/// </summary>
public event Action<GameObject> CanvasActivated;
/// <summary>
/// 当某个Canvas被隐藏时回调,参数为该Canvas根节点。
/// </summary>
public event Action<GameObject> CanvasDeactivated;
private void Awake()
{
foreach (var binding in bindings)
{
if (binding?.observer == null || binding.canvasRoot == null)
{
continue;
}
binding.canvasRoot.SetActive(false);
_lookup[binding.observer] = binding;
binding.observer.OnTargetStatusChanged += HandleTargetStatusChanged;
}
if (sharedHint != null)
{
_cachedInitialHint = string.IsNullOrEmpty(sharedHint.text)
? fallbackHintMessage
: sharedHint.text;
}
else
{
_cachedInitialHint = fallbackHintMessage;
}
}
private void OnDestroy()
{
foreach (var kvp in _lookup)
{
if (kvp.Key != null)
{
kvp.Key.OnTargetStatusChanged -= HandleTargetStatusChanged;
}
}
}
private void HandleTargetStatusChanged(ObserverBehaviour observer, TargetStatus status)
{
if (!_lookup.TryGetValue(observer, out var binding))
{
return;
}
bool isTracked =
status.Status == Status.TRACKED ||
status.Status == Status.EXTENDED_TRACKED ||
status.Status == Status.LIMITED;
if (isTracked)
{
ActivateCanvas(binding);
}
else if (deactivateOnTrackingLost && !binding.keepActiveAfterRecognition)
{
StartCoroutine(DeactivateAfterDelay(binding.canvasRoot));
}
}
private void ActivateCanvas(TargetCanvasBinding binding)
{
if (!binding.canvasRoot.activeSelf)
{
binding.canvasRoot.SetActive(true);
CanvasActivated?.Invoke(binding.canvasRoot);
}
ApplyHint(binding.hintMessage);
}
private IEnumerator DeactivateAfterDelay(GameObject canvas)
{
if (canvas == null)
{
yield break;
}
if (lostTrackingDelay > 0f)
{
yield return new WaitForSeconds(lostTrackingDelay);
}
canvas.SetActive(false);
CanvasDeactivated?.Invoke(canvas);
ResetHint();
}
private void ApplyHint(string message)
{
if (sharedHint == null || string.IsNullOrEmpty(message))
{
return;
}
sharedHint.text = message;
}
private void ResetHint()
{
if (sharedHint == null)
{
return;
}
if (!string.IsNullOrEmpty(_cachedInitialHint))
{
sharedHint.text = _cachedInitialHint;
}
else
{
sharedHint.text = string.Empty;
}
}
}
}
我现在遇到了问题,我是使用Vuforia engine在unity里面开发部署到android手机上的AR插花游戏,我现在只通过这五个代码实现了放置物体到AR识别的平面上,但是我本来还设置了点击物体即选中物体可以通过手机自带的多点触控来实现单指拖动物体,双指捏合缩放物体,单指左右滑动旋转物体,但是现在都没实现,请你仔细检查我的代码,告诉我哪些部分代码需要修改
最新发布