Dungeon Crawler Dev Diary

Make games! Discuss those games here.

Moderators: Bob the Hamster, marionline, SDHawk

User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Dungeon Crawler Dev Diary

Post by Mogri »

I've wanted to make a first-person dungeon crawler -- a game in the vein of Wizardry, Ultima, or Bard's Tale -- on and off for a long time. On the recommendation of several friends, I recently played Stranger of Sword City. It's a great game; I recommend it. It's inspired me to get off my rear and finally try my hand at it.

The first big obstacle was to figure out how to render the dungeon. This is a genre that has existed since the 1980s, so it's not like this is a new problem, but the early crawlers didn't animate your movement. When you stepped forward, your position updated instantly.

Image

This makes it hard enough to get a feel for where you are when the walls are distinct, as in the image above, but the dungeons typically have indistinct walls, and in some cases, moving could give no visual feedback at all. This makes it very hard to tell where you are. Now, arguably, that's part of the appeal... but that's not what I'm going for.

WARNING: The rest of this entry is very technical. If you are more interested in the design side of things, tune in next time.

Attempt 1: JS Raycasting

Raycasting is a technique that projects 2D images into a pseudo-3D space. The original Wolfenstein 3D and Doom are the best-known early examples of the technique, and while those games' visuals haven't necessarily aged well, the technique is still solid.

This tutorial gives you a very competent JavaScript raycasting engine. It's got a good framerate, and it looks pretty nice. It even has some visual effects: the rain, lightning, and lighting are all very nice.

This raycaster would work fine for what I wanted, though I'd have to add in ceiling and floor, but I didn't want to work in JavaScript. It's great if you want to make a browser game, but it's harder to package into a standalone game.


Attempt 2: Porting JS Raycaster to HaxeFlixel

So the next thing I tried was porting the raycaster to HaxeFlixel. Haxe is a sort of metalanguage that can compile into a wide variety of languages, and Flixel was, I guess, some large part of ActionScript, the language used to make Flash games. HaxeFlixel is a Haxe game engine based on Flixel, and it has a lot of nice features and good community support.

But HaxeFlixel is a 2D game engine. That's not, strictly speaking, a big problem: raycasting is a technique that predates 3D hardware acceleration. However, I ran into a large issue porting the JS raycaster: the way it worked was to render vertical slices of the wall image, scaling them to the appropriate height. Flixel lacked the support for this. It was a no-go.


Attempt 3: HaxeFlixel Raycasting

I didn't want to throw in the towel on HaxeFlixel just yet, so I dug around until I managed to find an ActionScript raycasting demo. The fact that it was so difficult to find one should have been a tip-off here, and I can't find the link to it anymore.

The source code for this was terrible. It took me a long time to decipher what the variables were supposed to do, and the entire thing was built around the assumption that your texture images were 64x64 pixels (for example, there was a lot of bitshifting by 6). The hardest part of this attempt was figuring out what in the world the code was doing.

Image

But hey -- it worked! Unfortunately, it worked at an absolutely miserable framerate, somewhere between .5 and 2 FPS depending on what was onscreen. See, what the code was doing was raycasting individual pixels rather than vertical slices like the JavaScript example. It worked fine at the demo's 320x200 resolution (which I guess should have been another tip-off), but I was running at 800x640, more than four times the pixels. The framerate was a bit better when I compiled to Flash, but I didn't want to make a Flash game, and it still wasn't a good framerate.


Interlude

I wasn't sure where to go from here. Raytracing looked like a no-go in Flixel. I tried for a while to reproduce the vertical slices raytracing, but it ended up giving me worse results than pixel raytracing somehow.

I considered a few other approaches at this point. I could go with a 2D-simulating-3D approach, like the games from the 80s did. To get animation out of it, I'd have to create individual graphics for each texture for each frame of animation. This almost didn't sound so bad until I considered rotation. When the player turns left or right, the textures would have to turn, too.

Eventually, I decided that HaxeFlixel wasn't going to work. I had wanted to avoid a 3D game engine (3D is hard!), but I was willing to consider it at this point.


Attempt 4: libGDX

Some amount of searching led me to a Java framework called libGDX. At this point, I should explain what a game engine does for you.

On one end of the spectrum, you have engines like the OHRRPGCE where you insert assets (i.e. graphics and music) and the engine takes care of the rest. These are by necessity very specific: the OHRRPGCE creates RPGs. That's a little reductive, obviously: you can script whatever kind of game you want.

On the other end of the spectrum, you have pure Java, C++, C#, whatever, with no engine at all. You can often find libraries to accomplish very specific tasks, but what you gain in freedom you lose in automation.

Most engines land somewhere in the middle. You can expect a game engine to help you display graphics, play sounds and music, and usually provide some sort of collision detection and other physics. In general, engines are either intended to create 2D games, like Flixel or the OHRRPGCE, or 3D games, like Unity, Unreal, and so forth. You could create a fully 3D game even in the OHRRPGCE, but you'd have problems like the ones I had in HaxeFlixel. It wouldn't run well. In the same vein, I've heard that it's surprisingly difficult to create a 2D game in Unity. I didn't really understand why before fooling around with libGDX.

libGDX is not so much an engine as a collection of Java libraries. That makes it sound worse than it actually is, since those libraries provide tons of utility, and it gives you the tools to make 2D or 3D games, or even to mix the two. (Even in a fully 3D game, you usually want a HUD or other 2D overlay, so a fully 3D engine would still need some 2D support.)

libGDX required a few paradigm shifts. The obvious one is that I was now working in actual 3D rather than raycasting. The less obvious one is that I was no longer working in pixels. This makes sense: 3D space isn't made of pixels, but from a certain point of view, neither is 2D space. It took me a few hours to get a grasp on this and make some simple surfaces show up.

Image

But show up they did, and I even got the early workings of a HUD going. (Thanks, opengameart.org.) Since this was a tech demo, it's not interactive, but it does what I need it to do, and it runs smoothly. I'm going to call this a win.

And now that I've resolved my primary technical concerns, I can turn my attention to what makes the game fun.




Appendix: More about these engines

I went into this accepting that I would have to write a large chunk of the game from scratch. I know of exactly one engine for first-person dungeon crawlers, and it's old enough to drink and not very flexible, either. If you're hoping to minimize the amount of code you write, stick to engines like the OHRRPGCE.

If you're OK writing a lot of code, HaxeFlixel is great. It's based on a well-loved language, it's entirely open-source, and it's got an astounding amount of community support. If you're looking to make a 2D game, take a look at HaxeFlixel. Notable games written in HaxeFlixel include Defender's Quest and its upcoming sequel. Every Flash game you've ever played could also be easily ported to HaxeFlixel.

libGDX is a little lower-level than HaxeFlixel. In HaxeFlixel, you put stuff on the screen. In libGDX, you set up a camera and render the individual objects. You're also responsible for disposing of objects when you're done with them. On the plus side, since this is Java, the IDE support is unmatched, and you get lots of extra capabilities outside of the already strong core libraries. Even so, I have a hard time recommending libGDX for general purposes. If you want a 2D game, go with HaxeFlixel; if you want a 3D game, why aren't you using Unity? But libGDX happens to cover my specific use case: I don't actually want a 3D game, but I happen to need 3D anyway. The best-known libGDX game to my knowledge is Delver, which is also a weird mix of 2D and 3D.
Last edited by Mogri on Fri Feb 03, 2017 4:58 pm, edited 1 time in total.
User avatar
Bob the Hamster
Lord of the Slimes
Posts: 7658
Joined: Tue Oct 16, 2007 2:34 pm
Location: Hamster Republic (Ontario Enclave)
Contact:

Post by Bob the Hamster »

This was really cool to read, thank you for sharing it! :)
User avatar
kylekrack
Liquid Metal Slime
Posts: 1240
Joined: Mon Jun 16, 2014 8:58 am
Location: USA
Contact:

Post by kylekrack »

I really appreciate the detailed explanations of this process. I actually learned quite a bit from it all; I know nothing about 3D.

Delver is real fun, and runs really well. If that's the vein of game you're looking into, I look forward to checking it out.
My pronouns are they/them
Ps. I love my wife
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

I'm not sure if this is what you were asking, but Delver is kind of a FPS-roguelike, which isn't what I'm making. For an example of a first-person dungeon crawler, check out Pheonix's Festivus. The big difference between that and a traditional game is that Festivus has predefined characters; usually, this sort of game has the player create his own party. I'm going to do that here, too, but I'll talk more about that in a future post.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

20 years ago, the last remnants of humanity gathered together to wage a final, desperate battle against the forces of Bal the betrayer.

They lost.

Bal's men killed all those who fought, then they killed all those who had helped. Those who remained -- largely those who were too young or too old to raise arms -- were gathered into what was left of the walled city of Cad Anam, never to venture forth again. Every year or so, a small army came to check on us, to ensure we were never again able to mount another resistance.

But six years have passed, and no one has come. And so we have begun to explore the utterly hostile landscape that surrounds our prison-home. What has become of our oppressors?



This is the story of the small group that survived to live with the consequences of their ancestors' great mistake: to forsake the Old Gods and worship Bal, who promised them wealth and prosperity but in the end brought only slaughter. They must uncover secrets best forgotten and tread paths that have gone generations without a footprint, all so they might learn the fate of mankind -- and perhaps alter it.

Few remain who remember the ways of the Old Gods, and many of the ordinances and supplications are lost to time. And yet, the Old Gods have their hands in these workings. Humanity may have abandoned them, but they will need them now more than ever.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Let's talk about map encoding.

Okay

Looking way forward into the future, it'd be nice if the game were moddable. To achieve this, I have several goals for the map encoding:
  • The data and metadata should be easily human-parsed, ideally in a standard format.
  • The map itself should be easy to read, since I don't plan to distribute a level editor.
The obvious answer to #1 is just to do the thing in XML. What about #2? That's a little trickier.

One possibility would be to render the entire dungeon in ASCII. That would look something like this:

Code: Select all

.....
+---+
|...|
+-+.|
..|.|
But that's pretty wasteful. About half of those characters are essentially meaningless -- the corners where the walls meet can be assumed. Wasting characters doesn't bother me except insofar as it makes editing the level take twice as long as normal. We could express the same level in far fewer characters:

Code: Select all

._._..
|_..|.
..|.|.
We sacrifice a small amount of readability (although it's not even immediately obvious in this example, which looks just fine) for a vast reduction in wasted characters. Here's a sample of what a full map file looks like:

Code: Select all

<maps>
	<map name="Streets of Cad Anam" w="22" h="22">
		<walls>
			|...|.._..|.._._......|.+_|+|+|+|_..|. _._..
			|_.>|...+_|.|.|.|.|.|.|.|_+.....+_|.|.|_..|.
			|......_|_..|.|_..._|.|.|_+.....+_|.|_._._|.
			|.|_.+._._._._._._._._|.|_++.+.++_|.|.._._._
			|.|...|_..._._._._._..|...|_|.|_|...|.._....
			|.|_._._|.|.+.+.+.+.+.|.....|.|.....|.._._|.
			|.........|.+.+.+.+.+.|_._._|.|_._._|_._.+|.
			|.........|_+_+_+_+_+_+.....|.|...+.|...|_|.
			|...................|......_..|...+.|.._._._
			|...................|.......|.....+.|_._._..
			|...................|_._._._|.....|.+_._._|.
			|...................|...........|...|.._._._
			|.._._._._._._._._._|.......|.._|...|_._._..
			|.._._._._._._._._._........|.._|.....|...|.
			|.....|.............|_._._._|.........|.|.|.
			|.|.|.|...............................|.|_._
			|.|.|.|...............................|_._..
			|.|.|.|...........................|.|...|.._
			|.|.|.|.............................|_|.|_..
			|.|.|................................_._._._
			|.|_._..............................|.+.|...
			|_._._?_._._._._._._._._._._._._._._+_|_+_|_
		</walls>
	</map>
</maps>
Format not finalized. The XML schema will probably change.

Now, the two wall glyphs we've been using all along (| and _) are really just the same wall -- we're using them as a sort of syntactic sugar to make the map easier to look at. We've added a few new characters here:
  • + for doors. These are opaque walls that you can pass through.
  • ? for secret doors. These are doors that look like walls.
  • > and < for one-way doors. These are doors from one direction and walls from the other.
The example map used above is the first floor of Dark Domain from The Bard's Tale II, at least until I got tired of filling it in. Going back to what I said about lack of readability, you can see that the closets running along the top right of the map are less obviously closets in the ASCII version, but if that's as bad as it gets, I can live with that. The bigger discrepancy is that the maps in Bard's Tale and Wizardry all wrap around, which I haven't decided whether I want to do here.

This isn't purely theoretical: the game is able to load maps from the file already. Here's our intrepid Joe Dungeonman walking up the bottom left side of the map:
Image


So far, so good -- but what about making the dungeon more visually interesting? Right now, our map conveys information about the walls, but what if we want one wall (or floor... or ceiling) to look different from the rest?

This is a problem I haven't actually tackled yet, but I have a few ideas.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Let's talk ab--
Let's talk about classes

Right. First-person dungeon crawlers are almost synonymous with some sort of class system. I can't think of a single commercial FPDC that doesn't have you assign classes to your characters.

These are handled in a variety of ways. On the one side of the spectrum, many older games lock characters into a single class for their entire lifetime. That's not very exciting from a character customization perspective, but the focus is on party composition. (Then again, many of these games don't offer a ton of flexibility in terms of party composition, either.) In some cases, like the Etrian Odyssey games, you can customize characters within their classes.

On the other side of the spectrum, there are games that let you freely reclass your characters, usually inheriting some abilities or other properties from previous classes. This offers a lot of flexibility early on, but the downside is that your party can end up very homogeneous. In Wizardry games, for example, you eventually want everyone to learn spellcasting.

Ideally, I'd like to have it all -- interesting character customization, important party customization decisions, and distinct characters throughout the game. So here's the approach I'm taking.

Each character is created with a single class, and you can't class change. However, upon reaching level 11, they can take a subclass, and thereafter, they're treated as being level X in their main class and level X-10 in their subclass. They get all of the abilities and equipment options of both classes.

Let's take an example. With a Wizard, I can double down on spellcasting by turning him into a Wizard/Priest, give him access to heavy armor by making him a Wizard/Sentinel, or give him a good attack option on turns he's not casting by making him a Wizard/Marksman. Within each class, new abilities are unlocked by level, so a Wizard/Sentinel is very different from a Sentinel/Wizard through much of the game, even in the unlikely scenario that you put the same stats on both.

There are tentatively nine classes and only six party slots, so you probably won't want to double up like that anyway. We'll talk about the individual classes in later entries.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

So what's the plan here

I have two motivations for running this thread: on some level, it's here to keep you informed, but the main one is that it's here to keep me on track. I don't intend to work on the game every day, but I do intend to keep working on it, and to that end, I want about two articles per week here.

This is where you can help. If I start falling off the wagon, it is your solemn duty to prod me back into activity. In the meantime, if there's anything you'd like to read about or any questions you have, you are very welcome to throw them down here. Your interest in this game is also helpful in motivating me.

As more of a soft rule, I've been alternating between technical and design in these articles. It's not the long-term plan, since I strongly suspect I will be writing code long after I run out of interesting ideas. (I will also probably be writing code long after I run out of interesting article topics, but we'll cross that bridge when we get there.)


Anyway, how about that game

In the original post, I mentioned that 3D is hard. "3D is hard!" I said. What makes 3D such a pain?


Asset creation

The obvious one is that it's harder to create 3D models than 2D graphics. I can open up MS Paint and make something recognizable in a couple minutes. Now, you might reasonably point out that I am not going to produce professional-grade 2D graphics, either, but I know a lot more people who can. I wouldn't even know where to start looking for a modeler.

Fortunately, while it's not exactly a nonissue in my case, it's as little of an issue as possible. It was the plan all along to project 2D textures onto 3D walls, and everything else -- monsters, portraits, and so on -- is still 2D.


3D space

In a 2D game, you refer to something's position onscreen in terms of (X, Y) coordinates. Just like in math class, X goes left to right and Y goes up and down. Simple!

In 3D, you need (X, Y, Z). X still goes left and right (to the extent that those words still mean anything), but now, "up" and "down" mean two distinct things. Is it Y or Z that determines how high something is off the ground? It's not immediately intuitive. Hamsterspeak lets you set npc z to make something float off the ground, but in this case, it's Y.

Once again, I am fortunate in that I need to worry about this very little. A dungeon map is essentially two-dimensional, and so there are only two Y-coordinates that I care about: 0 for the floor and 1 for the ceiling. Further, I can abstract away the third dimension so that I only have to figure out how it works once. I can forget after that (and already have).

Meshes, quads, vertices, and indices

The real problem with 3D is that it's an enormous paradigm shift. I'm dealing with a whole new paradigm here. What do those words mean? I could guess what vertices are based on years of math study, and I've heard the words "mesh" and "index" in other contexts, but that's about it.

It took quite a long time to get walls to appear. It took much longer to figure out how to make walls not all look the same. If you don't understand that, then consider the following code:

Code: Select all

    public Wall&#40;Sprite front, Sprite back&#41; &#123;
        assert&#40;front.getTexture&#40;&#41; == back.getTexture&#40;&#41;&#41;;
        front.setSize&#40;WALL_WIDTH, WALL_WIDTH&#41;;
        back.setSize&#40;WALL_WIDTH, WALL_WIDTH&#41;;
        front.setPosition&#40;front.getWidth&#40;&#41; * -0.5f, front.getHeight&#40;&#41; * -0.5f&#41;;
        back.setPosition&#40;back.getWidth&#40;&#41; * -0.5f, back.getHeight&#40;&#41; * -0.5f&#41;;
        vertices = convert&#40;front.getVertices&#40;&#41;, back.getVertices&#40;&#41;&#41;;
        indices = new short&#91;&#93; &#123;0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4&#125;;
    &#125;
That's the Wall constructor. I still don't understand that last line, though mercifully I could at least tell you what the rest of it is doing. Now, this wall is nothing more than a series of numbers. It doesn't even store the sprite information (though it does keep some information about which part of the texture map it needs to use). That's all upstream in the mesh builder.

At the end of the day, while the dungeon is being rendered at runtime, it could technically be built out in a 3D modeler. The result in either case is a mesh. We supply the mesh with a texture map to make it look like something. The walls themselves are quads, which is just short for quadrilaterals, and the Wall vertices represent the coordinates of the quad's corners in 3D space.

I'm sure this is all 3D 101, but I knew none of it two weeks ago, and I still don't understand what those indices are for.

Overlapping

Put it all together, and I had some problems to sort out. See, I was treating the walls as lines on a grid. It works great in theory, and it very nearly works great in practice, except for one tiny detail: walls that intersect at a T clip just a bit.

It's not too hard to figure out why this is happening. The very edge of all three walls occupies the same space. When that happens, the result is indeterminate. I'd like it to show a continuous wall when you're on the other side, but occasionally, you can see pieces of the third wall as you move.

Finding a solution for this forced me to actually figure out what I was doing to some extent. I couldn't shorten the walls, since then I'd have the opposite problem: you'd be able to peek between them from time to time. I tried for a long time to turn them from flat surfaces into thicker walls. Unfortunately, a quad has no thickness. I would need to turn every wall into four walls, and I'd have other problems at that point, too.

I ended up settling on a sneaky solution. See, every one of these flat walls is actually two quads: the back and the front. You will never see both of them at the same time, but they both exist, and they technically exist independently of each other. Think of a playing card. We think of it as a single surface with no thickness, but we could split it down the middle and have the front and back serve as two separate pieces.

So I split the walls very slightly so that they angle away from each other. This doesn't manifest visually, because the change is so slight, but even an epsilon change is enough to make it so that the walls no longer overlap. Problem solved!

...And now I just hope I don't have to deal with anything else like this.
User avatar
Bob the Hamster
Lord of the Slimes
Posts: 7658
Joined: Tue Oct 16, 2007 2:34 pm
Location: Hamster Republic (Ontario Enclave)
Contact:

Post by Bob the Hamster »

Nifty stuff!

Code: Select all

        vertices = convert&#40;front.getVertices&#40;&#41;, back.getVertices&#40;&#41;&#41;;
        indices = new short&#91;&#93; &#123;0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4&#125;; 
Mogri wrote:... I still don't understand that last line ...
Ooh! I know what that last line is!

I have been doing some procedural mesh generation in Unity3d lately, and I have run across the same stuff.

That list of short integers are indexes into the list of vertices you created in the previous step.

Each set of 3 defines the three corners of a triangle.

So your quads are probably made up of triangles that look like this:

Image

If you move your front and back a little further apart from one another, you could add four more triangles for the edges of the walls to give them real thickness
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Post by Pepsi Ranger »

Finally read through this entire post. Great ideas here.

One thing that comes to mind, even though I'm probably a little late in bringing it up, Duke Nukem 3D used a dedicated engine called the Build Engine that was quite easy to use, had 3D environments and 2D sprites that rotated to face you wherever you stood, and allowed for a simple placement method of objects, sounds, etc. What I liked about it was that it involved no coding (that I can recall). It meshed 2D map design with 3D visuals, and allowed you to choose sprites from a sprite sheet, and was pretty easy to mod if you had a good painting program and some sound files. It's only drawback was a lack of overlapping 3D space, but solid level design and trickery made it so that it wasn't needed.

I know you're not trying to work with a 3D FPS engine, but I wonder if there are any open source engines out there that work similarly to Build, while allowing for the custom variables, strings, etc. that you'd need for your game. I just remember it being one of the most user-friendly engines of its time, and other than the occasional crash, was pretty robust in what it could do. It was a level editor at its core, which you say you're not looking to implement for your own game, but it was a strong gateway for those interested in game design without programming skill. It was what helped me get a feel for game design, several years before I found the OHR.

I also bring it up because your screenshots look very similar to the stuff that the Build Engine produced, and I wonder if it makes sense to use an engine that keeps the look while minimizing the difficulty in producing it.

The major disadvantage of an engine like that, as far as I know, is the lack of modifications to the code itself. But I'd think 20 years later, someone would have come up with a similar engine with greater customization and easier interface. And if not, maybe you could be that someone who...never mind.

That said, props on the development journal. You have a good system in place. Keep it up.
Place Obligatory Signature Here
User avatar
Taco Bot
Meat, Cheese, and Silicon
Posts: 484
Joined: Fri Jul 18, 2014 12:15 am
Location: Santa Cruz
Contact:

Post by Taco Bot »

The build engine is super cool because its fake 3d, in the vein of Wolfenstein and Doom (just more advanced). That's why it can't render areas above other areas. (Though the later [closed-source :( ] versions of the engine used for Blood and Shadow Warrior implemented some neat trickery to allow this)
Sent from my iPhone
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Bob the Hamster wrote:Ooh! I know what that last line is!
That is very helpful. Thank you!

The principal problem with adding thickness is that I would need a side texture. That's not a huge obstacle, obviously, and I think I will eventually want to do it. We'll see.
Pepsi Ranger wrote:I know you're not trying to work with a 3D FPS engine, but I wonder if there are any open source engines out there that work similarly to Build, while allowing for the custom variables, strings, etc. that you'd need for your game.
I wouldn't doubt it, but I do think the level of effort involved to wrangle it into doing what I want would outweigh the convenience it would provide. And the nice thing about writing my engine from scratch is that I can make it do exactly what I want.
Last edited by Mogri on Fri Feb 17, 2017 5:56 pm, edited 1 time in total.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Today, let's talk about one of the game's signature battle mechanics.

Evocation

If you've played the SNES Lufia games, you're probably familiar with the IP system they use. It's a meter that charges vaguely like a Limit Break meter, and you can consume it to use special abilities based on your equipment.

I'm going to use a modified version of that system. Rather than simply charging when you deal or take damage, each character will charge based on different conditions depending on their class. Certain classes are more adept at evocation than others, and some classes generate very little evocation power.

Here's why evocation is great for this battle system:
  • It's renewable. It's going to be very hard to restore MP, but evocation gives your mages something to do without having to spend limited resources.
  • It's flavorful. Axes and swords can actually be functionally distinct rather than simply having different numbers and availability.
  • It differentiates classes. Martial classes tend to blur together, especially when they spend most of their turns using the Fight command. Giving different classes different goals for gaining evocation power helps them to play differently.
  • It adds an interesting design space. Two of the character classes are designed around using powerful evocation abilities. They may not get exclusive access to those abilities, but they can use them more often and/or more powerfully than the alternatives.
  • It makes less interesting equipment more exciting. In most RPGs, you choose your casters' weapons based exclusively on how much it boosts their magic attack -- it's not like they'll be swinging that stick anyway. When they use their weapon to get a "free" spell, it's much more meaningful. The same goes for other stat sticks; wait until you see what shields can do.
  • It makes weaker equipment more valuable. Daggers are the weapons you use when you can't equip anything better, right? But an evoked dagger can backstab, piercing armor and dealing critical damage.
  • It makes special equipment more special. That new sword doesn't have any more attack power than the one you're already using, but you've never seen its evocation ability before...
  • It adds more interesting decisions for dual-classing. You can subclass your Sentinel as another martial class to make it a better damage dealer, or you can go Sentinel/Evoker for lower HP but better and more frequent shield evocations. And that's just the tip of the iceberg for class combinations.
In short, evocation is going to play a central role in battles, and it's going to be awesome.
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Abstracting 3D to 2D

While the player might move from one floor to another, all that's happening there is that we're changing maps. In reality, the player is traversing a 2D dungeon, and if the player doesn't need the third dimension, then neither do I. I just need to convince the code of that.

Fortunately, I'm already close, despite not having written that much code. The application already reads in a two-dimensional map and projects three-dimensional walls. The only other layer of abstraction needed is the camera.

In a game like this, the camera is frighteningly close to being the same thing as the input processor -- or, in other words, most of the player's button-presses correspond directly to camera actions. It might seem tempting to put that input handling directly into the Camera class, but it's a bad idea:
  • The Camera would need to know about the Map in order to keep the player from walking through walls. This is undesirable.
  • The input handling would be spread across the code. The "open menu" handler would have to live somewhere else. Messy, though not the end of the world.
  • The real clincher is that pushing the up button doesn't always mean "move forward." Sometimes it means "move the menu cursor up."
Still, we can write the Camera class to expose actions that we'd link to button-presses, and by doing so, we give it a very nice interface to the classes that need to use it. First, we define a Direction enum, containing NORTH, SOUTH, EAST, and WEST, and we give it some utility functions:
  • getAngle() returns the angle of that direction.
  • getX() and getY() return the... uh... direction of the direction.
  • getLeft(), getRight(), and get180() return the directions to the left, right, and back of this direction.
Java enums are great, you guys. That lets me do this:

Code: Select all

    public void moveBack&#40;&#41; &#123;
        moveToX -= facingDirection.getX&#40;&#41;;
        moveToY -= facingDirection.getY&#40;&#41;;
    &#125;

    public void moveForward&#40;&#41; &#123;
        moveToX += facingDirection.getX&#40;&#41;;
        moveToY += facingDirection.getY&#40;&#41;;
    &#125;

    public void moveLeft&#40;&#41; &#123;
        moveToX -= facingDirection.getLeft&#40;&#41;.getX&#40;&#41;;
        moveToY -= facingDirection.getLeft&#40;&#41;.getY&#40;&#41;;
    &#125;

    public void moveRight&#40;&#41; &#123;
        moveToX -= facingDirection.getRight&#40;&#41;.getX&#40;&#41;;
        moveToY -= facingDirection.getRight&#40;&#41;.getY&#40;&#41;;
    &#125;
Why "moveToX" instead of just "x"? My motivation in writing my own camera class was only partially to abstract away the 3D -- it was also to handle moving the camera around. So we keep track of where the camera is as well as where it's going. Every time the camera updates, it moves towards the destination just a bit. Rotation is handled the same way.


Aside: The update function

At their heart, every game's programming boils down to this:

Code: Select all

while&#40;true&#41; &#123;
    handlePlayerInput&#40;&#41;
    updateGameState&#40;&#41;
    renderGraphics&#40;&#41;
&#125;
Or at least that's what games used to look like. Way back in the day, hardware was a lot slower, and you could count on a computer running at a given speed, give or take a bit. These days, most games try to run at 30 or 60 FPS, and so there's a delay built into each loop. (If you're implementing a fully-scripted game in Hamsterspeak, you'll have some version of that loop with a wait(1) somewhere.)

There are basically two ways to handle The Loop:
  • Cycle-based. Every N milliseconds, call The Loop, and update the game state by the same amount each time. This is how the OHRRPGCE works. When the game gets bogged down, it runs more slowly than usual, but you still get every frame.
  • Time-based. Every N milliseconds, call The Loop, and let it know how long it's been since the last call. Update the game state based on that time. This is how most modern games work. When the game gets bogged down, the framerate plummets, but the frames you get are the ones you'd get if the game were running at full speed (except in really bad cases).
There are advantages to each approach. It's easier to synchronize everything in the game when you can count on one frame being equal to one unit of game time. On the other hand, that really messes up games where timing is important -- for example, every real-time, online multiplayer game ever. On the other other hand, if timing your input is important, then you have different problems with each approach: either the player has to adjust to the lag, or the player has to play semi-blind, unable to see the dropped frames.

The platform I'm using, libGDX, supports a time-based cycle, which in practical terms just means it tells me how much time has passed since the last update. It's very easy to change that to cycle-based -- you just ignore the time parameter and go on your merry way -- but it's a lot harder to turn a cycle-based loop into a time-based loop.

Since it's available, I am indeed using a time-based loop, and to tie it back into our other topic, here's what that means for the Camera class:

Code: Select all

    public void update&#40;float delta&#41; &#123;
        final float moveDelta = delta * movePerSecond;

        isMoving = !isRotating && &#40;position.x != moveToX || position.z != moveToY&#41;;
        if &#40;isMoving&#41; &#123;
            // move the camera moveDelta units towards the destination
        &#125;

        isRotating = !isMoving && facingDegrees != facingDirection.getAngle&#40;&#41;;
        if &#40;facingDegrees != facingDirection.getAngle&#40;&#41;&#41; &#123;
            // rotate &#40;delta * radiansPerSecond&#41; radians towards the intended angle
        &#125;
    &#125;
Next time: Maybe I'll actually handle input??
User avatar
Mogri
Super Slime
Posts: 4668
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Post by Mogri »

Difficulty

I could write a whole book on balancing RPGs for difficulty, but today, let's talk about adjustable difficulty levels.

In a standard RPG, creating multiple difficulty levels is a matter of numbers. The easy (read: lazy) way is just to scale everything by a certain amount. Maybe you double the gold and XP everything gives you; maybe you multiply their HP values by 1.2 to make it harder. You don't need to consider individual enemies or individual battles. You don't even really need to test the various difficulty levels, especially if you offer the ability to adjust the difficulty mid-dungeon.

The middle-ground method is to adjust enemy stats by hand for each difficulty level. This gives much better results than flat scaling, since some enemies or encounters might be difficult for reasons unrelated to their Attack or HP values. For example, metal slimes from the Dragon Quest series have very high evasion and defense, taking only one damage from anything that's not a critical hit, but they have single-digit health. Doubling their Attack wouldn't really change the fight (they aren't dangerous), but if you bumped their health up or down by even 1 or 2 points, that would change the fight considerably.

The high-effort way to balance difficulty is to not only adjust stats by hand but also give enemies attacks that they can only use on certain difficulties. Maybe Zeromus can't use Big Bang on Easy, or maybe enemies get "desperation mode" attacks on Hard. Mega Man 10 did this really well: all of the bosses get a new attack on Hard mode, and the level layouts change on Easy mode. In comparison, Mega Man 9 (despite being the better game in most senses) only scales numbers on the alternate difficulties, making them less satisfying to play.

"Less satisfying" is one good reason why most RPGs don't offer multiple difficulty levels, even if the lazy method would make doing so very easy.

Here's the thing

Dungeon crawlers derive their difficulty from a number of factors. You could remove all of the battles from some of them and still have a blisteringly difficult game. Their dungeons are just that hard to navigate. On the other end of the spectrum, there are games where the dungeon is just a setting, and the difficulty comes from the combat. (This is a bit of an oversimplification. Many of the older games have difficult combat at the start, and then once you get your legs, the combat difficulty falls off and the dungeons become nigh-impenetrable.)

A single difficulty setting in a FPDC is going to do either too much or too little. Adjusting battle difficulty without modifying the dungeon won't help if the dungeons are too hard for a player to navigate, but you'll find battles too easy if you have to reduce the difficulty in order to make it through the dungeon.

To that end, I'm going to offer difficulty settings for the following:
  • Combat difficulty. I don't know how I'm going to balance different combat difficulties, but I will at least have the framework to support doing it the right way. (If I end up getting lazy here, maybe the theoretical mod community will pick up the slack.) At minimum difficulty, it will be literally impossible to lose battles.
  • Dungeon difficulty. To some extent, this can be adjusted automatically: easy mode turns off spinners, reduces trap damage, and so forth. Again, though, I'll want the ability to handpick dungeon features based on difficulty level.
  • Automapping. Mapping takes a lot of the edge off of navigation. I'll offer the ability to disable it, display only the outline (walls, doors), or display everything (traps, teleporters, and so on). The minimap can be configured separately from the full map.
  • Puzzle difficulty. Maybe. I'm not totally sold on this just yet, but I'm thinking puzzles can be disabled at lowest difficulty, and fewer hints can be given at higher difficulties.
Most games can benefit from adjustable difficulty, but because dungeon crawlers have so many different ways of being difficult, it's even more important here. You can adjust the game to meet your expectations.
Post Reply