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.)

Build:

  • 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

Run the demo:

  • 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.)

Demo Implementation

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
And at the end of the file, we registered functions that we need accessible from script:
  • 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.
TinScriptDemo.ts:

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.
  • 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.
asteroids.ts:

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.
  • 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.
  • 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.
  • 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.
⚠️ **GitHub.com Fallback** ⚠️