TGB/BreakoutTutorial5
From TDN
The following links can take you to other sections of the tutorial: The Breakout Tutorial Part 5 - Levels O'PlentyA Tetraweb TutorialBreakoutTutorial Part 5 Discussion Thread A Whole New LevelWe have been using the word 'level' in two ways throughout this tutorial, I hope it hasn't been confusing. A level in TGB is generally a specific Scenegraph and at least one GUI. It's also called a 'scene' in the TGB documentation, and in the current version of TGB Editor you Load Scene from the menu instead of Load Level. They are created and stored in the game/data/levels/ directory; and we have so far done everything in the last four tutorials on just one TGB level/scene, our playlevel.t2d. The other sense we have been using 'level' is in the game sense, a level as a particular section of an overall game. We are going to have many levels in our game, but only two TGB levels: the playlevel.t2d and a new one that will be our main menu, menulevel.t2d. Every one of our 'playing' levels will be just on playlevel.t2d loaded with a few different variables set. The other level/scene will be the main menu, where you start the game, see the highscores, and quit. Let's create that now. The Menu ScreenTwbt_pt5a.zip Drag the three ogg audio files into your game/data/audio directory. Drag the entire btn_img folder of button images into your game/gui directory. We want each button graphic to reside in game/gui/btn_img. Add the background image, mainback.jpg, to your TGB level editor using the Create ImageMap tool or by dragging it. We need to create the audio profiles for the button sounds and the menu background music, so add this to the end of your audioDatablocks.cs file.
new AudioProfile(menumusic)
{
filename = "~/data/audio/menumusic.ogg";
description = "AudioLooping";
preload = false;
};
new AudioProfile(buttonsound)
{
filename = "~/data/audio/buttonsound.ogg";
description = "AudioNonLooping";
preload = true;
};
new AudioProfile(buttonsound2)
{
filename = "~/data/audio/buttonsound2.ogg";
description = "AudioNonLooping";
preload = true;
};
Now in the TGB editor, create a new scene using the New Scene button. Save it as 'menulevel.t2d' and set Camera width to 200 and height to 150 (see fig. 5.a). Then drag the mainbackImageMap onto your new blank scene. Edit the imagemap by double clicking it, then in the Scene Object section set both X: and Y: to 0 and set the Layer to 20, pushing it to the back of our scene. Now save the scene.
A GUI for the MenuNext, we are going to use the GUI system for all the buttons on our main menu screen. As we mentioned before, the GUI Editor is powerful, but complex and beyond the scope of this tutorial. We are going to create our GUIs in script instead. Create a new file called mainmenuGUI.gui and save it in your game/gui directory. Add this to it:
if(!isObject(AudibleButtonProfile)) new GuiControlProfile( AudibleButtonProfile )
{
soundButtonDown = "buttonsound";
soundButtonOver = "buttonsound2";
};
This creates a new GUI control profile called AudibleButtonProfile. Inside this profile we can assign different audio events to different mouse actions. Any GUI button which uses this profile will automatically use these sounds for the different events. We are using the AudioProfiles that we added above. Now add this to the same mainmenuGUI.gui file:
new GuiControl(theMenuGui) {
canSaveDynamicFields = "0";
Profile = "GuiDefaultProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "0 0";
Extent = "1024 768";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
hovertime = "1000";
new GuiBitmapButtonCtrl() {
canSaveDynamicFields = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "850 697";
Extent = "154 54";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "quitgame(1);";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
bitmap = "./btn_img/btnquit";
};
This creates a new GUI control called theMenuGui which covers the whole screen. Then we add a new GuiBitmapButtonCtrl for the Quit button. We assign its profile as AudibleButtonProfile so it will have the mouse over and down sound effects. We set our quitgame() as its Command, and for the bitmap we set the btnquit images. One nice thing about the GuiBitmapButtonCtrl is that it handles image states automatically. You can see what we do with the images you unzipped. btnquit_n.png is the normal state, btnquit_h.png is the highlighted state, btnquit_i.png is the inactive state and btnquit_d.png is the depressed state.Now the quit button is added with its appropriate command. So we need to add some level buttons; here are three, we'll add more later. Paste this directly after the code above:
new GuiBitmapButtonCtrl(level1button) {
canSaveDynamicFields = "0";
isContainer = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "66 122";
Extent = "100 80";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "loaduplevel(1,3,3,3);";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
useMouseEvents = "0";
bitmap = "./btn_img/level1";
};
new GuiBitmapButtonCtrl(level2button) {
canSaveDynamicFields = "0";
isContainer = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "186 122";
Extent = "100 80";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "loaduplevel(2,3,3,3);";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
useMouseEvents = "0";
bitmap = "./btn_img/level2";
};
new GuiBitmapButtonCtrl(level3button) {
canSaveDynamicFields = "0";
isContainer = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "306 122";
Extent = "100 80";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "loaduplevel(3,3,3,3);";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
useMouseEvents = "0";
bitmap = "./btn_img/level3";
};
};
We are adding three more buttons, just like the first. But we are using the level button images, positioning them differently and setting their command to call loaduplevel. We'll write that function in a moment. Now we need to bring in this new gui file, so add this to the end of your exec.cs.
exec("~/gui/mainmenuGUI.gui");
Unique Level DataEach level is going to have unique aspects; different tilemaps, background music and graphic, maybe ball speed and power up rate. Also, each level except the first is going to start out locked, and will get unlocked by finishing the level before it. We want these unlocked/locked levels to persist even when the game is shut off, which means we'll be writing to a file. So create a file called leveldata.cs and save it in game/data/saves/. (You may need to create this saves folder.) Add this to the file:
new SimSet(levelList) {
canSaveDynamicFields = "1";
new ScriptObject(LEVEL1) {
backimageMap = "~/data/images/back1.jpg";
levelID = "1";
levelmusic = "levelmusic1";
levelName = "Level One Name";
locked = "0";
powerupPercentage = "20";
tilebreakpoints = "250";
tilehitpoints = "50";
tmap = "test.lyr";
};
new ScriptObject(LEVEL2) {
backimageMap = "~/data/images/back2.jpg";
levelID = "2";
levelmusic = "levelmusic2";
levelName = "Level Two Name";
locked = "1";
powerupPercentage = "30";
tilebreakpoints = "300";
tilehitpoints = "75";
tmap = "l2map.lyr";
};
new ScriptObject(LEVEL3) {
backimageMap = "~/data/images/back3.jpg";
levelBspeed = "100";
levelID = "3";
levelmusic = "levelmusic3";
levelName = "Level Three Name";
locked = "1";
powerupPercentage = "20";
tilebreakpoints = "325";
tilehitpoints = "75";
tmap = "l3map.lyr";
};
};
The 'levellist' SimSet this creates has three member scriptobjects, named LEVEL1, LEVEL2 and LEVEL3. We are going to use these objects to store specific information about the current level. Let's look at the LEVEL1 object in detail. First we assign the location of one of our background jpgs to the 'backimageMap' field. Then we give the level a 'levelID,' set the name of one of our looping music pieces to 'levelMusic,' assign a name to 'levelName' and set locked to 0, so this level will never start out as locked. Next we indicate the level's 'powerupPercentage,' which we won't get into until the next part of this series. The next to fields assign points for a tile being hit and being broken. The final field gives us the name of the tile map file that we are going to use for this level. It is important to note that all these fields actually do nothing by themselves, we are just making up the names so we can store our data. It's only when we load and pull out these field variables, and then set them in TGB script that they actually have an effect on our game. We are not allowed to save our files within our game directory. Files we save for highscores or level modification, etc. will be saved in different places on different systems, and we actually can't know exactly where they are. So we ship our game with a 'template' of the file stored in our game directory, and when the game is first run we save out a copy of it (we don't know exactly where it goes) and read and write to that file. If we ever can't find the file, we just rewrite it from our template. We need to load the levelist SimSet at the beginning of our theLevels.cs file. So add this near the top of that file, right after our global declarations, but before the beginning of the initializeLevel function.
%loadpath = "data/saves/leveldata_mod.cs";
if ((!isFile(%loadpath)) && (!isFile(%loadpath@".dso")) ) {
//modified save file doesn't exist, we need to create it from our template
exec("~/data/saves/leveldata.cs");
levelList.save(%loadpath);
} else {
//loading modified leveldata
exec(%loadpath);
}
$levelCount=levelList.getCount();
This may seem confusing at first. If you run this game on someone's computer, the leveldata.cs file and the leveldata_mod.cs file will not be in the same folder, they will be in completely different locations. First we declare the %loadpath to our leveldata_mod.cs file. Of course when we first run the game, that file won't exist. The next line uses isFile to check if the file is there (or if there is a compiled .dso version of it.) If not, the next line uses exec to pull in our template one that we created above. Now we have the levelList SimSet object, and the next line saves the entire object out to %loadpath, which now creates the leveldata_mod.cs file for us. The next time we played our game, the else would be executed and simply load our modified file with any changes that might have been made to it, specifically unlocked levels. (note from a reader: if you make changes to your leveldata.cs file, make sure, you find the leveldata_mod.cs on your hard-drive, and delete it. Otherwise, your leveldata_mod.cs file without the changes will be loaded every time.) The last line assigns the count of levelList to a global variable, $levelCount, so we always know how many levels our game has. Right now, of course, it would just be three. Can I See a Menu, Please?We can't test yet. Currently our game just starts with the playlevel.t2d, but we want it to always begin with the main menu. We need to remove the last five lines from the startGame function, which hides the cursor and loads the playlevel. Find the function in game.cs in your game/gameScripts/ directory and overwrite it completely with this:
function startGame(%level)
{
exec("~/exec.cs");
Canvas.setContent(mainScreenGui);
Canvas.setCursor(DefaultCursor);
//%res=toggleFullScreen();
new ActionMap(moveMap);
moveMap.bind("keyboard", "q", "quitgame");
moveMap.push();
$enableDirectInput = true;
activateDirectInput();
//enableJoystick();
loadupmainmenu();
}
So now instead of loading the playlevel we are calling a function loadupmainmenu. We'll add that function right now at the end of game.cs.
function loadupmainmenu() {
sceneWindow2D.endLevel(); //if we are in a level, end it
%level=expandFilename("~/data/levels/menulevel.t2d");
sceneWindow2D.loadLevel(%level);
$thescenegraph = sceneWindow2D.getSceneGraph();
Canvas.pushDialog(theMenuGui);
Canvas.popDialog(ScoreboardGui);
for (%x=0;%x<$levelcount;%x++) {
if (levelList.getObject(%x).locked==true) {
%id=levelList.getObject(%x).levelID;
%levelbutton="level"@%id@"button";
%levelbutton.setActive(false);
}
}
if ($levelmusic)
alxStop($levelmusic);
$levelmusic=alxPlay(menumusic);
$levelscore = 0;
}
Let's go over this. First we call the endLevel method on our sceneWindow2D. That may seem strange, but we will be using this function throughout the game, sometimes when the user is playing a level and wants to exit to the main menu. So we want to make sure we end any current playlevel.t2d before we load the menulevel.t2d. Next we get the expanded path to our menulevel scene and load it. Then we assign our $thescenegraph global variable so we have that for the various functions that need it. Then we use pushDialog that we saw in Part 4 to put the menu GUI onto the scene. This will add everything in that GUI, all the level buttons and the quit button. Next we have a for loop that uses $levelcount to loop through our levelList SimSet. It uses getObject to check each level object in turn and see if the 'locked' field in that object is true (1).If 'true' it then gets the levelID field and uses it to create a local variable called %levelbutton. So in our case, when the loop gets to scriptObject LEVEL2, it will find the locked field set to 1, so it will pull the levelID field, which is '2' and the %levelbutton variable will be set to 'level2button.' The last line in the loop uses the %levelbutton variable and calls setActive(false) on which ever button is locked. With our GUI buttons, this will do a couple things: the image_i inactive graphic will be displayed, and the button will not call it's command when clicked. The last lines in our function stops any level music that might be playing, then starts our menu music playing and sets the level score to zero. Save everything and test, you should see your game start up with the menu (See fig. 5.2). Mouse over the level and quit buttons and make sure you hear the sounds and see the buttons highlight. Level 2 and 3 should be using the dimmed graphic and not respond to mouse overs. The level button won't do anything, of course, because we have not written that function yet. The quit button will work because we tied it to our quit function. Loading the LevelSo now we need to write the loaduplevel function that the buttons are calling. Remember, they are passing, for example, (1,3, 3, 3) into the function, so we'll see now what we do with those parameters. Add this the end of your theLevels.cs file.
function loaduplevel(%levelnum,%i1lives,%i2lives,%i3lives) {
alxStop($levelmusic);
sceneWindow2D.endLevel();
%thelevel="LEVEL" @ %levelnum;
%level=expandFilename("~/data/levels/playlevel.t2d");
if( isFile( %level ) || isFile( %level @ ".dso")) {
sceneWindow2D.loadLevel(%level);
}
$thescenegraph = sceneWindow2D.getSceneGraph();
initializeEffects();
Canvas.pushDialog(ScoreboardGui);
Canvas.popDialog(theMenuGui);
Canvas.hidecursor();
initializeLevel(%thelevel,%i1lives,%i2lives,%i3lives);
}
Here are stopping the music, then ending the current TGB level (scene.) We take the %levelnum that was passed in and set %thelevel to "LEVEL" @ %levelnum, so, for example, clicking the level one button would result in "LEVEL1". Then we load our playlevel.t2d level/scene and assign our $thescenegraph global variable. The next line calls our initializeEffects function, then we push on our ScoreboardGUI, pop off our menu GUI, and hide the cursor. Finally, we call initializeLevel, passing it the %thelevel variable we just made and the %ilives variables that were passed to this function. We are going to change our original initializeLevel function to load in the fields specific for each level. Replace the entire initializeLevel function with this:
function initializeLevel(%thelevel,%i1lives,%i2lives,%i3lives) {
$currentlevel = %thelevel;
$tilebreakpoints = $currentlevel.tilebreakpoints;
$tilehitpoints =$currentlevel.tilehitpoints;
if ($currentlevel.levelBspeed) {
$levelBspeed = $currentlevel.levelBspeed;
} else {
$levelBspeed = $DEFAULTBSPEED;
}
initLifeIcons(%i1lives,%i2lives,%i3lives);
$backimage = new t2dStaticSprite() {
imageMap = back1ImageMap;
scenegraph = $thescenegraph;
size = "200.000 150.000";
Layer = "20";
CollisionPhysicsSend = "0";
CollisionPhysicsReceive = "0";
};
$backimage.imageMap.imageName = $currentlevel.backimageMap;
$backimage.imageMap.compile();
%tlayer="~/data/tilemaps/"@$currentlevel.tmap;
$thetileset.loadTileLayer(%tlayer);
$liveTilecount=$thetileset.countTiles("LIVE");
$theballs = new SimSet(){};
$thebase.loadBall();
$levelmusic = alxPlay($currentlevel.levelmusic);
}
Note how we added the parameters in the function now. We first assign the local variable %thelevel to a global $currentlevel, so we can use it later. Now we start referencing the level ScriptObject we created in leveldata.cs to get the specifics of this level. We assign the tile break and hit points based our ScriptObject fields, then we check to see if this particular object has a levelBspeed field. If it does we set the $levelBspeed to it, 'else' we set it to our constant $DEFAULTBSPEED. Next we call our initLifeIcons like before, but this time we pass it the %ilives variables instead of having "3,3,3" hardcoded. We set the background image the same way, by setting the imageMap.imagename to the backimageMap field, then compiling it as shown in the end of the Part 4. Then we use the tmap field to generate the path to our layermap file and use the loadTileLayer method of our tileset to dynamically set the map to the one designated for this level. Next we count the LIVE tiles just like before. We create the ball SimSet and load the ball, then finally we start the music, again using the levelmusic field. Play TimeNow you can save everything and test; click the Level 1 button and it should start the game, using your test.lyr tilemap. The 'q' key will still end the game. Let's add some other level tilemaps, we'll need them in a moment. Twbt_lvls.zip tmap = "test.lyr"; to tmap = "l1map.lyr"; so it loads the level one tilemap. If you saved everything and tested now, you may be surprised that you still see the test.lyr map when you play level one. That's because it is pulling from the 'saveable' leveldata_mod.cs file on your system. Remember, we look for that first and use it if we find it, otherwise we create it from leveldata.cs. The leveldata_mod.cs file is stored in your Application Data directory, which is in a slightly different place on Windows XP, Vista and Mac OS X. You could do a 'search' to find and delete it, but as we are testing we may need to do that repeatedly, and possibly reset other things, so a reset function makes more sense. Add this to the end of your game.cs file.
function resetGame() {
%loadpath = "data/saves/leveldata_mod.cs";
exec("~/data/saves/leveldata.cs");
levelList.save(%loadpath);
echo("Resetting leveldata_mod.cs from leveldata.cs");
}
This function simply execs our template, and saves it out the %loadpath, so it overwrites whatever is there. Save everything then start up your game. Open the console with ctrl-~ and enter resetGame() into the console. When you quit and restart, you should be able to start level one and see the new l1map.lyr tile map in place. Moving OnIf you break all the tiles on level one, nothing will happen yet. That's because we had commented out our levelAdvance call. Let's write that now. First, in tile.cs find your tileClass::breakTile method. Uncomment the line //levelAdvance(%tposition); Now, when you have cleared the last tile, this function will be called. We need to write it, but first we need an end-of-level sound effect and particle effect. Download the zip file (above) and extract it. Drag the ogg audio file it contains into your game/data/audio directory. Add the two images to your TGB level editor using the Create ImageMap tool or by dragging them to the editor. With levelendsImageMap you may need to double-click on it and change the image mode to cell. Make sure the cell width and height are set to 64. Then drag the levelendeffect.eff file to your game/data/particles directory. Quit and restart TGB. We need to create the audio profile for the level end effect, so add this to the end of your audioDatablocks.cs file.
new AudioProfile(levelFinish)
{
filename = "~/data/audio/levelfinish.ogg";
description = "AudioNonLooping";
preload = true;
};
We need to write levelAdvance now; add this to the end of the theLevels.cs:
function levelAdvance(%tposition){
if ($levelCount > $currentlevel.levelID) { //there is another level to be had
%nextlevel = $currentlevel.levelID+1;
} else {
%nextlevel=1;
}
%levelend = new t2dParticleEffect() { scenegraph = $thescenegraph; };
%levelend.loadEffect( "~/data/particles/levelendeffect.eff");
%levelend.setEffectLifeMode(kill, 6.0 );
%levelend.setSize("15 5");
%levelend.setLayer(4);
%levelend.setPosition(%tposition);
%levelend.playEffect(false);
alxStop($levelmusic);
alxPlay(levelFinish);
%ilives[0]=%ilives[1]=%ilives[2]=0;
for(%x=0;%x<$lifeicons.getCount();%x++) {
%ilives[%x] = $lifeicons.getObject(%x).lives;
}
schedule(500,0,"unlockNextLevel",%nextlevel,%tposition);
schedule(8000,0,"loaduplevel",%nextlevel,%ilives[0],%ilives[1],%ilives[2]);
}
Going over this function, the first thing we do is compare the number of levels with the levelID we are currently on, and if we have more to go we get the %nextlevel number by adding one, otherwise we set %nextlevel to 1, so we are looping around back to the first level. Next we use a local variable to set up and immediately play the levelendeffect at the position of the last tile broken. This position was passed to us in %tposition. Then we end the $levelmusic and play the levelFinish sound effect.We need to get the lives left for each life icon, so we can pass it on to the next level; we don't want to just reset them to full lives. We are using a standard array here; first we set %ilives[0] through %ilives[2] to zero. Next we count through zero to the total count of the SimSet, and for each we set one of our array variables to the 'lives' field of that $lifeicon object. To help visualize this, imagine that we have lost our first life icon completely, and have one life left in the second icon, and all three in the third life icon. What we want to pass to loaduplevel in that case is "3,1,0", and that is just what this loop gives us. Now we schedule a function called unlockNextLevel to happen 500 milliseconds in the future, passing it our %nextlevel number and the %tposition position. We'll write this function in a moment. Then we schedule a call to our loaduplevel function to happen eight seconds in the future, passing it the nextlevel as well as the lives for icons one, two and three. The reason we need to schedule the loaduplevel call to happen, as opposed to just calling it now, is interesting. If you can picture where we are in the code right now, we are in a function that was called by a breakTile method of the tileClass object. The code running right now is, in a sense, attached to that tileClass object. It called this levelAdvance function and when this function ends, it will return to that tileClass object and finish out the breakTile method, then the hitTile method which called it, etc. The first thing the loaduplevel function does is kill the currently running level, which deletes all the non-persistent objects on the level, which leaves all those threads hanging with nowhere to return to. This usually causes a crash. Most TGB programmers run into this problem at least once in their early scripting, an object calls code that eventually deletes itself. If you have an unexplained crash, it's a good place to look first. Unlocking the Next LevelLet's write the function that unlocks the next level. Add this to the end of your theLevels.cs file.
function unlockNextLevel(%nextlevel,%tposition) {
//stop the balls
%count = $theballs.getCount();
for (%x=(%count-1);%x >= 0; %x--) {
$theballs.getObject(%x).setAtRest();
$theballs.getObject(%x).setTimerOff();
}
//show unlocked graphic
%xv=getRandom(0,30)-15;
%yv=getRandom(0,30)-15;
%rv=getRandom(0,20)-10;
%levelunlocked = new t2dStaticSprite() {
scenegraph = $thescenegraph;
imageMap = "levelunlockedImageMap";
ConstantForce = "0.0 0.1";
Friction = "0.15";
Position = %tposition;
size = "50.781 23.438";
Layer = "3";
LinearVelocity = %xv SPC %yv;
AngularVelocity = %rv;
WorldLimitMode = "BOUNCE";
WorldLimitMin = "-95.000 -81.497";
WorldLimitMax = "95.000 128.955";
};
%thelevel="LEVEL" @ %nextlevel;
%thelevel.locked=false;
%loadpath = "data/saves/leveldata_mod.cs";
levelList.save(%loadpath); //save it so its unlocked forever
}
So first we stop all the balls. I know we can only have one ball in play right now, but in the next part of this tutorial we can have many more. We get the %count of the SimSet, then step through it backwards using %x--, and for each ball in the set we use setAtRest() to stop its motion and setTimerOff() to stop its onTimer method. If we didn't do that, it would slowly move back up to its default speed. Next we want to display our big graphic that says "Level Unlocked." We use getRandom to grab a small value (between -15 and 15) for our x velocity, y velocity and rotational velocity. This will give it nice slight push instead of just sitting there on the screen. We create the new sprite with the usual settings, setting the LinearVelocity and AngularVelocity fields using our variables. We also give it a small constant force down, adding a little gravity to its movement. Also, we give it a WorldLimitMode of "BOUNCE" and set the limits so if the starting %tposition was very close to the edge of the screen, it will bounce back towards the center.All this was preamble to what this function really does, which is unlock the next level. The last four lines do this. First we get our variable %thelevel dynamically by adding the %nextlevel to "LEVEL." Then we use this variable to set the locked field to false for the next level. Finally we set the loadpath, just like when the game starts up, and we save the levelList SimSet to the leveldata_mod.cs file so the unlocked level will persist forever. Save everything and test. Clear the level and when you do, you should see and hear the effects, the graphic should display, and after eight seconds you should be taken on to level two. Lose a few balls before you do it, so you can see the life icons state transfer to the next level. If you want things to move a little quicker, temporarily add this line to the very end of your initializeLevel function in theLevels.cs. $liveTilecount = 5; This line will make our game think there are only five tiles, so the level will be 'cleared' as soon as you break any five tiles. Don't forget to remove it after you have tested anything. One More ThingIt's time for a more elegant way to quit while playing, as well as a way to pause the game then return. We'll make a pause GUI that comes up when you press the 'p' key, with two options, resume game or go to the main screen. Twbt_pause.zip
new GuiControl(PauseGUI) {
canSaveDynamicFields = "0";
Profile = "GuiDefaultProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "0 0";
Extent = "1024 768";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
hovertime = "1000";
new GuiBitmapCtrl() {
canSaveDynamicFields = "0";
Profile = "GuiDefaultProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "0 0";
Extent = "1024 768";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
hovertime = "1000";
bitmap = "./btn_img/black.png";
wrap = "0";
};
new GuiBitmapCtrl() {
canSaveDynamicFields = "0";
Profile = "GuiDefaultProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "382 290";
Extent = "260 190";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
hovertime = "1000";
bitmap = "./btn_img/pauseback.jpg";
wrap = "0";
};
new GuiBitmapButtonCtrl() {
canSaveDynamicFields = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "382 336";
Extent = "260 54";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "pauser(true);";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
bitmap = "./btn_img/btncontinue";
};
new GuiBitmapButtonCtrl() {
canSaveDynamicFields = "0";
Profile = "AudibleButtonProfile";
HorizSizing = "right";
VertSizing = "bottom";
Position = "382 397";
Extent = "260 54";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
Command = "quit_to_mainmenu();";
hovertime = "1000";
groupNum = "-1";
buttonType = "PushButton";
bitmap = "./btn_img/btnmainmenu";
};
};
This creates a GUI control that covers the entire screen. It then loads a GuiBitmap with the black.png, which is a semi-transparent black image, to dim everything on screen. On top of that it puts another GuiBitmap which is the background graphic for the pause menu, pauseback.jpg. Then it buts two GUI buttons, just like our level buttons. The btncontinue one calls a function named pauser when clicked and the btnmainmenu calls a function called quit_to_mainmenu. We need to pull this GUI in so add this to the end of your exec.cs file:
exec("~/gui/guipause.gui");
We want to call the pause function when the 'p' is pressed, so find this line in the startGame function in game.cs
moveMap.bind("keyboard", "q", "quitgame");
and change it to
moveMap.bind("keyboard", "p", "pauser");
Now we'll write the pauser function. Add this to the end of your game.cs file.
function pauser(%val) {
if (%val) {
if ($thescenegraph.getScenePause()) {
Canvas.popDialog(PauseGUI);
$thescenegraph.setScenePause(false);
Canvas.hidecursor();
} else {
Canvas.pushDialog(PauseGUI);
$thescenegraph.setScenePause(true);
Canvas.showcursor();
}
}
}
Binding functions to a keypress automatically passes the function a %val, which is 1 when the key is going down and 0 when the key is coming up. We don't want to run the function two times when they press the key, so we wrap the entire set of statements in if (%val) First we check if the scene is already paused with getScenePause. If it is we pop our PauseGUI off, then setScenePause to false and hide the cursor, essentially restarting the game from where it was paused. If the scene wasn't paused, we do the opposite. We push the PauseGUI on, then setScenePause to true, then we show the cursor so they can click the buttons. Remember, this same function will be called from the 'p' keypress as well as from clicking the 'continue' button in the GUI. The final thing to do is write the quit_to_mainmenu function. Add this to the end of game.cs as well.
function quit_to_mainmenu() {
loadupmainmenu();
}
We didn't just call the loadupmainmenu function straight from the GUI button because later, in part 7, we will want to do a few things before quitting to the main menu. We need to pop this GUI off if the user quits to the main menu, so add this to your loadupmainmenu function in game.cs, right after 'Canvas.popDialog(ScoreboardGui)': Canvas.popDialog(PauseGUI); Save everything and test by clicking the 'p' key while playing. ConclusionWell, we're taking shape like a real game here. One missing element is something to break the monotony of simply smashing tiles. The next part of this tutorial will introduce this element. Thanks for reading, and feel free to post any comments or questions on the BreakoutTutorial Part 5 Discussion Thread. Continue on to:
|
Categories: TGB | GameExample | Tutorial








