Making portals
The gameobjects
The first important step to making portals is to make a GameMaster, Portal,
PortalCamera and PortalSpawner (Portal Orb in the image) gameobject. In the GameMaster we’ll keep
track
of the portals and their information.
We use the PortalSpawner to instantiate new portals and their cameras and we send their information
to the GameMaster.
void OnDeath()
{
//make the cameras
for (int i = 0; i < portals.Length; i++)
{
cameras[i] = Instantiate(cameraObject, new Vector3(transform.position.x + 10 * i,
transform.position.y, transform.position.z), Quaternion.identity);
GameMaster.GetComponent<PortalSetUp>().MakeNewRenderTexture(cameras[i]);
cameras[i].transform.parent = portalCameras.transform;
}
//portal rotation values
float x = UnityEditor.TransformUtils.GetInspectorRotation(gameObject.transform).x;
float y = UnityEditor.TransformUtils.GetInspectorRotation(gameObject.transform).y;
float z = UnityEditor.TransformUtils.GetInspectorRotation(gameObject.transform).z;
Quaternion rotation = Quaternion.Euler(x, y, z);
//make the portals
for (int i = 0; i < portals.Length; i++)
{
if(i == 0) //the first portal
portals[i] = Instantiate(portalObject, transform.position, rotation);
else // the second portal, rotated 180 degrees on the y and put behind the other portal
portals[i] = Instantiate(portalObject, transform.position + transform.forward * -75,
Quaternion.Euler(-x, y + 180f, -z));
portals[i].transform.parent = portalCollection.transform;
GameMaster.GetComponent<PortalSetUp>().AssignMaterialToPortal(portals[i], i);
}
// link cameras to the portals
GameMaster.GetComponent<PortalSetUp>().AssignPortalsToCamera();
//destroys this object, and it's clone
GetComponent<PortalableObject>().DestroyObject();
I also gave the portalSpawner a velocity so that it would move faster forward than the dragon, and
create the portal ahead of it.
void Move()
{
Vector3 forward = transform.TransformDirection(Vector3.forward) * 10;
rb.velocity = forward * speed * Time.deltaTime;
}
The GameMaster
The GameMaster object uses the PortalSetUp script. In that script we will keep track of how many
portals there are and their cameras, materials and render textures.
Because the dragon will be able to create multiple pairs of portals at once, we need to create our
render textures and materials through code. It’s important that each portal has its own render
texture and material, because that’s what makes it look like a portal to another position.
In the MakeNewRenderTexture() function we put the camera gameobject that we got from the portal
script in the “cameras” list. Furthermore, we make a new render texture and a new material and
assign each to a list as well.
public void MakeNewRenderTexture(GameObject cameraGameObject)
{
cameraGameObject.name = "Camera " + cameras.Count;
cameras.Add(cameraGameObject);
Camera camera = cameraGameObject.GetComponent<Camera>();
if (camera.targetTexture != null)
camera.targetTexture.Release();
camera.targetTexture = new RenderTexture(Screen.width, Screen.height, 24);
renderTextures.Add(camera.targetTexture);
//create a new material
Material material = new Material(Shader.Find("Unlit/ScreenCutoutShader"));
material.name = "NewPortalMat" + materials.Count;
material.mainTexture = camera.targetTexture;
materials.Add(material);
}
In the MakeNewRenderTexture we use the “Unlit/ScreenCutoutShader” shader for our material. This is
used to only display the appropriate view on the material, decided by the screen position. This
shader was written by Brackeys (n.d).
sampler2D _MainTex;
fixed4 frag (v2f i) : SV_Target
{
i.screenPos /= i.screenPos.w;
fixed4 col = tex2D(_MainTex, float2(i.screenPos.x, i.screenPos.y));
return col;
}
ENDCG
The next thing we do is assign the materials to the portals, we use the AssignMaterialToPortal()
function in PortalSetUp for that.
public void AssignMaterialToPortal(GameObject portal, int i)
{
//Child(0) is the mesh
portal.transform.GetChild(0).gameObject.GetComponent<Renderer>().material = materials[portals.Count];
portal.name = "Portal " + portals.Count;
portals.Add(portal);
}
The last thing that we’ll do in the PortalSetUp script is assign the “otherPortal” and the correct
camera to a portal. The portals come in pairs, so we have to assign each portal in a pair to each
other. We do that by looping through each portal and if it’s an even portal we assign the next
portal as the otherPortal, and if it’s uneven we assign the previous portal.
We also do something similair for the camera, we store the next/previous camera, because each portal
uses it’s partners camera for it’s texture.
public void AssignPortalsToCamera()
{
for(int i = 0; i < portals.Count; i++)
{
if (i % 2 == 0)
{
portals[i].GetComponent<Portal>().otherPortal = portals[i + 1];
portals[i].GetComponent<Portal>().portalCamera = cameras[i + 1];
}
else
{
portals[i].GetComponent<Portal>().otherPortal = portals[i - 1];
portals[i].GetComponent<Portal>().portalCamera = cameras[i - 1];
}
}
mainCamera.GetComponent<PortalCamera>().UpdateLists(portals, cameras, renderTextures);
}
Resizing the portal
I thought that it looked quite boring to shoot an object and then suddenly create the portals. So I
decided to make the portals smaller upon creation and make them bigger over time.
The portal gameobject has multiple children, among those there is a gameobject that contains the
mesh and a gameobject that has the particle effect. The mesh size is quite easy to manipulate, I
could just change the scale of the whole portal object and it would apply it to the mesh as well.
Particle effects on the other hand worked quite differently.
As you can see in the following image the particle effect is unaffected by the parent changing in
size:
The right portal has the parent set to a scale of 0.5.
That’s why I need to adjust the particle system size in a different way, I used the particle effect
shape radius for this.
I changed the size of the whole gameobject in a coroutine, so it could be done in an extended amount
of time without interupting other systems.
IEnumerator ScaleOverTime(float time)
{
ParticleSystem.ShapeModule ps = particleChild.shape;
float originalRadius = ps.radius;
Vector3 originalScale = transform.localScale;
float currentTime = 0.0f;
do
{
ps.radius = Mathf.Lerp(originalRadius, destinationRadius, currentTime / time);
transform.localScale = Vector3.Lerp(originalScale, destinationScale, currentTime / time);
currentTime += Time.deltaTime;
yield return null;
}
while (currentTime < time);
// sets the exact value, because Lerp never get's there
if(currentTime >= time)
{
ps.radius = destinationRadius;
transform.localScale = destinationScale;
yield return null;
}
}