BACK

Blender to Unity, Part Four: A Rain of Barrels

Alex Lowe - 02/16/2018 7:40 AM

Blender to Unity, Part Four: A Rain of Barrels

Alex Lowe - 02/16/2018 7:40 AM
FILED TO:
http://glowingbluecore.com/wordpress/wp-content/uploads/Blender-to-Unity-post-4-excerpt-large.png

This is the last tutorial in a four-part series of getting started in Blender and Unity. You’ll need Blender (free), Unity (free) and Photoshop (not free) as you machete through these steps with me. Having some basic coding knowledge is helpful too, but not necessary.

 

In this series:

Blender to Unity, Part One: Woe and Wisdom

Blender to Unity, Part Two: Unwrapping Seams and Your Sanity

Blender to Unity, Part Three: We Emerge From The Winter

Blender to Unity, Part Four: A Rain of Barrels

 

In the last tutorial we imported the barrel from Blender to Unity and made a simple game scene where we got the barrel to spin around. The Main script in the project was like this by the end of the tutorial:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour {

	public GameObject Barrel;

	private GameObject _barrel;

	// Use this for initialization
	void Start () {
	Debug.Log("Raptors- they should all be destroyed.");
	_barrel = Instantiate(Barrel);
	}

	// Update is called once per frame
	void Update () {
	_barrel.transform.Rotate(1.0f, 1.0f, 1.0f);
	}

}

Now for the last tutorial in this series we’re going to add some physics. By the end of this tutorial there’s going to be barrels raining down from the heavens and bouncing around every which way.

 

First, we’re going to add a sprite. Go to one of your favorite vector art programs and make a neat square graphic in png format that’s 512 x 512 px 512? Yes, 512 because it’s a power of 2, which has to do with the way graphics cards work.

 

I’ll use my old luddite version of Adobe Illustrator CS5, but there’s a few free online ones, like Method Draw  or Vectr

 

Anyway, here’s my nice colorful graphic:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-Repeating-Ground-Grid-01-300×300.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-Repeating-Ground-Grid-01.png

 

Now right click in your Assets window and select Import New Asset. Find your nice square png that you just made and select it. Your Assets window should look like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-1-assets-window-768×359.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-1-assets-window-1024×479.png

 

Ok, now go back to your Main script that you created last time. It should be as easy as double-clicking that “Main” icon in your Assets window.

 

Add the following 2 functions after the closing bracket “}” of your Start() function.



	Texture2D LoadPNG(string filePath) {

	Texture2D tex = null;
	byte[] fileData;

		if (System.IO.File.Exists(filePath)) {
		fileData = System.IO.File.ReadAllBytes(filePath);
		tex = new Texture2D(16, 16);
		tex.LoadImage(fileData);
		}

	return tex;
	}

	GameObject AddSprite (Texture2D tex) {

	Texture2D _texture = tex;
	Sprite newSprite = Sprite.Create(_texture, new Rect(0f, 0f, _texture.width, _texture.height), new Vector2(0.5f, 0.5f),128f);
	GameObject sprGameObj = new GameObject();
	sprGameObj.AddComponent<SpriteRenderer>();
	SpriteRenderer sprRenderer = sprGameObj.GetComponent<SpriteRenderer>();
	sprRenderer.sprite = newSprite;

	return sprGameObj;

	}

What do these functions do? LoadPNG is going to retrieve the nice pretty png file as a Texture2D object that Unity can use. AddSprite will accept that texture and use it to create a square plane object in the game that’s painted with that texture. Simply speaking, these two functions will help you actually put your png file in your game scene. Alright, but how do we use them? Go back into your Start() function.

 

Add the line that says “LoadPNG” in there. Your Start() function should look like this:


	void Start () {
	Debug.Log("Raptors- they should all be destroyed.");

	_barrel = Instantiate(Barrel);

	Texture2D texture = LoadPNG(Application.dataPath+"/Unity Repeating Ground Grid-01.png");

	}

Now you can press play and you’ll notice that the barrel is still rotating around just like it did in our last tutorial, but our nice png is absent. What gives? Well, we only created the Texture2D object, which is the first part of the equation. The second part is that we have to call up a new game object and paint it with the Texture2D.

 

Go back to your Start() function and add this new line:


	void Start () {
	Debug.Log("Dodson! Dodson! We got Dodson here!");

	_barrel = Instantiate(Barrel);

	Texture2D texture = LoadPNG(Application.dataPath+"/Unity Repeating Ground Grid-01.png");

	AddSprite(texture);

	}

The astute observer will also notice that I also changed the Jurassic Park quote.

 

Now press play again, and your scene should look a little like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-2-the-sprite-appears-300×264.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-2-the-sprite-appears.png

 

Nice! Your square graphic is in there. Now, we’re going to make a collection of these, and use them to fashion ourselves a “ground”.

 

Replace this line:


AddSprite(texture);

With this block:



//3 for width, 2.5 for the size of the texture, and 5 for the scale that we're applying.
//we have to finesse this a little too because I think the registration point is at the center
//of the sprite, not the top-left like I'm used to in 2D.
float startX = (- 3.0f*2.5f*5.0f / 2.0f) + 2.5f*5.0f/2.0f;

float startZ = 0.0f;

float startY = -5.0f;

float currentX = startX;
float currentZ = startZ;

int xStack = 0;
int zStack = 0;

	for(int i=0; i<9; i++) {
	currentX = startX + xStack*2.5f*5.0f;
	currentZ = startY + zStack*2.5f*5.0f;

	GameObject sp = AddSprite(texture);
	sp.transform.position = new Vector3(currentX,startY,currentZ);
	sp.transform.Rotate(90.0f, 1.0f, 1.0f);
	sp.transform.localScale = new Vector3(5.0f, 5.0f, 5.0f);

	xStack++;

		if(xStack == 3) {
		xStack = 0;
		zStack++;
		}

	}

And now your Start() function should look like this:


	// Use this for initialization
	void Start () {
	Debug.Log("Dodson! Dodson! We got Dodson here!");

	_barrel = Instantiate(Barrel);

	Texture2D texture = LoadPNG(Application.dataPath+"/Unity Repeating Ground Grid-01.png");

	//3 for width, 2.5 for the size of the texture, and 5 for the scale that we're applying.
	//we have to finesse this a little too because I think the registration point is at the center
	//of the sprite, not the top-left like I'm used to in 2D.
	float startX = (- 3.0f*2.5f*5.0f / 2.0f) + 2.5f*5.0f/2.0f;

	float startZ = 0.0f;

	float startY = -5.0f;

	float currentX = startX;
	float currentZ = startZ;

	int xStack = 0;
	int zStack = 0;

		for(int i=0; i<9; i++) {
		currentX = startX + xStack*2.5f*5.0f;
		currentZ = startY + zStack*2.5f*5.0f;

		GameObject sp = AddSprite(texture);
		sp.transform.position = new Vector3(currentX,startY,currentZ);
		sp.transform.Rotate(90.0f, 1.0f, 1.0f);
		sp.transform.localScale = new Vector3(5.0f, 5.0f, 5.0f);

		xStack++;

			if(xStack == 3) {
			xStack = 0;
			zStack++;
			}

		}

	}

What does this do? This is a loop with 9 iterations, and on each turn it creates a new sprite (your square graphic). If the term “sprite” is unfamiliar, google it. It’s an ancient and time-honored term in game development. Anyway, each time this loop creates a new sprite it rotates it so that it will be flat to the ground. Then is positions it so that it’s like tiles on a bathroom floor. At this point, it’s probably helpful to describe the coordinate system of Unity’s stage:

 

-x is to the left, +x is to the right

 

-z is away from the screen, +z is into the screen.

 

+y is toward the sky, -y is towards the ground.

 

So when we make our tiled floor, what we’re doing is arranging these sprites on the xz plane. Anyway, play your scene, and you should get something like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-3-the-sprite-floor-768×530.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-3-the-sprite-floor-1024×707.png

 

All right! Now we’re going to start into another aspect of Unity, the physics engine. Give your code a rest and go back to your Unity window.

 

In your Assets window, click on your Barrel prefab. The Inspector window on the right should look like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-inspector-window-229×300.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-inspector-window.png

 

Now, click Add Component, and in the menu that appears navigate to Physics -> Rigidbody and select it.

 

Now with your Barrel prefab still selected, the Inspector should look like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-inspector-window-with-rigidbody-166×300.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-inspector-window-with-rigidbody-566×1024.png

 

Now in your Assets window, right click and select Create -> Physics Material. Name it “Barrel Material”. Now, with your Barrel Material selected, have a look at your Inspector Window. It’s going to have a few different properties. Set “Bounciness” to 1:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-set-bounciness-to-one-300×268.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-4-set-bounciness-to-one.png

 

We’re going to use that material in a minute. In the Assets window, click on your Barrel prefab. With your Barrel prefab selected, go to your Inspector window and click Add Component again and select Physics -> Mesh Collider. Your inspector window should appear like this:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-inspector-window-with-mesh-collider-150×150.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-inspector-window-with-mesh-collider-508×1024.png

 

So what’s going on here? GameObjects in Unity are set up to be able to take many different configurations of **components**. Some of them change how the object is rendered, other change how the object behaves. And there’s a lot of different flavors of components. We’re selecting two components which will allow our Barrel prefab to behave like an actual physical object, complete with gravity, friction and bouncing. It’s an astounding amount of capability built right into Unity. We add a Rigidbody component to tell Unity that we want our Barrel to behave like a physical object. Then we add a Mesh Collider component to define the collision-boundary of our Barrel.

 

Anyway, you’ll notice in your Mesh Collider component that the mesh is preselected for us to our Barrel mesh. Unity made a guess and guessed that we want to use our rendering mesh as a collision mesh, and that’s fine. You’ll also notice that there’s a slot in there for a “Material”, and that’s where our physics Barrel Material goes:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-drag-the-physics-material-to-material-slot-768×386.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-drag-the-physics-material-to-material-slot-1024×515.png

 

Also in the Mesh Collider section of your Inspector window, make sure that you check that box that says “Convex”

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-check-convex-option.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-check-convex-option.png

 

Ok, now in your code, we have that Rotate command in the Update() function. Clear that out. Your Update() function should be empty like this:


// Update is called once per frame
void Update () {}

Your complete code for Main should be like this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour {

public GameObject Barrel;

private GameObject _barrel;

	// Use this for initialization
	void Start () {
	Debug.Log("Dodson! Dodson! We got Dodson here!");

	_barrel = Instantiate(Barrel);

	Texture2D texture = LoadPNG(Application.dataPath+"/Unity Repeating Ground Grid-01.png");

	//3 for width, 2.5 for the size of the texture, and 5 for the scale that we're applying.
	//we have to finesse this a little too because I think the registration point is at the center
	//of the sprite, not the top-left like I'm used to in 2D.
	float startX = (- 3.0f*2.5f*5.0f / 2.0f) + 2.5f*5.0f/2.0f;

	float startZ = 0.0f;

	float startY = -5.0f;

	float currentX = startX;
	float currentZ = startZ;

	int xStack = 0;
	int zStack = 0;

		for(int i=0; i<9; i++) {
		currentX = startX + xStack*2.5f*5.0f;
		currentZ = startY + zStack*2.5f*5.0f;

		GameObject sp = AddSprite(texture);
		sp.transform.position = new Vector3(currentX,startY,currentZ);
		sp.transform.Rotate(90.0f, 1.0f, 1.0f);
		sp.transform.localScale = new Vector3(5.0f, 5.0f, 5.0f);

		xStack++;

			if(xStack == 3) {
			xStack = 0;
			zStack++;
			}

		}

	}

	Texture2D LoadPNG(string filePath) {

	Texture2D tex = null;
	byte[] fileData;

	if (System.IO.File.Exists(filePath)) {
	fileData = System.IO.File.ReadAllBytes(filePath);
	tex = new Texture2D(16, 16);
	tex.LoadImage(fileData);
	}

	return tex;
	}

	GameObject AddSprite (Texture2D tex) {

	Texture2D _texture = tex;
	Sprite newSprite = Sprite.Create(_texture, new Rect(0f, 0f, _texture.width, _texture.height), new Vector2(0.5f, 0.5f),128f);
	GameObject sprGameObj = new GameObject();
	sprGameObj.AddComponent<SpriteRenderer>();
	SpriteRenderer sprRenderer = sprGameObj.GetComponent<SpriteRenderer>();
	sprRenderer.sprite = newSprite;

	return sprGameObj;

	}

	// Update is called once per frame
	void Update () {}

}

Now, play your scene, and you’ll see that your Barrel is falling! That’s cool, but you’ll also notice that it falls right through our floor. Let’s do something about that.

 

In your Start() function, the line that says:


_barrel = Instantiate(Barrel);

Change this to:


_barrel = Instantiate(Barrel);
_barrel.transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 15.0f, 3.0f);

We’re going to position the barrel near the middle of our ground and randomly between -3.0 to the left and 3.0 to the right on the x-axis. Now, right before the closing bracket “}” of your Start() function, add this block of code.


GameObject ground = new GameObject();
ground.transform.position = new Vector3(0,-5.2f,0);
ground.transform.Rotate(90.0f, 1.0f, 1.0f);

ground.AddComponent<Rigidbody>();
ground.AddComponent<BoxCollider>();

ground.GetComponent<BoxCollider>().size = new Vector3(20.0f, 20.0f, 1.0f);
ground.GetComponent<BoxCollider>().center = new Vector3(0f, 0.0f, 0.3f);
ground.GetComponent<Rigidbody>().isKinematic = true;

What’s going on with this? See, we made our nice tiled floor with our square png graphic, but that doesn’t interact with the physics engine. We need something to represent the ground which the barrel can smack into. So we have to make something for it to smack into. Some steps in the above code should remind you of things that you did in the Unity window with all those menus and clicking and dragging. We’re creating a new game object, we’re adding a few components to it, and we’re configuring those components. It has a RigidBody component, and a simple Collider component. We’re telling Unity that this is a solid box. And note the very important “isKinematic” property. What we’re saying here is “THIS THING DOESN’T GET KNOCKED AROUND” We’re giving it a fixed position and we’re expecting it to stay there no matter what collides with it. You’ll also note that we’re positioning the box so that its top face is just flush with our tiled floor. It’s going to look like the floor is causing the barrel to bounce, but what’s actually happening is that the barrel is colliding against our invisible ground object.

 

All right, play your scene. The barrel will bounce right off the ground! All right, let’s keep moving. What we want now is for barrels to continually spawn as the scene plays. We want Barrel Rain. We’re going to do some work in our Update() function. The idea is that we’re going to have an integer variable which ticks up by 1 with every frame. Whenever it reaches 50 or some other critical threshold, we’re going to create a new barrel.

 

First, add this function in after the closing bracket “}” of AddSprite():


GameObject AddFallingBarrel() {

//x y z: x moves us side to size. +y is up towards the sky, -y is down towards the ground, +z is toward the horizon, -z is toward the camera.
GameObject barrel = Instantiate(Barrel);
barrel.transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 15.0f, Random.Range(1.0f, 5.0f));
barrel.transform.Rotate(Random.Range(-50,-80), (float)Random.Range(-20,20), (float)Random.Range(-20,20));

return barrel;
}

That will take care of the instantiation of the barrel. It will generate it, place it randomly within a certain range and also rotate it randomly around the x,y and z axes.

 

Now this line near the top of Main:


private GameObject _barrel;

Replace it with these lines;


private int _barrelClock = 0;
private int _barrelLimit = 50;

Now back in Start(), these two lines:


_barrel = Instantiate(Barrel);
_barrel.transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 15.0f, 3.0f);

Get rid of them. Now, copy and paste this for your Update() function.


// Update is called once per frame
void Update () {
_barrelClock++;

	if(_barrelClock == _barrelLimit) {
	AddFallingBarrel();
	_barrelClock = 0;
	}

}

Your complete Main script should look like this:



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour {

public GameObject Barrel;

private int _barrelClock = 0;
private int _barrelLimit = 50;

	// Use this for initialization
	void Start () {
	Debug.Log("Must go faster.");

	Texture2D texture = LoadPNG(Application.dataPath+"/Unity Repeating Ground Grid-01.png");

	//3 for width, 2.5 for the size of the texture, and 5 for the scale that we're applying.
	//we have to finesse this a little too because I think the registration point is at the center
	//of the sprite, not the top-left like I'm used to in 2D.
	float startX = (- 3.0f*2.5f*5.0f / 2.0f) + 2.5f*5.0f/2.0f;

	float startZ = 0.0f;

	float startY = -5.0f;

	float currentX = startX;
	float currentZ = startZ;

	int xStack = 0;
	int zStack = 0;

		for(int i=0; i<9; i++) {
		currentX = startX + xStack*2.5f*5.0f;
		currentZ = startY + zStack*2.5f*5.0f;

		GameObject sp = AddSprite(texture);
		sp.transform.position = new Vector3(currentX,startY,currentZ);
		sp.transform.Rotate(90.0f, 1.0f, 1.0f);
		sp.transform.localScale = new Vector3(5.0f, 5.0f, 5.0f);

		xStack++;

			if(xStack == 3) {
			xStack = 0;
			zStack++;
			}

		}

	GameObject ground = new GameObject();
	ground.transform.position = new Vector3(0,-5.2f,0);
	ground.transform.Rotate(90.0f, 1.0f, 1.0f);

	ground.AddComponent<Rigidbody>();
	ground.AddComponent<BoxCollider>();

	ground.GetComponent<BoxCollider>().size = new Vector3(20.0f, 20.0f, 1.0f);
	ground.GetComponent<BoxCollider>().center = new Vector3(0f, 0.0f, 0.3f);
	ground.GetComponent<Rigidbody>().isKinematic = true;

	}

	Texture2D LoadPNG(string filePath) {

	Texture2D tex = null;
	byte[] fileData;

	if (System.IO.File.Exists(filePath)) {
	fileData = System.IO.File.ReadAllBytes(filePath);
	tex = new Texture2D(16, 16);
	tex.LoadImage(fileData);
	}

	return tex;
	}

	GameObject AddSprite (Texture2D tex) {

	Texture2D _texture = tex;
	Sprite newSprite = Sprite.Create(_texture, new Rect(0f, 0f, _texture.width, _texture.height), new Vector2(0.5f, 0.5f),128f);
	GameObject sprGameObj = new GameObject();
	sprGameObj.AddComponent<SpriteRenderer>();
	SpriteRenderer sprRenderer = sprGameObj.GetComponent<SpriteRenderer>();
	sprRenderer.sprite = newSprite;

	return sprGameObj;

	}

	GameObject AddFallingBarrel() {

	//x y z: x moves us side to size. +y is up towards the sky, -y is down towards the ground, +z is toward the horizon, -z is toward the camera.
	GameObject barrel = Instantiate(Barrel);
	barrel.transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 15.0f, Random.Range(1.0f, 5.0f));
	barrel.transform.Rotate(Random.Range(-50,-80), (float)Random.Range(-20,20), (float)Random.Range(-20,20));

	return barrel;
	}

	// Update is called once per frame
	void Update () {
	_barrelClock++;

		if(_barrelClock == _barrelLimit) {
		AddFallingBarrel();
		_barrelClock = 0;
		}

	}

}

Now play your scene.

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-a-rain-of-barrels-768×520.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-a-rain-of-barrels-1024×694.png

 

It’s raining barrels! And they’re crashing into each other and rolling on the ground and everything! What a riot!

 

All right, there’s just one more thing to do here, which is that we want this thing to conserve memory. As it stands now, we’re creating barrels *but we’re never destroying them*. Bad. We want some kind of condition by which barrels vanish from memory. Well, handily enough, they roll right off the ground and fall to infinity, so what we’re going to do is enforce a condition in which any barrel with a y < -15 is going to get destroyed. But how to do that? What we’re going to do is attach a new script to the Barrel prefab. If you’re confused by this idea, it’s time to re-orient. Our Main script is attached to an invisible GameObject called Bootstrap. Remember from last tutorial? We’ve designated Boostrap as the container for the Main script which governs the whole scene. What we want to do in this case however, is govern a certain class of objects within that scene. We need every Barrel to have our destruction condition “baked right in” and to do that, we add a script to the Barrel prefab.

 

So in the Assets window, right click and select Create -> C# Script. Call it “BarrelPrefab”. Double-click that your new BarrelPrefab script and overwrite it with this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BarrelPrefab : MonoBehaviour {

	// Use this for initialization
	void Start () {

	}

	// Update is called once per frame
	void Update () {

		if(this.transform.position.y < -15.0f) {
		Debug.Log("Clever girl");
		Destroy(gameObject);
		}

	}

}

Notice that gameObject is a variable that’s just sort of… there for us to call, because this script is attached to our BarrelPrefab. When you call Destroy(gameObject), Unity will clear that object out including any components that are attached to it. In theory, this should handle our memory management.

Now, back in Unity, select your Barrel prefab in your Assets window. Then go to the Inspector, and click Add Component. Select Scripts -> Barrel Prefab.

Now when you run your scene, it will behave much the same way as it did before, but you’ll notice that you’re getting these “Clever girl” logs every time a barrel is destroyed. You’ll also notice in your hierarchy window on the left that it’s both adding and subtracting barrels from the hierarchy. It seems to max out at around 15 barrels or so, and then you can leave it up to physics to move barrels out of the way and randomly kick them off to their doom. Our death-condition is very parsimonious in that sense.

 

Your scene is now a cacophony of barrels:

http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-final-barrel-rain-768×521.png http://glowingbluecore.com/wordpress/wp-content/uploads/Unity-barrel-rain-Step-5-final-barrel-rain-1024×695.png

 

And that’s the end of our tutorial series. In these four tutorials, we modeled a barrel in Blender, we imported it to Unity and made it dance around with physics and even guaranteed that the memory wouldn’t run away. Now you have enough of a base to be able to experiment on your own.