TGB/ScriptTutorials/ScoreTutorial
From TDN
[edit] Introduction
[edit] The Level Builder
[edit] Opening an existing Project
[edit] GUI ProfilesIn the main.cs file, we are going to add a new GUI profile. Profiles allow you to easily create a template for how GUI elements should appear. Our score elements will be purely text based, so this profile is going to set the size of the font and the color. Add this to the top of the main.cs file above the initializeProject function:
new GuiControlProfile (GuiText24WhiteProfile : GuiTextProfile)
{
fontSize = 24;
fontColor = "255 255 255";
};
This should be easy to understand, we are naming this profile GuiText24WhiteProfile and it's parent is the base GuiTextProfile. The font size is 24 and the color is set to white (255 255 255). [edit] The GUI Editor
[edit] Get Rewarded...for Eating
function FishFood::onLevelLoaded(%this, %scenegraph)
{
%this.startPositionY = %this.getPositionY();
%this.setLinearVelocityY(getRandom(%this.minSpeed, %this.maxSpeed));
// Initialize the score to 0 and display it.
$score = 0;
score.setText($score);
}
As the comment in the code suggests, we are creating a $score global variable and setting it to 0. The next line is telling our score GUI text control to display the value of $score. We have a lot of things named similarly, hopefully it is clear as mud. To repeat, we have a global variable now named $score, which will keep track of the score value. We have a GuiTextCtrl named score which originally we left blank and now through script are using the setText function to display the actual score. There is also the GuiTextCtrl which does not have a name but only contains the text "Score:".
function FishFood::onCollision(%srcObj, %dstObj, %srcRef, %dstRef, %time, %normal, %contactCount, %contacts)
{
if(%dstObj.class $= "PlayerFish")
{
%srcObj.spawn();
%dstObj.modifyLife(%srcObj.lifeValue);
// Take our score and add 10 points.
$score = $score + 10;
score.setText($score);
} else if (%dstObj.class $= "Fish")
{
%srcObj.spawn();
}
}
What we just did is when the fish food collides with the player, our score increases by 10 points and the new value of $score is displayed on screen.
End of original tutorial by Mike Lilligreen
[edit] Keeping Score: A New Hope[edit] More like a new headache...
Before you begin you should know that the way I designed this was a little stupid. The name and score should have been written on seperate lines instead of the same one. Also I should have used two GuiMLTextCtrls instead of just one. Regardless, you might still learn something. :) Enjoy,
[edit] Designing our GUI elements[edit] The main screen GUI
If you've read this far, you should definately know how to do this, so I will list data from my GUI files so you can verify settings (but I will delete every property I don't care about). Create everything listed and arrange it exactly or similar to how I have it.
new GuiTextCtrl(scoreCounterTitle) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "relative";
VertSizing = "relative";
position = "3 515";
Extent = "75 25";
text = "Score:";
};
new GuiTextCtrl(score) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "relative";
VertSizing = "relative";
position = "75 515";
Extent = "313 25";
};
new GuiTextCtrl(highScoresTitle) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "310 109";
Extent = "174 30";
text = "High Scores Table";
};
new GuiTextCtrl(nameTitle) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "251 160";
Extent = "64 64";
text = "Name";
};
new GuiTextCtrl(scoreTitle) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "508 161";
Extent = "64 64";
text = "Score";
};
new GuiMLTextCtrl(scoreTable) {
Profile = "GuiText24WhiteProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "253 253";
Extent = "360 240";
lineSpacing = "2";
};
Code 4.1: Data from mainScreen.gui
Make sure you save your gui file (save over mainScreenGui.gui).
[edit] The Name Dialog
Set it's name to namePopUpDlg. Select the GuiButtonCtrl. Change the command field to: MessageCallback(namePopUpDlg,namePopUpDlg.callback); Code 4.2: The command property of the OK button. From the left hand (first) drop down menu select GuiTextEditCtrl. Set it's name to nameEntryBox. Set position to "34 61", and extent to "231 18". Your right hand object tree should look like this:
new GuiButtonCtrl() {
HorizSizing = "right";
VertSizing = "top";
position = "97 88";
Extent = "110 23";
Command = "getPlayerName();";
};
new GuiTextEditCtrl(nameEntryBox) {
Profile = "GuiTextEditProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "34 61";
Extent = "231 18";
maxLength = "5";
};
Code 4.3: Code from namePopUpDlg.gui Save this file as namePopUpDlg.gui. What we have just done is create a generic dialog box that can recieve input and can say a short message when you display the dialog. [edit] Writing our Code[edit] File Input / Output
Create the new file fileIO.cs in your gameScripts folder. Add in the following code and save:
// ============================================================
// Project : ExampleMyFishGame
// File : .\gameScripts\fileIO.cs
// Copyright : © Indolent Games 2006
// Author : Aaron Goselin
// Editor : Codeweaver 1.2.2199.34718
//
// Description : File input/output
//
// : Use this as you like.
// : I wouldn't post this if I had a problem
// : with it being used. If you find it
// : particularily useful then feel free to
// : mention me in your credits or whatever. :)
// ============================================================
// Note: Your files will be located in data/files/
function writeFile()
{
// Creating our object variable with which to control our file
%file = new FileObject();
// If file is open for write already then ok do code, if it
// it is not open for write, open it.
if(%file.openForWrite("~/data/files/score.sav"))
{
%temp = 1;
// If file was empty, only write the current users name and score.
if ($x == 0)
{
error("___________");
echo("BEGIN WRITE\n");
%file.writeLine($playerNameEntered @ " " @ $score);
echo($playerNameEntered @ $score);
error("___________");
echo("END WRITE");
// Writing our lonely single score to the scoreboard.
scoreTable.setText($playerNameEntered @ " " @ $score);
// Close the file, delete the object, and exit this function.
%file.close();
%file.delete();
// Change the score text control to instructions.
score.setText("P = Play Again | Q = Quit");
$x = 1; // Resetting the file counter.
return;
}
// If the counter is 1 or higher
else
{
error("___________");
echo("BEGIN WRITE\n");
// If there was only one line read, print the current users name
// and score and print one line from the array (all that should
// be there.)
if($x == 1)
{
%file.writeLine($playerNameEntered @ " " @ $score);
echo($playerNameEntered @ $score);
%file.writeLine($lines.contents[%temp - 1]);
echo($lines.contents[%temp - 1]);
// Sending score to scoreboard.
scoreTable.setText($playerNameEntered @ " " @ $score
NL $lines.contents[%temp - 1]);
// Deleting the array, as it is no longer needed.
$lines.delete();
// Close the file, delete the object, and exit this function.
%file.close();
%file.delete();
return;
}
// If the function has made it this far the file should not be
// empty, or have only one entry. To be clear, if we made it this
// far, there is a file and it has more than one entry.
// Putting the current player's name and score into the file first.
%file.writeLine($playerNameEntered @ " " @$score);
echo($playerNameEntered @ $score);
// Now we write all the data from the array (or 9 lines anyway) into the file.
// Including the entry right above here, that makes 10 scores.
while(%temp != $x)
{
// If the file was empty
%file.writeLine($lines.contents[%temp - 1]);
echo($lines.contents[%temp - 1]);
%temp += 1;
}
}
error("___________");
echo("END WRITE");
%temp = 1;
//-----------------------------------
// Begin sending scores to scoreboard
//-----------------------------------
// Starting our string by putting the players name, a space,
// and the score into the %scoreOutput variable.
%scoreOutput = $playerNameEntered @ " " @ $score;
// Finishing off the string by adding on each line from the
// array with line breaks after each one.
while(%temp != $x)
{
%scoreOutput = %scoreOutput NL $lines.contents[%temp -1];
%temp++;
}
echo(%scoreOutput);
// Send the score to the scoreboard and we are done here.
scoreTable.setText(%scoreOutput);
//------------------------------------
// Finish sending scores to scoreboard
//------------------------------------
// Change the score text control to instructions.
score.setText("P = Play Again | Q = Quit");
echo("File Written");
}
// This happens obviously if the file fails to open properly.
else
{
error("File is not open for writing");
}
// Close the file, delete the object.
%file.close();
%file.delete();
}
function readFile()
{
%file = new FileObject();
// If file is open for read already then ok do code, if it
// it is not open for read, open it.
if(%file.openForRead("~/data/files/score.sav"))
{
// Make sure the file isn't empty or broken or something.
if(!%file.isEof())
{
$x = 1; // Make sure our counter is set to 1.
$lines = new scriptObject(); // Creating our array.
// Read until the end of the file.
while(!%file.isEof())
{
// Reading each line into a global array.
%temp = %file.readLine();
$lines.contents[$x - 1] = %temp;
// Counting how many lines we read.
$x++;
}
}
else
{
// Set counter to zero (indicating an empty array/file.)
$x = 0;
error("File was empty!");
}
// If the second entry into the array is a blank line,
// remove that index (so we can know there was only
// one line.)
if($lines.contents[1] $= "")
$x -= 1;
// Don't allow the file to keep more than 10 scores.
if($x > 10)
$x = 10;
writeFile();
}
// This happens obviously if the file fails to open properly.
else
{
$x = 0;
error("File does not exist, creating it.");
// File doesn't exist so creating it.
%openAFile = new FileObject();
if(%openAFile.openForWrite("~/data/files/score.sav"))
{
%openAFile.close();
%openAFile.delete();
writeFile();
//readFile();
//schedule(1000, 0, readFile);
}
else
{
// Ok now you are screwed, quitting.
quit();
}
}
%file.close();
%file.delete();
}
Code 5.1: fileIO.cs As you can see, the code is heavily commented. This is what I have chosen to do rather than step through my code with you. This file both writes to a file and reads to a file for you. All the logic is fairly well explained in the code I think...I hope. I'm getting pretty tired about now, and therefore lazy. What we have built is a very simple IO system. Later if you like you can research CSV (comma separated value) format, which would be one way you could
make the game actually sort the high scores and decide if the new score even qualifies. I may even update this later to include that, but for now we won't go into it.
[edit] I'm not a playa, I just code a lot
function PlayerFish::onLevelLoaded(%this, %scenegraph)
{
$FishPlayer = %this;
// Hides the score display... It also shows the onscreen score though :)
hideScoreDisplay();
// Make the name entry dialog pop up
MBOKText.setText("Please enter your name:"); // Sets the text of the dialog.
MBOKFrame.setText("Player Name"); // Sets the title of the dialog.
$fishGamePaused = 1; // Effectively pauses the game.
Canvas.pushDialog( namePopUpDlg ); // Displays the dialog.
moveMap.bindCmd(keyboard, "w", "fishPlayerUp();", "fishPlayerUpStop();");
moveMap.bindCmd(keyboard, "s", "fishPlayerDown();", "fishPlayerDownStop();");
moveMap.bindCmd(keyboard, "a", "fishPlayerLeft();", "fishPlayerLeftStop();");
moveMap.bindCmd(keyboard, "d", "fishPlayerRight();", "fishPlayerRightStop();");
moveMap.bindCmd(keyboard, "space", "fishPlayerBoost();", "fishPlayerBoostStop();");
// Setting keys p and q for Play Again and Quit respectively.
moveMap.bindCmd(keyboard, "p", "playAgain();", "doNothing();");
moveMap.bindCmd(keyboard, "q", "quitGame();", "doNothing();");
%this.lowerLife();
}
function playAgain()
{
// Check to see if we have the game paused, if so do this crap.
if($fishGamePaused == 1)
{
error("This is happening.");
// Resetting game variables.
$fishPlayer.life = 100;
$fishPlayer.dead = false;
$fishGamePaused = 0; // Unpausing the game.
$score = 0; // Resetting the score.
$x = 1; // Resetting the file counter.
score.setText($score); // Setting the onscreen score display to 0.
// Put the fish back in position, flip him (or her) back, and stop
// that incessant floating up that he/she so loves to do.
$fishPlayer.setPosition("0.502 -5.574");
$fishPlayer.setFlipY(false);
$fishPlayer.setLinearVelocityY(0);
// Yeah...I don't need to go over this again. :)
hideScoreDisplay();
// Get things going again.
$fishPlayer.lowerLife();
}
}
function quitGame()
{
// If game is paused when the user hits q then quit. Really you want
// a way for the user to be able to quit while the game is playing too
// but you can do what you want. I just thought q would be a bad in
// game quit function.
if($fishGamePaused == 1)
quit();
}
function doNothing(){}
function SceneWindow2D::onMouseMove(%this, %mod, %worldPosition)
{
//..
// Enable this to make the fish move with your mouse, but
// GUI interferes with it so you'd have to do it another
// way or know how to fix that. :) This makes the game
// way too easy anyway.
//..
//$FishPlayer.setPosition(%worldPosition);
}
function PlayerFish::updateMovement(%this)
{
if(%this.dead)
return;
if(%this.moveLeft)
{
$FishPlayer.setFlipX(false);
$FishPlayer.setLinearVelocityX( -$FishPlayer.hSpeed );
}
if(%this.moveRight)
{
$FishPlayer.setFlipX(true);
$FishPlayer.setLinearVelocityX( $FishPlayer.hSpeed );
}
if(%this.moveUp)
{
%this.setLinearVelocityY( -$FishPlayer.vSpeed );
}
if(%this.moveDown)
{
%this.setLinearVelocityY( $FishPlayer.vSpeed );
}
if(!%this.moveLeft && !%this.moveRight)
{
%this.setLinearVelocityX( 0 );
}
if(!%this.moveUp && !%this.moveDown)
{
%this.setLinearVelocityY( 0 );
}
}
function PlayerFish::modifyLife(%this, %dmg)
{
if($fishGamePaused == 0)
{
%this.life += %dmg;
if(%this.life > 100)
{
%this.life = 100;
}
else if (%this.life < 0)
{
%this.life = 0;
}
if(%this.life <= 30)
{
$fishGamePaused = 1; // Let different functions pause.
%this.dead(); // Oh noes! We be dead.
}
else
{
%this.updateLifeSize();
}
}
}
function PlayerFish::updateLifeSize(%this)
{
%lifeMultiplier = %this.life / 100;
%newWidth = %this.maxWidth * %lifeMultiplier;
%newHeight = %this.maxHeight * %lifeMultiplier;
%this.setSize(%newWidth, %newHeight);
}
function PlayerFish::dead(%this)
{
%this.setFlipY(true);
%this.setLinearVelocityY(-10);
%this.dead = true;
// Makes the score screen show up.
highScoresTitle.setVisible(1);
nameTitle.setVisible(1);
scoreTitle.setVisible(1);
scoreTable.setVisible(1);
// Makes the score display (that shows when playing) disapear.
scoreCounterTitle.setVisible(0);
// Read the file, then write is called from read.
readFile();
}
function PlayerFish::lowerLife(%this)
{
%this.modifyLife(%this.lifeDrain);
if(!%this.dead)
{
%this.schedule(500, "lowerLife");
}
}
function fishPlayerUp()
{
$FishPlayer.moveUp = true;
$FishPlayer.updateMovement();
}
function fishPlayerDown()
{
$FishPlayer.moveDown = true;
$FishPlayer.updateMovement();
}
function fishPlayerLeft()
{
$FishPlayer.moveLeft = true;
$FishPlayer.updateMovement();
}
function fishPlayerRight()
{
$FishPlayer.moveRight = true;
$FishPlayer.updateMovement();
}
function fishPlayerUpStop()
{
$FishPlayer.moveUp = false;
$FishPlayer.updateMovement();
}
function fishPlayerDownStop()
{
$FishPlayer.moveDown = false;
$FishPlayer.updateMovement();
}
function fishPlayerLeftStop()
{
$FishPlayer.moveLeft = false;
$FishPlayer.updateMovement();}
function fishPlayerRightStop()
{
$FishPlayer.moveRight = false;
$FishPlayer.updateMovement();
}
function fishPlayerBoost()
{
if(%this.dead)
return;
%flipX = $FishPlayer.getFlipX();
if(%flipX)
{
%hSpeed = $FishPlayer.hSpeed * 3;
} else
{
%hSpeed = -$FishPlayer.hSpeed * 3;
}
$FishPlayer.setLinearVelocityX(%hSpeed);
}
function fishPlayerBoostStop()
{
$FishPlayer.setLinearVelocityX(0);
}
Code 5.2: player.cs This file does pretty much the same thing as before, except I have added a few things. First off it pops our dialog box and gets the player name from the user. It also
includes functionality to pause the game and to display the high scores table when you die.
[edit] Goldfish are cheaper to buy than to feed
Despite my asinine way of labeling sections, this is almost our final file modification. The following is fishfood.cs, so eat it up.
function FishFood::onLevelLoaded(%this, %scenegraph)
{
%this.startPositionY = %this.getPositionY();
%this.setLinearVelocityY(getRandom(%this.minSpeed, %this.maxSpeed));
// Initialize the score to 0 and display it.
$score = 0;
score.setText($score);
}
function FishFood::onWorldLimit(%this, %mode, %limit)
{
if(%limit $= "bottom")
{
%this.spawn();
}
}
function FishFood::spawn(%this)
{
%this.setPosition(getRandom(-50, 50), %this.startPositionY);
%this.setLinearVelocityY(getRandom(%this.minSpeed, %this.maxSpeed));
}
function FishFood::onCollision(%srcObj, %dstObj, %srcRef, %dstRef, %time, %normal, %contactCount, %contacts)
{
if(%dstObj.class $= "PlayerFish")
{
%srcObj.spawn();
%dstObj.modifyLife(%srcObj.lifeValue);
// If the game is not paused, take our score and add 10 points.
if($fishGamePaused == 0)
{
$score = $score + 10;
score.setText($score);
}
} else if (%dstObj.class $= "Fish")
{
%srcObj.spawn();
}
}
Code 5.3: fishfood.cs I posted the whole file, but the only change to fishfood.cs is this:
// If the game is not paused, take our score and add 10 points.
if($fishGamePaused == 0)
{
$score = $score + 10;
score.setText($score);
}
Code 5.4: The only change to fishfood.cs I didn't even write the stuff inside the brackets, but you need this minor change to make things work properly. This simply makes sure the score doesn't update
when the game is paused.
[edit] Executions and other fun for the whole family
In game.cs make sure you have the following execs (and the code below the execs):
// Load our new dialog box
exec("~/gui/namePopUpDlg.gui");
// Execute all our gamescripts
exec("./fileIO.cs");
exec("./player.cs");
exec("./fishfood.cs");
exec("./fish.cs");
exec("./mine.cs");
// Freshening up our file counter
$x = 0;
Code 5.5: Execs for game.cs Add this code to game.cs right after the startGame function:
$fishGamePaused = 0; // Pause game flag
// When OK button is pushed, store textbox value and close the pop up.
function getPlayerName()
{
$playerNameEntered = nameEntryBox.getValue(); // Stores the entered name as a global variable
MessageCallback(namePopUpDlg,namePopUpDlg.callback); // Closes the dialog box
$fishGamePaused = 0; // Unpause the game
error("Entered name: " @ $playerNameEntered);
}
// Hides the high scores display and shows the onscreen score
function hideScoreDisplay()
{
highScoresTitle.setVisible(0);
nameTitle.setVisible(0);
scoreTitle.setVisible(0);
scoreTable.setVisible(0);
score.setVisible(1);
scoreCounterTitle.setVisible(1);
}
Code 5.6: Code for game.cs
[edit] ZOMG, this is almost fun now.[edit] Look at that, it still sucks, but not as much.
We now have a game that doesn't totally suck. There is actually a point to playing now, and a framework to make things even better. If you play around with this code and you have never worked with File IO, be aware that there are some slight dangers. If you get stuck in a loop that writes to a file, that file will fill up very quickly. Many times when I was working this out I did just such a thing, and a file being filled with blank lines can generate 15-30 megs in 30 seconds or so. Treat this code like your stove and don't leave while frying up some FileObject sandwhiches.
[edit] Some final screenshots
[edit] Source code files
Here are the source files.
|
Additional Resources
High Scores Object -- For those who may wish for a slightly more advanced High Scores table, this resource allows for you to create a High Scores table that limits the number of entries based on a configured value, and orders the results from Highest to Lowest, this resource also provides Save/Load functionality and can easily be integrated into the above mentioned GUI Examples with little work involved (An example, or an update to this resource may follow).
Categories: Basics | GUI | Getting Started | Script | T2D | TorqueScript | Tutorial

















