在Unity 2D中,物体穿过传送门时移动方向不正确?

7

在我开始之前,我想说如果这篇文章不是非常专业的写作,我很抱歉。我已经花了几个小时研究这个问题,但是一直没有找到解决方法,我感到很累和有压力。

我正在尝试让一个移动物体从一个特定点通过1个传送门进入,并从另一个传送门以与进入时相同的位置出来。因此,如果球从传送门的顶部进入,物体将从出口传送门的顶部出来;如果物体从传送门的底部进入,则它将从另一个传送门的底部出来。我的插图能力不是很好,但这就是我想要做的事情: enter image description here 在这里,您可以看到在两个图像中,物体都从蓝色传送门进入,并在其进入的位置从橙色传送门出来,所以顶部对顶部,底部对底部。

我实际上已经成功地做到了这一点,但现在我需要再次做到这一点,但这一次,其中一个门需要是水平的,而不是垂直的: enter image description here 所以我所做的是,当两个门都是垂直时,我保留了一个名为“exitIsHorizontal”的布尔值未选中(false),当它们中的一个在关卡的天花板上时,将垂直轴转换为水平轴。
我甚至已经让它工作了,但是它有一个需要修复的可重现问题。当物体进入传送门底部时,它像上面的图片一样正常工作。但是当你撞到传送门顶部时,物体会像你期望的那样从传送门另一侧出来,但是物体开始朝相反的方向移动,如下图所示: enter image description here 正确的出口位置,错误的出口方向。我还需要这个函数是动态的,这样如果说,物体从另一个方向撞击蓝色传送门,出口方向也会像这样切换: enter image description here 这是我的脚本:
    public GameObject otherPortal;
    public PortalController otherPortalScript;
    private BallController ballController;
    public bool exitIsHorizontal = false;

    List<PortalController> inUseControllers =  new List<PortalController>();

    // Use this for initialization
    void Start () 
    {

    }

    // Update is called once per frame
    void Update () 
    {

    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.gameObject.tag == "Ball")
        {
            ballController = other.GetComponent<BallController>();
            if (inUseControllers.Count == 0)
            {
                inUseControllers.Add(otherPortalScript);
                var offset = other.transform.position - transform.position;
                if(exitIsHorizontal)
                {
                    offset.x = offset.y;
                    offset.y = 0;
                }
                else
                {
                    offset.x = 0;
                }
                other.transform.position = otherPortal.transform.position + offset;
            }            
        }
    }

    void OnTriggerExit2D(Collider2D other)
    {
        if (other.gameObject.tag == "Ball")
        {
            inUseControllers.Clear();
        }

    }

这个脚本附加在两个门户上,以便两个门户都可以处理进入和退出。你看不到脚本中声明的任何变量(比如“otherPortal”),实际上它们是空的,我在编辑器中声明它们。
我敢打赌这只是一个非常简单的问题,我只是一直错过了它,我不知道那是什么。
1个回答

3

因此,门户基本上是一个虫洞。进入的物体将保留其本地位置和方向。

wormhole

功能:

Unity拥有用于在世界空间和本地空间之间进行转换的函数。

位置:

Transform.InverseTransformPoint

将位置从世界空间转换为本地空间。

Transform.TransformPoint

将位置从局部空间转换到世界空间。

jumping

方向:

要转换方向,您需要使用以下内容:

Transform.InverseTransformDirection

将世界空间中的方向转换为本地空间。与Transform.TransformDirection相反。

Transform.TransformDirection

将方向从本地空间转换为世界空间。

简单的例子:

一个脚本,你可以将它附加到两个门上。它可以移动带有标签"Ball"的物体到exitPortal中。

public class Portal : MonoBehaviour
{
    [SerializeField] Portal exitPortal;

    void OnTriggerEnter2D(Collider2D collider)
    {
        if (collider.CompareTag("Ball"))
        {
            GameObject ball = collider.gameObject;
            Rigidbody2D rigidbody = ball.GetComponent<Rigidbody2D>();

            Vector3 inPosition = this.transform.InverseTransformPoint(ball.transform.position);
            inPosition.x = -inPosition.x;
            Vector3 outPosition = exitPortal.transform.TransformPoint(inPosition);            

            Vector3 inDirection = this.transform.InverseTransformDirection(rigidbody.velocity);
            Vector3 outDirection = exitPortal.transform.TransformDirection(inDirection);

            ball.transform.position = outPosition;
            rigidbody.velocity = -outDirection;
        }
    }
}

你得到这个:

fun

复杂示例:

您需要3个脚本才能使其工作:

  • Portal:触碰可传送物体的物品
  • Warpable:通过传送门旅行的物品
  • Ghost:镜像 Warpable,在穿过传送门时显示

这就是 Ghost 的外观:

ghost

您需要两个额外的层——Portal和Ghost,其中碰撞矩阵设置如图所示。

matrix

脚本:

我已经在代码中添加了足够的注释,让您能够理解它正在做什么。

门户:

public class Portal : MonoBehaviour
{
    [SerializeField] Portal exitPortal;

    void OnTriggerEnter2D(Collider2D collider)
    {
        // When a warpable enters a portal create a ghost
        if (collider.TryGetComponent(out Warpable warpable))
        {
            // Create a ghost only if we haven't already
            if (warpable.Ghost == null) warpable.CreateGhost(this, exitPortal);
        }
    }

    void OnTriggerExit2D(Collider2D collider)
    {
        // When a warpable exist a portal; check if it has a ghost
        if (collider.TryGetComponent(out Warpable warpable))
        {
            // Teleport to the ghost; apply its position, rotation, velocity
            if (warpable.Ghost != null)
            {
                // Create vectors to compare dot product
                Vector3 portalToWarpable = warpable.transform.position - this.transform.position;
                Vector3 portalDownwards = -this.transform.up;

                // If warpable is on the other side of the portal you get a value that's more than zero
                float dot = Vector3.Dot(portalDownwards, portalToWarpable);
                bool passedThroughPortal = dot >= 0f;

                // If we passed through the portal then teleport to the ghost; otherwise just continue
                if (passedThroughPortal)
                {
                    warpable.Position = warpable.Ghost.warpable.Position;
                    warpable.Rotation = warpable.Ghost.warpable.Rotation;
                    warpable.Velocity = warpable.Ghost.warpable.Velocity;
                }

                // Destroy the ghost
                warpable.DestroyGhost();
            }
        }
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.magenta;
        Gizmos.DrawRay(this.transform.position, this.transform.up);
    }
}

可扭曲的:
public class Warpable : MonoBehaviour
{
    [SerializeField] new Rigidbody2D rigidbody;

    public Ghost Ghost { get; private set; }

    public void CreateGhost(Portal inPortal, Portal outPortal)
    {
        // Move the ghost object to the Ghost layer, this is so that ghost can collide with real objects, other ghosts, but not with the portal.

        // Ghost/Ghost      =   TRUE
        // Ghost/Default    =   TRUE
        // Ghost/Portal     =   FALSE

        GameObject original = this.gameObject;
        GameObject duplicate = GameObject.Instantiate(original);
        duplicate.layer = LayerMask.NameToLayer("Ghost");

        Physics2D.IgnoreCollision(
            original.GetComponent<Collider2D>(),
            duplicate.GetComponent<Collider2D>()
        );

        // Add the ghost component
        Ghost = duplicate.AddComponent<Ghost>();

        Ghost.observing = original.GetComponent<Warpable>();
        Ghost.warpable = duplicate.GetComponent<Warpable>();

        Ghost.inPortal = inPortal;
        Ghost.outPortal = outPortal;
    }

    public void DestroyGhost()
    {
        GameObject.Destroy(Ghost.gameObject);
        Ghost = null;
    }

    public Vector3 Position
    {
        get { return transform.position; }
        set { transform.position = value; }
    }

    public Quaternion Rotation
    {
        get { return transform.rotation; }
        set { transform.rotation = value; }
    }

    public Vector3 Velocity
    {
        get { return rigidbody.velocity; }
        set { rigidbody.velocity = value; }
    }
}

幽灵:

public class Ghost : MonoBehaviour
{
    public Warpable observing;
    public Warpable warpable;

    public Portal inPortal;
    public Portal outPortal;

    void FixedUpdate()
    {
        warpable.Position = OutPosition(observing.Position);
        warpable.Rotation = OutRotation(observing.Rotation);
        warpable.Velocity = OutDirection(observing.Velocity);
    }

    Vector3 OutPosition(Vector3 position)
    {
        Vector3 inPosition = -inPortal.transform.InverseTransformPoint(position);
        return outPortal.transform.TransformPoint(inPosition);
    }

    Quaternion OutRotation(Quaternion rotation)
    {
        return Quaternion.Inverse(inPortal.transform.rotation) * outPortal.transform.rotation * rotation;
    }

    Vector3 OutDirection(Vector3 velocity)
    {
        Vector3 inDirection = -inPortal.transform.InverseTransformDirection(velocity);
        return outPortal.transform.TransformDirection(inDirection);
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireSphere(warpable.Position, 1f);
        Gizmos.DrawLine(warpable.Position, warpable.Position + warpable.Velocity);
    }
}

最终结果是这样的:

warping


非常感谢您的回复,您的代码大部分都有效,但其中有几行已经过时,Unity无法运行它。这是我必须进行的适应性修改的链接,以使其在Unity 2019.3中运行:https://pastebin.com/xCCnzdwZ这基本上可以工作,但似乎会导致不一致的问题,有时物体不会通过传送门而只是弹开。还有其他情况,它将通过到达另一个传送门1帧,并立即返回到它进入的另一个传送门。您是否了解TransformDirection是否会导致这种情况? - Jhon Piper
@JhonPiper 好的,我已经用更好的代码示例更新了答案。最终,我决定在对象“OnTriggerExit2D”通过它时传送它,但前提是对象确实通过了它。当一个对象在半路进入门户并决定返回时,复杂性就出现了。所以在我的情况下,它使用点积。总体来说,这是一个非常有趣的挑战。 - Iggy
抱歉回复晚了,我花了过去的三天时间测试了您的原始代码。显然代码本身没有问题,问题在于您考虑了传送门的旋转角度而我却没有,此外我还使用2D胶囊碰撞体,而您则使用盒状碰撞体。所以一旦我将传送门翻转并解决了碰撞体问题,一切就如预期般开始正常运作。自那时以来,我还尝试了您贡献的带有幽灵对象的代码,虽然我认为幽灵对象与我的项目不太匹配,但我可以看出它的潜在用途... - Jhon Piper
对于其他可能遇到这个答案的人有用。我会将您的答案标记为正确并奖励您积分。我建议在最终答案中保留您代码的两个版本。无论哪种方式,您的两个版本的代码都可以满足我的需求,非常感谢您的帮助! - Jhon Piper
@JhonPiper 非常好 :) 我已经按照建议在答案中添加了简单的示例。祝你的项目好运! - Iggy
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接