Difference between revisions of "Code How To's"
(→Use Sound) |
(→Import a new Audio File) |
||
Line 230: | Line 230: | ||
* Assign the audio file to be either a Music or Effect sound | * Assign the audio file to be either a Music or Effect sound | ||
* Go to File->Build | * Go to File->Build | ||
− | * Save and close | + | * Save and close XACT |
* Open game.sln with Visual Studio | * Open game.sln with Visual Studio | ||
* Browse to the Content\Sounds folder in the Solution Explorer | * Browse to the Content\Sounds folder in the Solution Explorer |
Revision as of 15:41, 3 July 2008
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 Use Images
- 5 Create a New Type of Game Object
- 6 Get User Input
- 7 Manage a Heads Up Display
- 8 Display Text or Icons with the Ticker
- 9 Display Text Manually
- 10 Use Sound
- 11 Use the Particle Engine
- 12 Keep Score and Give Resources to the Simulator Mode
- 13 Write to the Experimental Log
- 14 Use the Game Script
- 15 Run the Game in the Lab
- 16 Publish the Installer
- 17 Use the ErrorLog
Submit a Bug or Other Issue
- Go to http://www.AutismCollaborative.org/bugs/
- 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
- See the Load a Texture section for naming conventions of texture assets
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 GameScreen
class NewGame : Gamescreen {...}
- 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(GameTime gameTime){...}
- 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 = gameTime.ElapsedGameTime;
- 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){...}
- Be sure to call each entities Draw and Update methods. Optional: Register all entities with the XNA game object.
This will have XNA automatically call their Draw and Update functions at appropriate times.
- Consider using a generalized render-queue such as GenericWorldSpace (not-yet-implemented as of Oct 2007)
- Use the Raise function to place the minigame gamescreen on the stack based game system.
- End the game using Done function. This will pop it off the game stack, returning the state of the game to the previous state.
- Flush the particle system (see the particles tutorial) if any emitters were created
- Log the GameEndSuccess/GameEndFailure code
- Show the score
Use Images
XNA supports most common types of images, including JPEG, GIF, DDS, etc.
Load a Texture
But before you can load a texture in code you must add it to the content pipeline:
- 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
Once that's done, you can load it memory with the XNA content manager:
Texture2D myTexture = Content.Load(@"General\myTexture");
The XNA content manager defaults to the content folder of the project, so all addresses can be relative from there. Also, the name of the image should be the same as the asset name. This can be set in the properties window.
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 minigame will need to draw something directly to the screen. Should the need arise, however, here's how it's done:
- 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 = TextureManager.Load(@"Content\General\myTexture"); 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 created using the AnimatedSprite class. Here's an example:
//Declare Animated Sprite //Parameters: Game object, SpriteSheet as a Texture2D object, Position as Vector2, FrameSize as Vector2, Number of Frames AnimatedSprite testAnimation = new AnimatedSprite(this, Content.Load<Texture2D>("TestSpriteSheet"), new Vector2(200, 300), new Vector2(64, 64), 16); //Pass a reference to the SpriteBatch that the AnimatedSprite will use testAnimation.SpriteBatch = spriteBatch; //Turn on the Animation testAnimation.Animate(true); //Turn off the Animation testAnimation.Animate(false);
- Do not forget to call the draw and update methods of the AnimatedSprite explicitly, or to add them to the XNA game.components array so to allow XNA to call them.
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) or mouse accessors (Xna.Framework.Input.MouseState) because tAC_Engine does a lot of caching and logging behind the scenes. Use tAC_Engine.InputState instead.
Keyboard
if (InputState.IsKeyDown(Keys.A)) { // The A key was pressed }
Mouse
if (InputState.IsMouseLeftUp && InputState.WasMouseLeftDown) { // The mouse was just clicked (clicks are typically registered on the release, not the initial press) }
Vector2 location = InputState.MouseLocation;
Manage a Heads Up Display
Use the HUD class for any HUD element (like health bars, menu buttons, etc.). Typically, you'll want to create a class that inherits from Widget2D. See MeteorWidgets.cs for examples.
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 Audio File
- Run the XACT tool from Microsoft DirectX SDK
- 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 AudioProject.xap file for the Minigame you wish to add a sound to.
- 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 i.e."@/MiniGame/Content/Audio/MySound.wav" and click OK.
- Drag the newly-added file to the Cue Name section in the Sound Bank:Sound Bank window
- Assign the audio file to be either a Music or Effect sound
- 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
Load an Audio Project
This needs to be done before you play any sound or effect. SoundManager.LoadAudioProject("AudioProject.xgs");
Play an Effect
SoundManager.PlayEffect("Pew"); // No file extension
Play Music
SoundManager.PlayMusic("MyMusic");
Use the Particle Engine
Particles are handled by the TACParticleEngine, a separate library that uses the AC_GraphicsEngine that has been modified to better fit the needs of the Autism Game. Features are added to it on an as-needed basis so if a new feature needs to be added and any problems arise in its integration into the current particle engine contact Brian Murphy.
Quick layout of TACParticleEngine:
The ParticleManager contains references to all loaded ParticleEngines. ParticleEngines contain a series of Emitters. Emitters contain a heap of Particles, which are the objects that actually get drawn.
Particle Effects are imported via its own unique pipeline using the existing XNA framework, so they can be called in a similar manner:
Make sure to include these packages so that we can create and access our particle engine objects:
using TACParticleEngine; using TACParticleEngine.Particles;
Inside of the class declaration make these global variables:
ParticleManager mParticleManager; ParticleEngine mTestEffect;
Make sure to initialize in the classes initialize method:
mParticleManager = new ParticleManager(Game); // Initialize the particle manager object with a reference to the current game mTestEffect = mParticleManager.Load("ParticleEffects/Explosion"); // Load in the given particle effect file by giving the // location at which it is compiled to
After these three things have been done, you'll now have an effect loaded into a Particle Engine object via the Particle Manager. However the Particle Engine doesn't mean anything if it doesn't do anything so let's set up the rest of the code we need for updating and drawing:
To get the engine to update, just call
mParticleManager.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
in the Update(GameTime gameTime) method.
As for drawing, a little more effort is required. First of, since the TACPArticleEngine makes use of the Camera class from the AC_GraphicsEngine, we will need to create and initialize one if we are to render our particles, so let's get our camera set if it hasn't been done so before for other drawing.
Add the following line to the list of global variables at the beginning of the class:
Camera mActiveCamera;
This will be whatever kind of camera we are currently using for drawing in case we may want other cameras for whatever reason.
Next we need to initialize the camera to any specific type of camera that derive from camera. As of now this is limited to FollowCamera, OrthographicCamera, PerspectiveCamera2D, and PerspectiveCamera3D. For now well us a PerspectiveCamera3D as our active camera.
mActiveCamera = new Perspective3DCamera(new Vector3(0, 0, -5), Vector3.Zero, Vector3.Up, MathHelper.Pi/5, 1, 1, 100);
Now that we have our active camera set, we can now draw by including the following line in the Draw(GameTime gameTime) method:
mParticleManager.Render(gameTime, mActiveCamera);
- Note that this assumes that the GraphicsDevice.Clear(Color.Black) and base.Draw(gameTime) method calls are also in the method as they are by default.
This will now draw any particles that have been loaded by the Particle Manager and have been set active, however the current particle engine we created has been set active yet so do that now.
This is done by calling the following line whenever you would want drawing of that Particle Engine to occur.
mTestEffect.SetActive(true);
This particle engine is now active, and if it had been previously active, it's lifetime would have been reset to 0. Using this call, we can also turn off the particle engine from creating anymore particles by giving it false as its parameter instead.
Besides this method, there are a series of methods that allow us to modify the drawing of the ParticleEngine itself. These are as followed:
SetEnginePosition(Vector3 Position) // Given a 3D vector, we can move the whole particle engine to the desired position
SetEngineColor(Vector4 Color) // Given a 4D vector of float values 0-1 in the form of RGBA, we can change the coloring of the whole particle engine // Keep in mind that this applies that much of the emitters' texture for each value, so a black picture with 1,0,0,1 as // its value will not be red but still black
SetEngineSize(Vector2 Size) // Given a 2D vector, we can modify the dimensions of the particle image, this is always 2D since particles in 3D are // simply billboard sprites
Create a New Particle Effect
To make a new effect you will need two things, a new particle file, placed into the appropriate directory, as well as whatever textures are needed in their appropriate directory. Make sure that after the particle file has been created and included into the project that it is set to compile using the particle importer and processor. If these options don't come up, you probably just need to include a reference to the TACParticleEngine within the projects content folder.
Here is the format of a particle file:
<particle> // This declares that we are making a particle effect
<emitter> // This declares that we are adding a new emitter to our particle effect
<textureName>Textures/SmokeParticle</textureName> // Directory of the texture the emitter will use
<blendEffect>AlphaBlend</blendEffect> // Blend options, typically uses Additive (for fiery effects) // and AlphaBlend (for smokey or piece effects
<positions>(0,0,50)</positions> // Default position th emitter appears at. Multiple Positions can be given in the form of // (x,y,z)(x,y,z)(x,y,z)..., this will create a spline emitter, which will form a smooth curve // using all the given points
<minVel>(0,0,0)</minVel> // Minimum velocity for particles. If this value is a zero vector and so is the maximum velocity, then // this will create either a point or spline emitter, depending on the number of positions given. If this // has any values of x, y, or z are < -1 or > 1, then a spray emitter is created, otherwise a directional // emitter is created. In either case, the number of positions should be 1.
<maxVel>(0,0,0)</maxVel> // This is the maximum velocity. Used only for spray and directional emitters. For spray emitters, this is // the direction in which all particles will move. In the directional emitter, this determines the end range // in which particles can move in
<emitLifetime>0.3</emitLifetime> // This is how long the emitter exists for in seconds
<particlesPerSec>50</particlesPerSec> // This is how many particles will be created every second the emitter is alive
<particleAmount>20</particleAmount> // This is the total amount of particles that can exist at any time. In most cases, this number // should be slightly over the emitLifetime * particlesPerSec, otherwise effect inconsistencies // could result.
<particleLifetime>3</particleLifetime> // How long an individual particle is alive for in seconds
<lifeLengthRandomizer>1</lifeLengthRandomizer> // This use to randomize the time a particle was alive for, however this has been taken // out. This will be removed in the future but until then, as with all the other tags, // this tag needs to exist otherwise the particle file won't compile
<minStartSpeed>0.7</minStartSpeed> // This is the minimum speed a particle can start at
<maxStartSpeed>1</maxStartSpeed> // This is the maximum speed a particle can start at
<minEndSpeed>0</minEndSpeed> // This is the minimum speed a particle can end at
<maxEndSpeed>0</maxEndSpeed> // This is the maximum speed a particle can end at
// *Note* currently rotation is not used due to the need to use pixel shader 2.0, which would limit the range of machine this could run // on, or at the least, would result in varied graphics between machines. This can either be fully used if the use of pixel shader 2.0 is // fine, or just scrapped if the use of pixel shader 2.0 seems like at will never happen.
<startRotationRange>6.28</startRotationRange> // Range at which a particle can start rotated
<minStartRotSpeed>-1</minStartRotSpeed> // The minimum speed at which a particle can rotate at start
<maxStartRotSpeed>1</maxStartRotSpeed> // The maximum speed at which a particle can rotate at start
<minEndRotSpeed>0</minEndRotSpeed> // The minimum speed at which a particle can rotate at end
<maxEndRotSpeed>0</maxEndRotSpeed> // The maximum speed at which a particle can rotate at end
<minStartColor>(1,1,1,0.3)</minStartColor> // The minimum values of RGBA that a particle can start at
<maxStartColor>(1,1,1,0.3)</maxStartColor> // The maximum values of RGBA that a particle can start at
<minEndColor>(1,1,1,0)</minEndColor> // The minimum values of RGBA that a particle can end at
<maxEndColor>(1,1,1,0)</maxEndColor> // The maximum values of RGBA that a particle can end at
<minStartSize>(200,200)</minStartSize> // The minimum size that a particle can start at
<maxStartSize>(200,200)</maxStartSize> // The maximum size that a particle can start at
<minEndSize>(300,300)</minEndSize> // The minimum size that a particle can end at
<maxEndSize>(300,300)</maxEndSize> // The minimum size that a particle can end at
</emitter> // Declares that the emitter has been set and is now finished being made
Here's some additional emitters to just show how multiple emitters in a single effect can be done
<emitter> <textureName>Textures/BasicParticleImage</textureName> <blendEffect>Additive</blendEffect> <positions>(0,0,120)</positions> <minVel>(1.001,1.001,1)</minVel> <maxVel>(0,0,0)</maxVel> <emitLifetime>0.3</emitLifetime> <particlesPerSec>1000</particlesPerSec> <particleAmount>400</particleAmount> <particleLifetime>2</particleLifetime> <lifeLengthRandomizer>1</lifeLengthRandomizer>
<minStartSpeed>2</minStartSpeed> <maxStartSpeed>2</maxStartSpeed> <minEndSpeed>0</minEndSpeed> <maxEndSpeed>0</maxEndSpeed>
<startRotationRange>6.28</startRotationRange> <minStartRotSpeed>-1</minStartRotSpeed> <maxStartRotSpeed>1</maxStartRotSpeed> <minEndRotSpeed>0</minEndRotSpeed> <maxEndRotSpeed>0</maxEndRotSpeed>
<minStartColor>(1,0,0,0.3)</minStartColor> <maxStartColor>(1,0,0,0.3)</maxStartColor> <minEndColor>(0,0,0,0)</minEndColor> <maxEndColor>(0,0,0,0)</maxEndColor>
<minStartSize>(1,1)</minStartSize> <maxStartSize>(1,1)</maxStartSize> <minEndSize>(0.1,0.1)</minEndSize> <maxEndSize>(0.1,0.1)</maxEndSize> </emitter> <emitter> <textureName>Textures/ExplosionParticle</textureName> <blendEffect>Additive</blendEffect> <positions>(0,0,60)</positions> <minVel>(0,0,0)</minVel> <maxVel>(0,0,0)</maxVel> <emitLifetime>0.3</emitLifetime> <particlesPerSec>100</particlesPerSec> <particleAmount>40</particleAmount> <particleLifetime>0.3</particleLifetime> <lifeLengthRandomizer>0.1</lifeLengthRandomizer>
<minStartSpeed>2</minStartSpeed> <maxStartSpeed>2</maxStartSpeed> <minEndSpeed>0</minEndSpeed> <maxEndSpeed>0</maxEndSpeed>
<startRotationRange>6.28</startRotationRange> <minStartRotSpeed>-1</minStartRotSpeed> <maxStartRotSpeed>1</maxStartRotSpeed> <minEndRotSpeed>0</minEndRotSpeed> <maxEndRotSpeed>0</maxEndRotSpeed>
<minStartColor>(1,1,1,0.7)</minStartColor> <maxStartColor>(1,1,1,0.7)</maxStartColor> <minEndColor>(1,1,1,0)</minEndColor> <maxEndColor>(1,1,1,0)</maxEndColor>
<minStartSize>(10,10)</minStartSize> <maxStartSize>(50,50)</maxStartSize> <minEndSize>(10,10)</minEndSize> <maxEndSize>(50,50)</maxEndSize> </emitter> </particle> // Declares the end of the particle file
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!
Creating Scripts in C# Code
Alternatively, scripts can created using C# code rather than Lua. A C# script either be object oriented or imperative. They are able to both take in objects and return values. The CSharpHelper class can be called to run these scripts using it's DoSimpleFile method. This will simply run all the code in the script. The value of this is that commonly edited code, such as configuration files, can be saved in a convenient separate location. The methods for CSharpHelper are well documented within the file itself. An example of a basic call to a C# sharp script is as follows:
CSharpHelper.doSimpleFile(Environment.CurrentDirectory + @"\Configs\Debug.confcss", this, "game");
This starts a C# file called "Debug.confcss" which is located in the Configs directory. The second parameter is a Game object. The object passed in can be of any type. The third parameter is the identifier of the object within the script. In this case the identifier is "game". This means that inside the script, lines such as:
game.Update();
while execute the passed in object's update function.
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
Publish the Installer
Create the Installer
We use InnoSetup to create the installer. Checkout their website (http://www.jrsoftware.org/isinfo.php) if you need to change the script, but simply publishing the game does not require any adjustment to the installer script.
- Install InnoSetup if necessary
- Run Autism Collaborative\Utilities\Installer Installer\isetup-5.2.2.exe
- Compile the game
- In the second menu bar (immediately below the top menu bar) within Visual Studio, change "Debug" to "ReleasePub" or "ReleaseLab". (In the box to the right, leave the default "Mixed Platforms".)
- ReleasePub is for our releases to the general public. This will usually be the correct configuration.
- ReleaseLab is for builds to be used in the lab. This will create the config file to run the game in "lab mode." The resulting exe file will actually be the same as ReleasePub, but constd.tac will be replaced with conlab.tac.
- Debug mode is used while developing, so do not build the game in debug mode when creating the installer.
- For the exact effects of the different build configurations on the game flow, unit stats, etc. see the config files in Autism Collaborative\Game\Game\Configs
- Goto Build->Build Solution
- In the second menu bar (immediately below the top menu bar) within Visual Studio, change "Debug" to "ReleasePub" or "ReleaseLab". (In the box to the right, leave the default "Mixed Platforms".)
- Compile the installer script
- Open Autism Collaborative\Game\Game\Installer.iss with InnoSetup
- Goto Build->Compile
- Goto Run->Run
- This creates the file Autism Collaborative\Game\Game\Output\tAC_Installer.exe
Upload the Installer to the Web
- Get a username/password for autismcollaborative.org from Matthew.
- Use a secure FTP client to login, such as WinSCP
- Either download it or run Autism Collaborative\Utilities\WinSCP.exe
- Login to autismcollaborative.org (port 22)
- Upload tAC_Installer.exe to your home directory (for example /home/zinsser)
- Do not overwrite the existing installer (/home/belmonte/autismcollaborative.org/downloads/AstropolisSetup.exe) since that would delete the installer from the website until the new installer finishes uploading (usually over an hour!)
- Once the upload is complete, enter the command "mv /home/zinsser/tAC_Installer.exe /home/belmonte/autismcollaborative.org/downloads/AstropolisSetup.exe" (you may have to open a terminal first, depending on your SFTP client)
- In the above command, substitute your username for "zinsser", but keep "belmonte" as the destination user directory
- If there is a permissions problem, contact Matthew to make sure your user has permission to the autismcollaborative.org directory!
Use the ErrorLog
When the game encounters an Unhandled Exception (like when an end user runs into a bug in our code), useful information is dumped to ErrorLog.txt. The user is encouraged to email that file to us so we can locate the problem they ran into. Although the ErrorLog will tell us what function the program died in, it cannot tell us specifically which line was the culprit (although ErrorLogs created when the game is running in Visual Studio will tell us exactly which line!).
We do, however, have the ability to log any custom information from a MiniGame. This may prove invaluable in debugging based on end-user error reports. All you (the programmer) must do is override the string MiniGame.DebugInfo() function. When ErrorLog.txt is created, it calls that function on the current MiniGame and appends the returned text to the bottom of the file.