Code How To's
This page provides tutorials for some of the most common actions a programmer may wish to implement. It may helpful to read the Code Overview first.
Contents
- 1 Submit a Bug or Other Issue
- 2 Coding Conventions
- 3 Create a New Minigame
- 4 Display a Static Image
- 5 Display an Animation
- 6 Create a New Type of Game Object
- 7 Get User Input
- 8 Manage a Heads Up Display
- 9 Display Text or Icons with the Ticker
- 10 Display Text Manually
- 11 Use Sound
- 12 Use the Particle Engine
- 13 Keep Score and Give Resources to the Simulator Mode
- 14 Write to the Experimental Log
- 15 Use the Game Script
- 16 Run the Game in the Lab
Submit a Bug or Other Issue
- Login to BugHost.com
- Developers sign in with the email address "collaborators@autismcollaborative.org"
- Ask August for the password
- Click the Submit button on the top navigation bar
- Fill out the fields that apply to this issue
Type of Bug:
- Functional Problem
- Any actual "Bug"
- Suggestion
- A possible change to the game that may improve it
- New Feature
- Something that absolutely must be implemented
- Use sparingly; make sure it's not actually a Suggestion
Severity:
- How much this issue affects the game/application
Priority:
- How soon the issue should be resolved (relative to other issues)
- The majority of issues should be Low priority
- Use High sparingly
Coding Conventions
- Place the name of the file, authors, and copyright info at the top of each file
- Any person who makes significant changes to a file should add their name to the list of authors in a file
- This helps anyone reading the code because they know who to ask if they have a question about something
- Any person who makes significant changes to a file should add their name to the list of authors in a file
- All private and protected class member variables are lowerCamelCase
- Private/protected class member variables are preceded with "m" (ie mLocalVariable)
- All functions, properties (sometimes called "smart fields"), and public class variables are UpperCamelCase
- Public variables should only be used with small classes that are only used by one or two other classes
- This code is intended to be viewed by many people who do not have strong programming backgrounds, therefore documentation is very important
- Document the purpose of each class's member variables, either next to their declaration or as a summary for their property accessor
- Create a summary documentation block for every function unless it is extremely obvious
- This can be done by placing the cursor above a function definition and typing "///"
- This block shows up in Intellisense when writing that function in code
- Add a summary to any properties that are possibly confusing or not straightforward
- Document every step of each algorithm (except for very obvious function calls like ResetSettings();)
- Someone should be able to read only the comments in a function and still understand what it does
- See MeteorMadness.cs for an example of the appropriate level of documentation
Create a New Minigame
- Declare the class
- Create a new folder under ColonySimulator
- Create a new file in that folder named NewMinigame.cs where NewGame is the name of the new minigame
- Place all new minigame-specific classes in that folder
- Have the class derive from MiniGame
class NewGame : MiniGame {...}
- Implement the Constructor
- Initialize game variables
- Declare a ScoreCard (see Scoring tutorial)
- Log the GameBegin code (see the Logging tutorial)
- Implement Update()
public override void Update(ContentManager content){...}
- Do not assume a constant call rate
- This function will almost always be called 60 times per second, but that rate may vary depending on CPU load
- At the beginning of each cycle, get the time since the last cycle like this:
- float dt = ColonyBaseApplication.GameManager.ElapsedSeconds;
- DO NOT use XNA.Framework.GameTime.ElapsedGameTime as tAC_Engine implements update timing differently than a standard XNA application!
- Build the time into all movement and timers, for example:
- Do not assume a constant call rate
myEntity.LifeSpan -= dt; myEntity.Position += myEntity.Velocity * dt;
- Implement Draw()
public override void Draw(SpriteBatch spriteBatch){...}
- Loop through all the game's entities and call their Draw() functions
- Consider using a generalized render-queue such as GenericWorldSpace (not-yet-implemented as of Oct 2007)
- Create a new game mode
- Add a new entry to ColonyBaseApplication.Modes = { ModeSelector, ColonySim, ...}
- Add the appropriate case to ColonyBaseApplication.SwitchMode(...)
- Add a new MiniGameInfo entry to ModeSelector.ModeSelector()
- End the game
- Flush the particle system (see the particles tutorial) if any emitters were created
- Log the GameEndSuccess/GameEndFailure code
- Flush the log (forces it to write any cached codes to disk)
- Show the score
- Switch the mode to ColonySim
ColonyBaseApplication.SwitchMode(ColonyBaseApplication.Modes.ColonySim);
Display a Static Image
Before manually drawing something to screen consider using some type of game object, be it an Entity, HUD element, or even a Ticker (see the tutorials below). Game objects all know how to draw themselves, so it is unlikely that a programmer will need to draw something directly to the screen. Should the need arise, however, here's how it's done:
- Import the content into the project
- Browse to the appropriate folder in Visual Studio's Solution Explorer
- For example, Content\MiniGames\MyImage.png
- Right click on that folder and select Add->Existing Item
- Select the image to import
- Browse to the appropriate folder in Visual Studio's Solution Explorer
- Load it in code:
GraphicsDeviceManager gdm = ...; // This is declared in GenericGameManager.cs ConentManager cm = ...; // Usually a parameter, as in Update(ContentManager content) SpriteBatch sb = new SpriteBatch(gdm.GraphicsDevice); // Basically a wrapper for a DirectX render target Texture2D texture = cm.Load<Texture2D>(@"Content\MiniGames\MyImage"); // Source image with no file extension Rectangle destination = ...; // Display destination Color tint = Color.White; // Tint the image with this color float rotation = 0f; // Rotation (in radians) Vector2 center = new Vector2(texture.Width / 2f, texture.Height / 2f); // Origin of rotation float zDepth = 1f; // Order to draw sprites within this spritebatch sb.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.SaveState); sb.Draw( texture, destination , null, // What part of the source image to sample from--null tells it to use the whole image tint , rotation, center, SpriteEffects.None, // How to mirror the image zDepth ); sb.End();
Display an Animation
Animations are built into Entities. There are no plans to support animation in any other context. Here's an example:
Animation anim = new Animation(new Vector2(100, 100)); // Create an animation that is 100x100 pixels when displayed anim.AddClip("Idle", content.Load<Texture2D>(@"Content/IdleSpriteSheet"), 5, 1f/60f); // a 500x100 spritesheet anim.AddClip("Walk", content.Load<Texture2D>(@"Content/WalkSpriteSheet"), 12, 1f / 60f); // a 400x300 spritesheet Entity ent = new Entity(anim, new Vector3(10f, 50f, 0f), new Vector3(100f, 100f, 0f)); // a 100x100 2D entity located at (10,50) ent.Play("Idle"); // Start the idle animation // ... ent.Play("Walk"); // Switch to the walking animation // ... ent.Draw(sb); // Draw to the SpriteBatch sb
Create a New Type of Game Object
All specific game objects (players, enemies, collectibles, projectiles, etc.) should derive from Entity. This gives them easy ways to draw themselves, move, collide with other entities, and be passed to many tAC_Engine functions.
Get User Input
DO NOT use the standard XNA keyboard accessors (Xna.Framework.Input.KeyboardState or Xna.Framework.Input.Keyboard) because tAC_Engine does a lot of caching and logging behind the scenes. Instead get keyboard input like this:
if (InputState.IsKeyDown(Keys.Left)) { // The left key was pressed }
If an exception is thrown stating "Unregistered Key Comparison" then add that key to the list in the InputState constructor.
Manage a Heads Up Display
Not yet implemented.
Display Text or Icons with the Ticker
string text = "w00t."; // Text to display (can be "") Texture2D image = null; // Can also be set to an image Vector2 imageSize = new Vector2(25f, 25f); // Size of the (optional) icon Vector2 offset = new Vector2(700f, 50f); // This will display the ticker 700 pixels to the right of the screen's // center and 50 pixels below the screen's center Vector2 velocity = new Vector2 (-10f, 0f); // Move left 10 pixels per second Ticker.Font font = Ticker.Font.Standard; // Regular text Color color = Color.DeepPink; // The manliest of colors float opacity = .5f; // 50% transparent float seconds = 5f; // Disappear after 5 seconds bool fade = true; // Fade in and fade out Ticker.Display(text, image, imageSize, offset, velocity, font, color, opacity, seconds, fade);
Display Text Manually
Most text displayed should be done using the Ticker, but if you really want to display text manually use the Nuclex font library (XNA now supports text, so the Nuclex library may eventually be removed). First you must import the desired font:
- Create an xml file for the new font called FontName.tffimport that looks like this:
<?xml version="1.0"?> <TrueTypeImport> <Path>Content\Fonts\MercuryII.ttf</Path> <Size>32</Size> </TrueTypeImport>
- Put the font (FontName.ttf) and FontName.tffimport in the Content\Fonts directory
- Set the following properties for FontName.ttf
- Build Action: None
- Copy to Output Directory: Copy if newer
- File Name: MercuryII.ttf
- Set the following properties for FontName.tffimport
- Build Action: Content
- Content Importer: TrueTypeFont - XNA Framework
- Content Processor: Bitmap Font - XNA Framework
- Copy to Output Directory: Do not copy
- XNA Framework Content: True
Now for the code:
ContentManager cm = ...; SpriteBatch sb = ...; BitmapFont bmf = cm.Load<BitmapFont>(@"Content\Fonts\Arial"); sb.Begin(SpriteBlendMode.AlphaBlend); bmf.DrawString(new Vector2(10.0F, 10.0F), "Manual Text!", Color.Azure); sb.End();
Use Sound
Import a new Sound
- Run Autism Collaborative\Utilities\XACT\Xact.exe
- This is the XACT tool from Microsoft DirectX SDK August 2006
- If--after following the steps in this tutorial--playing a file in code throws an exception about using the wrong version of XACT, try using Xact.exe from a different SDK build
- Open Autism Collaborative\Game\Game\Content\Sounds\AudioProject.xap
- Expand Wave Banks and double click Wave Bank in the panel on the left
- Expand Sound Banks and double click Sound Bank in the panel on the left
- Go to Wave Banks->Insert Wave File(s)...
- Select the file to import (Autism Collaborative\Game\Game\Content\Sounds\MySound.wav) and click OK
- Drag the newly-added file to the Cue Name section in the Sound Bank:Sound Bank window
- Go to File->Build
- Save and close XACT
- Open game.sln with Visual Studio
- Browse to the Content\Sounds folder in the Solution Explorer
- Right click the Sounds folder and select Add->Existing Item...
- Select MySound.wav and click OK
Play a Sound
Sound.Play("MySound"); // No file extension
Use the Particle Engine
Particles are handled by Sparx, a subset of the Pina library being developed alongside the Autism Game. Features are added to it on an as-needed basis so if a new feature is needed contact August or email support.
Particle Effects are imported via the content pipeline, so they can be called in a similar manner:
using Pina3D.Particles; ... Emitter mRootEmitter; mRootEmitter = Sparx.LoadParticleEffect(@"Content\Particle Effects\MyParticleEffect.spx"); Sparx.AddEmitter(mRootEmitter);
Don't forget to kill emitters when they are no longer needed.
mRootEmitter.Alive = false; // Kills the Emitter Sparx.Flush(); // Kills all Emitters
Emitters can be moved around:
mRootEmitter.Position2D = new Vector2(100f, 100f); // Emitter will move, but existing Particles will not be affected
They can also move and keep their Particles in the same relative location.
// COMING SOON (this old code does not yet work with the new 3D particle system //mRootEmitter.Transform = Matrix.CreateTranslation(100f, 100f, 0); // Particles will move with the Emitter
Any aspect of a Particle Effect can be changed at runtime. For example, to make an effect green add a Modifier:
mRootEmitter.RegularEmissionType.Modifiers.Add(new Modifier("whateverMyNameIs", 0f, 0f, Color.Green, Color.Green, 1f, 1f, 1f, 1f, 0f, 0f));
Or apply forces at runtime:
mRootEmitter.SubscribeEmissionsToForce(new Current("theWind", 0f, 0f, new Vector3(10f, 0f, 0f), 1f));
Create a New Particle Effect
This can be done entirely at runtime, however it is usually much easier (and more fun) to use they WYSIWYG editor SparxCreator3D. Please report any bugs to August or submit them to the bug database.
- Run Autism Collaborative\Utilities\SparxCreator3D.exe
- Make an effect
- Emitters can emit Emissions
- Emissions can be Particles or Emitters
- Modifiers can be applied to Emissions
- Forces can be applied to Emitters
Once an effect file is generated (MyEffect.spx) import it into the game to use it.
- In the Solution Explorer, browse to Content\Particle Effects
- Right click the folder and select Add->Existing Item...
- Select MyEffect.spx and click OK
- Change MyEffect.spx's Copy to Output Directory property to Copy Always
Keep Score and Give Resources to the Simulator Mode
Use the ScoreCard class to keep track of the score from a minigame. It provides easy ways to add and subtract resources, as well as an easy way to display them and pass them along to the Colony Simulator.
MiniGame has a ColonyScoreCard built into it, accessible by the Score property. Here are some examples of how to use it:
class MyGame : MiniGame { public override void Update(ContentManager content) { Score.Gather(ColonyResources.Money, 500); // Get 500 Monies MyGameObject mgo = new MyGameObject(); mgo.GiveMyGameCarbon(); // Get 1 Carbon Score.IncurDamageCost(100); // On CashOut, Money = Max (Money - DamageCosts, 0) int HowMuchMoneyIHave = Score.QueryResource(ColonyResources.Money); } public void DisplayTheScore() { bool playerWonTheMinigame = true; // Can be set to false if the player lost Score.Display(playerWonTheMinigame ); // Displays a copy of the current score and a message for succses/failure Score.CashOut(); // Transfers resources from MyGame to the Colony Simulator } // ... } class MyGameObject : Entity { public void GiveMyGameCarbon() { ColonyBaseApplication.MiniGame.Score.Gather(ColonyResources.Carbon, 1); } }
Write to the Experimental Log
As the game runs, certain actions are recorded and written to a file (Autism Collaborative\Game\Game\bin\x86\Debug\ExperimentalLog.txt or the corresponding Release folder). In the lab, these codes are also sent through the parallel port to sync the EEG data. Every action that pertains to an experiment must be logged in order to be analyzed (that's kind of the whole point of this game!).
First define the necessary game codes.
- Open Logger.cs
- Add up to 253 codes to public enum ExperimentalCode
- All codes must start with a unique "code identifier", an underscore, and then a descriptor in UpperCamelCase
- For minigames, the code identifier is 2 letters
- 3 codes are reserved for special parallel port functions with the software that runs the EEG machines used in lab tests
- All codes must start with a unique "code identifier", an underscore, and then a descriptor in UpperCamelCase
Now those codes are available for logging. Like so:
Logger.LogCode(Logger.ExperimentalCode.XX_Descriptor);
It is also possible to tell the logger to log a code every time a specific key is pressed:
InputState.Encode(Keys.A, InputState.KeyState.Down, Logger.ExperimentalCode.DEBUG_DudeJustPushedA);
Each minigame should remove the encodings when it is done, but it is also a good idea to decode the keys at the start of each minigame just in case.
InputState.Encode(Keys.A, InputState.KeyState.Down); InputState.DecodeAll();
Every minigame should also start with its GameBegin and end with its GameEnd[Success/Failure] codes.
It may be helpful to log events for debugging purposes. These codes have the identifier "DEBUG" and will not be logged unless the game is running in Debug mode. All new DEBUG tags must be explicitly assigned a value less than 0.
Use the Game Script
The tAC_Engine houses a [Lua] Virtual Machine. This can be used to tweak global parameters at runtime, load them from config files, drive cutscenes, or even save and load levels.
Function callbacks are defined in C# and then invoked from a Lua file or the in-game Debug console. In other words, C# tells Lua "you may use these functions: X,Y,..." and then Lua says "do X, do Y, etc."
Here are a couple of ways to leverage this system:
Make a Cutscene
- Create a new lua file in the Scripts directory
- That file can call any registered function callbacks
- In the Properties window, set Copy to Output Directory to Copy if Newer
- Call that cutscene with:
GenericBaseApplication.GameManager.PlayCutscene(@"Scripts\MyCutscene.lua");
To Register a Function Callback
- Create the function in C# (if the function does not already exist)
- It must be declared public and non-static
- If the need arises to register a static function, declare an instanced function in GenericLuaHelper or ColonyLuaHelper that simply calls the static function.
- Register the function with the Lua Virtual Machine
- Add the special LuaCallback annotation to the function
- Call the RegisterLuaCallbacks function in the class's constructor that contains the function.
- Example:
public MyClass() { // Constructor LuaHelper.RegisterLuaCallbacks(this); } [LuaCallback("SetMyVariable")] public int SetMyVariable(int value) { mMyVariable = value; }
Tweak Game Parameters
Exposing a variable to the Lua Virtual Machine allows it to be accessed by a any script (including config files) or changed at runtime in the drop down console (accessible in Debug mode by hitting "~").
- Expose a variable with a global setter/getter with a LuaCallback annotation
- Make sure to call the RegisterLuaCallbacks function in that class's constructor if the getter/setter is not in the Game Manager
- Set the default value for the variable in the config file (optional)
- There are 3 config files for the 3 configurations of the game builds
- Debug.conf, for development
- StandardRelease.conf, for public releases of the game
- LabRelease.conf, for use in the lab
- There are 3 config files for the 3 configurations of the game builds
Create Local Settings
Programmers may want to change certain properties on their machines without affecting the source-controlled code. They could change the resolution, sound settings, or even have the game bypass the regular startup sequence and launch right into the middle of a MiniGame all with no effect on other developers.
- Create a new file: Autism Collaborative\Game\Game\Scripts\LocalSettings.lua
- The Visual Studio project already has this file included, but VS won't be able to edit it until the file is created manually
- Add Lua commands and save the file
- Visual Studio can now be used to edit the file
- The file is run at the end of the Debug.conf script (if it exists)
- That means that this script is only run in Debug mode
- do NOT add LocalSettings.lua to source control!
Run the Game in the Lab
To enable the features necessary to run the game in the lab, the file conlab.tac must exist next to the game's executable.
From visual studio:
- Change the configuration to "ReleaseLab"
- Build the solution
From a public release of the game:
- Find "constd.tac," the standard encrypted config file
- Copy "conlab.tac" to the same directory
- Autism Collaborative\Game\Game\bin\x86\Laboratory\conlab.tac is generated when the game is compiled
- If the game finds conlab, it assumes it is in the lab and uses that config file
- If conlab is not present, it assumes it is not in the lab and uses constd.tac as the config file