Tuesday, August 31, 2010

I can has magecrawl on the internets?

So it might not look like much, but that's magecrawl running in a web browser! Levels generated, items created, monsters waiting for time to start ticking. The only thing missing is drawing and control.

My good friend Ben inspired the idea and helped implement some the magic.

Roughly the magic entails removing all references to libtcod from the game engine. That way, we can load all that code inside a web browser via silverlight, (which can't load native code). I "ported" this code from libtcod:
  • Shadowcasting Line of Sight
  • A* Pathfinding
  • Bresenham Line Generation
"Porting" could be described as the twisted application of force needed to move C code to compile and run in C#. Since none of them used pointer math too heavily, it was reasonable to attempt. The code looks ugly for C# code, but it works.

In theory, it should even save and load! I can use my same serialization code, just point it to "isolated storage" and away we go.

Obviously, a lot of work is needed to make it a real game frontend. But the fact that I could even get this far (and not have to rewrite everything in flash) is pretty awesome.

Saturday, August 28, 2010

A simple way to reduce memory usage of structs/enums in Dictionaries

I can't take credit for this tip, it was pointed out here.

As pointed out here, by default if you use a struct or an enum inside a dictionary, you will alloc objects every time you access the dictionary to box/unbox things. For example this will allocate both a Point an object to box it in:

var squareLookup = new Dictionary<Point, SkillSquare>()
squareLookup[new Point(0,0)] = null;

I ran into issues because I used something like this for drawing the skill tree, and when you scrolled we hit the dictionary a huge number of time.

The linked hint suggests doing something like:
public class PointEqualityComparer : IEqualityComparer<Point>
        public static PointEqualityComparer Instance = new PointEqualityComparer();

        public bool Equals(Point x, Point y)
            return x == y;

        public int GetHashCode(Point obj)
            return obj.GetHashCode();

var squareLookup = new Dictionary<Point, SkillSquare>
squareLookup[new Point(0,0)] = null; 

With this simple change, a profiled run of scrolled went from over 70% of all allocated memory being Points to so few it wasn't even broken out as an entry. This increased performance by a huge amount.

Friday, August 27, 2010

Testing - Baby Steps

I just committed the first nunit tests for magecrawl this morning. Automatic testing has been on my "nice to have, but not now" list for multiple releases. The reason in short is that writing tests isn't fun, which in some regards is a stupid reason, and in others is understandable (this project is my hobby, not my second job).

My first step into testing was 11 tests, testing the generators (Item, Monster, Map). They simply just poke each generator public API thousands of times, looking for a hang or crash. During development, I ran into a significant number of issues related to this, so this was a "low hanging fruit".

In the future, I'm planning on writing more tests, testing the game engine itself and more of the external libraries. However, I'm currently demotivated enough that I want/need to work on other more interesting things.

Tuesday, August 24, 2010

Modularization - The joys

Modularization, the process of breaking a monolithic software component into multiple separate components. To be honest, it isn't very much fun. For a year or so, I did a lot of it at work, enough so that my wife recognizes the word and knows it isn't very much fun. This is what I did all weekend.

The first question is why I would spend a significant portion of the weekend doing something I admit isn't fun as part of my "hobby". The truth is that in most hobbies there are parts that one does not enjoy. Runners don't always enjoy getting up early to run, some parts of video games are called "grinds", and practicing a skill or instrument isn't often fun.

Magecrawl during development had a few modular boundaries established early on, between the GameEngine and the UI, that has been honored. However, the "GameEngine" component has grown to include much of the games logic, enough that I felt for testability (more on that in further posts) and understandability, things had to be broken up.

Here is a picture of what I currently have:

A bit of explanation, everything that isn't MageCrawl.exe, GameUI, Interfaces, and Utilities used to be in GameEngine. Here's a rough idea of what everything does:

  • Utilities - Low level stuff, like the Point class, Preferences, and helper classes for saving and finding types at run time.
  • Interfaces - These are interfaces that the GameEngine as a whole provide that allow the GameUI and controls to query information about the current game state. Things like IPlayer, IMap, IGameEngine, etc.
  • GameUI - This is what draws the map, menus, etc.
  • MageCrawl - This is the game engine proper. It has an implicit dependency of GameEngine via MEF. It kicks everything off and runs the main game loop.
  • EngineInterfaces - This is a set of internal interfaces the game engine components use to talk about objects everyone needs to know about. For example IGameEngineCore describes the calls to the GameEngine the various modules can use to implement their behavior. For example, those interfaces might expose how to add a status effect to a creature, but to do that you need to know what a status effect is. But status effects live "above" this module, so we create an interfaces that we can expose at this low level, IStatusEffectCore (IStatusEffect is in Interfaces for the GUI).
  • Items - This guy technically existed last release. It was the newest written code, hence being formed as a separate module. This guy handles creating items and exposing their behavior via Attributes.
  • StatusEffect - This guy handles short and long term status effects, things like poison, haste, and the like. It acts by using the exposed interfaces in IGameEngineCore and attributes.
  • Actors - Technically, this module contains monsters (and their AI), along with the Character base class. Characters can wield equipment (Melee for all monsters right now though) and have StatusEffects upon them.  Player lives in GameEngine since it has a lot of other stuff (Spells, Skills, etc).
  • Maps - This was the guy I wanted to pull out. It exposes the map data structures, and the multiple ways to generate maps. Maps place monsters onto them self, and can contain items on the ground.
  • GameEngine - This is where everything else lives. It handles physics (who can move where, etc), combat (resolve attacks), magic (what does the spell do), and the like. 
This is a rough outline of MageCrawl's internal structure. I'll talk about it more in future posts.

Saturday, August 21, 2010

libtcod 1.5.1b1 released!

As many may know, Magecrawl uses libtcod (via libtcod-net) for both graphics and engine support (FOV, LOS, etc). Months ago, I created a SWIG'ed version of libtcod-net, so that I could stop supporting a hand-rolled wrapper. A (beta) release featuring that has now come out.

If you use libtcod via c#, I highly recommend upgrading to the new wrapper, as the old one is deprecated and no new work will be done on it. Also, the new version has OSX versions for both C# and native C/C++ developers.

Get the hotness here.

Thursday, August 19, 2010

Iteration 10 - The long road to slices (want to get out and help push)?

So I've spent this week doing "research" on what next lies in store for Magecrawl.

The first thing I've realized is I now have a good "family tree" for Magecrawl. While Magecrawl isn't developed enough for Crawl to consider it a legitimize offspring, it provides much of the inspiration and root ideas. Things like tactical gameplay, high difficulty, removal of any "grinding" gameplay, and a crazy good auto-explore are all things I aspire to have. Borderlands takes the role of the crazy uncle that rubs of a little bit. It provides such a varied equipment set that playing the game a second (and third) playthrough is fun and interesting. Diablo can be considered one of the grandfather figures. It to me was one of the great dungeon delve games of my youth.

My serious hope is that is the last "iteration" in the series to build up a game engine. Tasks I want to finish to put me in a great place include:
  • 55 open issues in my bug database, mostly minor and a few major features. Some major ones include:
    • Better monster AI using sounds or smell
    • Status effects from various schools
    • Better use of spell effect areas to differentiate the schools
    • "Typed" damage, just as fire or physical. Resistances to various damage types.
  • Refactor the GameEngine library into multiple separate libraries, pulling out:
    • Monster creation/behavior
    • Status effects
    • Map creation
  • Some form of basic integration testing for common operations.
  • Some form of "arena" for testing the weights of various skill tree nodes, along with the possibility of an awesome screen saver.
So yeah, that's a lot of work. I'm in no hurry to start working a masses of content for a "slice" game, so if it takes me multiple months, it isn't the end of the world.

While I'm still planning on working on Magecrawl (a lot), I feel like I'm in a good place to open up development to others if they are interested. Developing on Magecrawl offers:
  • A relatively clean/nice C# codebase
  • Licensed BSD
  • Works on Windows/Mac/Linux, development is possible on any platform
  • The possibility to implement high level functionality on a working game, as opposed to starting at square one with an @ walking around the screen (not that there is anything wrong with that)
If this sounds interesting, feel free to contact me at chris dot hamons at gmail dot com.

One last thing, I re-realized how awesome Ascii Dreams is. The "Designing a Magic System" serious of articles and the follow ups should be on the required reading list for any future roguelike developer (or game developer in general). I wish he updated it more often these days, the content is golden.

Sunday, August 15, 2010

Release aftermath and thoughts

So, the new release has been on the interwebs for a few days now. I figured a post with thoughts and future plans was in order.

  • Some people had issues getting things to run. All of them involved needing either the MSVC 2010 C runtime, or the required mac frameworks. I didn't have them listed on the release page, which has been fixed. In the far future, and installer for magecrawl would be nice to take care of stuff like this.
  • Very little feedback so far. Maybe the summer and quick release since the last tech demo have something to do with that...
  • No crashes or savegame corruption reports so far. If people got the game working, it seems to be working fine.
Now that it is out the door, the next question is "now what?". Here are my current thoughts:
  • Spend the next week collecting game play ideas. My wife pointed out this means I can play video games and call it "research". Magecrawl has matured past the point that calling it a tech demo is becoming a stretch, yet the gameplay is kinda bland. I want to come up with interesting mechanics to make combat more than "jam damage spell, unless I can hit more than one with a bigger spell".
  • Iteration 10 - Yes, one more. I have a ton of things I need to do, refactors, cleanup, and minor feature polish. 
  • Slice 1- Once I get the base in good shape, I need to come up with a "theme" for my slice. I plan on picking one skill tree, and one/a few map types, and create a lot of content. For example, maybe the arcane tree and ruins maps, or the fire tree and interesting caves. I'll jam it out to be a "full length" game, seriously this time. It might be shallow, but it'll be complete.

Thursday, August 12, 2010

Magecrawl Tech Demo III v2 Demo Video

I've wanted to put together a quick video demo'ing magecrawl for those who want to see the game before downloading it or are having trouble getting started.

I found some decent software which made creating the video easy enough to tackle tonight. If anybody has a pointer to free software that captures screen video and sound and outputs to youtube, please let me know in the comments. Jing, the software I used was easy, but it outputs only to a flash file unless you buy the pro version.

Eitherway, check it out here.

Magecrawl Tech Demo III v2 released!

I am pleased to announce the release of "Magecrawl Tech Demo III v2". Released two months after the previous Tech Demo III release, it adds features that I wished were part of that release, but not enough I feel to bump it up to number "IV". Highlights of the user visible changes in this release include:
  • Difficulty that won't melt your face
  • Concurrent Mac/Linux/Windows release with a single release zipfile
  • Monster and Items that scale as the game progresses
  • Long term persistent effects
  • "Backgrounds" - Starting configurations of skills and items
  • New skill trees with tons more skills
  • Smarter AI
  • New spells, items, and monsters
  • Bug fixes, and balance fixes
One important note for Linux users, due to a last minute found bug, you will be unable to save unless you add the following string into your Preferences.xml file

< UseSavegameCompression > False < /UseSavegameCompression >

If you run into any crashes, please send the created file "DebuggingLog.txt" to me (chris dot hamons at gmail dot com), along with any save file you have.

One last thing, please seriously consider filing a comment or sending me an e-mail both letting me know you checked out my game and your thoughts. Feedback is the breakfast of champions you know.

Now that I've made you read though all of that, binaries can be found here.

If you haven't run Magecrawl before, here's is what you need installed to make things work:
  • Windows - .Net 3.5 found here. You might need to 2010 C RTE here.
  • Mac - Mono found here. You probably need the frameworks SDL, and libpng.
  • Linux - Also mono found here, however you might want to try a copy installed by your package manager first
Here is how you run Magecrawl:
  • Windows - Unzip the zip file somewhere, and double click MageCrawl.exe
  • Mac/Linux - Unzip the zip file somewhere, open a terminal and cd to that directory, and run "mono ./MageCrawl.exe"

Wednesday, August 11, 2010

"Leaky" stamina, and other "balance" changes

So, I'm still hacking out changes to make Magecrawl playable.

The major change of the night implements what I hope is a solution to the HP problem I talked about last post. I decided the underlying problem was that going from 100% stamina to 5% stamina involved zero risk. Now, once your stamina goes below a cutoff (currently 80%), there is a chance that some of the damage will "leak" through and apply directly to the underlying health. The chance is inversely proportional to the amount of stamina, so you are much more likely to leak at 20% stamina than 75%. This changes make combat more dangerous overall, but having lots of stamina is still useful but not overpowering.

Other changes I've implemented this week include:
  • Map generator won't generate a horde of monsters on top of the player starting position
  • Monsters will notice the player from farther away when they are wearing heavy armor (noise).
  • Toughness skill will now give health instead of stamina, giving the player the option to "buy" more long term health.
  • Bug fixes, seriously lots of bug fixes.

Monday, August 9, 2010

The problem with HP...

I know I've linked to this tv trope, but it is seriously still true in magecrawl. For those not playing along at home, here's what the current system is:

HP is divided into health and stamina. Damage taken is drained from stamina first. Stamina quickly recovers in between battles (assuming you aren't noticed by enemies, you can't rest then). Health damage requires magical healing to fix up. Stamina represents your ability to dodge and block damage enough to get out only with scrapes, bruises, and surface cuts. Health represents actual wounds, while bandaged up enough not to get worse, would normally require weeks to heal on their own.

Let me show you my current problem with an example:

Given 100 health and 60 stamina, you fight a monster which on average does 20 damage per round and takes 4 rounds to kill. Given these stats, you'll exhaust your stamina every fight, taking 20 damage to your health. In five fights, excluding magical healing, you will be dead.

Now repeat with 80 stamina, you on average take little to no health damage, extending the number of one-on-ones to tens or hundreds. If you repeat with 100 or more stamina, you'll extend the theoretical number of battle you can last, but you'll never get close to the "jump" between 60 and 80.

This creates a problem with skills and armor that increase your stamina, at some point you'll be able to solo monsters with no danger of taking semi-permanent health damage. I have a few solutions I'm playing around with:

  • Make the line between stamina and health less clear cut. When you are at 100% stamina, you are sure any damage you take will be stamina related. As it decrease, there is a higher chance some of the damage goes to your health.
    • Advantages - Makes battles less one sided, as any blow after a round or two can be lasting.
    • Disadvantages - Adds a significant luck factor
  • Make healing stamina take resources
    • Advantages - Makes every battle matter
    • Disadvantages - Makes every battle matter, makes it easy to want to micro optimize which isn't fun. Starts to approach the dreaded "food" system in other roguelikes.
  • Make low stamina penalize certain attributes, such as mp total or time in between actions.
    • Advantages - Makes stamina damage matter.
    • Disadvantages - Can snowball, making you more likely to take even more damage. In a game with permadeath, this can get nasty.
  • Make the stamina and health levels relativity constant (they increase at a rate similar to monsters damage output). Skills/armor give other benefits than more stamina.
    • Advantages - Makes balancing easier. One you have a good setup, just make sure the rates of growth match.
    • Disadvantages - Intellectually unsatisfying. Keeps player from feeling any tougher, as at the beginning of the game to the end, the can pretty much only take x hits from a even level monster, where x stays constant.
I'm currently leaning towards the first option, but am unsure right now. 

Thursday, August 5, 2010

All primary feature work is done for iteration 9, finally...

So this morning I finished up the last issues I had targeted for iteration 9 in fogbugz. The release isn't yet ready to go out, as I have to play with the numbers to make the game fun/winnable. However, outside of bug fixes, I should be done make code changes for now.

I'm hoping that balancing should be "easy" now that I have filled in tech trees and have a reasonable spread of items, but as I've said before balancing games is hard. Do any other game developers have trouble balancing stats, abilities, items, and such to make a fun game? Any suggestions on ways to make it easier outside playing the game a bunch and make lots of small tweaks?

Wednesday, August 4, 2010

Skill Trees "Filled Out"

I decided to spend some time "filling out" a few skill trees, and have simple but complete trees for five sub-trees: Arcane, Fire, Light, Martial, Attributes. Each tree has between 7 and 10 node (except martial which has 37), and each node provides a new skill or improves an existing one. I still at some point need to write descriptions for the martial skills, but beyond that the skill tree is done for this iteration.

Sunday, August 1, 2010

Class vs Struct - It actually can matter

So, the second of my optimization realizations today involves the difference between structs and classes. For those who've been with this blog for awhile, you might remember a post I made half a year ago, here. In that post, I realized that a list of structs uses a lot less memory than a list of classes when each item is crazy small and there are a huge number of them.

Today, I've ran into the inverse. I made Point a struct back when I made that realization, thinking that it was small and therefore memory savings were worth the savings. However, when you stick an item into a dictionary as a key, it apparently makes a copy if it is a struct. It also makes a copy if you query the dictionary with a struct as the key. This meant I could rack up half a million or more point copies by scrolling for 15 seconds in my skill tree. This caused the memory manager to go crazy, and introduce the lag I was seeing. Switching it to a class helped a bunch.

When should you use struct vs when should you use class? Here's my current huristic:

If the item is large or you won't be having a crazy huge number of them - Use class, the possible optimization isn't worth it.
If a large number of the items will be stored in memory in a list - Try struct, as you'll save a pointer sized (4 or 8 bytes) amount for each item.
If you will be using it as a key (or value to a lesser extent) in a dictionary - Use a class, as each query on the dictionary and every access will make a copy.

GetHashCode's preformance really matters if you use it as a dictionary key alot

Just a quick post about a surprisingly small change that improved performance in magecrawl's skill tree view. I have a custom point class since there doesn't exist one inside c# outside of some GUI toolkits I don't want to force dependencies on.Since I overrode Equals, the compiler told me to override GetHashCode. I did so the naive way:
public override int GetHashCode()
    return X ^ Y ^ base.GetHashCode();

I have a spot in a tight loop that needs to lookup information based upon its position. I was previously using a List with a where LINQ call, which was insanely slow when I scaled up the number of skills. I figured this out using profiling and changed it to a dictionary. However, it was still too slow, so I read up on GetHashCode more and realized I could change GetHashCode for point to read:

public override int GetHashCode()
    return X ^ Y;

I didn't keep track of the numbers, but this very visibly made a huge difference. Keep GetHashCode in mind when you have data structures that are used as keys in large dictionaries. A small change can make a big improvement.