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.
transform. forward is the Vector pointing in the z Direction of the object. In 3D space, this is usually in front of you, but in 2D this points "into" the screen. You need to replace it with either transform.
This time we will learn to move 2D Object in Unity by scripting in C# using Game Components. To move our Player, we will need to add some logic to its dumb Game Object. We will do it by creating a Custom Component containing C# code and adding it to the Player Game Object.
This code is creating a Vector3 pointing to the right (1, 0, 0) and multiplying it by time elapsed since the last frame, and then calls Transform.Translate method with the resulting Vector3 which will move the Game Object that distance. TL;DR; This will make the player move 1 Scene unit per second to the right.
When Enemy.cs file is created, Unity reloads for a sort time and will register a new component to be added to Game Object named Enemy. Go ahead and add it to both Enemy Game Objects. Note: Remember to add the Enemy component to both enemies. To make the Player Game Object move, edit Player.cs file.
If you are using transform.position to move, just debug.log the object's transform.position.x or .y (or Z if necessary) to find which way it moves. Do you have the object moving currently? If so, is it using physics or translation? If physics, debug.log the x and y velocity to see where "forward" is in your game.
So a portal is basically a wormhole. An object that enters will retain its local position and direction.
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.
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:
You need 3 scripts for this to work:
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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With