Today
- Instantiate and Destroy
- Animation and scripting
- Prototypes and Work
Instantiate and Destroy
While the game is in play mode you can create game objects in the scene using the Instantiate method. If you want to fully remove a game object from the scene, use the Destroy method.
// a few ways of instantiating a prefab
Instantiate(prefab);
Instantiate(prefab, position, rotation);
Instantiate(prefab, position, rotation, parent);
// instantiate returns the newly created object
GameObject newObject = Instantiate(prefab);
To instantiate an object, you first need to create a Prefab of that object:
Design a prefab
- Create a 2D circle in the scene Hierarchy. Create > 2D Object > Sprites > Circle.
- Resize it using the Rect Tool and change the color in the Sprite Renderer.
- Add a Rigidbody 2D. Leave the gravity scale at 1. Change Collision Detection to “Continuous”. (this isn’t totally necessary, but should be used for high speed objects)
- Add a Circle Collider 2D.
- Create a new folder in the Project tab called “Prefabs”.
- Select the GameObject in the Hierarchy and drag it into the Prefabs folder in the Project tab. You’ll notice that the icon next to the game object becomes solid blue to indicate that it’s a prefab.
Note: You can click the arrow to the right of the prefab in the Hierarchy to enter prefab editing mode in the Scene view. Double-clicking on the prefab in the Project folder will also open the prefab editor. Editing the prefab here will change all instances of the prefab. The Hierarchy will change to reflect the structure of the prefab which can contain multiple child objects. Clicking on the left arrow takes you back to the normal Scene view.
- Delete the Projectile from the Hierarchy.
Build the Spawner
- Add an empty Game Object to the scene. Name it “Spawner”.
- Add a Player Input component to the spawner game object and drag the GamelabCocktailCabinetControls into the Actions property.
- Create a new script called “SpawnObject”
- Open the script and add this code:
using UnityEngine;
using UnityEngine.InputSystem;
public class SpawnObject : MonoBehaviour
{
public GameObject prefab;
// Spawn every time Button1 is pressed, feel free to use your own input action
void OnButton1() {
Instantiate(prefab, transform.position, transform.rotation);
}
}
- Save it and return to the editor.
- Drag the prefab from before into the Prefab property of the script.
Press play and try spawning some objects. Notice that new objects are added to the hierarchy with “(clone)” added to the end of the name. Even though the objects fall off the bottom of the screen, they still exist in the scene. Let’s get rid of the objects.
The script uses the position of the GameObject as the position where the object will be instantiated (as well as the rotation). If you don’t add a position and rotation, Instantiate will use the transform values stored in the prefab.
Destroy the objects
- Open the prefab editor by double clicking on the prefab in the Project tab.
- Add a new script to the prefab. Call it “DestroyObject”. Add this code to the script. Save the script.
using UnityEngine;
public class DestroyObject : MonoBehaviour
{
public float delayTime = 1f;
void Start()
{
// destroy after a delay
Destroy(gameObject, delayTime);
}
}
Destroy allows you to add a delay before the object is removed. Press play and spawn objects, then keep an eye on the hierarchy and notice the objects disappearing.
Build a playfield
Try creating a small level for the spawned objects to fall through. Consider classic pinball and pachinko machines. Use basic 2D sprites and colliders.
Catalog of Vintage Nishijin Machines
Also lots of examples on the Internet Pinball Database
Extra things to try:
- Spawning objects and adding forces to them
- Bumpers (i.e. add force to the object when it collides with specific objects)
- Multiple spawn points that use different inputs to spawn
- Points
Hint:
var obj = Instantiate(prefab, transform.position, transform.rotation);
obj.GetComponent<Rigidbody2D>().AddForce(Vector2.up * 5);
using UnityEngine;
using UnityEngine.InputSystem;
public class SpawnObject : MonoBehaviour
{
public GameObject prefab;
// Spawn every time Button1 is pressed
void OnButton1()
{
GameObject obj = Instantiate(prefab, transform.position, transform.rotation);
obj.GetComponent<DestroyObject>().delayTime = Random.Range(0.5f, 5);
Rigidbody2D rb2d = obj.GetComponent<Rigidbody2D>();
rb2d.AddForce(new Vector2(1,1) * 5f, ForceMode2D.Impulse);
}
}
Scripting Animations
To control different aspects of the animator inside of a script, you can get a reference to the component using the GetComponent method.
In addition to the public properties on the Animator component in the Inspector, the Animator allows you to control which animations are playing, the speed of the animations, and user-defined parameters within the Animation controller.
For some tests, open a scene that has an animated object (any of the examples from last class will work).
Changing the speed
Create a new script called AnimationController and add it to the object being animated. Rather than making the animator variable public and connecting the component to the script in the inspector, we’ll use GetComponent to automatically find and connect the animator.
Copy and paste this code into the script:
using UnityEngine;
public class AnimationController : MonoBehaviour
{
public float speed = 1f;
private Animator animator;
void Start()
{
// get the animator
animator = GetComponent<Animator>();
}
void Update()
{
animator.speed = speed;
}
}
Try playing the game. You can change the speed of the animation directly from the inspector. You could modify this script to have the speed change over time:
animator.speed = speed + Time.time;
Triggering an animation (Animator State Machine)
Rather than relying on the auto-play feature of the animator, it’s possible to create a trigger parameter that will tell the animator controller to change from one animation state to another. Let’s recreate the animation delay, but using an animation trigger.
-
Start by opening the Animator window either by double-clicking on the controller in the project tab or going to Window > Animation > Animator
- There should be a few different boxes which represent the current state of the Animator. Lines represent transitions between the states, with the arrows showing the direction of the transition. The Entry state will always transition to a “Default State” when the component becomes active. Because there is only one animation clip in the controller, that animation will always start playing immediately.
-
Create a new, empty state to use as an intermediate state. Right-click in an empty part of the state machine area. Select Create State > Empty
- A “New State” box will appear. Selecting it will bring up the state’s properties in the inspector window. You can modify the name of the state there or add an animation clip to the state on the Motion parameter.
-
Right-click on the new empty state and select “Set as Layer Default State” the transition from Empty will now go to the empty state.
-
Now create a new transition from the new state to the other animation clip in the controller. Right-click on the empty state and select “Make Transition” and then click on the other animation clip.
-
If you were to press play now, the animation would wait a moment in the empty state and then immediately transition to the next state. To turn off the automatic transition, select the Transition and in the inspector uncheck the “Has Exit Time” box.
- Now create a trigger parameter that will tell the controller when to transition from the empty state to the animation state. At the top left of the Animator window, change to the Parameters tab and click + > Trigger to create a trigger parameter. Name it “start”.
- Select the Transition again and in the inspector under Conditions, click the “+” button. This should automatically add the start trigger to the transition condition.
- If you press play and keep the Animator window open you can click on the circle next to the trigger parameter to transition from the empty state to the animation state.
- With the trigger set up, we can now modify the delay script to control the transition automatically using the SetTrigger method of the Animator class.
using System.Collections;
using UnityEngine;
public class AnimationController : MonoBehaviour
{
public float delayTime = 1f;
private Animator animator;
void Start()
{
// get the animator
animator = GetComponent<Animator>();
// use coroutine to create the delay
StartCoroutine(RunAfterDelay(delayTime));
}
IEnumerator RunAfterDelay(float delay)
{
// wait a moment
yield return new WaitForSeconds(delay);
// trigger the animation
animator.SetTrigger("start");
}
}
The function can take in the name or id of the parameter containing the parameter you would like to trigger.
Animation triggers and properties in other situations
Going from Idle to Walk and back.
For this example I’m adding the animation to the player character sprite.
- Select the player game object and open the Animation window. Click the “create” button if there aren’t any animations yet.
-
Create another clip so that there are two animations associated with this controller. You can add the keyframes for each animation later.
- Open the Animator window (Window > Animation > Animator). Make sure that the Idle animation has the transition from the “Entry” node.
- Create a transition from Idle to Walk and another transition from Walk to Idle.
-
Click the “Parameters” tab and click the ‘+’ button to add a new Bool parameter called “moving”
-
Select each transition and add a new condition in the inspector. For idle to walk, the condition is “moving: true” and walk to idle will be “moving: false”. You can also turn off any transition timing so that the animations will change immediately.
- Now open up your player movement / player controller script. You’ll need to add in a new variable for the Animator and get the Animator component in the Start method.
- Next you’ll check if the direction is zero and you’ll set the “moving” Animator Parameter using the SetBool method.
Here’s an example of a PlayerController script that sets the animation parameter inside the Update function, the script also shows another way to connect Inputs to callback methods:
using UnityEngine;
using UnityEngine.InputSystem;
public class SimplePlayerController : MonoBehaviour
{
public float speed = 1f;
Vector2 direction;
Rigidbody2D rb;
Animator anim;
void Start()
{
// shortcut for getting the current map of input actions
var map = GetComponent<PlayerInput>().currentActionMap;
// bind the movement to the "performed" -- similar to keydown
map["Move"].performed += ctx => direction = ctx.ReadValue<Vector2>();
// reset direction when button released
map["Move"].canceled += ctx => direction = Vector2.zero;
// get rigidbody
rb = GetComponent<Rigidbody2D>();
// get the animator
anim = GetComponent<Animator>();
}
void FixedUpdate()
{
// move the player
Vector2 newPosition = rb.position + direction * Time.deltaTime * speed;
rb.MovePosition(newPosition);
}
void Update()
{
// change animation based on movement
if(direction == Vector2.zero)
{
anim.SetBool("moving", false);
} else
{
anim.SetBool("moving", true);
}
}
}