Step 1. Create a separate module for the graphical scene. Let's name this module gui
.
using sys { Array } // Import `Array` class from `sys` module
using array; // Import various extensions to the array class
using sdl { Window } // Import `Window` class from the `sdl` module
//
// All scene items will implement the actor interface.
//
interface Actor {
// Called to redraw window.
// Should draw its appearance on the `dst` window.
paint(dst Window);
// Called on every frame, get milliseconds since last `tick` as a parameter.
// If returns false, its container deletes it.
tick(milliseconds int) bool;
}
// Group of actors
class Group {
+Array(Actor); // Inherits `Array` parameterized with `Actor`s
+Actor{ // Implements interface `Actor`
// Interface implementation (or base class overloads) always
// go in its own {}-scope, to avoid name conflicts between
// newly added methods and/or methods from different bases.
paint(dst Window) {
// `each` is the `Array` extension method. It executes its lambda for
// every existing element in the array.
each{ _.paint(dst) }
}
tick(milliseconds int) bool {
// `plow` is another Array extension method. See: array.ag.
// It executes lambda for each array element and removes element
// if lambda returns `false`
plow{ _.tick(milliseconds) };
// If group is nested in the another group, it automatically deletes
// if it gets empty.
size() != 0
}
}
}
Step 2. Create a main application module.
using sdl { Window, Sdl, Texture }
using utils { Random, forRange }
using gui { Actor, Group } // Import our newly created module
Step3. Entry point
Scene.init()?_.run();
This line of code must always be the last line of the source file. What it does:
- Creates an instance of the demo
Scene
class (not yet implemented). - Calls its
init
method, that should return the?Scene
pointer (optional temporary pointer to Scene):nothing
- if initialization is failed- or
this
if it's succeeded.
- Checks the result of initialization, and if it's not
nothing
, calls itsrun
method.
Step 4. Implement Scene
class Scene{
+Group; // Our `Scene` extends `gui_Group`
init() ?Scene { // So far its `init` and `run` methods are empty
this
}
run() {
}
}
Step 5. Initialize Sdl, window and run event loop
class Scene{
...
sdl = Sdl; // Scene will own an SDL library instance
w = Window; // ...And a Window instance
init() ?Scene {
sdl.initVideo() && // If sdl initialized successfully
w.createCentered( // and our window managed to create...
"Hello from Argentum!",
800, 600)
? this // ... then return `this`.
}
run() {
sdl.eventLoop `e { // Runs event loop till event `quit`.
// Lambda is called every 1/30 second.
paint(w); // Call `gui_Group.paint` method to draw on back buffer.
w.flip(); // Make back buffer appeared on screen.
tick(1000/30); // Call `gui_Group.tick` method to animate.
};
}
}
Instead of using 800 and 600 we could define and use named constants: const xWidth=800;
Now we can run our application and see an empty window:
Step 6. Load and uphold graphic resources:
class Scene{
...
flare = Texture; // Scene will own all textures
star = Texture;
background = Texture;
foreground = Texture;
init() ?Scene {
sdl.initVideo() && w.createCentered(...) &&
background.init(w, "back.jpg") && // Load all images
foreground.init(w, "fore.png") &&
flare.init(w, "round.png") &&
star.init(w, "star.png")
? this
}
run() {
sdl.eventLoop `e {
w.blt(background, 0, 0, 800, 600, 0, 0, 800, 600); // Draw back...
paint(w);
w.blt(foreground, 0, 0, 800, 600, 0, 0, 800, 600); // and foreground
...
};
}
}
Now our application gets more interesting:
Step 7. Add Flare actor
class Scene{
...
rnd = Random; // Create and store in our scene a random number generator
...
run() {
push(10, Flare.initAtBottom(this)); // In the beginning, add 10 Flares to the
// scene . Push is the Array extension method.
sdl.eventLoop `e {
...
rnd.get(0, (size() / 2) + 1) == 0 ? // Depending of the number of existing
append(Flare.initAtBottom(this)); // scene items, add more flares.
};
}
}
class Flare {
+Actor {
paint(dst Window) {
sprite? { // If sprite object exists
_.setColorMod(color); // ...made it of our color,
_.setAlphaMod(fade >> 8); // ..set our transparency
size = fade >> 8 >> 2; // ..and draw it with size
dst.blt(_, 0, 0, 64, 64, x >> 16 - size/2, y >> 16 - size/2, size, size);
}
}
tick(milliseconds int) bool {
x += dx; // Movement
y += dy;
dy += 1 << 13; // Gravity
fade += dFade;
dFade -= 140; // Fade-out speed
// Remove it from the scene if it's outside screen or had faded out.
y > -100 << 16 && y <= 600 << 16 && x > 0 && x < 800 << 16 && fade >= 0
}
}
initAtBottom(scene Scene) this { // Initializer method
r = scene.rnd; // Acquire scene's random generator
x := r.get(200 << 16, (800-200) << 16); // and use it for coordinates
dx := r.get(-x / 30, (800 << 16 - x) / 30); // and speed
dy := r.get(-18 << 16, -8 << 16);
sprite := &scene.flare; // Store association to the sprite that lives in the scene
color := makeBrightColor(r); // Call a global function
}
x = 0; // Flare fields
dx = 0;
fade = 0;
dFade = 4200;
y = 600 << 16;
dy = -10 << 16;
color = 0xff_ff_ff;
sprite = &Texture; // Unbounded &-pointer to the sdl_Texture.
}
// Standalone function.
// Make one of 8-bit-styled colors with the given `rnd` generator.
fn makeBrightColor(rnd Random) int {
r = (rnd.get(-1, 1) & 0xff) |
(rnd.get(-1, 1) & 0xff << 8) |
(rnd.get(-1, 1) & 0xff << 16);
r == 0 ? 0x0ffffff : r // B/c we have black background
}
Now our application shows multiple flying flares of different colors, opacities and sizes:
Step 8. Add sparkles (fill our scene with polymorphism)
Add a Sparkle
class, and make flares spawn sparkles:
class Sparkles {
scene = &Scene; // Non-owning reference to the `Scene`
x = 0;
y = 0;
r = 30; // Used for time-to-live, density and size
color = 0;
+Actor {
paint(dst Window) {
// If scene exists, use its random numbers.
// distributeInCircle - is a stand-alone function.
scene? distributeInCircle(_.rnd, r, r) x y {
dst.fillRect(this.x + x, this.y + y, 1, 1, color);
}
}
tick(milliseconds int) bool { (r -= 1) > 0 }
}
init(scene Scene, x int, y int, color int) this {
this.scene := &scene;
this.x := x;
this.y := y;
this.color := color;
}
}
// Make flares know their scene
class Flare {
scene = &Scene; // non-owning &-pointer
+Actor {
...
tick(milliseconds int) bool {
...
// If scene exists, maybe add a sparkle to it.
// ?`name allows to define own name instead of `_`
scene ?`sc sc.rnd.get(0, 2) == 0 ?
sc.append(Sparkles.init(sc, x >> 16, y >> 16, color));
...
}
}
initAtBottom(scene Scene) this {
this.scene := &scene; // Store a reference to our scene
...
}
}
// A stand-alone functions that:
// calls no more than `count` times its `callback` with coordinates
// randomly distributed in a circle of radius `r` using `rnd` renerator.
fn distributeInCircle(rnd Random, count int, r int, callback (int, int)) {
forRange(0, count) `i {
x = rnd.get(-r, r);
y = rnd.get(-r, r);
x*x + y*y < r*r ? callback(x, y);
}
}
Now our flares leave tails of fading sparkles:
Step 9. Make our flares explode with more flares:
class Flare {
+Actor {
tick(milliseconds int) bool {
...
// In the end of the flare life cycle depending on the generation
// maybe add 6..15 more flares with speeds distributed in circle.
fade == 0 && scene &&=s s.rnd.get(0, generation) == 0 ?
distributeInCircle(s.rnd, s.rnd.get(6, 15), 10 << 16) dx dy {
s.append(Flare.initAtFlare(this, dx, dy));
};
...
}
}
// Another named initialization method.
initAtFlare(parent Flare, dx int, dy int) this {
scene := parent.scene;
scene? sprite := &_.star; // Take a different sprite from the scene
x := parent.x;
y := parent.y;
color := parent.color;
generation := parent.generation + 1;
this.dx := dx;
this.dy := dy;
}
...
generation = 1;
}
This gives us secondary star-shaped flares:
The full source of demo.ag can be found in the Windows demo source file directory: "src/demo.ag".
This demo covers:
- Modules
- Tree-like structures.
- Interfaces.
- Initialization methods.
- Base classes/interfaces scope.
- &-references.
?=name
and&&=name
control flow constructions.- Stand-alone functions.
- Lambdas.
- SDL integration.