Press Enter to Search

Beginner’s Guide to Drawing with Futile

c
January 19th, 2013

I’m still relatively new to programming. I started learning to program from scratch about two years ago. One of the things that I’ve struggled with the most is drawing things programmatically.

I’ve looked for simple explanations but have never found one that really clarified and simplified the process. Over the past two years, I’ve tried countless times to dive in and try to understand it. In SDL. In Cocos2d. In QuartzCore. But until now, I just didn’t have enough experience to figure it out. After studying and messing around with the Futile code, I’ve finally figured some things out.

Before we begin, let us discuss prerequisites. You should already know how to set up a project in Futile. How to use FSprites and add them to stages. How to use FContainers. Basically, you should have a solid understanding of the basics of Futile. Also, keep in mind that for this blog post, I am using Futile’s development branch as of January 19, 2013. Matt makes lots of changes so there may be things that don’t work quite right if you’re reading this in the future.

My goal for this tutorial is to give beginners the type of tutorial I was looking for when I was getting started. This is really complex stuff, but I’ll try to clarify it as much as possible.


Here's the trapezoid we'll be drawing with some arbitrary values

Here’s the trapezoid we’ll be drawing with some arbitrary values

I want to show you how to draw a trapezoid. It’s a relatively simple shape, but it still requires some tricky maneuvering for beginners and explains the core concepts pretty well. I think it’s a good place to get started.


To draw the trapezoid, we need to split it into triangles

To draw the trapezoid, we need to split it into triangles

The first step of drawing any shape is figuring out how it is going to be constructed. Clearly a trapezoid can’t be made out of squares (FFacetType.Quad) so we need to go with triangles (FFacetType.Triangles). The trapezoid shape can easily be constructed with three triangles.

Make a new file called TrapezoidSprite and replace its contents with the following:

using UnityEngine;
using System;

public class TrapezoidSprite : FSprite
{	
	public float trapezoidWidth;
	public float trapezoidHeight;
	public float insetFromSide;

    public TrapezoidSprite (string imageName, float width, float height, float inset) : base() {
		trapezoidWidth = width;
		trapezoidHeight = height;
		insetFromSide = inset;
		
		// This initializes the sprite with a specified texture and indicates
                // that we want to use triangles to draw the trapezoid. The third parameter is 3 because
                // we are going to construct the trapezoid out of three triangles.
		Init(FFacetType.Triangle, Futile.atlasManager.GetElementWithName(imageName),3);
		
		// Just necessary setup code
		_isAlphaDirty = true;
		UpdateLocalVertices();
	}

This just initializes our sprite with the parameters we need to draw the trapezoid. The Init line is the important one. We are telling the renderer that we are going to create a sprite using three triangles and “paint” it with the specified image.


Next we’re going to add the PopulateRenderLayer method. This is where all the drawing takes place. Add the following code after the end of the initializer:

override public void PopulateRenderLayer()
	{
		if(_isOnStage && _firstFacetIndex != -1) // if this is false, the sprite hasn't been initiated correctly yet
		{		
			// _isMeshDirty is set to true when things need to be rerendered,
			// so we need to set it to false here because that's what we're doing.
			_isMeshDirty = false;
			
			// Since each facet (triangle) has 3 vertices, the first vertex index
			// is the index of the first facet multiplied by 3.
			int vertexIndex0 = _firstFacetIndex * 3;
			
			// This array contains the vertices that are used to draw the triangles.
			// The size of this array is 9 because there are 3 facets (triangles)
			// with 3 vertices each (thus, 3 * 3 vertices total)
			Vector3[] triangleVertices = _renderLayer.vertices;
			
			// This array contains the vertices that are used to apply the texture to the triangles.
			// The size of this array is 9 because there are 3 facets (triangles)
			// with 3 vertices each (thus, 3 * 3 vertices total).
			Vector2[] triangleUVVertices = _renderLayer.uvs;
			
			// This array contains the actual local vertices of the trapezoid.
			Vector2[] trapezoidVertices = new Vector2[5];
			
			// This array contains the vertices on the texture that will be
			// stretched to fit right on top of the trapezoidVertices.
			Vector2[] textureUVVertices = new Vector2[5];
			
			// This holds the colors of each vertex for each triangle.
			Color[] colors = _renderLayer.colors;

I understand that this may seem a bit overwhelming. If so, don’t worry. It will make sense later as we break down the drawing implementation. All we’re doing here is initializing all the stuff we need in order to draw everything.


The local vertices just define where the points of the trapezoid are (well, the points of the triangles that make it up)

The local vertices just define where the points of the trapezoid are (well, the points of the triangles that make it up)

Since we now know our trapezoid’s vertices—based on the vertices of the triangles that make it up—we can store them in the trapezoidVertices array. Add the following code right after the code you previously added (still within the PopulateRenderLayer method):

// Here we create all the vertices of the trapezoid.
trapezoidVertices[0] = new Vector2(0, 0);
trapezoidVertices[1] = new Vector2(insetFromSide, trapezoidHeight);
trapezoidVertices[2] = new Vector2(trapezoidWidth / 2f, 0);
trapezoidVertices[3] = new Vector2(trapezoidWidth - insetFromSide, trapezoidHeight);
trapezoidVertices[4] = new Vector2(trapezoidWidth, 0);

Next, we have to think about how we’re going to apply our texture to the trapezoid shape. Imagine you have a trapezoid-shaped brownie pan and you need to cover it with a square sheet of plastic wrap (not sure why you wouldn’t just finish the brownies instead of saving them for leftovers). Obviously, you can’t just set the plastic wrap on top of the tray. You have to stretch it or shrink it in some places to make it fit perfectly with the trapezoid shape. This is exactly what we need to do with our texture.

Before we get into actually applying the texture though, we need to talk about UV’s. Just think of UV’s as XY’s because that’s what they are. They are X (horizontal) and Y (vertical) values. The only reason they’re called UV’s is because X and Y are already taken in the 3D space.


No matter the size of your texture atlas, these are the uv points

No matter the size of your texture atlas, these are the uv points

UV’s are relative. No matter the size of your texture atlas, they always range from 0 to 1. For example, the point exactly in the middle of the atlas is (0.5, 0.5).


The texture uvs are dependent upon where the actual image is located in the texture atlas

The texture uvs are dependent upon where the actual image is located in the texture atlas

The UV values for a specific image within an atlas depend on where the image is located. For example, if an image’s bottom left point is located at the bottom middle of the atlas, the uvBottomLeft value will be (0.5, 0).


We need to pick points on our texture to match up with the vertices of the trapezoid. Basically, we're stretching the texture over the trapezoid shape.

We need to pick points on our texture to match up with the vertices of the trapezoid. Basically, we’re stretching the texture over the trapezoid shape.

This is the part that it took me a long time to understand. Returning to our trapezoid-shaped brownie pan analogy, this is where we actually stretch the plastic wrap over it. We just need to tell the renderer which points on the texture to match up with the vertices of the trapezoid and it will stretch it out for us.

Add the following code after your previous code (still within the PopulateRenderLayer method):

// This is the width of the specific image in relative uv values
// (not the width of the image in pixels)
float uvWidth = (_element.uvTopRight.x - _element.uvTopLeft.x);
			
textureUVVertices[0] = new Vector2(_element.uvBottomLeft.x,                _element.uvBottomLeft.y);
textureUVVertices[1] = new Vector2(_element.uvTopLeft.x,                   _element.uvTopLeft.y);
textureUVVertices[2] = new Vector2(_element.uvBottomLeft.x + uvWidth / 2f, _element.uvBottomLeft.y);
textureUVVertices[3] = new Vector2(_element.uvTopRight.x,	           _element.uvTopRight.y);
textureUVVertices[4] = new Vector2(_element.uvBottomRight.x,	           _element.uvBottomRight.y);

This just defines the points on the texture that we want to match up with the points on the trapezoid. The _element variable is the FAtlasElement for the specific texture.


Finally, we need to draw the trapezoid and “paint” it with our texture. Before we do that though, you need to understand how the renderer actually draws the triangles.

Each triangle in the trapezoid can be defined based on the vertices they're made of

Each triangle in the trapezoid can be defined based on the vertices they’re made of

Each triangle is made of three vertices. A triangle can be defined by the vertices that are used to make it.

It is very important that you understand how the renderer draws. Think of the renderer as a very stupid artist who only knows how to draw triangles. The only way it knows what to draw is if you feed it the three points of a triangle. Then you just keep doing this until all your triangles are covered.

Since a lot of our vertices overlap, we only needed to define five for our trapezoid. However, our stupid artist (the renderer) only understands triangles, so it needs nine vertices (3 triangles * 3 vertices). Even though we know some of the vertices are used multiple times (so we just make five), the renderer does not care (so it needs nine).

Next, add this code:

// This just colors all the vertices on all the triangles
// to whatever color is set for the sprite.
for (int i = 0; i < 9; i++) {
	colors[vertexIndex0 + i] = _alphaColor;
}
			
// Here is where we construct the three triangles. We just tell it
// which vertices on the trapezoid to use for each triangle and it
// applies those values to the rendering matrix.
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 0], trapezoidVertices[0],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 1], trapezoidVertices[1],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 2], trapezoidVertices[2],0);

_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 3], trapezoidVertices[1],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 4], trapezoidVertices[2],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 5], trapezoidVertices[3],0);
			
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 6], trapezoidVertices[2],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 7], trapezoidVertices[3],0);
_concatenatedMatrix.ApplyVector3FromLocalVector2(ref triangleVertices[vertexIndex0 + 8], trapezoidVertices[4],0);
			
// Here is where we actually assign the points on the texture to
// the points on the triangles. In other words, we are stretching the
// texture so that the points on the texture line up with the vertices
// of the trapezoid. Since we're drawing it with triangles, we have
// to do this for each triangle so the renderer knows how to "paint" them.
triangleUVVertices[vertexIndex0 +  0] = textureUVVertices[0];
triangleUVVertices[vertexIndex0 +  1] = textureUVVertices[1];
triangleUVVertices[vertexIndex0 +  2] = textureUVVertices[2];
			
triangleUVVertices[vertexIndex0 +  3] = textureUVVertices[1];
triangleUVVertices[vertexIndex0 +  4] = textureUVVertices[2];
triangleUVVertices[vertexIndex0 +  5] = textureUVVertices[3];

triangleUVVertices[vertexIndex0 +  6] = textureUVVertices[2];
triangleUVVertices[vertexIndex0 +  7] = textureUVVertices[3];
triangleUVVertices[vertexIndex0 +  8] = textureUVVertices[4];
			
// This just tells the renderer that we made changes.
_renderLayer.HandleVertsChange();

First we set the color of the vertices to whatever color is set for the sprite (if you wanted, you could assign random colors to different vertices and would end up with a colorful mess.) All we have left after that is to draw and “paint” the triangles!

Don’t worry about all the fancy matrix stuff. I don’t really get it either. All you need to know is that this is where we give the stupid artist (the renderer) the triangle vertices so it knows how to draw them.

Throw a curly brace at the end of the method and the class and you’re done! Now just go into your main scene and add this code:

TrapezoidSprite sprite = new TrapezoidSprite("Futile_White", 300, 100, 75);
sprite.x = Futile.screen.halfWidth;
sprite.y = Futile.screen.halfHeight;
Futile.stage.AddChild(sprite);

The “Futile_White” texture is created in Futile.cs. It’s just a 16×16 pixel white square. We can stretch it to our heart’s desire. Perfect.

Try running it! You should get something like this:

The trapezoid, rendered in all it's glory by our stupid artist.

The trapezoid, rendered in all its glory by our stupid artist.

But there’s one problem. We wanted to place the trapezoid in the center of the screen, but instead, the bottom left vertex is in the center of the screen. Go back to the PopulateRenderLayer method and add the following code right after you create all the vertices of the trapezoid:

// Offset the vertices so that (0, 0) is the middle of the trapezoid
// instead of the bottom left vertex.
for (int i = 0; i < 5; i++) {
	trapezoidVertices[i].x -= trapezoidWidth / 2f;
	trapezoidVertices[i].y -= trapezoidHeight / 2f;
}

Run it again and you should get this:

Now the trapezoid is exactly in the middle.

Now the trapezoid is exactly in the middle.


That’s it! I encourage you to play around with it. The awesome thing about a sprite that gets rendered like this is that you can make changes on the fly. For example, we could change sprite.trapezoidWidth and the renderer would redraw it with the new width (just make sure you set _isMeshDirty to true so it knows to redraw)! You can make some really crazy effects this way.

Another thing you can do is mess around with the colors. For example, if you replace this:

colors[vertexIndex0 + i] = _alphaColor;

with this:

colors[vertexIndex0 + i] = new Color(RXRandom.Range(0, 1f), RXRandom.Range(0, 1f), RXRandom.Range(0, 1f), 1);

you end up with this:

This is what happens if the vertices are all colored randomly.

This is what happens if the vertices are all colored randomly.

Play around and let me know what you come up with!

You can get all the code used for this project here.

Thanks so much for reading. Please let me know if you have any questions!

Follow me on Twitter and/or get my game Polymer!

There are no comments yet, add one below.