Lachlan's misadventures in games programming

Friday, 29 August 2014

I don't want to set the world on fire

8/29/2014 02:41:00 pm Posted by Lachlan No comments

TL;DR version - Scroll to the bottom for cool fire spewing animations

I've spent the last days adding at least a basic animation framework, and a fairly cool animation to Atlas Warriors. As a test, I added a blue shimmer when a Healer heals another enemy - which worked just fine but isn't particularly interesting.

The primary reason I wanted it was for implementing Dragons. What is a dragon without some good and proper fire breathing? So, without further ado...


Lame, I understand - but at least it wasn't another Arrow In The Knee reference

The first step was an algorithm for determining flame coverage over the area fired. That algorithm can be used (with the actual distance) for determining who to ignite, and (by looping the distance from 0 to the actual distance) for creating the animation.

The algorithm is far more simple then what I've probably made it. My pseudo code also probably gives away that I'm a lawyer - not a compsci or programming major... so I hope you can at least understand it. Also know that some was more trial and error'd then well planned. This is sure as hell not best practice!

  • Given:
    • Source
    • Target
    • Desired Distance
    • Desired Radius at distance
  • Calculate angle between Target and Source
  • Calculate the end point ( end(x,y) = (sin(angle) * distance, cos(angle) * distance) )
  • Calculate angle to the other end points. The other end points are separated by 1 unit, perpendicular to the end point, in distance radius in each direction. Put the angles in list J
  • Create a 2D array (G) initialised to zero from [-distance,distance][-distance,distance]
  • For i in J
    • For k := 1 to Distance
      • x = sin(i) * k
      • y = cos(i) * k
      • If x,y blocks flame then break
    • xpart = frac(x + .5)
    • if (xpart < 0) then xpart = xpart + 1
    • ypart = frac(y + .5)
    • if (ypart < 0) then ypart = ypart + 1
    • xint = floor(x + .5)
    • yint = floor(y + .5)
    • G[xint,yint] = G[xint,yint] + xpart/8 + ypart/8
    • G[xint+1,yint] = G[xint,yint] + (1-xpart)/8 + ypart/8
    • G[xint+1,yint+1] = G[xint,yint] + (1-xpart)/8 +(1-ypart)/8
    • G[xint,yint+1] = G[xint,yint] + xpart/8 + (1-ypart)/8
  • Return G
This returns a grid showing how much flame hit each section. That G will have some fractions where only part of a square was hit by the flame. I'm going to use those fractions as the chance of igniting a monster on those tiles. I also use them in the animation.

A sensible person (including possibly me in the future) would: leverage whatever antialiased line algorithm they had access to (including, for instance the pygame ones), do the drawings of a black line on a white surface and use the darkness as the percentage of the square hit. I may still migrate to this in the future. It does still have to be looped to have the collision detection.

The animation is quite simple. As described already, the above algorithm is looped from 0 to the desired distance. For each frame, it loops over the grid and draws a character in a foreground color  and a background color all selected to reflect the value. Mine use > 3, >2, >1, >.75, >.5, >.25, >0.15 and > 0. It does nothing on a 0.

The end result is reasonably good, if not yet perfect. I will note that it will be coming from Dragons - not from the player as in these gifs. They will hopefully be sufficiently horrifying when you have an encounter with them!

Any comments would be appreciated.

Saturday, 23 August 2014

Better Levels, and 'Second Wind'

8/23/2014 10:37:00 am Posted by Lachlan No comments
Since the last post, I've been working on two things. Firstly, I've made the levels more interesting. I've made heavy adjustments to how it plans new rooms, and added the possibility of rooms interconnecting.

I've also added a 'Kill or be Killed' mode (which is essentially the Second Wind mode from  Borderlands). One of my goals has been to keep the figures (like HP and damage) low. Having Kill or be Killed allows me to keep the figures low and make the game a little more harsh knowing that a player will often be able to get themselves back.

The game is never going to be the greatest roguelike - but I'm hoping it'll be a bit of freely available fun. It's also proving to be a good learning exercise.

Not looking forward to trying to balance it though.

Wednesday, 20 August 2014

Atlas Warriors

8/20/2014 01:02:00 am Posted by Lachlan No comments

I've been spending some time working on a Roguelike in Python. It has been so far confirmed to be completable, but way incomplete and unbalanced.

The goal is for a game to be completable in 30 to 60 minutes.

I'm experimenting with a couple of things that will hopefully work out well. I've got an interesting system for calculating accuracy comparing the attackers to hit. Secondly, I've got a heal-on-exploration mechanic where you only replenish health as you explore more areas. I'm trying to keep the numbers nice and low (including HP) - so I'm hoping that levelling up might be almost treated as a method of healing.

Some screens are below

More news will (probably) follow.

Thursday, 14 August 2014

Python - Parsing XML to Object

8/14/2014 12:50:00 am Posted by Lachlan , 2 comments
My apologies for it having been a while.

In the course of programming a roguelike in Python, I wrote the following chunk of code which might be helpful to others. Given a filename of an XML file and a constructor, it parses the XML file and uses the constructor to fill the object with the contents of the XML file (so - a field called 'SPAM' in the XML file would automatically populate the variable SPAM in the object created by the constructor). If the variable is a boolean or integer, it'll copy the XML as such.
import xml.etree.ElementTree as ET

def parse(filename, objectConstructor):
    tree = ET.parse(filename)
    root = tree.getroot()
    items = []
    for i in root:
        item = objectConstructor()
        for j in i:
            t = item.__dict__[j.tag]
            v = j.text
            if isinstance(t, bool):
                v = v == 'true'
            elif isinstance(t, int):
                v = int(v)          
            item.__dict__[j.tag] = v
    return items