FLTK Demo - KidneyThief/TinScript1.0 GitHub Wiki
I created a demo, using FLTK's interface to create a window, expose functions for DrawLine() and DrawCircle(). With this simple framework, combined with TinScript, I created a very simple (but hopefully reminiscent) version of "Asteroids". (The important thing to note - other than the awesome programmer art - isn't the game itself, but that using TinScript, it was built in under an hour, as a result of the development speed provided by runtime iteration.)
- Download the TinScript 1.0 zip file https://github.com/KidneyThief/TinScript1.0
- Download the FLTK (version 1.3.2) installation from http://www.fltk.org/software.php
- Within your FLTK installation, navigate to: \\fltk-1.3.2\ide\VisualC2010 and build the fltk.sln
- From your TinScript installation, navigate to \\TinScript1.0-master\TinFLTKDemo
- Open the TinFLTKDemo.sln, you may have to "Update VC++ Projects" (right-click the solution) for 2012
- Edit the properties for TinFLTKDemo and make the following changes:
- Configuration Properties -> C/C++ -> General:
Edit the Additional Include Directories, to include a relative path to fltk-1.3.2 - Configuration Properites -> Linker -> General:
Edit the Additional Library Directories, to include a relative path to fltk-1.3.2\lib
- Configuration Properties -> C/C++ -> General:
- F5 to run the demo!
- If instead you choose to run the TinFLTKDemo.exe directly, out of \\TinFLTKDemo\Debug (or Release), then copy the files "TinScriptDemo.ts" and "asteroidsd.ts" from \\TinFLTKDemo into the executable directory.
- You'll see two windows - a command shell, and a blank window titled "TinScript Demo"
- Focus the command shell, and execute the following (case sensitive):
- Exec("asteroids.ts");
- StartAsteroids();
- A ship will appear (programmer art!) and in about 5 seconds, the "asteroids". Click on the TinScript Demo window to focus input.
- The controls are:
- 'j' to rotate counter clockwise
- 'l' to rotate clockwise
- 'i' to thrust
- 'space' to fire
- You're invulnerable for about 2 seconds if you get hit, but once you are hit 5x times, the game will be over.
- Re-type StartAsteroids(); at any time, to restart the game.
- Have fun!
(Below this point is an overview of the implementation... relatively detailed and verbose.)
TinFLTKDemo.cpp:
- TinScript is integrated into the executable in three places:
- TinScript::CreateContext() is called from within main() to initialize the language.
- MainUpdateLoop() retrieves any commands typed into the shell, and calls TinScript::ExecCommand() to execute.
- A pseudo simulation time with GetTickCount() is passed to TinScript::UpdateContext().
- TinScript::DestroyContext() is called at the end of main(), for cleanup
- Rendering wrappers:
- DrawLine(), DrawCircle(), DrawText()
- CancelDrawRequests()
All draw requests are tagged with the object ID, for ease of use, so for example, the 4x line requests used to draw the ship can be replaced in a single frame, with one function call.
- Time related functions:
- SimPause(), SimUnpause(), GetSimTime()
Note: Any of these functions, e.g. SimPause(), can be called during the game by typing them directly into the console.
- SimPause(), SimUnpause(), GetSimTime()
On the script side, this implements a basic framework to create and manage objects at a generic level.
- The namespace 'DefaultGame::' can be thought of as the "manager" of objects.
- DefaultGame::OnCreate()
- Starts a schedule to cause DefaultGame::OnUpdate() to be called the first time
- Creates a CObjectGroup to contain all objects created, so they can all be updated
- DefaultGame::OnDestroy()
- Since all objects in the game are owned by the CObjectGroup, deleting that group cleans up the entire game.
- DefaultGame::OnUpdate()
- Calls OnUpdate() on every object in the group
- Schedules a call to itself to update the following frame (works the same as Fl::repeate_timeout())
- NotifyEvent()
- Is called from code, through TinScript::ExecF()
- Receives events and passes them along to whatever "game manager object" was created.
- DefaultGame::OnCreate()
- SceneObject is a base implementation for an object that has position, a radius (for collision), and some need to be updated (at least to be drawn).
- SceneObject::OnCreate()
- Declares the object members position and radius.
- SceneObject::OnDestroy()
- Cancels all draw requests to ensure there's no trace of the object on screen.
- SceneObject::OnUpdate()
- Draws a circle by default, to represent the object.
- CreateSceneObject()
- A global function wrapper, to instantiate, initialize, and add an object to the current game.
- SceneObject::OnCreate()
Using this generic object framework, the individual objects are given depth.
- Asteroid:: is used to implement the "big circles" that break apart when you shoot them (stating the obvious, but...)
- Asteroid::OnCreate()
- Links to SceneObject::, so we can use their default implementations, as needed.
- Adds a velocity member, initialized to zero so we don't drift.
- Adds the asteroid object to a set, so we can loop through all asteroids and check for bullet collisions.
- Asteroid::OnUpdate()
- Calls a helper function UpdateScreenPosition(), which applies the velocity to the position, and if need be, wraps the position around the edges of the screen.
- Renders itself as a circle.
- Asteroid::OnCollision()
- Calculates headings for the two smaller pieces to be traveling
- Determines what size pieces the asteroid should split into (if it is big enough)
- Creates the two fragments at its position, and applies an increased velocity using the two headings.
- Finally, having been replaced by fragments, destroys itself.
- SpawnAsteroid()
- Is a helper function to choose a random location *not* in the center of the screen
- Determines a random trajectory.
- Spawns an instance of an asteroid at the above location, and applies the initial velocity.
- Asteroid::OnCreate()
- Ship:: implements the object that receives input to rotate, thrust, and fire bullets
- Ship::OnCreate()
- Also linked to the default SceneObject namespace.
- Declares a member for velocity, and one for rotation - to allow us to set the ship's direction.
- Members related to how many lives we have are declared.
- On the initial creation, we are invulnerable.
- Ship::OnUpdate()
- Calls the helper UpdateScreenPosition() to apply velocity to the position, and if needed, wrap the position around the screen edges.
- Drawing of the ship is four lines - almost an isosceles triangle with the apex rotated to indicate the ship's direction.
- Ship::ApplyThrust()
- Applies a fixed amount of acceleration to the velocity, in the direction the ship is rotated.
- Ship::OnFire()
- When the 'fire' button is pressed, this method is called.
- Ensure we're allowed to fire (alive, not more than X bullets at once, and not within the firing cooldown).
- Uses the helper SpawnBullet() to create a bullet starting at the tip of the ship's nose, and heading in the ships direction.
- Sets a time a short distance into the future, at which time we'll be allowed to fire again.
- Ship::OnCollision()
- Checks to see that we're not invulnerable, and decrements the number of lives we have left.
- If we've run out of lives, pauses the game, awaiting a call to StartAsteroids() to restart.
- If not, then we set invulnerable to true, and use a schedule() call to reset invulnerable back to false in a second or two.
- Ship::OnCreate()
- Bullet:: objects are fired from the ship, and collide with asteroids to break them apart.
- Bullet::OnCreate()
- Also linked to the base namespace SceneObject.
- Bullets have both velocity, and and expiration time (in case they don't hit anything).
- Bullet::OnUpdate()
- Uses the helper function UpdateScreenPosition() as do all other moving objects in the game.
- Checks to see if their time has expired, and self destructs.
- Bullet::OnCreate()
- AsteroidsGame:: is the "game object manager" for this particular game.
- AstroidsGame::OnCreate()
- Links to its base namespace of DefaultGame
- We use two sets (CObjectSet, unlike CObjectGroup, does not imply ownership), one for tracking the asteroids, and one for tracking bullets - just to make collision detection a bit easier.
- We also create a "delete set", so as objects are collided with, and deleted, we can delete them all at once at the end of the update loop.
- AsteroidsGame::OnUpdate()
- Uses a while loop to look each bullet currently active on the screen
- A "collision" is simply if the bullet is close enough to an asteroid, to be within the asteroid's radius.
- If we detect a collision, we notify the asteroid by calling its OnCollision() method, and we add the bullet to the set to be deleted.
- After we check the bullets, we loop through all asteroids, and see if any of them collide with the ship.
- If we detect a collision, we notify both the ship and the asteroid through their OnCollision() methods.
- Finally, we delete anything added to the delete set.
- AsteroidsGame::OnKeyPress()
- This is called by NotifyEvent(), and handles the input received from FLTK.
- In this case, rotation, thrust, and firing are all implemented.
- StartAsteroids()
- First we destroy the current "game object manager" to ensure a clean slate
- Then we create the object manager that is our specific AsteroidsGame.
- Third, we create the ship.
- Finally, we use a schedule to give the player 5x seconds before spawning all the asteroids.
- AstroidsGame::OnCreate()