I realise it's been a very long time in between updates, but I'm writing to tell you that Project GoldScript is not dead. Far from it!
I had an idea for a completely different (and basically totally unrelated) web application venture around the end of 2014 and had a very busy 2015 where I spent most of my (very little) free time working on that project. With that app gearing up for an alpha launch hopefully in the fourth quarter of this year, I came back to devoting more time to the engine and its first game in 2016. I've continued that same work this year when I have had the time.
To be truthful, taking breaks from the codebase has been incredibly helpful. I was able to look at the decisions I made and the structure I implemented with a fresh pair of eyes and from a more experienced vantage point. What is really encouraging though is that, even after a good amount of time away from it, my belief and passion for the project is still as strong as ever.
After returning to the engine, I realised that code wise, everything was mostly fine, but it needed a hefty refactor to really make things robust enough to keep pushing forward. So I buckled down and began polishing the code. I mean really polishing. As a result my change log in between the last updates and now has become gargantuan. This work I've put into making the code cleaner and more structured has made it more efficient to add new features to the engine, so it's definitely been worth it.
I'd split the changes into two categories, features and code. Features are most likely what non-programmers would like to know about so I'll go through these first. Keep in mind these features are all related to combat which is the first of the three main modes I plan on developing.
The ground work for the unit AI manager and behaviour engine has been completed. This means I have a basic framework which enables me to build different behaviour patterns. Units can have preset behaviours and these can be altered at any time based on situational changes.
So, a unit might have the set behaviour of "Terminator" (destroy the weakest reachable foe) but if they have team mates who are below a certain HP level and they have healing items or abilities, they could alter their behaviour for that turn to "Saviour" (heal injured friends). This is just a simple example but illustrates the flexibility I now have to build unit behaviours which have ranges of intelligence.
Units on the map can now be split into different teams. There's currently no limit on the number of teams. Technically there could be a battle between 4 teams or whatever, which has pretty cool implications for encounters that differ from the usual formula. It also opens the door to some really interesting possibilities in terms of network play or PvP.
I've confirmed the validity of my combat level/map building process. This resulted in me removing texture mapping from the engine entirely as this feature is far better handled by third party tools. It simply makes no sense to spend time reinventing the wheel and my time can be better spent on other things.
Here's a short walk through of how the flow works:
Here's an example of a raw map. This has been built within the engine's toolset. It's just 3D blocks at the moment with basic materials. Shadows are cast in-engine:
Here's what the level looks like after it's been exported out of the engine, loaded into Blender, textured, exported out of blender as a single mesh, then re imported back into the engine (yeah, the shadows are gone, this is a bug at the moment, something to do with materials):
The new flow involves me creating what are called "base tiles" in the engine where I can apply meta data to them (depth, terrain, traps, treasure etc.,) and modify their vertices. I then export these tiles as a file type which Blender can load. I have no idea about 3D graphic texturing, but with a bit of help and watching some tutorials, I was able to get something done as a quick proof of concept.
To test this I've migrated the Dark Orbonne Monastery map, which was created in
v0.0.4 of the engine (I'm on
v0.0.13now). This took a bit of work as the data for what defines a level has changed a bit since I made it. To make this work, I wrote a simple map migrator tool to deprecate/propagate map properties and then reloaded it in the current version. It worked like a charm!
Tiles can now have a depth property which will cause units to "sink" a little bit into them. Quite useful for terrain types like water or lava.
Cameras are hard. This is something I've learnt continually throughout this project. I have to give a massive thanks to my good friend Manthrax who's helped me out a lot here to get my camera code spot on.
So! The camera now has a kind of in built AI. What this means is that the camera will auto position and rotate itself to ensure that the current combat unit is not occluded from the player. This was a lot harder than I thought it would be, but the result is working pretty well now, perhaps a little too well!
Right now each tile has a camera quadrant computed for it on map load. A single quadrant means that the camera rotates automatically very often, it can be slightly disorienting, so perhaps a list of acceptable quadrants would be better. Then if the camera is already in one of these, it can stay put.
In any case this was immediately essential as soon as I implemented AI and the first bits of combat UI.
Yep, that's right, I've made the first steps towards a combat UI with the implementation of a radial menu for combat actions. I'm pretty happy with this. It's by no means designed to completion, but it's functioning well.
However, it raises some questions about screen resolutions and adaptive user interfaces. As a web developer, on the front end everything I do is almost always 100% responsive. Good web applications have to deliver a consistent user experience across a hugely variable set of devices, each with their own screen sizes and resolutions. This is of course true for games as well, but it's not something I ever really thought about until I tried to make one!
Right now I'd like to be able to deliver this kind of user experience in the engine as well, so I'll need to come up with ways to do that. I'm not sure right now if the typical web solution of CSS media queries will suffice (but perhaps they will). The radial menu's ring size, and its icons will need to adapt to the player's screen resolution so that they remain consistent and don't hamper the player experience.
When units cast effects on other units, the player needs to know what the result of that cast was. In the engine, this is called combat feedback.
There are currently four types of feedback:
- Info - for informing the player of general things like if a hit roll has failed
- Damage - for informing the player of the damage an effect cast has caused
- Buff - for informing the player of a buff that was applied
- Debuff - for informing the player of a debuff that was applied
I've only got animations and styles for the first two right now, but it's not a lot of work to add more.
Sound has been implemented, anything in the engine can now trigger a sound to play, combat levels can have a BGM set which will play when the level is loaded. I even made some sound effects and got the track I wrote for the Dark Orbonne Monastery tech demo loaded in as well.
Previously I had only completed the Move and Wait actions, but now Cast has also been done. Cast is the action used for basically everything a unit can do to another unit in their turn, so attacking, casting spells or using items - these are all casts. The Castaction relies heavily on my promise based animation classes which I talk a bit more about in the code changes section.
In previous videos you might have seen some awkward jumping animations, back then these were done in a kind of hacky way. Now I've implemented proper animations for jumping, hopping and falling with the shadows perfectly matching the surfaces the unit moves across. Once again a big thanks go to Manthrax for the help in integrating linear interpolation to get the movement working smoother!
You might notice from the screens that I completely redesigned the toolset interface. The new design looks more like a traditional desktop app with a file menu and toolbar kind of like other editing programs, it also gives me a lot more screen real estate to see what's going on. This is assisted by draggable dialog boxes which come in handy especially when dealing with an isometric world.
The engine now supports retina displays. This was a fairly easy thing to implement as Three supports it as a property of its
WebGLRenderer class, but is pretty cool nonetheless.
ES6 / ES2015
The entire app has been completely refactored to use ES6 / ES2015 language standards. This is made possible through awesome tools such as Webpack and Babel.
This means that the code style has been updated to modern standards and all linting is now handled through ESLint.
Oh and everything is module based now as well!
React + Create React App
All DOM based 2D UI is now done via React. This has been a massive improvement over what I was using previously (Handlebars). There are tonnes of awesome components to leverage and since I personally love React, it's been great to work with it on this project.
I'm also using
create-react-app as my boilerplate to handle all my Webpack config.
Decoupling game code from engine code
The GoldScript Engine is being designed in a modular fashion. Think of it like an OS which you can install software on. In this case the engine is the OS and a game is the software.
My game package is a separate repository and is pulled into the engine via
So, how are these separated out? Basically the game defines things like combat rules, combat effects, unit statistics and so forth. The engine doesn't care about any of this. It simply runs the formulas it receives from the game to figure out how an effect cast might turn out. The game package also contains all the static assets (art, level data, sounds etc.,) that are needed for the engine to run the game.
This has allowed me to cleanly separate different kinds of logic and basically means that making different games for the engine should be easy. Simply create a new game package, import that into the engine and, as long as the game has all the required methods, it should run.
The development environment for the engine is now handled through my NWJS custom command line interface package Northwest.
This tool allows me to run the latest version of NWJS and gives me commands to publish the engine/game for Windows, Mac and Linux.
I wrote my own Finite State Machine which basically runs the entire engine now. Every machine is also a container for more machines, it's basically a recursive pattern.
This has given me absolute control over what user inputs do between every possible state the engine can be in.
All animations in the game are now handled through Promises. This has given me an awesome, readable API to work with when sequencing anmiations.
My animation class also handles frame based animations, which is perfect for sprite sheets.
Three r69 to r86!
This was massive. For anyone that works with three you'd know that this is a pretty experimental framework. Breaking changes happen all the time.
I jumped almost 20 versions ahead. I know, madness right?
It wasn't TOO bad... well it was pretty bad. But I got through it and learnt a lot in the process.
Overall it probably took me about a month of part time coding to get everything ported.
It was well worth it though. The latest version gives me access to all kinds of cool new features and improvements, so I'm happy I bit the bullet and got it done.
I hope you enjoyed this update which was like... 3 years in the making? Sigh.
I will try to blog more often... but the problem is it takes time, which is my most precious resource right now. This blog alone took me a pretty hefty amount of time to write and put together. I do realise it's important though so I'll try to make time for it.
It's been a crazy ride so far, but I'm still having heaps of fun, so I'm not giving up yet!
Next up will be sorting out my adaptive UI concerns, I don't want to move too far forward without a solid solution for this problem.
Until next time!