The Rooms System
The Corgi Engine has, for a long time, offered the ability to connect different scenes together, or to use teleporters to link parts of a level together. With v6.2, the Corgi Engine now comes with a powerful and versatile Rooms system. This lets you cut a level into “rooms” (whether they’re visually actual rooms or just zones is up to you), and connect these Rooms together using the Teleporter class (whether these Teleporters are represented visually as doors, portals, nothing, or anything else is also up to you). It leverages the power of Unity’s Cinemachine camera system to handle camera transitions between rooms, as well as a few MoreMountains classes to handle fades and sprite masks. This page explains how to do that, and covers the other features and options of the Rooms system.
The RetroVania demo scene is a perfect example of that system, showcasing many Rooms, and different ways to setup teleporters. The connection between Rooms 8 and 5 for example will remind you of Megaman transitions, while others will be more akin to what you’d find in games like Super Metroid.
To start things off, you’ll want to create Rooms in your level. Whether your level is made of tiles, of 2D sprites or 3D models doesn’t matter. To create a room, simply create a new empty object, add a BoxCollider2D to it, and a Room component. Resize the BoxCollider2D so that it matches the size of your room.
Nested under the Room, as children, you’ll want to add :
- a confiner : an empty game object with a CompositeCollider2D (add it first), the same BoxCollider2D as the Room with UsedByComposite set to true, and a Rigidbody2D set to Static body type (unsimulated). Make sure your BoxCollider2D’s doesn’t have an offset. Set IsTrigger:true on all of of these.
- a Cinemachine Virtual Camera. On this camera you’ll likely want to have a CinemachineCameraController (with ConfineCameraToLevelBounds set to false, as we want it to confine to the rooms bounds, not the level’s), and a CinemachineConfiner. On that CinemachineConfiner’s inspector you’ll want to set the BoundingShape2D as the confiner you just created
- one or more Teleporters (we’ll get to that)
The Room class is responsible for resizing the confiner dynamically on Start to match the camera size, keeping track of its status (visited or not) and triggering events when the player enters the room, exits it, enters it for the first time.
Once you’ve created a room, the best way to go to add more is to duplicate it, reposition it, and resize its colliders to make more. Make sure to give them unique names. While not mandatory, this will make finding them later much easier.
Setting up simple rooms
Setting up a first room
- in a fresh install of Corgi Engine v7.2.1 on Unity 2019.4.29f1
- open the MinimalLevel demo scene
- create a new empty game object, name it Room1, position it at -9.5,0,0
- add a BoxCollider2D to it, set its size to 20,11, then add a Room component to it, set its layer to IgnoreRaycast
- add an empty child object to it, name it Confiner, add a CompositeCollider2D to it, geometry type: polygons, then copy and paste the Room’s BoxCollider2D on its child, and check UsedByComposite on it, on the Rigidbody set Body Type to static
- add another child to Room1, name it VCam, add a CinemachineVirtualCamera to it, and a CinemachineCameraController to it, uncheck ConfineCameraToLevelBounds on it
- on the CinemachineVirtualCamera, set ortho size to 3, and Body to Framing Transposer, then add a CinemachineConfiner (Add extension dropdown at the bottom of the virtual camera’s inspector), in Confine Mode : Confine2D, and drag the Confiner object into its BoundingShape2D slot
- on the Room1 object, drag the VCam object into the Virtual Camera slot and the CinemachineCameraConfiner slots
- open a new inspector for the Confiner object, lock it, and drag the BoxCollider2D from that inspector into the Room’s Confiner slot
Creating a second room
- select Room1, duplicate it, name it Room2, position it at 10.5,0,0
- on Room2’s vCam, set its CinemachineVirtualCamera’s Priority to 0
- select the left wall of the MinimalLevel (named “Platform100x1000 (6)”), duplicate it, move it to 0,0,0 to create a wall between the 2 rooms
Linking both rooms with teleporters
- create a new empty child under Room1, name it Teleporter1, add a SpriteRenderer to it, set its Sprite to AchievementIcons_6 (a simple yellow circle)
- set its layer to Default
- position it at 8,-4,0, add a BoxCollider2D to it, isTrigger:true, add a Teleporter component to it
- under the Rooms foldout, set CameraMode:CinemachinePriority
- duplicate the Teleporter object, name it Teleporter2, parent it to Room2, position it at -9,-4,0, change its SpriteRenderer color to black
- on Teleporter1, under the Destination foldout, drag Teleporter2 into the Destination slot
- still on Teleporter1, under the Rooms foldout, drag Room1 into the CurrentRoom slot, and Room2 under TargetRoom
- on Teleporter2, under the Destination foldout, drag Teleporter1 into the Destination slot
- still on Teleporter1, under the Rooms foldout, drag Room2 into the CurrentRoom slot, and Room1 under TargetRoom
- press play, move right, once you connect with the yellow teleporter press space to get to the other room
Now that you’ve got multiple rooms, it’s time to add ways to go from one to the other. This is done using the Teleporter class, and its many options.
To start things off, add an empty game object within your room, add a BoxCollider2D to it (IsTrigger set to true), and add a Teleporter script to it. Teleporters have a ton of options. You can define activation requirements, activation conditions, amount of activations, prompts, input, feedbacks, and much more. For a door between rooms, maybe you’ll just want to set AutoActivation to true, so that any Character that gets into the collider gets moved to the new room.
The Teleporter specific fields are at the bottom of the inspector (the top is common to all ButtonActivated zones).
The only mandatory things to set will be the Destination, where you’ll have to drag and drop another teleporter, as well as the Target Room, where you’ll need to drag and drop a Room.
In the Teleporter section you can decide whether that teleporter only affects Player characters, an exit offset (added to the Teleporter’s transform position when something exits it), whether the teleportation should be instant or a tween between entry and exit positions, and whether x or y positions should be maintained.
The Freeze section lets you choose whether you want the teleporter to freeze time and/or the Character when entering it.
In the Rooms section you don’t have to set the CurrentRoom if your Teleporter is nested under a Room, it’ll be automatically detected. You have different camera mode options, Cinemachine priority is the recommended one. Teleport Camera will move the current active camera to a new position, while Cinemachine priority will change virtual camera’s priorities to give priority to the destination room’s virtual camera.
When getting into a Teleporter and activating it (by pressing an input, or by simply entering it, or via script, depending on your settings), you trigger a series of events known as the Teleporter Sequence. It’s designed to be as customizable as possible, and easy to extend. It’s made of several steps, whose duration (in seconds) you can customize from the bottom of the Teleporter’s inspector.
By default, the steps work as follows (note that not all of each step’s events may happen depending on your settings) :
- SequenceStart : activates the zone (reducing the Use counter, triggering feedbacks, etc), prevents the camera from following, freezes time, freezes the character
- wait for InitialDelay
- AfterInitialDelay : fades the scene out via MMFader
- wait for FadeOutDuration
- AfterFadeOut : teleports the object, either instantly or by starting a smooth position tween, starts the camera transition, lets the current room know that the player has left, starts the mask transition
- wait for DelayBetweenFades
- AfterDelayBetweenFades : makes the camera follow again, fades the scene in via MMFader
- wait for FadeInDuration
- AfterFadeIn : nothing, for now
- wait for FinalDelay
- SequenceEnd : unfreezes time and the character
Each step is a separate method that you can easily override should you want to add more features.
The Teleporter’s inspector lets you check whether or not you want to trigger a Fade when transitioning to your Destination. This in turn will trigger a MMFadeEvent, that will be caught by a MMFader object in your scene, if you have one. Most demo scenes do, usually within their UICamera object. By default the engine comes with two faders, the classic MMFader (which will tween a UI element’s opacity - usually a black opaque sprite stretched across the whole screen, but not necessarily), and the MMFaderRound, which will apply a mask transition across a UI element.
Usually when dealing with multiple rooms in a level you want to obfuscate the rooms the player isn’t in at a specific time. Of course how you decide to do it is closely tied to your rendering choices. While it’d be a bit useless to try and provide a 3D solution for that, as it’d likely mess with your specific shaders, the engine comes with a 2D solution for that issue : the MMSpriteMask. You can create one from scratch, or simply copy the one in the RetroVania demo, it’s ready to use.
On its inspector, you’ll be able to have it automatically setup all renderers in the scene at start (which is useful otherwise it actually masks things in scene view, making level edition trickier). If you want an object to be ignored by the Mask at runtime, simply put a NoMask tag on it.
The Rooms system described above is great if you want complex interactions and a lot of control over transitions. But sometimes you may just crave something simple. That’s when the Corgi Engine’s Cinemachine Zones come in.
As shown in the video above, they will let you divide your level in sections (defined by a collider, so they can be shaped and positioned however you want). When the player (or any object/layer you define) enters a zone, a camera transition will occur. What’s great is that zone setup is as automated as possible, and setting up an entire level will take only a few clicks.
You can look at the MinimalCinemachineZones for a reference of that (you’ll find said zones in its hierarchy under Level/CinemachhineZones). Creating a zone is very simple, here’s all you have to do :
Setting up simple Cinemachine Zones
- In this example we’ll work in MinimalCinemachineZones, and for that we’ll disable all existing CinemachineZones. Feel free to adapt sizes and positions to your own context.
- Create a new empty gameobject, name it MyZone, position it at -71,-2.5,0
- Add a BoxCollider2D to it, set its size to 31,11.5
- Add a CorgiCinemachineZone component to it
- Create an empty child object, name it VirtualCamera
- Find a virtual camera you like. We’ll use the one under MinimalCameraRig, right click on the CinemachineVirtualCamera component, copy component, and then “paste component as new” on our empty VirtualCamera object
- At the MyZone level, drag the new VirtualCamera into the VirtualCamera slot. As this is our first zone in our level, we’ll also check CameraStartsActive. We want the Player to activate this zone, so we’ll select the Player layer in the Trigger Mask dropdown, and we’ll check SetupConfinerOnStart to let the engine handle all the complex confiner setup.
- Press play, your camera will now be confined to that zone.
- Exit play mode, and duplicate MyZone into a new MySecondZone object. Move it to -40,-2.5,0
- Notice at the bottom of the inspector you can change the zone’s gizmo color, pick one you like
- Select the second zone’s virtual camera, and under Lens, set the OrthoSize to 3
- Press play, move right, you’ll transition to the new zone, with a more zoomed in camera
- You can now add more zones to cover your whole level, position them however you like
- If you want to tweak camera transitions between zones, that’ll be on the CinemachineBrain on your camera (in this case the Regular Camera object under MinimalCameraRig). You’ll want to change the DefaultBlend and its associated duration.