Outline | Previous: UI | Next: Outro
TODO
YouTube | Source before | Source after
Create level 2 reusing a lot from level 1 but change various configurations such as having the cloud spawn HoverGuy enemies.
The win condition for this level, to be added later, will be to jump over each of the breakaway platforms we add here.
How
Create prefabs:
- Open Level1 and create prefabs for:
- Main Camera
- Canvas
- EventSystem
- 1 Platform (any is fine, we will use this as a starting point in Level2).
- 1 Ladder
- EvilCloud
- LevelController
Add prefabs to level 2:
- Open Level2.
- Delete the Main Camera.
- Drag in the following prefabs:
- Main Camera
- Canvas
- EventSystem
- EvilCloud
- Set the sprite's Order in Layer: 15
- GameController
- LevelController
- Select the Director
Breakaway sections:
- Add a Breakaway GameObject and sprite, we are using Art/jumperpack_kenney/PNG/Environment/ground_stone_small_broken.
- Rename to "BreakawayBlock".
- Layer: Floor
- Scale: .7, .7, .7
- Add TouchMeToWin.
- Touchable Layers: Character
- Add PolygonCollider2D.
- Add Rigidbody2D.
- Freeze the Position (X and Y) and Rotation.
- Add BoxCollider2D:
- Check Is Trigger
- Size it to capture the Character walking or jumping over.
Layout level:
- Add the Platform, Ladder, Hammer prefabs:
- Copy / paste GameObjects as needed to layout the level.
- Position the breakaway blocks so that their top is slightly above the platforms.
- Note that it's okay to 'Break the prefab instance' while making changes.
- Adjust the collider sizes on each of the platforms.
- Add a Rigidbody2D to each of the center platforms.
- Freeze the Position (X and Y) and Rotation.
- Add the Mushroom. We are using mushroom_red.
- Add PolygonCollider2D.
- Add Rigidbody2D:
- Freeze the Position and Rotation.
Configure the enemy:
- Drag the HoverGuy prefab into the scene.
- Rename it "HoverGuy2".
- Remove the FadeInThenEnable component.
- Enable the WanderWalkController:
- Time Before First Wander: 0
- Change the RandomClimbController
- Odds of going up: .1
- Odds of going down: .9
- Create a new prefab for HoverGuy2 and delete the GameObject.
- Select the EvilCloud:
- Thing To Spawn: HoverGuy2
Test:
- The cloud should animate in (we'll change the animation next) and then HoverGuy2s should spawn.
What does it mean to 'Break the prefab instance'?
This dialog sounds more serious than it is. Breaking the prefab instance means that Unity will no longer tie this GameObject to a prefab - so if the prefab were to change the GameObject will not receive the update.
The prefab itself is still in-tact and may be used for other objects or scenes.
When freezing the rigidbody a warning appears, why do it this way?
When you freeze all constraints on the rigidbody, Unity presents a warning that this may not be an efficient way to achieve your goal of preventing the object from moving. We have a bit of a unique use case for this case -- we will be removing these constraints once the end of the level is reached, allowing them to fall to the ground.
Alternatively you could have not included the rigidbody at all until the end of the level. This is style preference, as well as a bit of a performance consideration as there is overhead to having a frozen rigidbody and there is overhead with adding a new component to a GameObject.
YouTube | Source before | Source after
How
Intro Timeline:
- Select the EvilCloud's sprite GameObject and create a new animation Animations/CloudLevel2Entrance.
- Record any sequence you'd like.
- Select Animations/CloudLevel2Entrance and disable looping.
- Create a 'Timeline' file at Animations/Level2Entrance.
- Select the EvilCloud's sprite GameObject
- Playable: Level2Entrance
- Open the Timeline Editor window:
- Add an Animation Track for the cloud:
- Add an Animation Clip for CloudLevel2Entrance.
- Update the speed if needed.
- Add Activation Tracks for the Hammers, Ladders, and LevelController.
- Time them to start at the end of the animation.
- And end at the end of the timeline.
- Add TimelineEventPlayable:
- Event type: Almost At Start
- Position where you want the camera shake to occur.
- Add TimelineEventPlayable (again):
- Event type: Start
- Position to start when the animation ends.
- Close the Timeline Editor.
- Then disable the Hammers, Ladders, and LevelController.
- Add an Animation Track for the cloud:
Outro Timeline:
- Create a new Scene named Scenes/YouWin:
- Add it to Build Settings.
- Return to Level2.
- Create a new animation on the cloud for the end of the game, named Animations/CloudLevel2Exit.
- Uncheck Loop Time
- Create a new Timeline Animations/Level2Exit and select it in the Playable Director.
- Open the Timeline Editor:
- Create an Animation Track for the cloud's CloudLevel2Exit clip.
- Adjust the speed.
- Add TimelineEventPlayable:
- Position it to start during the animation, when you would like the platforms to start falling.
- Change the Event Type to End.
- Add ChangeScenePlayable:
- Position it to start a few seconds after the TimelineEventPlayable began, when you want to transition to the YouWin scene.
- Scene Name: YouWin
- Create an Animation Track for the cloud's CloudLevel2Exit clip.
- Select the LevelController:
- End of Level Playable: Level2Exit
- Switch the Playable Director back to Level2Entrance.
Test:
- When you start Level2, the intro timeline plays and then spawns begin similar to Level1.
- Walk over each of the breakaway platforms to trigger the end of the level timeline and then transition to the YouWin scene.
YouTube | Source before | Source after
Once the character has touched each of the Breakaway platforms, make the level collapse.
How
Create UnfreezeAndDisablePlatformers:
- Create script Code/Effects/UnfreezeAndDisablePlatformers:
using UnityEngine;
public class UnfreezeAndDisablePlatformers : MonoBehaviour
{
protected void OnEnable()
{
Rigidbody2D myBody = GetComponent<Rigidbody2D>();
myBody.constraints = RigidbodyConstraints2D.None;
PlatformEffector2D effector
= GetComponent<PlatformEffector2D>();
if(effector != null)
{
effector.enabled = false;
}
}
}
Configure GameObjects:
- For each center platform, excluding the bottom:
- Add UnfreezeAndDisablePlatformers.
- Disable the component.
- Remove RotateOvertimeToOriginal.
- Update EnableComponentsOnTimelineEvent for each GameObject individually:
- Event Type: End
- Component list: UnfreezeAndDisablePlatformers
- Add UnfreezeAndDisablePlatformers.
- Select the mushroom:
- Add UnfreezeAndDisablePlatformers.
- Disable the component.
- Add EnableComponentsOnTimelineEvent:
- Event Type: End
- Component list: UnfreezeAndDisablePlatformers
- Add UnfreezeAndDisablePlatformers.
Breakaway when touched:
- For each breakaway block:
- Add UnfreezeAndDisablePlatformers.
- Disable the component.
- Update TouchMeToWin for each block:
- Component To Enable: UnfreezeAndDisablePlatformers
- Add UnfreezeAndDisablePlatformers.
Test:
- The intro should play at the start of the level.
- Every time you touch or jump over a breakaway block, if falls.
- Entities should freeze and the outro Timeline should play after you break each of the breakaway blocks.
Explain the code
'using' clauses at the top of a file brings APIs into scope. Used for:
- UnityEngine.MonoBehaviour
- UnityEngine.PlatformEffector2D
- UnityEngine.Rigidbody2D
- UnityEngine.RigidbodyConstraints2D
using UnityEngine;
We inherit from MonoBehaviour, which allows this script to be added as a component on a GameObject.
public is optional here. Used for consistency.
public class UnfreezeAndDisablePlatformers : MonoBehaviour
{
OnEnable is a Unity method which is called each time the component is enabled.
protected is optional here. Used for consistency.
protected void OnEnable()
{
Here we get reference to the rigidbody on this GameObject.
Rigidbody2D myBody = GetComponent<Rigidbody2D>();
We set the constraints to None, this removes each of the constraints we added to the rigidbody while designing the scene.
myBody.constraints = RigidbodyConstraints2D.None;
Here we get a reference to the platform effector, if any.
PlatformEffector2D effector
= GetComponent<PlatformEffector2D>();
Check if there is a platform effector on this GameObject and if so, disable it.
if(effector != null)
{
effector.enabled = false;
}
}
}
YouTube | Source before | Source after
Design a scene to display when the player wins the game. Here we drop a bunch of random "GG"s from the sky.
How
Configure scene:
- Open the YouWin scene.
- Configure the Camera color.
- Add the GameController prefab.
Create RandomGG:
- Create script Code/Effects/RandomGG:
using UnityEngine;
public class RandomGG : MonoBehaviour
{
[SerializeField]
float minScale = .05f;
[SerializeField]
float maxScale = .25f;
protected void OnEnable()
{
transform.localScale
= Vector3.one * UnityEngine.Random.Range(minScale, maxScale);
TextMesh text = GetComponent<TextMesh>();
text.color = UnityEngine.Random.ColorHSV();
Bounds screenBounds = GameController.instance.screenBounds;
transform.position = new Vector3(
UnityEngine.Random.Range(screenBounds.min.x, screenBounds.max.x),
screenBounds.max.y + 10,
0);
}
}
Create a GG GameObject:
- Create an Empty GameObject named "GG".
- Add TextMesh:
- Text: "GG"
- Font Size: 56
- Anchor: Middle Center
- Alignment: Center
- Add BoxCollider2D:
- Size it tightly around the GG letters.
- Add Rigidbody2D.
- Add SuicideIn
- Time: 30
- Add RandomGG.
- Add TextMesh:
Keep in bounds:
- Create an Empty GameObject, named "Bumper".
- Add BoxCollider2D
- Size and position multiple to guard the screen edges.
Spawn GGs:
- Create Prefabs/GG and delete the GameObject.
- Create an Empty GameObject named Spawner.
- Add Spawner component
- Thing to spawn: GG
- Min time: 0
- Max time: .5
- Add Spawner component
Create AnyKeyToLoadScene:
- Create script Code/UI/AnyKeyToLoadScene:
using UnityEngine;
using UnityEngine.SceneManagement;
public class AnyKeyToLoadScene : MonoBehaviour
{
[SerializeField]
string sceneName = "Menu";
protected void Update()
{
if(Input.anyKeyDown)
{
SceneManager.LoadScene(sceneName);
}
}
}
Configure Spawner:
- Add AnyKeyToLoadScene to the Main Camera.
Test:
- GGs of random color and size should fall from the sky and bounce off each other.
Explain the code
'using' clauses at the top of a file brings APIs into scope. Used for:
- UnityEngine.Input
- UnityEngine.MonoBehaviour
- UnityEngine.SceneManagement.SceneManager
- UnityEngine.SerializeFieldAttribute
using UnityEngine;
using UnityEngine.SceneManagement;
We inherit from MonoBehaviour, which allows this script to be added as a component on a GameObject.
public is optional here. Used for consistency.
public class AnyKeyToLoadScene : MonoBehaviour
{
This is a Unity-specific attribute that exposes a field in the Inspector, allowing you to configure it for the object.
[SerializeField]
This defines which scene to load when a key is pressed. You can change the default in the Inspector.
string sceneName = "Menu";
Update is a Unity method which is called each frame.
protected is optional here. Used for consistency.
protected void Update()
{
Check if any key has been pressed. This includes keyboard, mouse, and controllers.
if(Input.anyKeyDown)
{
Here we load the requested scene. This will first destroy all of the GameObjects in this scene and then load the next.
SceneManager.LoadScene(sceneName);
}
}
}
Why a TextMesh instead of UI Text?
UI Text does not work with physics. It's intended to be used on a Canvas and not have any interaction with objects in the world.
Text Mesh can be added to a GameObject, allowing you to add a rigidbody for gravity and a collider to get them bouncing around. Note that features built for the UI Text component, for example the Outline component, are not compatible with the Text Mesh.
RandomGG could be 3 components.
Testing / debugging tips
- TODO
Questions, issues, or suggestions? Please use the YouTube comments for the best fit section.
Support on Patreon, with Paypal, or by subscribing on Twitch (free with Amazon Prime).
License. Created live at twitch.tv/HardlyDifficult August 2017.