Devlog 2 - The Prototype
DUE DATE: June 1, 2023
The Initial Commit
I first had to setup a GitHub repository for where I’d keep all of my work - version control is super important so we have to make sure that’s all good and well. I didn’t do anything fancy, just a simple private repository with the standard .gitignore
file for Unity. A .gitignore
basically just helps us keep useless files we don’t need to keep track of off of our storage, so we only store what is absolutely necessary for the project.
Of course, to get started with Unity I had to decide on a version of Unity to use. I just went with the recommended version (even though Unity’s recommendation system is kind of strange), 2021.3.24f1.
Then I decided to use Trello to help track my progress and stay on top of what needs doing and what has been done, so I went ahead and spent a bit of time plotting out my general milestones, the namesakes of the chapters of this journal – The Prototype, The Alpha, The Beta, and The Final Stretch (vertical slice). Whether or not I’ll actually make those deadlines remains to be seen, as I’m going to be diving head-first into Unity with quite literally zero experience in both the engine and with its associated programming language, C#. But hey, I’ve worked with tons of languages in the past, so it doesn’t seem that difficult to get a grasp on. I’m more worried about getting comfortable with Unity and understanding how it operates. C# just looks like Java’s more prim-and-proper cousin anyways, so I don’t think it should be all that complicated.
The Prototype
Now, funny thing about the prototype – I made the mistake of being so overly eager to start work on the project that I blasted through a ton of coding before making an actual commit… Oops. I’ll do my best to recount my thought process as I tried to tackle this stage. I will try to keep this journal easily readable, however for the first section, the systems I’m going to detail are fairly fundamental to the rest of the game, so I’d like to do a somewhat deeper dive. Beyond that, I’ll keep the nerd language to a minimum.
Let’s get to work
It was time to get started on the game!... I was kind of intimidated by the sheer number of places I could start. However, I took a step back and tried to break down the absolute fundamental core of the game that was necessary in order for the rest of the game to start taking shape. I ended up making a pretty straightforward checklist that I could follow to help keep me on track. Before those though, I set up the simple stuff – a grid with a basic tilemap of ugly test sprites and an ugly test room, to help get familiar with Sprite Renderers.
The Grid at it’s default position has the corners of the squares act as the coordinates, but that’s not what we want. So all I did was offset the X and Y of the grid by 0.5 so that the centers of the cells line up with the coordinates. Nice! Now a Vector2 like [2, 2] , refers to the center of the cell located 2 units to the right and 2 units up from the origin. That’s all just fancy math talk to say we now have a grid that we can easily play around with.
Action Queue System
Design
Now this one is quite a large task in and of itself, but I feel like it’s kind of the basis of what makes BugByte what it is, so I think this one should be tackled first and foremost. It’s going to require a fair bit of designing. The way I see it right now is that every creature in the game (called a “Byte”) will send a sort of “packet” to this Action Queue, with information on an “Action” that it wants to perform. So, if a creature, let’s call it Bob, wants to move one square to the right, for example:
Bob will send a packet to the Action Queue manager that says “hey, so I’m Bob, and I want to move to the right, by a distance of one square”, so on and so forth.
The same would go for attacks and pretty much any other thing that any living creature (or “Actor” as I ended up calling them) wants to do within the confines of the game.
So, just to hammer it home with one more example, if Bob wanted to cast the Scratch attack:
Bob will send a packet to the Action Queue manager that says “hey, Bob again – I would like to cast my Scratch attack in the direction I’m facing, if that’s cool.”
And so, after receiving one of these actions, if the player ends their turn, which’ll likely be handled by the player itself, the Action Queue manager will go ahead and run through all the fun little packets that the player sent to it and have them go through, one by one, in the order they were received.
Implementation
After sniffing around for a bit of guidance online, I started to realize that, for my idea of the Actions, what I might want to use is a feature called “coroutines”. A coroutine, without getting too deep into the weeds, is effectively for things that happen which are continuous, meaning it’s something that will take a bit of time to perform. For example, an Action to move the player one square to the right should probably be a coroutine, as the movement doesn’t happen instantaneously – it’s continuous, in that it would take some duration of time as we watch the player character slide over to its destination. If I didn’t declare it to be a coroutine, then the player would instantly teleport to their destination, which is not what we want. Actions should take time to complete, so they player has feedback and can read what the heck is going on - and so I ended up coming up with this pretty simple implementation:
I decided to make a generic abstract Action class that will hold the general structure of what every action should have and be able to do. Notice that the functions that say “IEnumerator” are the coroutines. “Execute” is an abstract method that will be where the child Actions actually implement their logic, and so in order to perform an Action, you simple have to invoke the “Perform” method. I also ended up making it so you can chain Actions in succession which I think might come in handy later.
Perform simply just invokes the “Execute” logic and waits for it to finish (a nice feature of coroutines) before attempting to perform a chained action, and then sets a “finished” flag to true. This way, we can check if an action is done being completed with our “IsFinished” method, which is also a recursive check to see if said Action and any potential chained actions are completed.
So now we can extract a lot of information about any given action, which is handy! The action queue will have to execute actions in order, so having them be coroutines is important as it grants us the ability to “yield” and wait for a coroutine to finish before progressing in the code block.
Also I should note that early on I used a lot of properties which is a concept unique to C# as far as I can tell, so uhhh there’ll be a lot of that while I get comfortable. I also know that there are a lot of public sets and gets for things that don’t need or shouldn’t have them. Not important for those who aren’t into the techy-techy coding stuff, but I figured I would worry about those later and just worry about getting things working right now. In general though, it's good practice to keep as much as you can private - having public fields is generally considered bad practice.
…
Of course, since Action is an abstract type, it means I can’t create it necessarily – it’s abstract, a generic Action doesn’t really make sense on its own. I had to make some concrete Actions, which I did for attacking and moving. For the sake of keeping this uncluttered, here are the Execute functions for them:
For attacking, it’s pretty straightforward. I’m jumping the gun a little bit, but effectively each Attack has a reference to its base ScriptableObject (essentially the attack’s core information, logic, etc), and the base ScriptableObject has all the attack’s logic contained inside in the method “DoAttack”, so we just invoke that coroutine and wait for it to finish. Easy enough.
Moving was also pretty simple. A simple loop that LERPs (linearly interpolates) the player’s position between their starting position and the destination over a certain amount of time specified. Right now as you can see in the comments it ignores collision checks for obstacles, but we’ll worry about that later.
With that all taken care of, I created the big bad class that would manage these Actions – The ActionManager. I decided to make it a singleton, so anything anywhere can send Actions to it. It’s already a fairly big class (a big red flag for something that could probably use decoupling), but here are the important bits:
It maintains a list for the player’s actions, and the enemy’s actions. This is important because the player’s actions should happen first, then the enemy’s would take place after.
The ActionManager gives the player and enemies a way to add an Action to their respective lists. Once the player ends their turn, enemies (which I’ll get to in a future section) will instantly queue up whatever Actions they were programmed to queue up. This is a really simple implementation just for testing though.
This is where the bulk of the work happens. Once the player ends their turn, the ActionManager will first perform all of the player’s actions that they sent before ending their turn (in sequential order), wait for them to finish, then perform all of the enemies’ actions at once. I think this is kind of cool to have the enemies all act at once, but it might cause some annoying timing problems in the future. I’ll leave it in for now though.
The “Execute __ Actions” functions are pretty straightforward, as they just go through each Action and “perform” them, waiting for one to finish before moving on to the next:
Once that’s all done, the ActionManager empties out the action lists and waits for the player to end their turn again. And that’s the main loop!
Grid-based Movement
This one is pretty straightforward – be able to move Actors on a grid. Simple enough. This is tied to the Action Queue system, however, which is why there’s so much preamble in the above section. Since our grid is nicely aligned so that the integer values of coordinates match up with the centers of each cell, we can simply just have movement occur on integer values. It doesn’t really make sense to move 0.5 squares – that means you can end up between grid squares, on the edge of grid squares, and so on, which shouldn’t happen! So a player can move from [0,0] to [1,0], to [2,0], etc. Pretty easy.
Flexible Creature Types and Party System
Since this game is a creature-catching game, I need to be able to create a lot of creatures. To that end, it shouldn’t be a hassle to make a new one, or edit an existing one’s values, or remove one entirely. In addition, the player should be able to maintain a “party” of creatures, which they can switch between. This one was a bit to wrap my head around at first, but I think the ScriptableObject concept would yet again prove useful here. It would work like Pokemon in that creatures would have some kind of basic template, with base stuff like their base stats, their name, their sprites, base health, and so on. So now you can easily create a new creature by simply right-clicking, and selecting “Create New Byte” – then all the designers have to do is fill out the fields that a creature needs. Nice!
Oh, I should mention what "Attack Pools" and "Attack Pool Groups" are! The way I saw it is that whenever a creature levels up to a certain level (probably every 3 levels), they'll be presented 3 random attacks, with the choice to add 1 to their attack list. That's what I aim to achieve with these Attack Pools! Each Attack Pool has a level range, and so if a creature hits the attack-adding milestone and their level falls within that range, then (when I inevitably build the system later) the UI will be able to pull 3 random attacks from whichever Attack Pool level range they fall into! Lastly, an "Attack Pool Group" is just a way to group up the Attack Pools, nothing fancy.
Flexible Attacks
kind of analogous to the above point – I want any creature to be able to possess any kind of attack as part of their learned attacks. As well, I want each attack to handle their logic independently of whoever invoked it, so that I can effectively make anything do an attack without having to do additional programming or edge case considerations. I took basically the same approach here as I did with the creature templates, so that there are different simple templates of attacks – attacks that spawn an object, attacks that lunge the user forward and so on. Then you can right click and create new attacks in the same way, and you can set their base damage, cooldown, cost, etc. The only problem for a designer is that the attack’s logic is still very code-oriented. I don’t really know how to solve this to make it easier for designers yet but I’ll worry about that later. For now, this works for me!
Event System & Health Bars
So, one thing I started to realize is that there's going to be a lot of things happening at a time, and I'm probably going to have a lot of objects listen for when some of those things happen. This, to me, seemed like a great opportunity to implement the Observer design pattern! However, I was tempted by the idea of using a static Singleton GameEvents class that would house all of the events in my game. That way, any object in my game can easily invoke an event from anywhere and those who want to pay attention to that event can subscribe from anywhere.
For example, the most practical use case for this right now is for health bars! We want to know whenever a Byte takes damage. But we also don't want the code we write for the health bar to be entirely tangled and dependent on the player or the enemy - I want to be able to just slap a health bar on something that has health and not have to worry about the rest. This is easily achievable with Interfaces! an Interface is effectively a code contract - I can define an interface called IDamageable
(the I is so I know its an interface), and I can say that anything that's damageable has Health, and can take damage. Pretty simple.
Now, whenever a class implements this interface (basically, agrees to the contract), it means they are now also of the type IDamageable
. This makes a health bar super simple to implement. We just have to check if the thing the health bar is attached to is of typeIDamageable,
and then it's guaranteed that they have health we can display! All thanks to the Interface contract.
Back to events - we may want to know when a creature takes damage, and when it takes damage, which creature that was! Something like this:
Now whenever a creature dies, it can just invoke the "ByteDeath" event, and anything that's listening for when a creature dies gets notified!
Combine this with the Interface we implemented above and we now have a nice simple health bar!
Progress so far
With all of that said and done, the foundation of the game is in place. Here’s the fruits of our labour!:
… Yeah, it’s not really that impressive at a glance – scribbly placeholder sprites, ugly tiles, and no animations. However, there’s plenty going on in the backend that I am proud of and I think will work reasonably well going forward. There’s enemies, you can move around, attack, and all that good stuff. This game is kind of tricky to prototype because it’s heavily dependent on systems, and not so much on emergent physics or gameplay, so it’s slow going but I think there’s a good start for a prototype here.
For the most part my designs are modular and decoupled in the sense that not a lot of code relies on other specific bits of code to work. Granted, there still definitely is that stuff, but in an environment like this where time is limited and I don’t have the time I’d like to sit down and design the code architecture, I tend to take the approach of getting it working and refactoring as I go. Now that we have our building blocks, we can get started on the more exciting stuff – full functionality, content, and if there's time, visuals!
Until next time!
-Arda
Get BugByte - Vertical Slice Demo v1.3
BugByte - Vertical Slice Demo v1.3
A Funky Roguelike Creature-catching Dungeon Crawler!
Status | Prototype |
Author | Friendly Fin Games |
Genre | Action |
Tags | Cute, Dungeon Crawler, Pixel Art, Prototype, Retro, Roguelike, Singleplayer, Turn-based Strategy, vertical-slice |
More posts
- BugByte v1.3 Update - Music!Aug 28, 2023
- Devlog 5 - The End (?)Aug 12, 2023
- Devlog 4 - The BetaAug 12, 2023
- Devlog 3 - The AlphaAug 12, 2023
- Devlog 1 - IntroductionAug 12, 2023
Leave a comment
Log in with itch.io to leave a comment.