简体   繁体   中英

Object exits portal moving the wrong direction in Unity 2D?

Before I start I'd like to say sorry if this isn't highly professionally written, I've been working on this for hours banging my head against the wall trying to figure this out with no luck, I'm tired and stressed.

I'm trying to get a moving object to enter through 1 portal at a specific point, and come out the other portal at the same point it entered. So if the ball enters the top of the portal, the object will come out at the top of the exit portal, and the if the object enters from the bottom of the portal it will exit the bottom of the other portal. I'm not the best a illustration, but here's what I want to it do: 在此处输入图片说明 Here you can see in both images the object enters the blue portal and exits the orange at the point that it entered, so top to top, bottom to bottom.

I've actually gotten this to work fine, but now I need to do it again but this time, one of the portals needs to be horizontal instead of vertical: 在此处输入图片说明 So what I've done, is make it so when both a vertical, I leave a bool called "exitIsHorizontal" unchecked (false), and when one of them is on the ceiling on the level, it translates the vertical axis to a horizontal one.

I even sort of got that to work, however it's got a reproducible quark that needs to be fixed. When the object enters the bottom of the portal, it works fine like you'd expect and just like the image above. But when you hit the top of the portal, the object comes out the other side of the portal like you'd expect, but the object begins moving in the opposite direction as see in this image: 在此处输入图片说明 Correct exit location, wrong exit direction for some reason. I also need this function to be dynamic so that if say, the object hit the blue portal from the other direction that the exit direction would switch sides as well like this: 在此处输入图片说明

Here is my script:

    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();
        }

    }

This script is attached to both portals so that both Portals can handle both entering and exiting. And any variable you don't see declared with anything in the script making it essentially null (such as "otherPotal") I declare the in editor.

I'll bet it's something extremely simple that I just keep missing, I just don't know that something is.

So a portal is basically a wormhole. An object that enters will retain its local position and direction.

虫洞

Functions:

Unity has functions for transforming from world space to local space and vice versa.

Position:

Transform.InverseTransformPoint

Transforms position from world space to local space.

Transform.TransformPoint

Transforms position from local space to world space.

跳跃

Direction:

For transforming directions you will have to use:

Transform.InverseTransformDirection

Transforms a direction from world space to local space. The opposite of Transform.TransformDirection.

Transform.TransformDirection

Transforms direction from local space to world space.

Simple Example:

One script that you attach to both portals. It moves objects with the tag "Ball" over to the 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;
        }
    }
}

You get this:

乐趣

Complex Example:

You need 3 scripts for this to work:

  • Portal: The thing that touches warpable objects
  • Warpable: A thing that travels through the portals
  • Ghost: A mirrored warpable that shows while going through a portal

This is what the ghost looks like:

鬼

You will need two additional layers – Portal and Ghost, with the collision matrix set as in the image.

矩阵

Scripts:

I've added enough comments within the code for you to make sense as to what it's doing.

Portal:

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);
    }
}

Warpable:

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; }
    }
}

Ghost:

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);
    }
}

And the final result is this:

翘曲

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM