Jump to content

  • Log In with Google      Sign In   
  • Create Account

/* Why you crying? */

If I were to make Golem now...

Posted by , 23 August 2016 - - - - - - · 880 views

If I were to go back and remake Golem today, what would I do? Where would I start? As I have mentioned in previous entries, I would almost certainly use an engine now instead of rolling my own. (Although, to be fair, back then there wasn't nearly the wide range of available engines to do this stuff with, so rolling your own was almost a given.) Without having to worry about the low level details, I would be free to worry more about the stuff that makes it an actual game. I've become a better programmer, so I like to think that I would make fewer mistakes now, mistakes that would turn the game into spaghetti. Still, though, even with all that I've learned in the last couple decades about game making, I think I would probably start Golem now with the same thing I started with back then: drawing the ground.

Think about it: in an ARPG like Diablo, you spend a LOT of time looking at the ground. Camera above the ground looking down, dude walking around. The ground fills the frame nearly 100% of the time. To me, poring over screenshots of Diablo and Diablo 2, it seemed the natural place to start. Draw the ground, then figure out what kinds of other stuff to draw on top of it. In 1997, my options for drawing the ground were somewhat limited.


When I first read Designing Isometric Game Environments by Nate Goudie in a print issue of Dr. Dobbs Journal in 1997, I had already played Diablo 1. I had also already created a number of small prototype tile-based RPGs, starting in the late 80s. I had dipped into assembly language programming in the early 90s, to write "fast" tile blitting routines. I had a basic understanding of how tile maps were supposed to work, so I took what I knew and what I read in that isometric article, and I dove in.

Golem went through various different stages as I learned about tiles and tilemaps. From base tiles with no transitions, to transition permutations, to alpha-blended transitions for greater flexibility. I spent a lot of time looking at screenshots, playing with paint programs, trying to hand-draw some dungeon floors. I remember in the early 90s I had found a program for DOS called NeoPaint, and I was still using it in the late 90s to try to draw dungeon floors, right up until I discovered GIMP. I struggled to try to reproduce Diablo and especially Diablo 2, without really knowing or understanding how such things were done. At some point, I had the epiphany that they most likely created their tiles from photographic sources. Digital photography was starting to take off, and I remember stumbling on Mayang's textures in the early 2000s. Most of the tiles in the Golem screenshots still in existence were created from texture sources downloaded from Mayang's. I remember just being floored at the number of textures available there, and during that time I really worked on learning various editing techniques to make tiles from photo sources, to make them seamless and create transitions.

In the beginning, all of the render and blit operations were written in assembly. The first generation of Golem map rendering had its roots in a tile map system I wrote in the early 90s for DOS. At that time, my computing budget was limited. Hard drive space was at a premium, all drawing had to be done in assembly language to get the kind of performance a game required. With the switch to isometric in 97, I devised a very simple run-length encoding scheme to encode the graphic tiles. See, the isometric tiles are diamond shaped:

Posted Image

From the very first, I understood how wasteful this configuration was. All that empty space, all those transparent pixels. My early thought was to encode the image so that only the pixels (plus some book-keeping stuff for row offsets) were stored. That saved a lot of disk space, plus I wrote custom assembly routines to draw these RLE sprites with pretty decent performance.

The first iteration, I had no idea about transitions. I had played a lot of JRPGs on Nintendo systems, so I was pretty familiar with tile maps. All those first Golem tilemaps were transitionless. I don't have any of those screenshots anymore, but it was essentially like this:

Posted Image

Those are newer textures; the ones I made for Golem initially were... pretty terrible. I thrashed around a lot in those days, trying to figure out techniques and learn tricks. Eventually, I read about tilemap transitions (probably in the gamedev.net resource section) and started creating permutations. Dirt to grass, dirt to water, grass to water, etc... If you've ever done transition permutations like that, you know that the number of permutations can explode pretty rapidly. Thus, this later revision of Golem's map rendering had to carefully constrain terrain placement. Gone were the days when I could just throw whatever tilemap configuration at the renderer I wanted; I had to be careful in placing types so that, for example, I didn't end up with a dirt tile that bordered grass on one side and water on another. That just wouldn't work. Still, even given the limitations of tile permutations like that, the simple addition of transitions was a pretty good leap forward in quality. The result was something like this:

Posted Image

Eventually, though, that limitation started to really chafe. Just... you know, sometimes you really want to have a dirt tile with grass on one side and water on the other. You know? So I learned some more tricks, read some more articles. Somewhere around this time, I switched to DirectDraw for rendering, then moved on to Direct3D. We're talking early versions here. I kinda wanna say I started with DirectX 4. DirectX had been a thing for a few years, and I probably stuck with the old assembly stuff for far too long, but you gotta understand, kids. Back then, the internet was still mostly just sacks of bytes carried around on the backs of mules. You kids and your reddits and your facespaces, you just don't know what hardship is. Hardship is connecting to the World Wide Web with a dialup modem, hanging three T-shirts over your computer to try to muffle the connection noise because there's no volume control on it and your roommates are trying to sleep. Hardship is not even knowing that marvelous wonders such as DirectX or OpenGL even exist, because the only real reference you have is Michael Abrash's ponderous tome on graphics programming, with its chapters on Mode X, already a relic of a bygone era by that point.

Anyway, with the switch to modern APIs, things really changed. I could use alpha blending, something my old assembly routines didn't really allow. Now, I could create the transitions as alpha-blended decals overlaid on top of base tiles. Suddenly, I could have that mythical dirt tile bordered by grass and water, without having to create approximately 900,000 different terrain permutations. It was amazing!

Again, those early dev screens are lost. But here is a quick example of how that can look:

Posted Image

Any number of transitions could be overlaid upon one another to create as complex a transition as was required. Of course, this required me learning a few more tricks on tile creation. (Again, those early tiles were pretty garbage, though I was getting better by that point.)

It was around this time that I started branching out into a lot of different areas. I was building the character control stuff, combat stuff, inventory handling and management, etc... I was learning how to model in 3D, using the amazing Blender software that I had discovered. Blender was in its infancy, having just been released as open source by a crowd-funding campaign. But even then, it was pretty powerful and it opened up a LOT of options and avenues for me. I was learning how to create walls and gates and skulls and creatures. It was an amazing time.

I started to dislike how the tile transition schemes were plagued by the appearance of grid-based artifacts. You could see the tile-basedness of it. I let that ride for a long time, though. At some point later (after I had already abandoned Golem) I started experimenting with decal-based splatting systems that would splat terrain and terrain decorations down on top of a terrain, randomizing locations of splats to mitigate the grid constraints. I could lay down a base terrain, then splat decoration on top of it using alpha-masked splats.

Posted Image

With the rapidly expanding hard drive sizes and graphics card capabilities, the RLE system I had encoded my stuff in all along quickly became a liability. I didn't really need to optimize for hard drive space anymore, and it required an extraneous step of unpacking the encoded sprite to a OpenGL texture. I never did replace that system; I just worked around it. But since I had plenty of space, I could now do tile variations: different variations of a single terrain type that could be mixed and matched to eliminate the appearance of repetition. Even just having 2 variations of a given type made a large difference:


Adding more variations only made it better.

And that's pretty much where I left it all off. Using alpha-blended transitions or detail splats to hide repetition, texture variations, etc... I was able to make some pretty decent stuff, slightly approaching what Diablo 2 accomplished (if with nowhere near the artistic skill). Unfortunately, this was the end. The awkwardness of the graphics pipeline, combined with the general shittiness of the codebase, killed the project. Eventually, my old hardware was upgraded to new and the Golem was not copied over. Pretty soon, it was nothing but rotting bits on a disconnected harddrive in the bottom of a dusty, spider-webbed cardboard box.


So, back to the question of if I were creating Golem now, what would I do? First and foremost, I would ditch the tile-based transition scheme altogether. Using a custom shader to blend different terrain types together using a blend texture is the way I would go. It's what I've been using in my Goblinson Crusoe project for a long time now. Using such a scheme has quite a few advantages. The grid is no longer as large a problem. There is some repetition, due to using tiling textures, but the actual transition configurations are no longer tile-bound. A couple different base textures, a map to blend them together, and some revised ground terrain generation rules for the random generator and you're in business:

Posted Image

Couple it with the decal detail splatting to add some decorations, and you can get some really nice results with relatively low performance cost.

The base version allows 5 terrain types (black in the blend map is the base, R, G, B and A channels control the upper layers). But you could throw another blend texture in there for 9 types if desired. Texture arrays make it easy, since you can bind as many textures as you need.

I put together a quick test of it. The funny thing is that it only took me about an hour to get the framework up, and implement a quick randomly generated ground terrain and have it scroll around with the WASD keys. The power of a pre-existing engine, coupled with the countless hours I spent up to that point learning about how all this shit works and writing shaders and camera code for other projects. What took me literally years in the early days to accomplish, I can now accomplish in an afternoon and with better results besides. Such is progress.

If I were to make Golem now, this is how I would start.


Posted by , 05 August 2016 - - - - - - · 1,239 views
golem, urho3d, engine, rpg and 1 more...

In the previous post, I commented how I didn't see any use in re-factoring and re-building Golem, the way evolutional is re-factoring Manta-X. I'd like to elaborate on that a little bit.


It's not that I don't find any value in the game itself; Golem is a game that I most definitely would like to revisit at some point. I feel like I had a lot of good ideas with it, and I liked the setting and story I was writing for it. It was fun to play, as far as I got with it, blending some of my favorite aspects of Diablo and the various roguelikes I was playing at the time. I could certainly see myself going back to it some day.


However, that doesn't mean that I have any need to try to refactor it as it stands. You see, refactoring that thing is a trap. A warm, cozy, comfortable trap. I could jump in and start refactoring. I've got a thousand ideas even now, as I write, for what I could do to start improving things. There are things I would do better, things I could rewrite, etc... I've got an idea for a message passing scheme that... well, let's just say that this is how it starts. I spent decades of my life in this trap. Decades. Call it framework-itis, if you want. Call it engine-sickness. Whatever. It's a trap.


I have started to work on countless framework/engine projects in the past. I've written numerous prototypes, demonstrating some new aspect of engine technology that I hadn't explored yet. I've written object frameworks, message passing schemes, resource handlers. There is always a reason for me to jump back into that mess, but the thing is I already know how that is going to end. I already know how my time will be wasted.


I've mentioned before, I'm not much of a software engineer. I'm better than I once was, certainly, which just makes it more tempting to say, "I can do better this time." I could dive in, write some framework code, munge around with more modern OpenGL versions, build a new object framework, yadda, yadda, yadda. But eventually I'm going to run into some roadblock. Some poorly designed bit, some system I lack the knowledge or skill to architect gracefully. The spaghetti-ing will start. I'll begin running headlong into limitations of the engine, I'll start kludging in bits to make something work. Before you know it, months or years have passed and I am right back where I was for just so many years of my life: writing engine code, rather than writing a game, and getting bogged down in a mess of code that is spiraling out of control. That's how it happens with me. It's a road I've been down before. I've never been a good-enough programmer to avoid the trap, and I don't think that I ever will be.


It's already started, you see. I've got a project skeleton in my projects folder right now. I've got some new libraries I've never used before copied into that project, libraries that I am itching to get a feel for and learn about. The skeleton project builds without errors or warnings; it opens a window, loads some shaders and draws a red/green/blue triangle. I've copied over the source files from Golem. I've started writing a tool to un-crunch the data files in order to recover the assets and try to make use of them again. It has begun.


I gotta stop the madness.


Over the years, since I joined this site (officially in 2004, unofficially somewhat before then, in the days of the splash screen), I have come into contact with a lot of really talented and skilled programmers. evolutional has been hugely influential, off and on through the years. Washu, Promit, swiftcoder, SiCrane, Fruny, phantom, superpig, hplus0603, Hodgman, Servant, Apoch, eppo.... the list goes on and on. A lot of these people know programming in a way I never will, and I was always a little bit envious of that. But more than anything, over the years I've learned to accept my limitations and understand that I need to play against my strengths. Writing low-level framework code in an elegant and efficient manner, regardless of how I enjoy it, is not one of my strengths and never has been.


So the chief thing I would do differently this time around would be to use an engine. I've been using Urho3D for quite some time, and have become very comfortable with it. However, if I use an engine then automatically, by default, the vast majority of the existing Golem code becomes an unusable non-sequitur. Animation handling? GUI? Object hierarchy? Just no real practical way to shoe-horn it into Urho3D's existing component-based framework. Not without completely re-writing everything, which is pretty much what I would end up doing. The re-factor then becomes a completely new project altogether. It would be less labor-intensive to just scratch everything and start fresh, building directly against the architecture of the engine. It would certainly align more closely with the way I currently think about game development. I have left behind many of the attitudes and beliefs that I held when I was working on Golem. I don't think I could fit in those pants anymore.


But I wouldn't start work on Golem again right now, not with Goblinson Crusoe already on my plate. Nuh uh, no way. One medium-scale RPG is already too much work for one dude. No sense adding another.

On The Topic Of Retrospectives...

Posted by , 01 August 2016 - * * * * * · 1,278 views

Evolutional's recent post about digging through some old code inspired me to take a look at some of my own. I had recently found a copy of the working development folder of my old game, Golem, on a hard drive in a box, and while I've peeked into it out of morbid curiosity, I haven't felt the inclination to dig very deep until now.


Golem was an isometric ARPG heavily inspired by Diablo and, later, Diablo 2. I had grown up playing the original Atari console and, later, the original Nintendo and the Super Nintendo. I didn't get to have a whole lot of time playing PC games. My first PC RPG was Wizardry 4, and holy shit was that a mind-bender. I played JRPGs on the Nintendo, mostly: Final Fantasy, Dragon Warrior, etc... Loved Secret of Mana, loved the Zelda games, loved Illusion of Gaia. But playing Diablo was like an awakening. On PC, I had mostly played Nethack up to that point: turn-based dungeon delving rendered in awkward ASCII graphics. But Diablo... well, Diablo was kinda like Nethack (not nearly so deep, obviously) but it looked good. To my young eye, the isometric graphics of Diablo were stunning. Playing Diablo literally altered the course of my life, in ways that I only really now understand. While I had dabbled in game development since the early days of DOS and the advent of 256 color graphics in ModeX, it wasn't until I played Diablo that I really had in mind a vision of what I wanted to do and make.


Posted Image


I started work on Golem some time around 1998. Some of it was just scribblings in notebooks made during lunch break at work, some of it was assembly-language tile drawing routines I wrote later, some of it was C code written for the old Borland Turbo C++ compiler. Over the years, it evolved away from assembly and into OpenGL as I learned new technologies. Many of the first posts I made here on gamedev.net were in regards to this game. In a way, I've been working on this game (in some form or another) ever since. I've certainly spent a lot of time on isometric game prototype development, at any rate, even if little of that original code is still in use. But during the summer of 2004 is when Golem in its final (sadly, uncomplete) form took shape. I was between jobs, having returned (briefly) from Arizona to Wyoming. I had not yet met my wife, bought a house, had kids, or any of that. Everything I owned, I could fit in the back of my old Toyota 4x4. I slept on a cot in my dad's basement. By day, I hiked the juniper and cedar covered hills just outside of town, and by night I hunched over my keyboard, listening to Soundgarden on repeat and furiously pounding out 69,094 lines of the shittiest code you ever did see.


Posted Image


Evolutional's restropective is a thoughtful look at how a maturing developer revisits his old code with an eye toward understanding how he was then, and understanding how he has improved over the years since that code was written. He starts the whole thing off with a look at the project's structure, and while he has criticisms about how he structured things, I gotta say this: Manta-X was a paragon of order and structure compared to this:


Posted Image


Take a look; drink it in. That's the root level source directory for Golem. Are there source files there? Sure are. Are they mixed in with intermediate build .o files? You betcha. Are there miscellaneous game data scattered all throughout like nuggets of gold? You're damned right there are! In the finest code-spaghetti Inception tradition, there are even archives of the game code directory in the game code directory. At a quick glance, I see bitmap fonts; I see sprite files for various player characters and props; I see tile sets; I see UI graphics. Sure, there's a data folder in there, and sure during later development all game-relevant data was finally sorted and organized into the data/ folder structure (it's actually pretty clean now), but all that old testing and development cruft is still in there, polluting the source tree alongside build artifacts like little nuggets of poo fouling the bed sheets.


Rest assured, I have improved--in this regard, at least.


In the last few hours, I've opened up several source files at random and perused their contents, trying to get back into the mindset of Josh v2004, or VertexNormal as I was known back then. This process can also be summed up as 'what the bloody hell was VertexNormal thinking?'


Elephant in the room here: yes, there are singletons. Hoo boy, are there ever singletons. In the final day, when I have to stand before the judgement bar of God and account for my actions here in this mortal life, the question is gonna be asked, sternly and with great gravity: "Did you ever abuse the singleton pattern?" And since God sees all and I will not be able to lie, squirm or weasel, I'm gonna have to say: "Absolutely, I did. Many, many times." It's not going to be a proud moment. (In this envisioning, God looks a lot how I imagine Washu looks; make of that what you will.)


So, rough idea of how it's organized from a high level. We've got a Map, and a MapBuilder. The map, of course, is the level. The MapBuilder is a utility I wrote to encapsulate (and I use the term encapsulate loosely, and with liberal license) the various things I needed to randomly generate levels. We've got a ScriptContext. We have a MiniMap. We have a GlobalAlloc object allocator. We have an EffectFactory. We have an SDLWrap (a wrapper of all things SDL related, naturally). We have a BuildInterface (whatever that is). We have a ScriptInterface, a PlayerInventory, an ItemFactory, and more. All of them, singletons.


All of them.


Look, I kind of get what VertexNormal was doing here. I get it. Software engineering has never been my strong suit, and to this day I continue to make terrible decisions. And back then, I didn't even know what the term 'dependency injection' meant. But man, you guys. Man.


Some day, someone is gonna write a book on how to kill a project, and under the chapter titled "Dependency Spaghetti Through Singleton Abuse" there will be no text; just a link to the repo of Golem. Every knotty little problem, every little grievance and aggravation that led to me ultimately abandoning Golem, finally and completely, can be traced back to the abuse of singletons. Singletons are everywhere, threading a knotty little web of dependency throughout the entire project. Dive too deeply into the source, and the threads of that web can strangle you.


Now, I'm humorously critical of this project, but the important thing about it is that when I look at this project, I see a kid who was trying his hardest to learn. He was learning about object lifetime management, learning about encapsulation, learning about how to build a game in general. He was making mistakes and working to rectify them, and expanding his knowledge in the process. I don't see any value in doing with this project what evolutional is doing with Manta-X; unlike Manta-X, I don't think there is anything of interest to be had from that here. To refactor this thing would be a large task, and ultimately pointless since there is little here that I haven't accomplished, more efficiently and more effectively, in later prototypes. But I think it IS kind of a valuable exercise to look at it and think about how I would do things differently now, knowing what I know in the present.


Aside from the singleton abuse, there are a few issues.


1) VertexNormal still didn't have a good grasp on object ownership and lifetime. The existence of so many singletons demonstrates this, but so too does the structure of the various object factories and allocators. Knowing who owns what, who is responsible for what, and who is just using what, is a large part of game engineering.


2) VertexNormal made the mistake of using custom binary asset formats for everything, even in early stages of development. That meant that I was constantly fussing about with building tools to pack/unpack assets, even as asset formats were constantly changing and evolving. A lot of time was wasted maintaining and modifying the various sprite packing utilities. I had the idea of saving memory by storing graphics as run-length encoded files, using a custom RLE scheme I had devised. It was a carryover from the earlier days, when the rendering was done in assembly and the graphics were rendered directly from these RLE data chunks. But with the switch to OpenGL, the pipeline was modified to include a step for unpacking these graphics to textures, meaning that the steps of packing and unpacking were made extraneous. Disk space was cheap by that point, so there was just no point to keep fighting the binary packing system as I did. I'm amazed that I implemented as many objects as I did, considering each was a binary packed asset that had to be specially constructed. It is especially ironic that I struggled with the binary asset descriptions, when I had already embedded Lua into the project, and Lua is in itself a fantastic tool for data description. These days, all my assets are described as Lua tables rather than binary blobs, and it's easy enough to compile them to binary format.


3) VertexNormal was still figuring out how to draw isometric games and make them look good. The tile-basedness of Golem is painfully obvious in every screenshot. Which isn't necessarily a bad thing, but I made some bad choices with how I was drawing the levels that made it difficult to mitigate the tile-basedness. These days, I make greater use of shaders and techniques that can eliminate the grid and increase flexibility. I'll probably try to talk more about this later, but it is remarkable to realize exactly how much time and effort I have spent on figuring out how to draw isometric levels.


4) This was VertexNormal's first introduction to Lua. It's where I learned to love the language, even if back then I had absolutely no friggin idea how to properly make Lua and C++ work together, in a way that helps rather than hinders. I had difficulties with passing objects back and forth across the interface, hence the existence of the ScriptContext class which was used to set various 'current' objects for manipulation by script. I still struggle with exact implementations, but I have a much better grasp on things now. In Golem, it is obvious I had troubles knowing whether Lua or C++ should be responsible for a given task. Many things that I would now implement in script were then implemented in C++, and vice versa. This is an ongoing learning process.


There are other issues, but these are the main takeaways from my high-level strafing run.


It was a fun little excursion. It reminded me of who I was, back when I thought I had, just, a whole lot to say. I was full of energy, full of excitement. There were a thousand articles I was going to write, talking about all the things I was learning and figuring out, articles that I just never seemed to get to because I was always off chasing something else. It was fun to revisit that. Much of that excitement has faded, now. I no longer feel like I really have a lot to say, or that anybody really cares what I have to say anyway. That's not fishing for comments or arguments to the contrary or anything, it's just the truth as I see it. While I still enjoy doing this stuff, it's not my main passion or focus anymore, and hasn't really been for quite some time. Every day that passes, the field/hobby/industry pulls further ahead of me. People are doing stuff now that we could only dream of back in the day. And with how my time is split between work, family, writing, woodworking, hiking, hunting and fishing, etc, etc, etc... Game development just doesn't consume nearly as much of my emotional and mental capacity as it once did. It's a melancholy thought.

Haste, slow, and refactoring

Posted by , 09 July 2016 - - - - - - · 1,910 views
goblinson crusoe, urho3d, rpg and 1 more...

As part of the AI and control refactor, I refactored the basic components that provide the turn-basedness for the unit controllers.


Previously, the turn system was controlled by 2 components: the Turn Scheduler, which runs scene-wide and provides a 'heartbeat' on a specified timer; and the command queue component per combatant, that listens for the heartbeat and synchronizes a unit's actions to that beat. This allows me to control the pace of the gameplay; by changing the interval of the heartbeat, the battle can be sped up or slowed down.


However, having the combat heartbeat controlled globally like this has some disadvantages. First is that unit actions only take place on heartbeats, so if the player selects a movement command, then the unit will not start to move until the start of the next heartbeat. This results in a noticeable hesitation, that gets worse as the battle speed is slowed down. Another disadvantage is that it might be useful to be able to speed up or slow down the update interval depending on certain factors, such as the presence of a haste or slow effect on the unit.


So, I refactored. The Turn Scheduler no longer provides the heartbeat. Rather, the heartbeat is encoded into the command queue executor component, which knows about any haste or slow effects on the unit. In addition to this refactor, I actually implemented haste and slow effects to test it all out.


Haste/Slow comes in 3 different flavors: modify movement speed, modify casting speed, and modify attack speed. Thus, you could have bonus to cast speed and penalties to attack speed, and the system correctly accounts for portioning out action points accordingly. Additionally, the movement/cast/attack speed modifiers are used to speed up or slow down the rate of animation for a particular action, as well as the duration of time it takes to complete the action. This change has the benefit that if a unit has a haste spell on to speed its movement, when it moves it will move across the map more quickly. While the animation speed and timings are visual-only, the haste/slow modifiers also affect the number of action points the unit is given.


For example, say you have a movement speed modifier of +25% and a cast speed modifier of +10%. If the action points are set to grant 10 points per unit in a given turn, then the unit will actually receive 12 movement points, 11 casting points, and 10 attack points. Using movement points will reduce the casting and attacking points proportionally. So in a given turn, the unit could take 12 steps, or cast 11 spells that take 1 turn to cast, or make 10 attacks that take 1 turn to cast, or some combination of miscellaneous attacks. (Actually, the unit receives 12.5 movement points, but the points are truncated.)


Posted Image


The three numbers next to the character portrait indicate how many points the unit can use for movement(green), casting(blue) or attacking(red).


I have made (and am still in the process of refining) some other changes to how player units control. Initially, a movement command was executed by selecting the Move button or pressing the hotkey 'm', which would display the movement preview grid. Left-clicking on the grid would initiate a move to the clicked location and hide the preview grid. Similarly, executing actions such as using a workbench or looting a tree required that the action button be selected or the hotkey be pressed, then the target clicked. It's kind of clunky. Even the simplest of actions, such as clear-cutting a grove for wood resources, requires just way too many clicks and keypresses to execute. So I have begun to refine things a bit. Instead of having to manually select move, you just click to move. If your unit is in a waiting state, then the move will be calculated and executed. Similarly with using/looting/melee attacking. Just click on the thing to loot or use. Also, I have added functionality to bind a skill to the right mouse button, so that skill may be used without the 'select skill, select target, left click' loop. Instead, right-clicking will execute the bound skill automatically at the mouse cursor location. I am still in the process of refining everything, and a lot of the UI hasn't really been updated to reflect these changes, but already the gameplay is much tighter and less clunky, by far.


Posted by , 22 May 2016 - - - - - - · 2,668 views
goblinson crusoe, urho3d and 2 more...

In between work, sleep, family, home and yard projects, and being sick (all the usual bull-shit cop-out excuses you have all come to expect from me over the years) I have been getting in some work on Goblinson Crusoe.

So, the Great AI Refactor has begun in earnest, and as I suspected it would, it has run its tentacles all throughout the project. Up til now, the enemies have all been running off a script object creatively titled TestRandomWanderAI, which obviously randomly selects between FireShotgun, Melee and Area Heal to cast, and which tries to approach GC if a path presents itself. (The class has, uh... grown, somewhat, since it was first created.) Creation-time conditionals in the class designate some loose class-ish tendencies to favor one or the other of the skills. For example, the red-skinned 'shamans' favor fire and healing, while the blue-skinned 'bruisers' favor melee. I've kept this TestRandomWander component around, because inside it are templates and patterns for many of the actions an AI needs to perform. But it is no longer instanced into any of the enemies. In its place is the more generic AIController component.

AIController supplies a healthy set of generic functions (many of them derived, with refactoring and improvement, from the patterns inside TestRandomWander) that provide common functionality. However, instead of the hard-coded think() method of TestRandomWander, the AIController must be supplied a think() function at creation time. Much of the hard work of abstracting out the think() process was already done; I just needed to formalize the structure of the AIController, and re-factor out some of the behavior into separate think() functions supplied when instancing a mob of a given type. I've now finished specifying the existing shaman, bruiser and looter AIs, as a proof-of-concept for myself, and the new system works well. A great deal of 'test code' and placeholder stuff has vanished, in favor of more flexible, more 'real' code.

However, some expected issues are rearing their heads. So far, I have been operating on a very loose, ad-hoc 'design' for the combat system, as regards stats (which stats? what do they do?), buffs, how damage is specified in skill casts, and so forth. I have purposely kept the system flexible. I don't, for example, have a specific set of damage types in play. Damage types are possible, and I use quite a few of them. For example, all the test fire skills deal some measure of fire and siege damage. And some of the test equipment confers bonuses such as +fire_attack and -siege_defense, as a proof of concept. But any given skill could specify any kind of arbitrary damage type if desired. You could conceivably have a Fireball that does 16 to 47 base points of "happy silt stuffing" damage. This damage would apply, taking into consideration any "happy silt stuffing" attack and defense bonuses, if any. The flexibility has been nice, but I think it's probably time to start nailing down concrete damage types and thus simplifying the combat resolution code.

Additionally, with the addition some time back of the Melee skill, I ran into a conflict. When I first started fleshing out the skill system, along with the set of test skills, I imagined the skill system operating such that each skill had a different specification for each rank of the skill, and the damage ranges for the skill would be encoded in the data description for the skill. For example, a Fireball with one single point (assuming a Diablo 2-style skill point advancement system, as I currently envision) might do 3 to 6 damage, while a L2 Fireball might do 7 to 10. Numbers are for illustrative purposes only. I would list the various ranks of Fireball in an array, index it by the caster's Fireball skill level to get the proper one to cast. Segregating it by ranks this way more easily allows me to add extra 'juicy bits' to higher-ranked skills. For example, adding additional bounces to the bouncing fireball spell, adding some kind of DoT ignition debuff at higher ranks, etc...

The system has worked well, but Melee plays differently. In fact, in most games of this basic nature, Melee plays differently. It makes sense to encode the base damage of a fireball skill in the skill itself, and buff/debuff the final values based on caster stats. However, the base damage of a melee swing typically is determined by the equipped weapon, rather than the skill itself. And so, just like that, the addition of Melee has caused a necessary re-design. All of a sudden, for one particular special skill, I have to obtain the base damage ranges from somewhere else other than the skill description itself. It's a relatively minor issue on the face of it. Just call (?) and obtain melee ranges, where (?) is... And there we have the next issue. I don't have the formalized equipment system in place yet. I have A equipment system in place, but not THE equipment system. The test weapons in place can do things such as switch stance and apply stat buffs/debuffs, but that's it. There is nowhere I can currently call up to ask "what is my base damage range for a melee swing?" That ? just doesn't exist yet. And so, with the melee controller, I have added another set of hard-coded test values, to my shame. I need that melee-ing bruiser to do SOMETHING when he swings. So I hardcode some values, and add yet more items to the TODO list.

Still, these kinds of small-but-annoying issues help to refine the various interfaces between components that make up the guts of a game like this. A simple question of "where do base range values come from?" can have far-reaching implications in your design. So many factors can affect it--weapon type, character level, skill level, etc--and all of these factors can possibly live in different places depending on the structure of your combat-enabled entities.

Additional things have also cropped up in this refactor. At any given time, I have more units on the battlefield now than I used to, and a lot more 'things' happening around the map. So my non-polished UI is really starting to show its cracks. I made a few enhancements, but I still have far to go. For example, I added the capability of highlighting the currently-hovered unit with a halo color of green for friendly units, red for enemies, and yellow for neutrals, in order to help make decisions on the battlefield:

Posted Image

This really helps, especially since I'm using the same goblin model and skin(s) for so many different unit types, both friend and foe. However, I still have not updated the hover UI panel to show things such as health, class/unit type, etc... all the vital information you need to decide whether to approach a unit or run away. (The UI, I fear, is going to continue to haunt me for a long time.)

Another thing that has cropped up is a big one that I have been putting off for awhile: performance. In fact, I've been systematically making this problem worse, with my shader-based shenanigans. But the performance is really starting to become critical now, as I add more units, more lights, and just more stuff in general, to the play field. I've made some preliminary stabs at optimizing things: LoD models and materials, tweaking draw distance to strike a happy medium between tactical awareness and performance, etc... But I am still hitting low double-digits to high single-digits framerate on a depressingly consistent basis, and the visual lag and general unresponsiveness makes it feel janky and rough.

That quad-planar shader, with its 32+ texture samples per light in forward rendering, is just one expensive heavyweight bastard, and I really need to start figuring out how to take back some of my framerate. For now, I have 'disabled' the quad-planar texturing on the vertical surfaces of the hex cells; those are now simply wrapped in a standard diffuse+normalmap texture, with only the cap still receiving the quad-planar. The long and short of it is, the quad-planar shader is probably going to go away. In it's place, I am working on a shader that blends from the diff+normal of the hex pillar's vertical surfaces, to the existing texture blend that occurs on the tops of the hexes. I have decided it probably isn't necessary to permit more than one stone/dirt type on a single hex cell, as long as the 'interesting' blending occurring on the tops remains. And by making that simple adjustment, I can eliminate a metric fuckton of texture samples per light. Even with just the temporary placeholder material with a single rock texture, I have already gained back enough framerate to put me back in the high 20s, low 30s on a fairly consistent basis, and with more LoD refinement I can certainly get even more. Hopefully, I can manage to keep the look-and-feel I have enjoyed so far (if with a few limitations that didn't exist before).

Anyway, just thought I'd drop a few lines to let folks know I am still working on GC, if slowly and extremely painfully.




Posted Image


Some fun things in this shot. I added a code path to these goblin types to enable them to build bridges in order to reach Crusoe. These particular guys are Fireball casters, and I gave them Chain Fireball as well. In this shot, 3 of the mobs started building bridges to get to GC, then one of them randomly chose to cast a Chain Fireball at GC. The chain struck the other two goblins, as well as obliterating a number of already-constructed bridge sections, and triggered a 3-way brawl between the goblin and his now-pissed-off colleagues.


AI is fun.