TGB/BreakoutTutorial

From TDN

(Redirected from TGB/Tutorials/Breakout)

Contents

The following links can take you to other sections of the tutorial:

The Breakout Tutorial Part 1 - Making the Scene
The Breakout Tutorial Part 2 - Balls to the Wall
The Breakout Tutorial Part 3 - Miles of Tiles
The Breakout Tutorial Part 4 - Dying to Score
The Breakout Tutorial Part 5 - Levels O'Plenty
The Breakout Tutorial Part 6 - Power Play
The Breakout Tutorial Part 7 - Highscores and Farewells


For Part 1 - Making the Scene, please continue reading below.

The Breakout Tutorial Part 1 - Making the Scene

A Tetraweb Tutorial


Overview and Scope of Project

The purpose of this tutorial is to guide a TGB beginner or intermediate level user through the creation of a complete game with multiple levels, high scores, sound effects, particle effects and reasonably fun gameplay.

Several things to note about this tutorial

  • This tutorial works with TGB 1.6 and above. It also works with minor changes in 1.13, but has not been as thoroughly tested.
  • Please use the BreakoutTutorial Discussion Thread to comment or suggest changes to this tutorial, rather than editing it directly. A great deal of time and effort went into its creation, and I would like to moderate all changes when possible.
  • It assumes knowledge of using the level builder, best gained by doing ALL of the introductory tutorials included with the program; the fish game and others. This tutorial, especially this first part, will move quite quickly through some basic level builder stuff.
  • No behaviors are used. The behavior system is fine as a method of adding logical, reusable scripting behavior to the level builder. However this tutorial in large part is about scripting your game, and uses the level builder only briefly. Better to learn how to code script first, before you learn how to abstract it into a drop down menu in the level builder.
  • This tutorial will be script heavy. (See above.) 98% of it will be typing or copying and pasting into a text editor. I recommend a good one, with project level file management, so you can jump from cs file to cs file quickly.
    For Windows, Torsion or E Text Editor. For Mac, TextMate or XCode. Both have TorqueScript bundles for syntax highlighting and other functionality.
  • The art in this tutorial is one step above quick and dirty. After you complete it, feel free to go back and substitute your own, better art. I don't recommend you do this, however, until you have completed the entire tutorial.

Let's Get Started

Create a new project, save it as Breakout.

Create a new level, save it as 'playlevel.t2d'. Click on the Edit tab and in the Camera settings, set the width to 200 and the height to 150. Make sure the design resolution is 1024x768. (See fig. 1.1)


Figure 1.1
Figure 1.1


Figure 1.2
Figure 1.2

Adding Walls

Click on the Create tab and drag a Scene Object onto the scene view. (See fig. 1.2)

Double-click the new scene object to edit it, and open the Scene Object section. Set the following: Position X: -98.000, Position Y: 0.00, Width: 3.000, Height: 150.000, Group: 3.

Expand the Scripting section and set the class to wallClass (remember, case is important so make sure it you don't call it 'wallclass' or 'Wallclass'.)

Now expand the Collision section and uncheck Send Physics and Receive Physics, and check Receive Collison. You can leave everything else as is.

Now select your new left wall and copy it and then paste it immediately. You may need to zoom out to see it. Your copy is now selected, so in the Scene Object section change the -98.000 for the X value to 98.000. Now you have your right wall.

Again choose paste, to paste another copy of your left wall, and with it selected open its Scene Object section and set the following: X: 0.000, Y: -74.000, Width: 200.000, Height: 3.000. And now you have your top wall.

Adding the Lost Ball Trigger

Finally, click the Create tab and drag a Trigger onto the scene view (Fig 1.2 again)

Double-click the new trigger to edit it. Deselect Leave Callback in the Trigger section. In the Scene Object section set these values: X: 0.000, Y: 84.500, Width: 200.000, Height: 17.000.

Next, in the Scripting section set the Class to lostTrig and in the Collision section uncheck Send and Receive Physics.

Figure 1.3
Figure 1.3

You should now have a scene that looks like Figure 1.3. The walls are going to bounce the ball back onto the playing field and the trigger along the bottom is going to send a message when the ball enters it, letting us know that the ball is lost.



Ship and Shield

Twbt_ship.zip

The ship and shield are separate objects, to give us more flexibility later on in the game. Download this zipped file (above), and drag the two pngs it contains into your game/data/images folder.

Figure 1.4
Figure 1.4
In the Create panel of the editor add a new image map (see fig. 1.4) and add both the ship base and the shield.

Adding the Ship Base

Next, drag the shipbaseImageMap onto the scene view and double-click it to edit. Set the following in the Scene Object section.
X: 0.000, Y: 64.819, Layer: 1, Group: 2.

Next, in the Scripting section, set the Name to theBase and the Class to baseClass; also check the Use Mouse Events box. (Remember that capitalization matters.)

Now, expand the Collision section and check Send Collision, Receive Collision, Send Physics and Callback. Make sure Receive Physics is not checked.

While in the Collision section let's set the collision polygon for our ship base.
Figure 1.5
Figure 1.5
Hover the mouse over the base, click the first icon to edit the collision poly, and make it look as much as you can like figure 1.5.
Click the selection tool along the top to exit edit mode and save your polygon.

Finally, open the World Limits section and choose CLAMP from the dropdown. Then set the Min. Bounds to X: -95.175, Y: 50.000 and the Max. Bounds to X: 95.175, Y: 72.918






Adding the Shield Arch

Now drag the ship_arcImageMap onto the scene view and double-click it to edit it. In the Scripting section, set the Name to theArch and the Class to paddleClass

Next, expand the Collision section and check only Receive Collision and Callback, nothing else.

Figure 1.6
Figure 1.6
Now we are going to mount the shield to the base. Later in this tutorial we will be mounting objects in script, but for this time we will do it in the editor. Select the base, and then mouse over it and select the second icon to Edit the Objects Link Points. Add one as close as possible to the center. (See fig. 1.6) Click the selection tool along the top to exit edit mode.

Figure 1.6.5
Figure 1.6.5
Now we will mount the shield. Select it and select the fourth icon when you mouse over it. (See fig. 1.6.5)





Figure 1.7
Figure 1.7
Then attach the shield to the mount point you created. (See fig. 1.7)

Figure 1.8
Figure 1.8
Next, we'll set the collision polygon for our shield. Hover the mouse over the shield, click the first icon to edit the collision poly, and make it look as much as you can like figure 1.8.
Click the selection tool along the top to exit edit mode and save your polygon.





The Scripting Begins

We are going to make our first small steps into scripting now. By the end of this tutorial we will have written about a hundred functions spread across dozens of separate .cs files. These will all be pulled in with exec statements, and we want to organize it all so we don't have chaos.

Start by opening your gameScripts/game.cs file and add the line below to the top of the startGame function.

	function startGame(%level)
	{
		exec("~/exec.cs");

Now whenever we want to include another file with a group of functions we just add it to our exec.cs file.
Let's do that now. Create a new file in your /Breakout/game/ directory called exec.cs and add the following line to it:

	exec("./gameScripts/base.cs");

Now create a new file in your /Breakout/game/gameScripts/ directory called base.cs. Add this code to it:

	function baseClass::onLevelLoaded(%this, %scenegraph) 
	{ 		
		$thebase = %this;
		%this.setDamping(4.8);
		%this.setRestitution(1.0);
		%this.setMaxAngularVelocity(0);
		%this.setFriction(0.3);
		%this.setCollisionResponse(CUSTOM);
		%this.setMaxLinearVelocity(350);
	}

We gave our base the class baseClass so when the level is loaded, this function will be run. The first line assigns the base to a global variable so we can always access it throughout the game. The next five lines modify the physics of how it responds to movement and collisions.

For instance, it will be moved left and right by the mouse, but we want it to slow down quickly when you stop moving it, which is just what the setDamping method does. The setMaxLinearVelocity method ensures that it doesn't get moving too quickly.

We'll set a couple things on the arch now, which we had set to paddleClass. Add this function below the previous one in base.cs.

	function paddleClass::onLevelLoaded(%this, %scenegraph) 
	{ 
		%this.setRestitution(1.0);
		%this.setCollisionResponse(CUSTOM);
		%this.setMountInheritAttributes(true);
	}

Note that we are setting the MountInheritAttributes field to true here. Primarily, we want this arch to be invisible if the base suddenly becomes invisible some time in our game. We could also have set this in the TGB editor; there is usually more than one way to do things in TGB.

Moving with the Mouse

Save base.cs and then add the following two functions to game.cs right after the startGame function.

	function sceneWindow2D::onMouseMove(%this, %modifier, %worldPosition, %mouseClicks){
		movePaddle(%worldPosition);
	}
	
	function sceneWindow2D::onMouseDragged(%this, %modifier, %worldPosition, %mouseClicks){
		movePaddle(%worldPosition);
	}

Now whenever the mouse (or trackpad) is moved, it will call these functions. Our movePaddle function will take the %worldPosition variable, so lets write that now; you can add it right below the functions above in game.cs.

	function movePaddle(%worldPosition) {
		%newXPos = getword(%worldPosition,0);
		if (%newXPos < 0) { %posmod=-1; } else { %posmod = 1; }
		if (mAbs(%newXPos) > 65) %newXPos = %posmod * 65;
			
		$thebase.setImpulseForce((%newXPos)*$vmod,0);

		sceneWindow2D.setMousePosition("0 0");	
	}

Let's go over this line by line, then we are almost ready to finally test what we have so far.
First, we use the getword method to grab the x position of the mouse and put it into a local variable, %newXPos. %worldPosition will be in the format "50.000 200.00" and the getword method will give us just the first word, the x position, which is all we care about.

Next we compare it to 0, to see if the player is moving the mouse left (less than zero) or right (greater than zero) and we make our local %posmod variable either -1 or 1.

Next, we see if the absolute value of %newXPos is greater than 65; we are checking if it is more than 65 or less than -65, which would mean the user moved their mouse very fast. If this is the case, we make the value either 65 or -65 by multiplying 65 by the %posmod variable we generated in the previous line. We are capping the maximum speed that the mouse can be moved at 65 in either direction.

The next line uses the global variable we set earlier, $thebase, and gives the base a shove in the x direction only, either left or right, using setImpulseForce. But what is that $vmod global variable?

Before we get to that, lets look at the final line in the movePaddle function. We are using setMousePosition to force the mouse back to the exact center of our scene window. We want to kind of freeze the mouse there and use its movement to control our base; that's why we are computing the movement of the mouse based on a zero center screen position in this movePaddle function.

Now back to that $vmod global variable.

It turns out that when we use the setImpulseForce method on our base, we get a pretty good result, as far as speed and degree of control, when we multiply the %newXPos amount (which is how far the mouse has moved left or right) by 2.0. So we could say
$thebase.setImpulseForce((%newXPos)*2.0,0);

but that also seems like a pretty good number to have as a global variable, something we can change. We can imagine when we might want to make that $vmod 0.5, for example, and have our player moving through mud; or make it 10.0 and have them moving like they are on ice, barely able to control the base. Potentially fun stuff.

We are going to have a place to put global variables and functions relating to all of our levels, so lets make a new file in the gameScripts directory called theLevels.cs and put this in it:

	$VMODSTANDARD = 2.0;
	$vmod = $VMODSTANDARD; //paddle speed factor

Why two variables? We are following the convention of using UPPERCASE globals as constants. So $VMODSTANDARD will always be 2.0 throughout the game, whereas we may at some point modify $vmod. Conveniently, when we want to put $vmod back where it belongs, we can always use $VMODSTANDARD to reset $vmod equal to 2.0.

Now, of course, we need to bring this new file in, so add this to your exec.cs file.

	exec("./gameScripts/theLevels.cs");

Ready to Test

Make sure everything is saved, all your scripts and your playlevel.t2d in the TGB level editor. Let's press the Play Level button and see what we have.

Welcome back. Pretty nice, but lets get rid of that distracting cursor in the middle of the screen. Add this the end of your startGame function in game.cs.

	  Canvas.hidecursor();

and add this

	Canvas.showcursor();

to the end of the endGame function in the same file. (The showcursor call is not necessary in TGB 1.5 and higher, but it is in 1.13.)

Also, lets put the cursor in the middle of the screen when we start the game. You may have noticed a strange movement when you first move the base, that is because the mouse may be anywhere on the screen when we first call movePaddle and our math will be off. So put this in your startGame function, right after Canvas.hidecursor().

	sceneWindow2D.setMousePosition("0 0");

And while we are here, lets put in a more graceful way to exit, at least temporarily. Add this line to startGame right above movemap.push();

	moveMap.bind("keyboard", "q", "quitgame"); 

This will call our quitgame function when you press the 'q' key. Add that function right after startGame in game.cs.

	function quitgame(%val) {
		if (%val)
			quit();
	}

The %val local variable tells you if it is a keypress or a keyrelease. Without the if (%val) line, this function would be called twice when the user presses the 'q' key; once when it is pressed and once when it is released.

One More Thing

Before we finish up with this Part 1 of our Breakout Tutorial, I want to add one more thing. One of the best aspects of using the TGB system is its wonderful particle effects engine. It really sets TGB apart and it enables you to turn good game ideas into polished products.

With that in mind, it's my belief that you should add particle effects as you develop a game, rather than wait until the end in a 'final touches' phase. One reason we play games is for the visual rewards, so you really cannot overdo the particle effects. When you are developing your own games and you find yourself thinking 'That should probably spark when it hits that thing,' stop and add the spark effect.
Figure 1.9
Figure 1.9


I think the base should emit a shower of plasma when it moves, and stop emitting when it stops moving, so lets add that right now.



Twbt_pe1.zip

Download this zipped file (above) and add the newparticles.png to the project by creating a new imageMap. Make the image mode CELL so it breaks it into four separate frames.

Now add the basetrail.eff file, which defines the particle effect, to the game/data/particles directory. Quit and restart the TGB editor and you should see the new effect showing up in the Particle Effects section of the Create tab. Don't forget to reopen our playlevel.t2d level.

We are going to modify the baseClass::onLevelLoaded function to attach this effect to the base. Find that function in your gameScripts/base.cs file and add this at the end of it, after %this.setMaxLinearVelocity(350);.

	%trail = new t2dParticleEffect() {
		scenegraph = %scenegraph;
		effectFile = "~/data/particles/basetrail.eff";
		useEffectCollisions = "0";
		effectMode = "INFINITE";
		effectTime = "0";
		size = "15.000 15.000";
	};
	%linkIndex = %trail.mount($thebase, "0.000 1.000");
	%trail.playEffect(false);
	$thebase.myTrail = %trail;
	$thebase.myTrail.isplaying=true;

First, we load the new effect into the local variable %trail, adding a few settings. Then we use %trail.mount to attach the effect to the base, using our global variable $thebase that we set earlier. Then we play the effect, assign the local variable %trail to a new field in our base object called $thebase.myTrail so we can always access it. We also do the same with a new field that we make in our effect so we can easily keep track of whether it is playing or not with $thebase.myTrail.isplaying.

If you test the game now, you should see the constantly running plasma beneath the base. But that's not quite what we want. We will need to adjust the flow of the effect based on the movement of the base, and to do that we will need to use a timer to constantly check on that movement. Add one more line to the onLevelLoaded function, at the end.

	%this.setTimerOn(250);

This will run an onTimer function every 250 milliseconds, or four times a seconds. Add this to the base.cs file, after the previous function.

	function baseClass::onTimer(%this) {
	    %speed = getword(%this.getlinearVelocitypolar(),1);
		if ( (%this.myTrail.isplaying==true) && (%speed < 5) ) {
			%this.myTrail.stopEffect(true,false);
			%this.myTrail.isplaying=false;
		} else if ( (%speed > 5) && (%this.myTrail.isplaying==false) ) {
			%this.myTrail.playeffect(false);
			%this.myTrail.isplaying=true;
		}
	}

Going over this, first we get the current speed of our base using the getword method again with getlinearVelocitypolar. Now we do two tests; using our isplaying field, we see if the effect is playing and the speed is slow, below 5. If so, the next to lines stop the effect and set isplaying to false so we know it is stopped. The else test does the reverse, if the speed is faster than 5 and the effect is not playing we start it and set isplaying to true.

Test it now to see it in action.

Conclusion

Well, we laid a lot of groundwork here, but we have a good deal of scripting ahead of us. The next part of this tutorial will get some stuff bouncing around. Thanks for reading, and feel free to post any comments or questions on the BreakoutTutorial Discussion Thread.



Continue on to:
The Breakout Tutorial Part 2 - Balls to the Wall