与对象(项目)进行交互时,如何进行分层?

时间:2020-09-27 05:55:06

标签: c# unity3d

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

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列表中能够控制项目的可交互顺序,这就是为什么我认为也要使用reorderablelist而不是仅使用List的原因。

我有点卡住了。

1 个答案:

答案 0 :(得分: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类中即可。