Tuesday, July 17, 2018

Thoughts on Spacejacked and Game Design

Spacejacked for PS Vita is finally out! It feels as though a huge weight has been lifted off my shoulders. Time for a retrospective on what could have been improved.

One of the things I would have liked to see in Spacejacked (and the rest of the Rotten Mage games) is more depth. In other words, I want some higher order effects in the game. By higher order effects, I'm talking about chain reactions, i.e. systems interacting with each other, resulting in more complex outcomes.

Now, Spacejacked does have some of these higher order effects. Some examples I can think of are:
  • Using a Stasis Turret to slow down enemies so that they can get damaged for longer by the other turrets.
  • When a Mimic dismantles a turret, the METAL from that turret is regained, which allows you to build another turret in a pinch without having to spend time finding one to dismantle.

It's interactions like these that make gameplay interesting for me. I've been playing some Mario + Rabbids: Kingdom Battle lately, and I enjoyed how the various mechanics worked together to create unexpected scenarios. Here are a few mechanics in the game:
  • Some weapons have special effects, like hitting the target so hard that they bounce around the arena.
  • Some characters (like Mario) have a special ability to shoot out-of-turn when an enemy crosses their line of sight.

Here's a possible outcome with those rules: during the enemies' turn, one of them could walk into Mario's line of sight, causing Mario to fire at it, bouncing it off the walls and into other characters on the map, in turn bouncing these other characters and changing the positioning of both teams. With this combination of mechanics, it's possible to see a complete reversal in fortunes and turn the tide of the battle.

I've always been fascinated by simple rules combining to give rise to complex systems, just like in Conway's Game of Life and other cellular automata. It's an elegant kind of depth. For Spacejacked, I can imagine a chain reaction where an enemy does an area attack that also hurts its allies, among whom are some enemies that explode upon death, triggering even more damage and chaos. Or maybe in Rotten Mage's fighter jet game prototype, we could make it such that destroying a massive plane causes debris to fly everywhere and knock other planes off their course.

In essence, we should be thinking more about how our mechanics interact with each other, because these can result in situations that get players excited, that get them talking. I want to experience more serendipitous moments out of our games, like being in a tight spot fending off a bunch of aliens while having no METAL to spare, then having the Mimic dismantle a turret elsewhere (which is normally a bad thing), providing me with resources to build just one more turret so that I have just enough firepower to win my battle.

Sunday, March 20, 2016

Native Steam Integration in Haxe, FRESteamWorks-Style

In a previous post, I mentioned that we used FRESteamWorks for Steam integration in our Adobe AIR targets of Spacejacked. FRESteamWorks is an AIR native extension (ANE), so it doesn't work for C++ targets. Possible solutions include steamwrap, but I wanted to keep the same Steam integration code between the AIR and native builds. My solution was to write a Haxe port of the ActionScript interface to the C++ code used in FRESteamWorks.

TL;DR: This post is mainly about how I integrated the C++ code of FRESteamWorks with Haxe. Interesting bits include getting Haxe and C++ to interact without NDLLs, and working with Haxe ByteArrays in C++.

Directory structure

The general layout of the Spacejacked project looks like this:
I placed Valve's official SteamWorks C++ headers in thirdparty/steamsdk/include/, and its shared libraries in thirdparty/steamsdk/lib/. I only needed the CSteam C++ code from FRESteamWorks (the code that wraps the Steam API), which I placed under thirdparty/ventero/src/CSteam/. I wrote the C++ side of the Haxe-C++ integration in thirdparty/ventero/src/HxSteamWorks/.

The Haxe interface was put under src/linux_steam/com/amanitadesign/steam/. The most interesting part of this Haxe interface is in FRESteamWorks.hx; the rest are just straightforward translations from ActionScript to Haxe.

To package Steam's shared libraries together with the application, I added the following lines to application.xml (I'm using OpenFL):

Haxe side of the interface

FRESteamWorks.hx provides the Haxe glue to FRESteamWorks. I added the following metadata to set C++ compilation flags via hxcpp:
If I'm not mistaken, the paths above (e.g. line 20) are relative to where Build.xml is located in the project output.

I kept the original FRESteamWorks' use of Flash's event dispatching system to provide status information about Steam API calls. I wanted to avoid keeping track of Haxe objects on the C++ side, however. Since there'll only be one instance of FRESteamWorks, I keep track of that instance in the Haxe code, and provide a static function for calling via C++:

Most of the methods in this class are simply calls to functions defined in HxSteamWorks.cpp via untyped __cpp__(...). For example:
More complicated ones look like this:
Super hacky, I know =P

Later on, I realized that accessing Haxe objects in C++ wasn't as hard as I had imagined.
More on that below.

C++ side of the interface

HxSteamWorks.cpp is the C++ side of the glue. It's basically adapted from the original FRESteamWorks.cpp.

OpenFL events are fired by calling the Haxe dispatch static method mentioned above:
Haxe package names translate to C++ namespaces. Note that, as mentioned by wighawag, hxcpp appends _obj to all generated class names.

Accessing the data stored in a ByteArray:
I'm not sure if this is the proper way of doing things, but Haxe objects can be passed around in C++ through hxcpp's hx::ObjectPtr<T>. This pointer-like class allows you to access the methods of the original Haxe instance of T.

The hxcpp implementation of ByteArray allows you to get the underlying bytes via byteArray->b->GetBase().

Things to improve

  • Much of the inline C++ code in FRESteamWorks.hx could be done in HxSteamWorks.cpp instead;
  • The untyped __cpp__(...) one-liners should be replaced by calls to functions with @:native declarations to give cleaner code;
  • I should have rearranged the thirdparty/steamsdk directory so that I can just drag and drop the SDK in, instead of splitting up the files the way I did;
  • I haven't adapted this for Windows, but the logic should be similar (only the compilation options should change).

Notes

All the stuff above was done with hxcpp 3.2.102 and Haxe 3.2.1. The gist I used in this post can be found at https://gist.github.com/jonongjs/57666f3900839cf734d4. I only ported the parts of the FRESteamWorks API that I needed, but I hope that what's described here can be of use to anyone who's trying to interface Haxe with C++.

Special thanks to the developers of FRESteamWorks; it's a great project.

Friday, March 11, 2016

Problems using Adobe AIR's adt over ssh

I was trying to create a build machine for Spacejacked over the last few days. The setup is a Mac Mini running Virtualbox and virtual machines for OS X Yosemite, Windows 7, and Arch Linux. We have a script on the Mac Mini that ssh's into each VM and triggers the build there.

We use Adobe AIR to deploy the game on Windows and OS X. We do this by packaging the game with AIR's captive runtime so that users don't need to install AIR on their systems. One requirement of this process is that captive runtime bundles must be created on their target OS via adt. (Hence the virtual machines.)

I noticed that our automated build process would get stuck when running adt via ssh on the OS X VM. It turns out that the Java processes that adt spawns were getting stuck, as mentioned in a question on StackOverflow. The solution to the problem was simple, just as in the accepted answer on StackOverflow: I simply configured the OS X VM to automatically log in with Terminal open, et voilĂ , everything was resolved.

Sunday, February 28, 2016

Spacejacked is Done!

Woot! My teammates and I at Rotten Mage have finally launched our action shooter and tower defense hybrid game called Spacejacked! It's currently available on Steam for Windows, OS X and Linux.

In Spacejacked, you play as space engineer Dave Paprovsky, who has to defend his ship's multiple rooms all at the same time against an alien attack. Expect fast-paced action with bits of tower defense thrown into the mix.

Some technical stuff


We built Spacejacked with Haxe, using the HaxeFlixel 2D game engine, deploying it to Windows and OS X via compilation to Flash then wrapping with Adobe AIR. (Actually, we had an issue with the OS X build on launch day, and had to deploy a native build instead.) The Linux build was a native build using hxcpp.

Steam integration was done through the FRESteamWorks Steam API wrapper for AIR. I hacked out a Haxe wrapper around the C++ CSteam class of FRESteamWorks for the native build. Steam integration was surprisingly easy to do with the help of FRESteamWorks. The people who worked on it have my gratitude.

We did a lot of tweening in Spacejacked, most of the time using either TweenX or Flixel's own FlxTween. I love TweenX's chainable syntax and feature set, which made tweening extremely simple. Something we should have done, but didn't, would be to make all the tweens run at the same rate (FlxTween uses a constant time-step per frame, while TweenX uses real-world time). In fact, we probably should have used only one tweening library, but oh well.

Problems

We started using Haxe in the beginning of 2014. Flixel was at 3.2.2, and its dependencies were OpenFL 1.4.0 and Lime 0.9.7. These library versions are sorely outdated today, but because upgrading these libraries sometimes broke our builds, we decided to stick to the versions above.

Using outdated libraries has caused us some problems, though. For instance, I had to recompile the SDL dependency of Lime to include udev support just to enable in-game gamepad connect/disconnect detection. This, in turn, led to recompiling the Lime NDLLs.

Another problem we encountered was in the native builds, where we were running out of OpenAL audio sources because they weren't getting released fast enough. We wrote some hacks in Lime to fix this.

We also had to write our own gamepad handler code because Flixel back then didn't have gamepad support in Flash. Thankfully, jdpalmer's ActionScript 3 gamepad library took care of that for us.

Final thoughts

Overall, I enjoyed developing Spacejacked in Haxe, though using some of the libraries in their early stages caused a bit of grief. Things are much better today, and I look forward to developing another project in Haxe. I like that Haxe has a powerful macro system, and would love to harness it more.

I learnt a lot through this project, and did a lot of manual work that could have been avoided just by writing a little more Haxe. Here's hoping that I don't commit the same mistakes in the next project!

Lastly, I would like to express my appreciation of the Haxe and HaxeFlixel communities, as well as the developers of all the libraries we rely on. Many thanks!

Sunday, May 3, 2015

Getting Euterpea's MIDI output to work on OS X

I've been getting back into learning Haskell again. One of the textbooks I'm using is The Haskell School of Music, a Haskell textbook with a focus on computer music. The book uses Euterpea, a computer music library developed in Haskell.

Euterpea has been well-tested on Windows and Linux, but I've been having a hard time getting its play function to work on OS X. One way around the problem is to use the writeMidi function to output a MIDI file and then play it back, but I would rather have the ability to listen to the output immediately.

It turns out that the play function has been generating MIDI events correctly, and all I have to do is to redirect the events to a (software) synthesizer. I used FluidSynth for this, which can be easily installed via Homebrew or MacPorts. Unfortunately, on OS X, running FluidSynth alone was insufficient to get any audio output. I had to enable the IAC Driver via OS X's Audio Midi Setup program, and redirect its output to FluidSynth's virtual port via a patch program (I used MIDI Patchbay for this).

And that's all that's needed. Off to make some noise!

Friday, December 19, 2014

Using OpenFL Templates to Modify Functionality in Haxe Libraries

I recently had to modify some functionality in the FlxAnimationController class in HaxeFlixel 3.3.6 for our game Spacejacked.

Since the change was rather minor, and I didn't want to clone the FlxAnimationController and FlxSprite (which uses FlxAnimationController) classes, I decided to use the templating capabilities of the OpenFL project XML format.

FlxAnimationController is located under the flixel/animation directory of the Flixel project source folder. To replace the functionality, I copied FlxAnimationController.hx to templates/haxe/flixel/animation/FlxAnimationController.hx in my own project folder and made my own changes there.

To get OpenFL to use this file instead of Flixel's, I simply added this template tag to my application.xml:
<template path="templates" />


Wednesday, October 15, 2014

Using Adobe AIR Tools to Turn an SWF into a Native Executable

Here's a post to remind myself how to do this.

We have a strange bug in the Windows build of Spacejacked: the game hangs for long periods (usually more than a minute) at random intervals. I suspect it has to do with the audio code in the game, but haven't figured out a way to track it down.

As a temporary workaround, we're packaging the Flash version of the game into a native Windows executable via Adobe AIR.

Steps:

  1. Adobe requires a code-signing certificate for digitally signing your executables. Either follow the instructions on http://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html and get one from a certification authority, or create your own (if you're just making a test build) like so:
    adt -certificate -cn ADigitalID 1024-RSA SigningCert.p12 39#wnetx3tl
    This creates a .p12 cert named "SigningCert.p12" with common name "ADigitalID" with password "39#wnetx3tl". The full command specification can be found at http://help.adobe.com/en_US/air/build/WS901d38e593cd1bac1e63e3d128fc240122-7ffc.html.
  2. Create an application descriptor XML file, as mentioned in http://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff1.html.
    For example, here's our spacejacked.xml:
    <?xml version="1.0" encoding="utf-8" ?>
    <application xmlns="http://ns.adobe.com/air/application/3.2">
        <id>com.rottenmage.spacejacked</id>
        <versionNumber>0.0.1</versionNumber>
        <filename>SPACEJACKED</filename>
        <name>Spacejacked</name>
         <description>
            <text xml:lang="en">SPACEJACKED</text>
        </description>
        <copyright>Copyright (c) 2014 Rotten Mage Pte Ltd</copyright>
        <initialWindow>
            <title>SPACEJACKED</title>
            <content>
                SPACEJACKED.swf
            </content>
            <visible>true</visible>
            <resizable>false</resizable>
            <maximizable>false</maximizable>
            <renderMode>direct</renderMode>
        </initialWindow>
        <icon>
            <image16x16>icons/smallIcon.png</image16x16>
            <image32x32>icons/mediumIcon.png</image32x32>
            <image48x48>icons/bigIcon.png</image48x48>
            <image128x128>icons/biggerIcon.png</image128x128>
        </icon>
        <supportedProfiles>desktop extendedDesktop</supportedProfiles>
    </application>
    
    where SPACEJACKED.swf is the name of the SWF file we are wrapping. Note that the namespace specified in xmlns in the <application> tag should be set to an appropriate version that corresponds to the SWF version you are targeting; details at http://help.adobe.com/en_US/air/extensions/WSf268776665d7970d-2e74ffb4130044f3619-8000.html.
  3. Create a directory structure for packaging (details at http://help.adobe.com/en_US/air/build/WS901d38e593cd1bac1e63e3d128cdca935b-8000.html). For a quick-and-dirty solution, we use:
    /SPACEJACKED
        SPACEJACKED.swf
        spacejacked.xml 
    with the certificate just outside of this structure.
  4. Finally, from within the root of the packaging directory, run in the command prompt (for a Windows build):
    adt -package -storetype pkcs12 -keystore ..\SigningCert.p12 -target bundle ..\SPACEJACKED_game spacejacked.xml SPACEJACKED.swf
    which will prompt for the certificate's password (created in step 1). This creates the executable in a directory called SPACEJACKED_game one level above the packaging directory.
That should be it. The instructions are similar for other platforms. Note that (as of writing) native bundles/builds can only be created on the same platform, i.e. Windows bundles can only be created on Windows, Mac .app bundles on Mac OS X, Linux .deb/.rpm on the appropriate Linux distros.