Wow, three months since my last post. That's bad.
Three months a go I was deep in to working on Perling and things were going really well! But...I think I must've burnt out though because by the end of July I had very little motivation and even less energy. To an extent I've always tried to force myself to make progress, but I broke. I broke big time! I went back to my parents for a few days with the hope of making lots of progress, but I did nothing at all and in all honestly I probably needed it!
So since July 11th I've actually not done much game development, that is until a couple of weeks ago. "So you've made lots of progress with Perling right???" no, barely any I am afraid, but I have been working on getting Ninjah ready for Windows, OSX and Linux.
Ninjah was originally created for XNA with the Xbox 360 in mind and while it was possible to distribute for PC, I wasn't entirely comfortable with the installation process or the way it handles media, specifically licensed music. Anyway, a group of people who were generous enough to give their time and talented enough to get it working have almost entirely ported Monkey so it can build for the Blitz Max programming language (brilliant for Windows, OSX and Linux games!). The project is called MonkeyMax and as far as Ninjah is concerned it runs flawlessly
I had always hoped the BlitzMax target would appear, but never really knew when it'd happen so didn't think about it too much. The great thing about it is, you can bundle everything in to a single executable file which is perfect for me, especially as I am hoping to distribute it myself.
I will be selling Ninjah for Windows (and soon to be OSX and Linux too) for $0.99 via paypal. More information is available via the Ninjah Mini Site. You buy once and you get it for all three platforms as they become available. I feel like I am in some sort of limbo at the moment where I know deep down Ninjah for Windows is ready, but because I am selling it directly from my site and there's no peer review system like there is for XBLIG I feel a bit more vulnerable about releasing a product that might not work for some people (not that I actually think it won't work!).
I'm all ready to go, I have press releases written, contacts listed and media ready, I guess I just need to man up and release it! ... but maybe I will wait to hear back from a few more testers before I go... :S
OSX and Linux
I don't own a Mac, but a friend who also has BlitzMax (and more importantly a Mac) has been able to build and test it for me and everything appears smooth and fine. I need some clarification on where I should be storing save data, but apart from that I am happy. For Linux I have got it built just fine and have even created a debian package for proper distribution, but I dunno... with Linux I never quite know how confident I am about it working for people. I think I will have to release it and hope people are understanding if something's not quite right.
I'd rather not fire fight issues with 3 OS's at a time, so I will release the Windows version first, give it a while to settle, then concentrate on the Linux release and finally the OSX release. I'm leaving the OSX release until last because I am less able to fix things for it (I can't justify buying a Mac just yet) so I will hope that any problems surface for Windows and Linux first.
Thanks for reading
I am really happy with the new map format. I had expected it to make things easier but didn't really know what that'd translate to in practice. Well having been able to use the new changes this weekend, I can safely say that it translates very well.
I've only added two layers so far. Mud which will be found in marsh areas and pathways which can go throughout the whole level. The important part (in my opinion) is that adding each layer look around 30 minutes, and most of that time went towards editing the map tileset (which always takes me ages...). I was also very quickly able to implement an alternative MapUnitTile (MapUnitTileAnimated) which allowed me to quickly add some basic animations to certain layers. In my test example I could randomly trigger a ripple effect in the deep water layer.
I hadn't thought of it until now, but I can use this system to very quickly add a designated shadow layer which can draw the shadows to add a bit of depth. I've got plenty of idea for other layers I am yet to implement too, but figure I now need to do some more actual game play stuff.
So, the game play... I've been neglecting it recently and spending a lot of time getting the bare minimum code/graphics in place for the in game items. Feeling quite content with my map progress I decided I better make a decent effort to get the quest generation process going and it turns out it is as complicated as I had expected.
I want each level to have a series of randomly generated quests. These quests are optional, but the more quests completed when leaving a level, the higher your XP multiplier is (XP will be used to unlock more features in game). Quests are generated based on three things..
- The world features (does it have much water? Are all areas easily accessible)
- The items available at the start of the level (e.g. quests involving coins can only happen if there are coins at the start)
- What the available items are capable of (e.g. a squirrel can be burned / drowned / confused / loved / hit / captured etc)
Items 1 and 2 are obviously decided during the generation process but I've opted to manually do the third part via a CSV file. This is simply because I imagine it's much easier for me to just decide what these items can do instead of trying to get clever about it.
I am at a stage where I can check specifically if a quest is possible (e.g. Collect at least 50 coins) but I am yet to decide how the system will go about selecting the quests. I've not thought about it too much, but I guess I need to randomly select some possible quests and then give them a difficultly rating some how! It sounds like it could get quite complicated, but I really want to guarantee that as many levels as possible are a lot of fun to play (funnily enough though, I almost think it's important that some of the levels are a bit pants as well, to help with the sense of satisfaction of finding a good level).
Right now I am specifically setting the quests at the start, but even then it at last allows me to make use of the wonderful voice work that Wiggly has kindly recorded for me
It needs some tweaking, but it's getting there.
Yesterday I posted about the first real time I've felt out of control of something while coding Perling. Luckily it wasn't for long and I am now very much in control of what's going on again. Below I will describe the issues raised in the previous post and what the new system does to rectify them.
The new system which is new and great and not old and rubbish
Issue 1 : Unnecessary instance creation
The world is no longer a 3d array but a 2d one which houses a MapUnit class. The MapUnit class can draw 1 or more sub tiles and again, this is calculated to mean as few tiles are drawn as required. The only areas of the map where more than one sub tile draw is needed, is at a boundary (e.g. grass changing to sand). Normally the tile will be a solid 16x16 square without any gaps, and so, there is no need to draw or even check behind this tile. The MapUnit holds a list of MapUnitTiles and a list is a very simple and efficient way of storing information. There is no iteration through unnecessary instances so there is no per instance testing required, I can just assume all items added to this list are required.
So this change means I go from 640,000 instances on a 400x200 map, to around 88,000 (obviously this can change on a per map basis).
Issue 2 : Unnecessary variables per MapTile
I was storing all sorts of crap in the MapTile class because I thought it'd be useful but in reality the only thing I was making use of was the IsVisible:Bool and the drawing coordinates. I say drawing coordinates, but really I was storing variables that were used to calculate the drawing coordinates each and every frame... even though they won't ever change. I guess it seemed like a good idea at the time, but really it's a bit amateur when I think about it now.
The new system stores two variables per MapUnitTile, DX:Int and DY:Int. These describe which part of the sprite atlas to draw. As all tiles are 16x16 I do not need to store this information. To future proof myself, when and if I need to do more complicated drawing commands (e.g. animated shore line), I will extend the MapUnitTile object with a MapUnitTileAnimiated class. As long as both classes have a Draw() method, it will have no implication to the parent MapUnit. This is a better way of doing things because I am not saying "each tile could potentially be animated, so I will store animation variables in each of them" I am very specifically saying "this tile is animated, so make it so, else make it a standard 2 variable Map Tile).
At this stage then, I am storing many fewer instances, as well as storing much less information on a per instance basis. Saying that an integer is 4 bytes and a float is 8 bytes (plucking out of the air here but still...) and saying the old system has 15 ints and 3 floats per instance (I'll ignore the bools because I assume they are one bit), the estimated comparison of memory usage is...
Old: ( 400 x 200 x 8 ) x ( ( 15 x 4 ) + ( 3 x 8 )) = 53,760,000 bytes (around 52Mb of memory)
New: ( 400 x 200 x 4*) + ((400 x 200 x 1.1** ) x ( 2 x 4 )) = 1,024,000 bytes (1Mb of memory )***
- * These 4 bytes are for an integer stored in the parent MapUnit object at each map location. It describes the main type of tile on that particular cell (e.g. the upper most one)
- ** As an over cautious calculations I will say on average there are 1.1 MapUnitTiles per MapUnit (probably less though)
- ***The new system does have some memory overhead because of the lists used for the MapUnitTiles but I believe it to be negligible.
Now 52Mb of memory isn't a lot on a PC/Laptop, but on a low end phone it is (and this is ignoring all of the media that gets loaded in to memory!).
Issue 3 : Unnecessary calculations required during generation process and in game
Making a one off calculation process more efficient (e.g. generating the level at the start), isn't as important as one at run time (e.g. interactions between items in game). However from a development perspective, the more simple it is, the better. Because there was no topological summary of each X/Y coordinate, I had to do lots of calculations that probably weren't necessary with a bit of planning. Having a MapUnit at each X/Y coordinate now lets me store some general information.
I store whether the X/Y coordinate in question is an obstacle or not and I store the main type of terrain on that specific MapTile. IsObstacle:Bool is self-explanatory but the MainType:Int lets me state on a per coordinate basis what the tile is. So it could be WATER_DEEP, SAND, LIGHT_GRASS or any of the other terrain types. Doing this allows me to easily check whether a specific item is overlapping a certain type of terrain. A simple example would be "If Item.OnFire = True and Item.IsOver(WATER) = True Then Item.OnFire = false" which extinquishes an item that's on fire, if it's over water. The IsOver() function checks the item against the map to see what it's current on.
Before, I had to go in to each layer of an MData[y][x] coordinate and work out what the tile actually was. Because this now gets done once during the generation process, I save an awful lot of time at the start as well as during run time.
Issue 4 : Not easily extendible
Before, adding a new type of tile (e.g. a desert) would've been a lot of hassle because I had backed myself in to a corner with how things worked. Now though I can easily extend whatever parts are required without having to completely change every existing element. I can extend MapUnits and MapUnitTiles when and if required and the significance in the order of the layers (e.g. Deep water > water > shallow water > sand ...) is, well, insignificant now. It get's worked out during the generation and that's that. I believe the new system will allow me to add things I am not yet aware of... which leads me on to Issue 5.
Issue 5 : It worked so became established
I guess this is still as much of an issue now as it was before. Everything could be just fine for months and then one day I will think "oh... I can't really do this" for a specific feature and be in the same boat. I am yet to have a fully locked down plan for what Perling needs yet, so while I don't know exactly what will be happening, I guess I can't really do anything about this issue and so just ignore it! I've definitely thought more about allowing for the unexpected, but who knows if I have thought about it enough yet?
There we have it. I guess in retrospect this was a good thing. The code is more efficient, the memory usage is WAY more efficient and the framework is now more extendible and generally easier to use. I am a better coder for it and am now better equipped to make Perling as good as possible
Even though "blip" is a pretty pathetic word (even more so when surrounded by quote marks), "blip" is probably too strong of a word for what I've been up to. Regardless it left me with the first strong feeling of "I am losing control of this" with regards to Perling.
So recently I got Perling running on Android via my HTC Desire. I am yet to compress sound files for each target platform and as the game loads them all straight away, I found myself getting out of memory errors. I assumed I had some sort of memory leak, but it wasn't at all, I was literally just trying to cram too much in to memory at one go. It got me thinking about how I store the level data and while I had deemed it OK before, all of a sudden I was extremely unhappy with how wasteful it was.
Oh and before I carry on, I realise I've not actually posted any videos or screen shots here even though I have done elsewhere (usually via ScreenshotSaturday, or my Youtube channel) anyway, here's a photo of Perling running on my HTC Desire. And I will make more of an effort to post media here as well.
Anyway... so I got thinking about the way I store the level data and I wasn't happy with it at all. At this stage I am literally talking the terrain.
Hi there, right...
while I have always been making Perling with it in mind that it will be on multiple platforms, I've not really tried it other than running it as HTML5 or XNA (for Xbox). HTML5 is quite slow (expectedly) but it functions completely as expected.
When Monkey started supporting the PS Vita I very quickly tried it and got the game running with literally no changes required which is amazing. Unfortunately while on the most part the Vita SDK is good, the support for joypads is poor. "Out of the box" you cannot emulate the analogue sticks of the Vita system unless you are testing on an actual PS Vita. Some clever chap over on the PS forums has made a Pad Server that lets you stream in the state of a PC joypad and go from there, but really this shouldn't be necessary and the sooner Sony sort this out the better. That said, once the Pad Server is running, the game runs perfectly, so that's reassuring when/if I get a PS Vita.
Before I had even started Perling, I had planned for it to be playable with touch screen controls. While I don't actually like touch screen "virtual stick" like controls, realistically, it's what I have to aim for if I want any chance of releasing Perling on mobile devices. So I implemented some great Virtual Stick code for Monkey and got the game working very easily again. It stutters a bit (especially when using the SetColor() command) but it works entirely without intervention. Monkey makes it very easy for you to say "For this platform do this... but for Android, do something else". This is great for scaling between different targets. So I can basically say a particle explosion has 50 particles for all platforms, unless it's Android, in which case only make 10. I have a global "Control" class that handles all of the input and it's been made to handle the various platforms without the main game logic needing to be aware of it. Until I attempted running this on Android it was just sat there doing nothing, but I am actually quite proud of myself for having the foresight to implement it in the way I did, I know it has saved me a lot of grief now!
PC / Mac / Linux
While I know I can get Perling working on a PC using XNA, I've not actively made any effort to get the game running on Mac or Linux. I was aware the GLFW target would allow for these platforms, but because GLFW runs like a dog on my PC, I've not really pursued it. I had hoped that someone would make a BlitzMax target and then I would just use that (I want to use BlitzMax for Ninjah as well). In the past week MonkeyMax has become a feature complete BlitzMax target for Monkey This is great news because it gives me confidence in distributing games for PC, Mac and Linux now. I love that I won't need to rely on any .NET or XNA frameworks for the game to run and I can bundle all of the assets in to the executable as well.
Last night as a trial I installed the MonkeyMax target and successfully got one of the included demos running. I have Linux installed on my PC, so I will have to give that a go and I am currently attempting to get OSX running in virtual box, but I am not having so much luck just yet!
I was going to write about "finding a theme" as well, but this post is too long as it is
while I've still barely scratched the surface, I've been making lots of progress with Perling. Until now I've been working on things with very little focus on planning and basically been messing around with things when and if I've wanted...
In part 2 we optimised the object iteration process to perform as few checks as possible, finally we need to code the part that causes objects to react to interactions with other objects.
Continued after the break
In part one, I got to the point where we were able to iterate through all in game objects and check to see if they were overlapping.
This is great, but in its current state every single object will check itself against every single other object every frame. On a small scale this isn't a problem, but really we want to limit checks as efficiently as possible. I admit I am definitely naive to how much processing a "standard spec" computer (whatever that means) is capable of these days, but I always feel slightly uncomfortable if I don't at least attempt to optimise things. Years a go, I spent a whole day implementing what I thought was an amazing way of reducing CPU usage for a uni project (Pogo Fred). When I was actually able to run the optimisation in its entirety, it made no improvement whatsoever, in fact, it actually slowed things down ever so slightly. I didn't feel bad for trying and failing, but thought maybe I should give computers more credit!
To optimise these checks, we have to dismiss objects for checking as quickly as possible. I do this on a number of levels which I will go through below. Please click "Continue Reading" for more.
Before I start : Not that I think anyone actually takes anything here as a source of advice but... this obviously may be a completely stupid and incorrect way of doing things. All I will say is, it is a solution for a problem I have when programming with Monkey while at the same time being aware of how Xbox games created through XNA really do not like garbage (as you might be aware from my previous posts about Ninjah). I'd also like to state that I'm not writing this in a "hey, everyone should check this out" kind of way, more a "I've ready plenty of useful posts and it's only fair to contribute as well".
Ninjah is a really simple game, the amount of interactions between in game objects is minimal. To the point where I can list them...
- Ninjah against Level
- Ninjah against Coins
- Ninjah against Exit
- Rope against Level
- Bullets against Level
That's it. That's literally it. The code very specifically did these things. I went through each level block to see if Ninjah was touching them, did the same for each coins, did the same for the exit. Then I checked the rope (assuming it was out and not yet attached) against each level block and finally I checked the bullets against each level block (if bullet over wall, remove bullet).
Perling (now just a working title because I don't like it any more...), could potentially have 100 different object types and they could all interact with each other. So there could be 100 x 101 check combinations (the extra 1, is the level itself) and even if there are just 5 of each object type that's 100 x 101 x 4 checks (4 being 5 - 1 because you don't check an object against itself). That's 40400* checks for interaction per frame. (* There is almost certainly more to my some what crude calculation, but it's fine for an understanding of how many checks it'll do)
So, how do you check all of these objects? Hard coding it would lead to the below...
Ok, first check all the player objects against all the coin objects. Now check the player against all gem objects. Now check against the sparkly objects, and now the boxes, and now trees..... OK now check all the coin objects against the gem objects, and then the sparkly objects and then....
... nah. Click "Read More" to continue.
So what's been going on? In short, nothing
The last post spoke about procedurally generating a world map. I was going to use this for a game I had planned on making years ago, Virtual Evil. However, I soon realised I wanted to make a game that as best as possible made itself. So no fantastic story, no scripting, no plot, just a (hopefully large) set of rules that let's the game make its own random entertaining scenarios.
As mentioned before, I really enjoy making components of a game (for example a certain type of enemy), however I really struggle actually implementing them in a reasonable way (I still have no real idea why). So the plan is to randomly generate as much as possible automatically. An example might be an enemy that is scared of water so avoids rivers/lakes and when set on fire it targets similar enemies and runs at them. It's up to the level generating function to decide which enemies and tasks happen in each level.
Instead of having levels 1,2,3...100. The player will select levels by name, either by very specifically writing a name in themselves letter for letter (it could be a word, phrase, or random jumble of letters!) or by using the included level name generator that spits out such gems as "Unsightly Bumpy Minister" or "big screeching fight".
My previous attempts at level generation relied on drawing everything, so to summarise "draw some trees, draw a river and then draw a town", it seemed almost child like and while I think it had its charm, it was crap. I knew that Perlin Noise existed but because I didn't entirely understand it, I ignored it. Having found a PHP implementation of Perlin Noise, I decided to have a closer look and found out it wasn't quite as scary as I first thought (that's not to say it isn't brilliant still!). Being more comfortable with PHP than any other language, I was able to actually understand what was going on (or at least understand what the code was doing on a per line basis) and I decided to rewrite it in Monkey which allowed me to generate levels from within the game.
"So you should be just about done now right?"
Hah, not even close. I've been really lazy for the past month or so and would say I am barely started. I still don't know exactly what I want to add, so I have no idea of scope yet either! I bought a new computer with the notion "A faster computer means I will be able to develop and compile things quicker", unfortunately and if I am honest not unexpectedly it has been "Now I have a computer capable of playing all the games I've been buying from Humble and Steam bundles that I was previously unable to. Oh and Minecraft, I can now play Minecraft". You know what though? It's been fun!
I installed Monkey, Visual Studio C# and Jungle IDE on my new PC earlier this week and am now ready to carry on when and if I see fit. I'm not too fussed about setting myself targets at this stage, but it would be nice to be at least doing some work.
For anyone interested (hi Shereen!) Ninjah is still plodding along making sales on Xbox Live Indie Games and recently hit the 5,000 sales mark Originally I had said I would be really chuffed with 2,000 sales (a number I plucked out of the air), so to see the number going up a bit each day still is fantastic. There's still a niggling feeling that there is plenty I could make better, I guess I should just use this as inspiration to make a better version. I would love to make it available for the PC, but am a bit concerned about protecting assets I've licensed, notably the music which currently would just be included as a WMA file with no encryption. If it were my own music I wouldn't mind, but I am weary of hurting those who made the music.
There we go. Thanks for reading (all 3 of you)