TGB/BreakoutTutorial2

From TDN

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 2 - Balls to the Wall, please continue reading below.

The Breakout Tutorial Part 2 - Balls to the Wall

A Tetraweb Tutorial

BreakoutTutorial Part 2 Discussion Thread

A Note About Fullscreen

You may get the impression from game forums and elsewhere that forcing a user to fullscreen for your game is never acceptable. However, if a game requires complete and total mouse or trackpad control in realtime, like this Breakout Tutorial, then full screen is appropriate and correct. If a game does NOT require such complete and total control of the mouse in realtime, then full screen is inappropriate and rude.

To illustrate, a Suduko game that forces the user into full screen is annoying; it prevents the user from multitasking, checking email, etc. A Marble Blast kind of game, where the mouse controls the rolling marble, that plays in a window and NOT full screen is equally annoying. When the mouse leaves the window and you lose control, it takes you out of the realtime experience and is frustrating.

For a game such as we are building, full screen is the most appropriate option. Hiding the cursor in windowed mode is not recommended.

However, developing a game while switching back and forth to full screen is also annoying, so we are not going to do it. For your reference, here is what you would add to your gameScripts/game.cs file to the top of the startGame function, right above the movemap lines:

	%res=toggleFullScreen();

And at the bottom of gameScripts/game.cs in endGame after the showcursor() line add:

	%res=toggleFullScreen();

You can try it with the fullscreen toggle if you like, but then you should immediately comment both lines out.

	//%res=toggleFullScreen();

Ok, now we don't need to talk about fullscreen any further in this tutorial.

Let's Have a Ball

Twbt_ball.zip

Download this zipped file (above), and add the two pngs it contains to your game using the Create a New Image Map button in your level builder. With the particles4.png image you may need to double-click on it after it has been added and change its Image Mode to CELL.

Then drag the flow.eff file into your 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.

The basicball.png is a very simple circle for the ball. The flow.eff effect we will attach to it will add a nice constant smoking effect to the ball.

Load the Ball

We are going to need a new .cs file for all of our eventual ball functions. Create a new file in your /Breakout/game/gameScripts/ directory called ball.cs. We need to bring this new file in, so add this to your exec.cs file:

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

Now let's start our loadBall function. Add this to your new ball.cs file.

function newBall(%ballLoc,%ballVector) {
	%theball = new t2dStaticSprite() {
		scenegraph = $thescenegraph;
		imageMap = "basicballImageMap";
		canSaveDynamicFields = "1";
		Position = %ballLoc;
		size = "5.000 5.000";
		class = "ballClass";
		CollisionActiveSend = "1";
		CollisionActiveReceive = "1";
		CollisionPhysicsSend = "1";
		CollisionPhysicsReceive = "0";
		CollisionCircleScale = "0.7";
		CollisionDetectionMode = "CIRCLE";
		CollisionResponseMode = "BOUNCE";
		CollisionCircleSuperscribed = "0";
	};

Here we are creating a new ball sprite. When we call it, we are going to pass the function two variables: the location and the vector, which is a combination of direction and speed. Most of these lines should be self-explanatory; you've seen them in the level builder. Now add this to the newBall function:

		%theball.setCollisionMaxIterations(3);
		%theball.setMass(10.8);
		%theball.setMaxAngularVelocity(4);
		%theball.setMaxLinearVelocity(150);
		%theball.setRestitution(1.0);
		%theball.setFriction(0);
		%theball.setCollisionCallback(1);
		%theball.setConstantForce( 0, 0.5, true );
		%theball.setLinearVelocity(%ballVector);

This is another method of setting variables. Almost every field you can set directly when you create a script object, or a sprite in this case, has a corresponding method you can use to set it.

Note that we have now set the Position to %ballLoc and the LinearVelocity to %ballVector. The setConstantForce line above puts a constant, slight downward pull on the ball. This turns out to be necessary if the ball is bouncing on a fairly even line left and right. You don't want it to take forever to work its way down to the paddle.

Now we close out the newBall function by adding our flow effect to the ball.

	%theflow = new t2dParticleEffect() {
		scenegraph = $thescenegraph;
		effectFile = "~/data/particles/flow.eff";
		useEffectCollisions = "1";
		effectMode = "INFINITE";
		effectTime = "0";
		size = "15.000 15.000";
	};

	%linkIndex = %theflow.mount(%theball, "0.000 0.000");
	%theflow.playEffect(false);
	%theball.myFlow = %theflow;
	
	return %theball;
}

Here we are creating a new constant effect and mount it to our ball. This should look very familiar to you, from when we mounted the basetrail effect to the base. We then return the ball object to whatever called it.

You may have noticed the $thescenegraph global variable in the function above. We need to set objects to the scenegraph many, many times in this tutorial, so it's easiest to set it to the global variable at the outset. Add the following line to the end of the startGame function in game.cs.

	$thescenegraph = sceneWindow2D.getSceneGraph();

Now that we have the newBall function we're going to need to call it of course. Later in this series we will be adding other ways to call this newBall function. For now, we want to call it when the level starts up and we need to attach it to the base until the user clicks to shoot. We want it to move with the base so we are going to temporarily mount it, and we will do this using the loadBall function. Add this to the base.cs file right after the onTimer function.

	function baseClass::loadBall(%this) {
		$ballLoaded = newBall(%this.getPosition(),"0 0");
		$ballLoaded.setCollisionActive(0,0);
		$theballs.add($ballLoaded);
		%linkIndex = $ballLoaded.mount(%this, "-0.00 -2.229");
	}
First we call our newBall function using %this.getPosition() and "0 0" for the speed vector.
The mounted ball.
The mounted ball.
The %this will be the base object (this is a method in the baseClass) so this will create a newBall where ever the base is, with no speed. We assign this to a global variable $ballLoaded so we will be able to tell later if the base has a ball mounted to it.

We use the setCollisionActive(0,0) method on the new ball so it isn't constantly colliding with the base before we shoot it. The next line adds it to our $theballs SimSet, which we will explain in a moment. The last line does the actual mounting to our base; the "-0.00 -2.229" is just the offset, so it appears above the base instead of right on top of it.

Now that we have our loadBall function (which calls our newBall function,) we have to find a place to call our loadBall function. Open the theLevels.cs file that we created last time and we are going to add a rudimentary initializeLevel function to it.

	function initializeLevel() {
		$theballs = new SimSet(){};
		$thebase.loadBall();
	}

This function is quite small now, it will grow as we work through this tutorial series. First we make a new SimSet called $theballs. We do this because we want to have the option of having multiple balls in our game, and this SimSet is a handy way to keep track of them all. We won't actually have more than one ball until Part 6 of this series, but we know we want this functionality, so we add it now. SimSets are useful containers of unlimited objects; like arrays on steroids. You can count them, add to them, cycle through them, etc.

The next line actually calls the loadBall function. Now all we need to do is call the initializeLevel function. Add this line to the end of the startGame function in game.cs, right after the $thescenegraph line.

	initializeLevel();

It's finally time to test it. Save everything, make sure you have the playLevel.t2d level loaded in your level builder, and play your level. You should see the new ball attached to your base, moving with it as you move your mouse.

Shoot the Ball

Now that we have the ball attached, we want to shoot it. The mouse click is the best place to do that, so add this function to your game.cs file, right after the sceneWindow2D::onMouseDragged function.

	function sceneWindow2D::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks) {
		fireButton(true);
	}

This is simply calling a fireButton function when the user clicks. We are going to eventually have a few different functions associated with firing, so we'll make a new file called firing.cs in your /gamescripts/ folder and add this function to it:

function fireButton(%val) {
	if (%val) {
		if (isObject($ballLoaded)) {
			$ballLoaded.dismount();
			$ballLoaded.setLinearVelocity("0 -60");
			$ballLoaded.setCollisionActive(1,1);
			$ballLoaded = 0;
		}
	}
}
The shooting ball.
The shooting ball.
Right now we call this with fireButton(true) so the if (%val) line will always be true. Then it uses isObject to see if we in fact have a ball loaded up, and if so the next four lines are executed.

We dismount() the ball from the base, then we set it in motion straight up with the setLinearVelocity line. Then we turn collision back on for the ball, and finally, we erase the $ballLoaded global variable so we know we don't have an attached ball any more, and further calls to the fireButton will do nothing.

We have to pull this new cs file in, so add this to the end of your exec.cs file:

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

Go ahead and save everything and test. Clicking the mouse should launch your ball and you can bounce it with the paddle.

A Pinch of Physics

Right now we are using the standard BOUNCE physics, which is fine. We want to add a little more natural response to our game though, and we are going to start now by adding something in a collision callback. What we want is for the ball to get a faster push if the paddle is moving relatively fast. Add this to your ball.cs file right below the newball function:

	function ballClass::onCollision(%srcObject, %dstObject, %srcRef, %dstRef, %time, %normal, %contacts, %points) {
		if (%dstObject.class $= "paddleClass") {
			%speed = getword($thebase.getlinearVelocitypolar(),1);
			if (%speed > 150) { 
				  %force = t2dVectorScale(%normal,(-%speed*2));
				  %srcObject.setImpulseForce(%force, false);
			}
		}
	}

Let's go over this. The ballClass::onCollision function is called whenever an object of that class (with collision response set) hits something. Right now we only have one item of that class, but later in this series we will have quite a few on screen at once. First we check if the %dstObject is "paddleClass." We aren't worried right now about the ball hitting walls or other things. Then, if it's the paddle, we use the getWord function to get just the speed from the getLinearVelocityPolar function, which returns the angle and speed. Now we check if the %speed is greater than 150, which we are somewhat arbitrarily determining is 'fast.'

Now, if the %speed is less than 150 we don't do anything, and the BOUNCE response system just goes about it's business and sends the ball off at the same speed it was going in the reflected angle. However, if it is faster than 150, the next two lines compute a force and direction based on the normal, and set an additional impulse force on the ball in that direction.

If you don't get that completely, don't worry about it. I'll leave an overview on normals and vector addition for a different tutorial, preferably done by someone with a firmer grasp on vector math than me. However, later in this series we will be doing a little more of it, so I may write a quick sidebar then.

If you save and test it now, and slam the paddle into the ball, you will see the subtle result of our impulseForce. However, we have a problem with the ball staying that fast, and even faster if you hit it again with great speed.

What we want, is a standard, fair speed for the ball; and if the ball gets going fast, or it is slowed down, we want it to return to that speed. So we are going to need to check the speed constantly and adjust it. Also, we may want different levels, or different skill settings, to have different default ball speeds, so this sounds like a job for global variables and the onTimer function.

Add this to theLevels.cs, right below the $vmod lines.

$DEFAULTBSPEED = 90;
$levelBspeed = $DEFAULTBSPEED;

Like we did for $vmod in the last part of this tutorial, we are setting a permanent global default ball speed constant here, and then setting $levelBspeed to it. $levelBspeed may change throughout levels or with different powerups, but $DEFAULTBSPEED will always be 90.

Add this to the newBall function in ball.cs, right after the setLinearVelocity line.

	%theball.setTimerOn(100);

This, of course, will have our ball begin calling an onTimer function every 100 milliseconds, or ten times a second. Let's add that function now, in ball.cs right after the ballClass::onCollision function:

	function ballClass::onTimer(%this) {
		%angle = getword(%this.getlinearVelocitypolar(),0);
 		%speed = getword(%this.getlinearVelocitypolar(),1);

		if (%speed > ($levelBspeed+2)) {
			%this.setlinearvelocitypolar(%angle, %speed-4);
		} else if (%speed < ($levelBspeed-2)) {
			%this.setlinearvelocitypolar(%angle, %speed+4);
		}
	}

First we get the angle and speed of the ball. Then we want to compare the speed to our desired $levelBspeed, but we want to give it some room. We don't really need to adjust the speed ten times a second, that's just a waste of cycles. So we compare to see if the current speed is more than the desired speed plus two. If so, we use setlinearVelocityPolar to reduce the speed by four. We use the ELSE to do the same check if the ball is slower than our desired speed.

Now save and test, and you should see the ball always return to the default speed, even after you give it a strong wallop.

Let's Not Have a Ball

It's been annoying that if you miss the ball, you have to quit the game and restart to test it. We've had that lost ball trigger down at the bottom of our screen since the last part of this tutorial, doing nothing. Now we are going to use it. Add this to your game.cs file, at the end:

	function lostTrig::onEnter(%this, %obj) {
		if (%obj.class $= "ballClass") {	
			$theballs.remove(%obj);
			%obj.safeDelete();
			if ($theballs.getCount()==0) {
				$thebase.loadBall();
			}
		} else {
			//not a ball
		}
	}

Triggers are very useful objects. Here we use the onEnter function, which is called whenever some object enters the trigger area. The %obj variable contains that object. Our first line checks if it is a ballClass object. Currently, that's all it could be her, but later in this series it could be other types of objects.

The next line removes the ball object from our $theballs SimSet that we created earlier to hold all of our eventual balls. Then the safeDelete line actually kills the object. It's important to remember that a SimSet is like an array of objects; removing an object from the SimSet does not delete the object, it just takes it out of the SimSet list. You still have to actually delete the object.

The next line runs getCount on the $theballs SimSet to see if there are no more balls in play. Currently, of course, it will always be zero here, but later we could have any number of balls in play. And we don't want to mount a new ball until there are no more bouncing around. The $thebase.loadBall(); line does the actual reloading, and you can shoot the ball again.

Now save everything and test.

Bounce Effects

Remember from Part 1 our theory that you can never have too many particle effects. When the ball hits something, I want some kind of visual artifact, so lets add that.

We have two kind of effects in our game: constant ones like the ball flow effect which are really part of the game graphics, and we have reusable isolated effects, explosions and such. We don't want to load in the .eff file file every time we want to display a reusable effect, so we are going to initialize them once and assign them to global variables so we can simplify their use.

Twbt_pe2.zip

Download this zipped file (above), and add the particles3.png it contains to your game using the Create a New Image Map button in your level builder. You may need to double-click on it after it has been added and change its Image Mode to CELL.

Then drag the chip.eff file into your to the game/data/particles directory. Quit and restart the TGB editor. Don't forget to reopen our playlevel.t2d level.

Now add this to the end of your game.cs file.

function initializeEffects() {
	$wallbang = new t2dParticleEffect() { scenegraph = $thescenegraph; };
	$wallbang.loadEffect( "~/data/particles/chip.eff");
	$wallbang.setEffectLifeMode(stop, 0.2 );
}

This initializeEffects function is going to be responsible for loading all the various effects we are going to use and assigning them to global variables. Using this method, you will see how easy and resource friendly it is to play different particle effects.

So the function above loads and assigns an effect to the global variable $wallbang. Before we use it, let's be polite and clear it as we end the game. Still in your game.cs file, add this to the very beginning of your endGame function:

	if ($wallbang) $wallbang.safeDelete();

We also need to call this initalizeEffects function, so add this to the end of your initializeLevel function in theLevels.cs.

	initializeEffects();

Now let's put it into action. We want to see the chipping effect when the ball collides with things, like the paddle, so add this to your ballClass::onCollision function in your ball.cs file, at the end but still inside the if "paddleClass" brackets:

	$wallbang.setPosition(%points);
	$wallbang.playEffect(false);

This uses the %points local variable, which the function sets as the center of the collision, to set the effect to that position. It then plays the effect.

We want to see the same effect when the ball bounces off a wall, which is not of type "paddleClass," so we need to add an else if to our collision function for the "wallClass." Make your entire ballClass::onCollision function look like this:

	function ballClass::onCollision(%srcObject, %dstObject, %srcRef, %dstRef, %time, %normal, %contacts, %points) {
		if (%dstObject.class $= "paddleClass") {
			%speed = getword($thebase.getlinearVelocitypolar(),1);
			if (%speed > 150) { 
			  %force = t2dVectorScale(%normal,-(%speed*2));
			  %srcObject.setImpulseForce(%force, false);
			}
			$wallbang.setPosition(%points);
			$wallbang.playEffect(false);
		} else if (%dstObject.class$="wallClass") {
			$wallbang.setPosition(%points);
			$wallbang.playEffect(false);
		}
	}

Save everything and test the level. You should see our wallbang effect as you hit the ball, and as it hits the wall.

One More Thing

Believe it or not, it's already time for some sound effects. Just like the visual aspect of particle effects, sound effect add to the visceral reward of playing a game; and if you train yourself to add them as you think of them, you are more likely to beef up your game with them.

We treat them just like the particle effect above. We are going to load them once in the beginning, and then use them throughout the tutorial.

Create a new file in gameScripts called audioDatablocks.cs and add this to it:

	new AudioDescription(AudioNonLooping)
	{
	   volume   = 1.0;
	   isLooping= false;
	   is3D     = false;
	   type     = $GuiAudioType;
	};

You've probably seen this before in other audio tutorials. We are creating a type of audio which doesn't loop, just plays once and stops. Now we need some audio to play.
Twbt_au1.zip
Download this zipped file (above), and drag the three audio ogg files it contains into your game/data/audio directory. Now add this to the end of the audioDatablocks.cs file:

	new AudioProfile(wallhit)
	{
		filename = "~/data/audio/wallhit.ogg";
		description = "AudioNonLooping";
		preload = true;
	};
	new AudioProfile(shieldhit1)
	{
		filename = "~/data/audio/shieldhit.ogg";
		description = "AudioNonLooping";
		preload = true;
	};
	new AudioProfile(shieldhit2)
	{
		filename = "~/data/audio/shieldhit2.ogg";
		description = "AudioNonLooping";
		preload = true;
	};

This creates three new AudioProfiles ready for us to use, all of the NonLooping type. Now we need to bring this cs file in, so add this to your exec.cs file.

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

That's all we need to do to have these sound effects ready to play. Now we just use alxPlay to play the sound, calling it by the name we give each profile above. We need to modify the ballClass::onCollision function in ball.cs to play a sound when the ball hits the wall. So add this to the second else if in that function, right above that second set of $wallbang lines:

		alxPlay(wallhit);

That's all there is to it. Save everything, play and listen to your ball hit the wall.

Just for variety, we want to play one of two different sounds when the ball hits the shield, not the same old sound all the time. You'll notice that we created two shieldhit sounds above called shieldhit1 and shieldhit2. When the ball hits the shield we are going to pick one of them to play at random.

Add these lines to the ballClass::onCollision function in the %dstObject equals paddleClass section, just above the $wallbang lines:

	%r=getRandom(1,2);
	%hitsound="shieldhit"@%r;
	alxPlay(%hitsound);

This assigns %r to either 1 or 2, then appends it to "shieldhit"; so %hitsound will either equal "shieldhit1" or "shieldhit2" and we can use alxPlay to play that sound.

Your entire ballClass::onCollision function should now look like this:

	function ballClass::onCollision(%srcObject, %dstObject, %srcRef, %dstRef, %time, %normal, %contacts, %points) {
		if (%dstObject.class $= "paddleClass") {
			%speed = getword($thebase.getlinearVelocitypolar(),1);
			if (%speed > 150) { 
				%force = t2dVectorScale(%normal,(-%speed*2));
			 	%srcObject.setImpulseForce(%force, false);
			}
			%r=getRandom(1,2);
			%hitsound="shieldhit"@%r;
			alxPlay(%hitsound);
			$wallbang.setPosition(%points);
			$wallbang.playEffect(false);
		} else if (%dstObject.class$="wallClass") {
			alxPlay(wallhit);
			$wallbang.setPosition(%points);
			$wallbang.playEffect(false);
		}
	}

Now save everything and play the level. You should hear different sounds when you hit the ball with your paddle.

Conclusion

Well, it may seem like we don't have much to show for our hard work, but what we are doing is building a nice, logical clean framework that we can build on without getting lost in our code. The next part of this tutorial will introduce what the game is all about, breaking tiles.

Thanks for reading, and feel free to post any comments or questions on the BreakoutTutorial Part 2 Discussion Thread.



Continue on to:
The Breakout Tutorial Part 3 - Miles of Tiles