Bravewater - The Backstory, The Re-Code, The Future
Howdy there! To those of you that haven't heard of Bravewater before, welcome! And to those of you that haven't heard from me in a while - welcome back!
Previously I had been posting updates about this game I call Bravewater on its own website. I discovered itch.io today and found it's an amazing platform that offers literally everything I could ask for, so I'll be moving future updates here. Change your bookmarks if you've got em!
For those that had been following the old website - my last update was over a year ago. You're probably thinking "what happened?" Well let me tell you...after I catch everyone up on some history.
In The Beginning...
I started working on Bravewater in the winter of 2016. I had just wrapped up my senior year in college and decided I wanted to put my programming degree to good use. So I bought a Udemy course on Unity and got to work...
...the first pass at the game was rough. I had never programmed anything outside of little things for my classwork. I also had no clue what I was doing in Unity, so a lot of my code was a mess cut and paste from StackOverflow to do basic prototyping,
Eventually after about a year of work, I was able to get the hang of Unity. I had a general idea of what I wanted things to look like, and things started to come together:
After another year off and on, I managed to get a pretty decent looking prototype going:
And shortly after that, I got the battle system working:
All of this was between December of 2016 and April of 2019. After which I was...
Burnt.
Out.
Every time I thought about working on Bravewater I just shook my head and said "nooooooooooope".
And there were a lot of reasons for that.
It's Time For A Re-Do
This comic has always resonated with me ever since one of my professors showed it to me in college:
Do you see that monstrosity on the right?
That was Bravewater by the end of 2019.
Do you remember how I said I started this fresh out of college? And that I had never written anything larger than a class project?
Surprise: that means my software design architecture skills were about that of a toddler with a Little Tykes plastic hammer and a couple tins of Play-Dough.
*everything* was tightly connected together.
Movement code was deeply tied to animation code.
And the interacting with things code.
And the getting items code.
And the battle code.
It got to the point that I dreaded adding new features because it meant I'd have to go back and touch some part of the code spaghetti that I wrote when I just started, which worked perfectly until I would change one line, and then it would all break.
The dialogue system was so inflexible despite my attempts to make it more flexible. I had to hack the hell out of it to hide/show the NPC's name/image on demand, or momentarily hide the dialogue to show a different prompt, or make it do anything outside of talking to a NPC.
I could go on and on, but I'll save you the boredom.
TLDR: It was a disaster.
The Great Re-Write of 2020
So I set out to make a better foundation. I started from the ground up, except this time I was armed with the knowledge I gained from
A. 3 years of game development
B. 4 years of professional software development experience
So what did that get me to? Well let's start off with...
The UI
Okay, I know what you're thinking. "It looks way worse!"
But I have my reasons!
When I told my friend about my game development process, he told me "Why are you focusing on finishing something completely right away? Focus on just getting the basics done first and then you can always go back and clean it up later."
And he's absolutely right. I would spend weeks on something like dialogue getting the UI to look perfect and be perfectly animated. But why?
Well, in the case of the UI, I was using the Unity animation system to do its animations. That animation system is insanely inflexible. You pretty much have to get it right the first time, or lord help you if you need to change it.
I saw a YouTube video mentioning that you should animate your UI using a Tweening library in code instead. It offers much more flexible animations and more fine grained control over how your animation works.
And wow, what a change that was! I no longer had to get animations perfect right away because they were in code! I could make animations based off the size of a UI element, so I wouldn't even have to hard-code a size into the animation. That was an incredible change to my workflow, and that alone let me prototype things way faster.
So the UI is, as the friend put it, "'Among Us' inspired" for the time being ;)
Now let's move on to...
The Pathfinding
I lamented how I had to use a third party A* pathfinding library to make characters walk around obstacles. The library I was using was $100 in its paid version, which I needed as it allowed for asynchronous pathfinding so the game wouldn't lock up every time I needed to move a character.
$100 is a steep price to pay for a couple files of code!
I was able to use some Unity asset store credit I got for reporting bugs to Unity to get it for free when it went on sale one day, but I found even the paid version was still difficult to use and didn't improve the performance as much as I had hoped. I would have been really upset if I paid $100 of real money!
The same friend I mentioned earlier said I should just write pathfinding myself. That was a daunting task in 2017 - implementing a whole pathfinding algorithm? No way! But I thought "is it really that bad now?"
It wasn't. In fact, it was a piece of cake!
The best part is I was able to make the pathfinding code perfectly optimized for what I need. The third party library worked for both 2D and 3D games, so it had a lot of setup required to make it work for 2D specifically. I also had to set the pathfinding area manually, so if I added extra ground to my world level, I'd have to edit the pathfinding gameobject to include this new area.
My system just takes a Unity 2D grid gameobject and figures out the pathfinding area automatically. I give it a list of gameobjects alongside of that to keep track of as collision objects, and unlike the third party library, I can update their collisions on a per-gameobject basis, so I don't need some heavy operation that locks up the entire game recalculating the collision objects. So much more efficient!
(The GIF makes it look a little laggy, but it's buttery smooth in game!)
And finally, the best part of the re-write so far, imo:
The Interaction System
Like I mentioned earlier, the dialogue system (text shown when talking to characters, reading sign posts, getting items, opening chests...) was super inflexible before the re-write.
The original system centered around XML files written to make dialogue scenes. They were sorta kinda similar between talking to NPCs and reading things like sign posts, but different enough that I couldn't re-use the code between them. There was a bunch of hacky crap to make the dialogue system be more flexible to do more things as time went on.
Why?
Why did it all have to be a mess?
Why did NPCs and signposts have to be different systems? They did the exact same thing except one showed a picture and a name, and one didn't!
I realized that my logic for making them separate seemed to center around "Well I don't want to have to write longer XML files for these scenes".
Screw that! These files are going to get written once and then basically never touched again. Does it really matter if writing a dialogue scene with a NPC takes longer if it's only going to be done once, and I can offer way more flexibility with the entire system in the process?
So let's walk through what I did to make things better:
I started with a switch to JSON (JavaScript Object Notation) instead of XML, because I had to do a lot of work last time to make a web based dialogue scene editor work with XML. JSON is basically native to web apps, so that will be great to work with when I eventually build that editor again. Plus, Unity has a built in JSON parser that is incredibly easy to use.
Let's take a look at that JSON:
{
"interactions": [
{
"name": "npc_bob_debug_01",
"preActions": [
{ "type": "ShowDialogue" },
{
"type": "SetNPCNameText",
"parameters": [
"Bob Says:"
]
},
{ "type": "ShowNPCName" },
{
"type": "SetNPCImageIdentifier",
"parameters": [
"Bob"
]
},
{ "type": "ShowNPCImage" }
],
"lines": [
{
"text": "Hello!",
"emotion": "happy"
},
{
"text": "Welcome to the debug world! My name is Bob, and this is coming from the new system!",
"emotion": "happy"
},
{
"text": "Glad everything works!",
"emotion": "happy"
}
],
"postActions": [
{ "type": "HideDialogue" },
{ "type": "HideNPCName" },
{ "type": "HideNPCImage" }
]
},
{
"name": "prop_signpost_debug_01",
"preActions": [
{ "type": "ShowDialogue" }
],
"lines": [
{ "text": "This is a sign post!" },
{ "text": "It's a sign post even though it looks like a circle." }
],
"postActions": [
{ "type": "HideDialogue" }
]
}
]
}
For each interaction we've got a couple things:
- A name identifying the interaction. This is so the system knows what to reference when I interact with a certain gameobject (either a NPC, in the case of npc_bob_debug_01, or a sign post in the case of prop_signpost_debug_01)
- Lines of dialogue that are to be read. Each line has the line of text and an optional emotion that can be associated with it (used to set a NPC's image to match the text being read). That identifier field mentioned above combines with this to generate the sprite name. So "Bob_happy" would be the name of the image sprite used in all of those lines of dialogue for npc_bob_debug_01
- PreActions and PostActions. These are actions that are called before and after lines of dialogue have been read, respectively. These actions can be literally anything, and I'll go into that in a bit. Each action has an optional string array of parameters that can be passed into it. In the case of "SetNPCNameText", you'll see "Bob Says:" passed as a parameter. This is how I set the text shown above the dialogue for the NPC.
You'll notice this is exactly the same between NPCs and Sign Posts. Because they do the exact same stuff, so why do they need to differ?!
This interaction system is incredibly flexible, and this time I mean it!
Do I want to do some stuff before I even show the dialogue box? I can do it all in the PreActions.
Do I want to go into a cutscene after dialogue? Do it in the PostActions.
So let's talk about those actions. Each of those action strings is actually the name of a class that extends the abstract class CompiledAction, which has a method to execute taking in the gameobject that triggered the interaction as well as a reference to the entire interaction as well:
public abstract class CompiledAction { public abstract IEnumerator Execute(GameObject sourceGameObject, CompiledInteraction parentInteraction); public List<String> parameters; }
Here's that ShowNPCName action, for instance:
public class ShowNPCName : CompiledAction { public override IEnumerator Execute(GameObject sourceGameObject, CompiledInteraction parentInteraction) { DialogueUIManager.Instance.ShowNPCName(); yield return null; } }
You'll notice that the execute method is an IEnumerator. This allows me to call them as Unity Coroutines, allowing them to be sequential and allow for timing delays if needed.
But why would I need a reference to the parent interaction and source game object, you might ask?
Take a look at the JSON for how I handle opening chests:
{ "interactions": [ { "name": "chest_unopened", "preActions": [ { "type": "PopulateChestContentsLine" }, { "type": "ShowDialogue" } ], "lines": [ { "text": "You open the chest..." }, { "text": "You find {CHEST_CONTENTS}!" } ], "postActions": [ { "type": "HideDialogue", }, { "type": "MarkChestOpened", }, { "type": "GivePlayerItem", }, { "type": "ResetChestContentsLine", } ] }, { "name": "chest_opened", "preActions": [ { "type": "ShowDialogue" } ], "lines": [ { "text": "This chest has already been opened." } ], "postActions": [ { "type": "HideDialogue" } ] } ] }
And a few of the associated actions:
public class PopulateChestContentsLine : CompiledAction { public override IEnumerator Execute(GameObject sourceGameObject, CompiledInteraction parentInteraction) { Chest chest = sourceGameObject.GetComponent<Chest>(); string chestContents; try { int result = Int32.Parse(chest.contents); chestContents = "$" + chest.contents; } catch (FormatException) { // Not a number, so it's an item rather than gold chestContents = chest.contents; } parentInteraction.lines[1].text = parentInteraction.lines[1].text.Replace("{CHEST_CONTENTS}", chestContents); yield return null; } }
That's how we make the dialogue for chests specific to the contents of the chest. You'll see we use that sourceGameObject to get a reference to the chest and read its contents. We do the same to mark the chest as opened later on.
Unfortunately, that change of chest opening text persists since dialogue is generated when the game loads, so we need to reset it afterwards back to its original value (which I declare when the dialogue is generated):
public class ResetChestContentsLine : CompiledAction { public override IEnumerator Execute(GameObject sourceGameObject, CompiledInteraction parentInteraction) { parentInteraction.lines[1].text = parentInteraction.lines[1].unmodifiedText; yield return null; } }
Phew, that's a lot. But isn't it cool that I can use the exact same code for NPCs, sign posts, and even chests? The possibilities are endless!
That said, is this new system perfect? No. I can already see areas to improve as I reflect on this post. But in general it's flexible enough that improving or adding to it isn't a nightmare because the foundation is rock solid. And that's been the goal of this re-write. Get to a state where things aren't tightly coupled together and are easy to work on. I'm very happy with the progress that's been made.
The Best Part?
Having previous Unity experience as well as that professional software dev experience means this re-write has gone incredibly fast.
I think the original dialogue system took me months to get right. This one? Less than 2 days. This entire re-write has been over the course of a month, and I've made nearly as much progress as I did over almost an entire year the first time. I'm super proud of that.
So What's Next?
I've pretty much wrapped up the foundation of the RPG side of things. There are going to be a few more pieces to finish before I get to working on the battle system V2, but that shouldn't take too long before that gets prototyped out too.
Also - itch.io is an incredible platform and so much better than trying to make these blog posts on a custom website. From now on, expect to see future updates here. At some point, I'll redirect the old Bravewater website to show up here.
Thanks for reading! I hope to share more with you as the development journey continues, and I hope you'll stick around.
Get Bravewater
Bravewater
A 2D Turn-Based RPG!
Status | In development |
Author | Bravewater |
Genre | Adventure, Role Playing |
Tags | 2D, JRPG, Turn-based, Turn-Based Combat |
Languages | English |
Leave a comment
Log in with itch.io to leave a comment.