0

这是我附加到我想要交互的对象的可交互项目脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;

public class InteractableItem : MonoBehaviour
{
    public enum InteractableMode
    {
        Description,
        Action,
        ActionWithoutThrow
    };

    public InteractableMode interactableMode = InteractableMode.Description;
    public float distance;

    [TextArea(1, 10)]
    public string description = "";

    public bool IsAnyAction()
    {
        return interactableMode == InteractableMode.ActionWithoutThrow || interactableMode == InteractableMode.Action;
    }
}

此脚本使用 InteractableItem 脚本:

using UnityEngine;
using System;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;

[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
    public List<InteractableItem> lookObj = new List<InteractableItem>();
    public GameObject objToThrow;
    public Text text;
    public float weightDamping = 1.5f;
    public bool RightHandToTarget = true;
    public float throwSpeed;
    public bool handFinishedMove = false;

    private List<InteractableItem> allDetectedItems;
    private Animator animator;
    private InteractableItem lastPrimaryTarget;
    private float lerpEndDistance = 0.1f;
    private float finalLookWeight = 0;
    private bool transitionToNextTarget = false;

    void Start()
    {
        animator = GetComponent<Animator>();
        allDetectedItems = new List<InteractableItem>();
    }

    // Callback for calculating IK
    void OnAnimatorIK()
    {
        if (lookObj != null)
        {
            lookObj.RemoveAll(x => x == null);

            InteractableItem primaryTarget = null;
            
            float closestLookWeight = 0;

            // Here we find the target which is closest (by angle) to the players view line
            allDetectedItems.Clear();
            foreach (InteractableItem target in lookObj)
            {
                Vector3 lookAt = target.transform.position - transform.position;
                lookAt.y = 0f;

                // Filter out all objects that are too far away
                if (lookAt.magnitude > target.distance) continue;

                float dotProduct = Vector3.Dot(new Vector3(transform.forward.x, 0f, transform.forward.z).normalized, lookAt.normalized);
                float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
                if (lookWeight > 0.1f && lookWeight > closestLookWeight)
                {
                    closestLookWeight = lookWeight;
                    primaryTarget = target;
                    allDetectedItems.Add(target);
                }
            }

            if (primaryTarget != null)
            {
                if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f))
                {
                    // Here we start a new transition because the player looks already to a target but
                    // we have found another target the player should look at
                    transitionToNextTarget = true;
                }
            }

            // The player is in a neutral look position but has found a new target
            if ((primaryTarget != null) && !transitionToNextTarget)
            {
                if(primaryTarget.interactableMode == InteractableItem.InteractableMode.ActionWithoutThrow)
                {
                    RightHandToTarget = true;
                }

                lastPrimaryTarget = primaryTarget;
                //finalLookWeight = Mathf.Lerp(finalLookWeight, closestLookWeight, Time.deltaTime * weightDamping);
                finalLookWeight = Mathf.Lerp(finalLookWeight, 1f, Time.deltaTime * weightDamping);
                float bodyWeight = finalLookWeight * .75f;
                animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
                animator.SetLookAtPosition(primaryTarget.transform.position);

                if (RightHandToTarget && primaryTarget.IsAnyAction())
                {
                    Vector3 relativePos = primaryTarget.transform.position - transform.position;
                    Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up);
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight);
                    animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget);
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 1f * closestLookWeight);
                    animator.SetIKPosition(AvatarIKGoal.RightHand, primaryTarget.transform.position);

                    // -> new code block
                    if (finalLookWeight > 0.95f) // here you can play with a value between 0.95f -> 1.0f
                    {
                        

                        if(primaryTarget.interactableMode == InteractableItem.InteractableMode.Action)
                        // call your funtion to shoot something here
                        StartCoroutine(ThrowObject(objToThrow.transform, primaryTarget.transform.position, 30f));
                    }

                    if(finalLookWeight > 0.9f)
                    {
                        handFinishedMove = true;
                    }
                }
            }

            // Let the player smoothly look away from the last target to the neutral look position
            if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget)
            {
                finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, Time.deltaTime * weightDamping);
                float bodyWeight = finalLookWeight * .75f;
                animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
                animator.SetLookAtPosition(lastPrimaryTarget.transform.position);

                if (RightHandToTarget)
                {
                    Vector3 relativePos = lastPrimaryTarget.transform.position - transform.position;
                    Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up);
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight);
                    animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget);
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 0.5f * closestLookWeight);
                    animator.SetIKPosition(AvatarIKGoal.RightHand, lastPrimaryTarget.transform.position);
                }

                if (finalLookWeight < lerpEndDistance)
                {
                    transitionToNextTarget = false;
                    finalLookWeight = 0f;
                    lastPrimaryTarget = null;
                }
            }

            // Show primary object found by the player
            if (primaryTarget != null)
            {
                text.text = primaryTarget.description;
            }
            else
            {
                text.text = "";
            }
        }
    }

    IEnumerator ThrowObject(Transform objectToMove, Vector3 toPosition, float duration)
    {
        float counter = 0;

        while (counter < duration)
        {
            counter += Time.deltaTime;
            Vector3 currentPos = objectToMove.position;

            float time = Vector3.Distance(currentPos, toPosition) / (duration - counter) * Time.deltaTime;

            objectToMove.position = Vector3.MoveTowards(currentPos, toPosition, time);

            yield return null;
        }
    }
}

问题是当我在游戏中有两个非常接近的对象(项目)时,它们之间的距离例如为 1 或小于 1。然后 IKControl 将与第一个可交互项目一起使用,但永远不会与第二个近乎可交互的项目。

例如,如果有两个立方体并且它们之间的距离是 1 或小于 1,或者即使它们之间的距离是 100,那么首先交互哪个?如果 List lookObj 包含 20 个项目,我想首先与项目 15 交互,然后是 1,然后是 5,然后是 19?

如何设置与项目交互的顺序?

我想以某种方式使用 reorderablelist 但不确定这是否是解决方案以及如何做到这一点。

现在我做了一些丑陋但丑陋的事情:我从一个项目中销毁了脚本并添加到另一个项目以创建一些订单:

Destroy(securityKeyPad.GetComponent<InteractableItem>());
            StartCoroutine(DestroyKeyPad1());
            var naviInteractable = navi.AddComponent<InteractableItem>();
            naviInteractable.distance = 0.5f;
            navi.GetComponent<InteractableItem>().interactableMode = InteractableItem.InteractableMode.ActionWithoutThrow;

但是第二个项目还没有添加到lookObj中,所以这是另一个问题。

我想制作一些编辑器脚本,如果我将 InteractableItem 脚本附加到对象,它将在运行游戏之前自动将其添加到编辑器中 IKControl 脚本中的lookObj列表!

然后在lookObj List 中能够控制项目的可交互顺序,这就是为什么我也想使用reorderablelist 而不是只使用List。

我有点卡在这一点上。

4

1 回答 1

0

听起来您想根据某种标准选择特定目标,例如您在 foreach 循环中计算的“lookWeight”。我建议在 InteractableItem 中创建一个额外的“lookWeight”字段:

public class InteractableItem : MonoBehaviour
{
    //...
    public float lookWeight;
}

然后,您可以将这个“lookWeight”保存在 foreach 循环中,然后在循环完成后对其进行排序/排序。包含 Linq 命名空间以支持排序:

using System.Linq;

foreach (InteractableItem target in lookObj)
{
    //...
    target.lookWeight = lookWeight;
    allDetectedItems.Add(target);
}

// put into ascending order by look weight and set target to first item
if (allDetectedItems.Count > 0)
{
    allDetectedItems = allDetectedItems.OrderBy(x => x.lookWeight).ToList();
    primaryTarget = allDetectedItems.First();
}

if (primaryTarget != null)
//...

如果您想订购其他东西,而不是“lookWeight”,只需将其添加到 InteractableItem 类即可。

于 2020-09-27T07:19:35.283 回答