Kha Shmup Tutorial Part 1
Continuing from the previous tutorial we’ve made sure we are running a recent version of Haxe (I am running 3.2.1), a recent version of NodeJS (5.0.0 for me), as well as the git version control system.
Game Features
Throughout this tutorial we will be building a small space shooter. I want to build a small game, but to make sure its not trivial. This game will have the following features:
- A ship the player can move around.
- Enemies who randomly spawn.
- The ability to shoot lasers to destroy enemies.
- A score representing the amount of enemies destroyed, represented with text.
- The “ability” to lose if the player collides with an enemy.
- A few game states, such as main menu, playing, and game over.
- Music and sounds.
- Object pooling on the bullets the player can shoot, as well as the enemy.
I think this will be enough to showcase several of Kha’s features and allow for further expansion. This tutorial will be built in a somewhat naive fashion - we are mostly trying to provide a starting point for making a game in Kha. Refactoring into a solid code-base is left as an exercise to the reader :P
Getting Started
Kha is typically handled as a git submodule to another project, which is how we will be using it. Check out a basic empty project by typing the following into your console:
git clone --recursive https://github.com/KTXSoftware/Empty.git
After cloning if you want to rename the directory feel free to do so.
You’ll see a few files and directories in here:
- Kha - this contains the sources for the Kha framework as well as the tools it uses. You shouldn’t have to mess with this.
- Sources - is where all of the source files for our game will live.
- khafile.js - this is the build file for the entire project.
khafile.js
Let’s open up khafile.js and get rid of everything and replace it with the following:
let project = new Project('KhaSmup');
project.addSources('Sources');
resolve(project);
This defines a project named ‘KhaShmup’ and makes sure to tell the project to use the files found in the Sources directory. Let’s check out that directory now.
In Sources we find two files - Empty.hx, and Main.hx. Main is the entry point for the program and Empty is the “game.” We can get rid of this as we’ll be using a class for our own game.
Main.hx
Start by opening up Main.hx and enter the following:
package;
import kha.System;
class Main {
public static function main() {
System.init({ title: "KhaShmup", width: 800, height: 600 }, init);
}
private static function init() {
var game = new KhaShmup();
System.notifyOnRender(game.render);
}
}
This class has an entry point main which initializes Kha’s system, sets the window name as “KhaShmup”, the width of the window to 800, the height to 600, and provides a callback of the function init once the System is ready to go.
The callback is where we create an instance of our game and we tell the system that when it renders we want to call our game’s render method.
KhaShmup.hx
Create a file in the Sources directory named KhaShmup.hx and add the following lines to it:
package;
import kha.Color;
import kha.Framebuffer;
import kha.Image;
import kha.Scaler;
import kha.System;
class KhaShmup {
private static var bgColor = Color.fromValue(0x26004d);
public static inline var screenWidth = 800;
public static inline var screenHeight = 600;
private var backbuffer: Image;
public function new() {
// create a buffer to draw to
backbuffer = Image.createRenderTarget(screenWidth, screenHeight);
}
public function render(framebuffer: Framebuffer): Void {
var g = backbuffer.g2;
// clear our backbuffer using graphics2
g.begin(bgColor);
g.end();
// draw our backbuffer onto the active framebuffer
framebuffer.g2.begin();
Scaler.scale(backbuffer, framebuffer, System.screenRotation);
framebuffer.g2.end();
}
}
This is our main game class. We start by instantiating a “back buffer” - this allows us to use double buffering so that we draw to a “back” buffer - one that is not rendered to the screen yet, and when it becomes time to do so we just swap the currently rendered buffer with our back buffer.
In the render method we recieve a framebuffer - the currently rendered buffer. We then get a member “g2” from our backbuffer. This is an instance of the graphics2 api in Kha. This is pretty much the api you will be using for 2d rendering, html5 canvas, etc. Kha versions its apis into numbers, so there is a g4 representing 3d graphics, multiple audio apis, etc.
With this instance we clear it to our preset background color - which is kind of a dark purple.
We then draw our purple backbuffer onto the frame buffer. The Scaler.scale method basically scales the “source” image - our backbuffer, to the size of our “destination” image - the framebuffer, and the renders the source onto the destination. The begin and end methods on the graphics instances are done so that Kha can batch draw operations where possible, providing pretty great rendering performance.
Running the application
There are a number of different ways you can run a Kha application but the way that I typically do is to navigate to the root of the project directory and to run the following:
node Kha/make html5
node Kha/make --server
This will compile the html5 target and then serve it on localhost:8080. If you go there you should see a big purple screen.
If you don’t you can compare your code to the part-1 branch of my repo:
https://github.com/jamiltron/KhaShmup/tree/part-1
An interesting thing about the build system is that Kha will generate projects for your targeted platform. For example if I wanted an XCode project for OSX, running
node Kha/make osx
would build an XCode project in the build directory. I then could open up in XCode allowing me to use all the tools associated with that platform. That’s a pretty cool feature as sometimes tracking down issues in the output of compiled Haxe code from the terminal can be pretty frustrating. However if I want to quickly compile and run natively for osx, I could just run
node Kha/make osx --run
Again, if you have any issues, compare your code to my repo: https://github.com/jamiltron/KhaShmup/tree/part-1
I know it seems like we haven’t gotten that far, but getting all of the required software and spinning up a project will be a good starting point for rendering an image on screen, which we will get to in part 2.
Thank you for reading!