All of your Rose of Eternity
needs moving forward can be found at my new blog,
http://www.theroseofeternity.com. I hope
to see you there!
12/26/16 -Monday: YOU CAN NOW
FIGHT IT OUT WITH THE CPU FROM BEGINNING TO END...
|
Once I was finished with the
Kamikaze & Zone AI implementations, I
already knew there was only one thing left to do: Get some
basic (and I mean basic!) combat logic in so that this new AI
and I could actually fight against each other. Turns out, I
was able to get more than I expected in. Either way, the end
result is that you can now fight it out with the CPU from
beginning to end.
HEALTH BARS
As I sat down to start planning out the
combat system, I immediately started to think about displaying
health bars, and how difficult it would be. I didn't
originally plan to include them, but the more I thought about
it, from a usability standpoint, it's better to be able to get
a quick idea of the HP of your allies/enemies, versus having
to highlight them to see it in their character sheet.
I gotta say, of all of the things I
have done with Unity so far, this was... really easy...
Below each unit (which I may switch to
above them at some point), I just added a UI canvas, and under
that, an image. What does this image consist of? Just a damn
background color. Green for allies, red for enemies.
The most
intriguing part of creating this was how I would end up being
able to show the health bar going down, as you can see in
today's GIF. Turns out, each UI element has a scale that goes
from 0 to 1 for each coordinate (x, y, x). If I modify the x
coordinate to something less than 1, the image gets shorter in
length. If I left align the image to the canvas it sits in,
when I lower the scale, it looks like the health has decreased
from the right to the left.
So I just had to hook this into
existing systems to check the current HP of the unit, get the
% of their total HP, and then apply that value to the scale,
and voila, I have prototyped health bars that decrease in
length as the unit takes more damage.
Implemented in like an hour, which is
how I wish all things would go :)
COMBAT DAMAGE AND DEATH
Nothing too special going on here. It's
pretty hardcoded, as of now, the damage a unit can apply is
equal to their level times 2. Yep, that's it. I'll worry about
weapons, armor stats, etc. later.
Death was pretty easy as well. Whenever
current HP <= 0, destroy the unit, and also remove references
to it from everywhere else, like the Turn Order GUI, tile
data, etc.
ANIMATION
I was definitely going for something
quick and dirty. Some sort of way of showing people that the
attack happened, but nothing I wanted to spend too much time
on. I ended up move the unit toward the target half a tile,
then half a tile back. Like I said, quick and dirty.
USED ABILITY
I added this more so because I was on a
roll, so I figured, why the hell not? This is really something
that would be used for abilities, but decided to show the used
"weapon" in each attack. Yes, those weapons are hardcoded. And
when I say hardcoded, I mean, it's just a string set on each
unit via their inspector. As with everything else in this
prototype, I can just hook in the real logic later.
------------------------------------------------------------------
So yeah, with all of the above done, I
was able to get the game in a great working state. I didn't
talk about the selection menu, because I'd like to leave that
for its own blog entry. But just know that it was a *shock*
quick and dirty implementation.
Next, I'll be working on... Well, for
once, I actually need to keep this a secret!
Till tomorrow...
12/22/16 -Thursday: THAT SWEET
ZONE DEFENSE...ERR, ZONE AI....
|
After I implemented my Kamikaze A.I., I
knew the fun was over for a bit, because the code was
disgusting as all hell, and I had to bring some sort of
decency to it, lest I lose my mind. So that's what I did
during the earlier part of the day. And damn, it was boring as
shit.
When working on this game, the best
parts are when I have a very clear goal, and it's all about
just implementing it. It's a whole other story if I have a
vague goal of what I want to do (i.e. refactor shitty A.I.
code), and then spend 80% of the time trying to figure out the
best way to do it.
When this happens during the day, for
whatever reason, I start to nod off. I'm definitely a night
person. I can be up from like 7pm-2am no problem, but 2 in the
afternoon? If I'm not working on some pretty cool stuff, it's
curtains for me.
After taking a few breaks, and after
the wife and my daughter came home, I got a little
re-energized, and was able to pin down exactly what I needed
to do, and dammit, just do it. Of course, this will
definitely change later. But you know,
prototypes.
Once I was done refactoring the main
code, it was pretty easy for me to extend my base
AI class with ZoneAI,
and implement a function that makes sure that if an enemy is
not within their zone, do nothing.
It's funny, because this is one of
those things you don't really think of (well, I don't anyway)
until you have to implement it. We've all seen this type of
enemy, and for me, mostly in the Fire Emblem
series. You're happily klling enemies left and
right, but there are some enemies that don't approach. Then
you finally get in range of them (usually the range is a
combination of thier move + attack), and they come barreling
towards you.
See below for an example of my
implementation:
The first enemy unit attacks because
you're within their zone, but the other 3 do nothing, which
is, well... you know, zone A.I. So yeah, it works :)
Of course, what is the point of all
this A.I. stuff without enemies or the player being able to
attack? Yep, that's next on my plate for things to prototype.
Till tomorrow...
12/20/16 -Tuesday: LET'S DO
THIS!!!
|
Okay, I've procrasinated enough. I've
been trying to handle all of these other things (which
are important), but at the end of the
day, it's all lead up to this. The first implementation of
artificial intelligence in this game.
And god damn, what a start to it all.
I knew I wanted to start out with the
most basic A.I., and move on from there. When I mean basic,
I'm talking about just having the enemy units move towards the
ally units, kamikaze style. There's no regard for what will
happen to them, they just find the nearest opposing faction
unit, and move towards them. Nice and easy. Or, so I think
everything is when I sit down to start implementing it :)
Forgetting about the actual code
structure, which I'll come back to at another point, I just
wanted a quick and dirty implementation of having the enemy
make their way towards the player. Well, I did create an
AIManager class, but it's just a spider
web of a bunch of functions at the moment.
First thing I had to create was a
function that would find the nearest enemy unit. That was easy
enough, and I was able to code that function within minutes.
Next came my first sort of stumbling block. The idea was, once
the target was found, an appropriate tile next to them had to
be found. My first solution was to build a collection of all
the tiles that the unit could move to. From there, I would
calculate the distance between each tile in that collection
and the tile that the nearest enemy was standing on. Once I
found the lowest, that would be my target tile to move to.
From there, I would make use of
existing code to actually find a path to the target, then move
them there.
See results below:
I immediately realized there were
issues. If you look closely:
-
The 1st enemy is making their way
to the nearest target (the teal character), but then pulls
up. Why?!
-
The 2nd enemy starts moving south,
but then starts cutting south-west, instead of proceeding
over the desert bridge.
-
The 3rd enemy is confused as shit
and does nothing.
-
The 4th enemy seems to know what is
going on.
After a bit of debugging, it was pretty
clear that the issue was that while I have built in the logic
to not highlight un-walkable tiles, or have the pathfinding
ignore said un-walkable tiles, this same logic was not used
when finding the right target tile before a move. For
instance, with the 2nd enemy, they're clearly not thinking
about the water, so they think that the fastest way to the
target is by heading south-west, instead of realizing that
it's not worth the time, and that a better path needs to be
found.
Then I had an epiphany!
Why in the hell was I trying to write
logic to find a target tile, when I sort of already had that
done in my Pathfinder class?! Sure,
there were some minor differences, the biggest one being that
the Pathfinder class requires the
source/final target tiles, and if the target tile cannot be
reached, it returns nothing. In this case, the enemy won't be
able to reach the target in one move, which is why I'm going
through this work to determine the best tile to move them to,
a tile that's closest to the target.
Then I realized that I could just pass
in the final target's tile to the Pathfinder
class, then once the path was generated, I could
remove all tiles from it that the enemy actually couldn't
reach. That would give the shorter path I needed, while
maintaining the core logic of the path generation.
See results below:
Now all the
enemy units are moving in the right direction! Woo hoo!
Tomorrow, I'll need to take this
spaghetti code, and refactor the shit out of it. From there,
I'll start working on some other basic AI templates. Maybe a
unit that won't move towards you until you're within range. Or
maybe a defender unit that follows around a leader. The
options are limitless!
Till tomorrow...
12/17/16 -Saturday: FIXING
PATHFINDING...
|
So, I implemented pathfinding a while back. But like
everything else I've been working on, I just did a quick and
dirty implementation and moved on. Fast forward to today, and
now that I'm getting ready to finally embark on that journey
that is Unit A.I., I've had to take
a small detour to fix up a known bug.
In today's GIF, you can see me selecting a tile across the
water for a particular unit. And, you can see them do the
right thing, which is go around the water, across the desert
bridge (why do I even have that there, lol), and to the
destination. But this wasn't what would happen a few days ago.
You'd get the following:
As you can see, the unit is going right across the water,
which is definitely incorrect. So, how did I get here, and how
did I fix it?
When you think about how I've implemented pathfinding, there's
2 main components:
--------------------------------------------------------------------
TILE SELECTION
This is where you, the player get to select which tiles you
can move to. I've been discussing things related to this for
the past few weeks, most notably, my blog entries about tile
highlighting. Long story short, I make sure to block tiles you
cannot go to. Water, mountains, etc. You know, the usual
stuff. Nothing too crazy here.
TILE MOVEMENT
This is where things got hairy during my initial
implementation. You see, while I block the player from
selecting a final destination tile,
I wasn't blocking the pathfinding logic from said tiles. So,
as you can see in the last GIF, you can select the tile across
the water, but when the path is generated by my pathfinding
class, any and all tiles were up for grabs.
--------------------------------------------------------------------
Turns out, I needed to implement this concept of
Tile Cost. This is basically a number that tells
the system how much it will cost to enter a tile. Originally,
this value was calculated by finding the range between tiles.
So if you had a tile at coordinate (0, 0), and at (1, 0), the
cost to move to the tile would be 1.
The real way it needs to be
calculated is by looking at some properties I have in my
TileData class, most important,
IsWalkable, and Movement.
The former gets a cost of Infinity, while the
latter has a value of (abs(tself) + 1). So for grass terrain,
the movement modifier is 0, so the cost ends up being 1.
Desert terrain has a movement modifier of -2, so its cost ends
up being (abs(-2) + 1), which ends up being 3. So what do all
these numbers mean?
Simply put, when the pathfinder is trying to figure out the
best way forward, let's say it's looking at some water, which
is not walkable. It's cost will be infinity, so the next thing
the pathfinding is say, "Oh shit, infinity is a lot, is there
a better path? Oh, look, there is some grass terrain that has
a cost of 1. Yeah, I think I'll take that one." That's why in
the main GIF, you see the unit avoiding the water, as it
should.
Now that I have this implemented, I just need to write some
unit tests to confirm that the unit will always avoid high
cost tiles, then I'll be good. Then I can finally start
writing some A.I. Oh man, this is gonna be fun. About time to
get this game looking like a real game :)
Till tomorrow...
12/15/16 -Thursday: TURN ORDER GUI
WORK...
|
As I discussed at the end of my last journal entry, I have a
Trello card, called "Allow Clickable
Turn Order GUI". Well, I finally got around to addressing it,
and it turned out to be pretty easy.
So what was I attempting to do, you ask? Well, since I want to
eventually support larger battlefield, which would result in
more units, I figured it would be nice if while looking at the
Turn Order GUI if you could click on the portrait of the unit,
and have the camera pan to them, while also highlighting the
area they can move to. Nothing revolutionary, I know, but
something that I really wanted to get done.
It turned out to be pretty easy because I already had built in
functions to do the following:
The majority of the work was
implementing "Mouse Pointer Click" events for the individual
portraits, which undernath the hood, or standard
Unity UI Image components. I created my own
class, TurnOrderImage, and
implemented said events, and within a half hour or so, wired
everything up.
While I was working on this, other
ideas started coming to. The portrait the player is hovering
over should be highlighted. Or maybe increase in size by 5% or
so, so it's clear it's the one being hovered over. I started
going down that rabbit hole, but the end result ended up with
the possiblity of creating custom shaders, and other things I
just didn't have the time to deal with. So I just left this as
is.
What's next? Well, the only card I have
left has to do with writing unit tests for 5 classes. It's
nothing glamarous, but I am about to
start working on some simple enemy unit AI, and all of these
classes will play a part in it. So, I might as well get them
fully tested, as I'll be making tons of changes, and I don't
feel like breaking a bunch of shit, and not knowing until much
later.
Till tomorrow...
12/10/16 -Saturday: 3RD TIME WAS
THE CHARM!!!
|
One of the cards
I have on my Prototype board in
Trello
was "Show Attack Tiles". I've already had the logic to
highlight the movement tiles for a while, but I always knew
that I wanted to come back and incease the range of
highlighted tiles to show where the unit can't actually damage
other units with its equipped weapon.
The idea actually came from the
Fire Emblem series, in particular, the
ones on the 3DS (though I'm sure the GBA ones also did this).
Honestly, that's the only turn based RPG series I've played in
the past decade. Of course, I came up with Final
Fantasy Tactics, Shining Force, etc., but I
haven't played those in a damn long time. But as far as I can
remember, those games didn't have that. If you wanted to see
if you were within range of hitting an enemy after a move, you
would have to first move there, then check. This way removes a
step, which in itself doesn't seem like much, but if you add
that up over hours of playing the game, plus add in the fact
that you could potentially see X enemies you're within range
with, versus checking each one... You see where I'm going with
this.
And this is really at the core of how
I'm attempting to design this game. I want to eliminate as
many superfluous steps as possible. The player should just be
able to get on with the game, and not bothered by pop up
confirmation buttons, or the aforementioned issue of finding
out if you're within range of attack and enemy.
Here is an example of how it is with
Fire Emblem:
Pretty self explanatory. So, as you can
see by looking at today's GIF, you'll see the following 4
units and their movement/attack ranges (denoted by the color
of the circle that represents the unit):
-
Pink: Movement 2 - Attack Range 1
-
Dark Blue: Movement 2 - Attack
Range 2
-
Teal/Light Blue: Movement 4 -
Attack Range 2
-
Green: Movement 5 - Attack Range 1
Some things to consider for each of
those units:
-
They cannot move to a tile that is
occupied by another, and the attack highlighting does not
show up for friendly units
-
While a tile may be unwalkable for
one unit (mountains, water, etc.), they can still attack
other units at those unwalkable units (i.e. flying unit)
-
If the unit has enough movement to
technically make it to the other side of a unwalkable
tile, but that tile ends up being orphaned (i.e. no
connecting tiles), then that tile is inaccessable, and
therefore not highlighted. Of course, if that unit can
fly/teleport, that's a different story. For now, I'm not
even dealing with that. One thing at a time :)
Now, this took me... I guess 3 days to
complete? I cannot remember, but I did have my daughter with
me Friday night/all day Saturday as my wife was away, and I
try not to be a terrible Father, so of course I couldn't do as
much as I would normally get done with her here to help. I
ended up implementing different solutions, and the 3rd one
ended up being the charm.
FIRST ATTEMPT
At first, I was like, "Oh yeah, this
will be easy. I already implemented the highlighting of the
movement tiles, so adding an additional x tiles to iterate
over, and making them red will be easy."
And I did just that. The movement
highlighting logic loops over all 8 directions for x number of
tiles the unit can move. So I just increased that by 2, and
was hoping I would be on my merry way.
Then, it became apparent to me that I
had a terrible bug in the system, where I was showing orphaned
tiles. I had never really noticed it until I moving towards
the water on the map with units that had a movement of >= 5.
What I ended up seeing was this orphaned blue tile surrounded
by red tiles. No bueno.
SECOND ATTEMPT
For this next attempt, I had to take a
step back and think about it. How could I not show these
orphaned tiles. I decided to start down a path where I would
show all tiles, then run another
function that would locate the orphaned the tiles and remove
them from the game. This all happens pretty quickly, so the
player would never see it, but it's not that efficient. Again,
I'm in prototype mode, so I didn't let those details derail
me.
I had some logic coded up in between
playing with my daughter and putting her to sleep. When I came
down to my man cave, I was excited to try it out. It
seemed to be working... Most of the
time, but there were edge cases where the right attack tile
was not highlighted, or a movement tile, for that matter.
THIRD ATTEMPT
At this point, I was getting a terrible
feeling I was going to have to leverage my pathfinding code to
do something like... Check if every movement tile could
actually be reached. Again, not terrible efficient, but again,
this is a prototype.
Luckily, before I went down that path,
I realized I had a bug in my IsOrphanedTile
function, where it was only checking N, E, S, W
tiles. All NE, SE, NW, SW tiles were ignored. Sigh... Even
more, I realized that I had the same bug in my
HighlightAttackTiles function as well. Double
sigh... Also, because yay for code re-usability, I had all the
logic for both attack and movement highlighting filtered to a
single function, which calls IsOrphanedTile.
Which of course doens't make sense for attack
tiles, since those could legitimately be orphaned. Triple
sigh...
Once I realized the error of my ways, I
quickly coded up fixes, and voila, the shit worked.
So the way it works is as follows:
-
Clear out all highlighted tiles
-
Highlight all movement tiles (which
will now handle kicking out orphaned tiles properly)
-
Taking the existing movement tiles,
highlight all attack tiles. This is heavily predicated on
the highlighted movement tiles being created properly.
Now, I have some small code changes
before checking it in, merge the branch to master ,and moving
on to the next card I have.
Speaking of that next card, it's "Allow
Clickable Turn Order GUI". This terribly ordered card title
simply means that when the user clicks on a portrait on the
Turn Order GUI, the camera will focus on that unit on the map.
I've never dealt with handling those types of events on UI
objects before, so should be fun.
Till tomorrow...
P.S. All this logic I just put in
should just work for the CPU. We'll see...
12/2/16 -Friday: ONE HELL OF A
MONTH!!!
|
STATE OF THE UNION
Well, well, well... I just had one hell
of a month! I mean, everything was firing on all cylinders.
Most of the free time I had, whether it be on the train, or at
home after the family went to sleep, was spent doing some sort
of work. And if you look at the number of posts I had last
month, you can see how it directly ties into how productive I
was.
So what happened, you ask? Simple...
Destiny: Rise of Iron.
It came out on September 20th, and I
knew that I wanted to put my all into that game. Single player
story, exotic quests, a little PVP, and ultimately
normal/heroic raids. Going in, I knew what it was. I wasn't
going to be able to do much at all, while I put my all into
the game that I knew would be my last for a while.
Staying true to my intentions, around
the time I finished all activities, leaving me not much of
anything else to do, I decided to shelve it. I mean, I would
have shelved it until the Spring 2017 update anyway, but I
knew what I was going to end up spending my time on.
It's been one hell of a run. I ended up
prototyping the following:
-
State Machine
-
Pathfinding for player controlled
units
-
Unit Turn Order (including UI)
-
Map Scrolling with WASD keys and
mouse
-
Unit Tests
-
Added SFX and dynamic music
I have more than achieved the goals I
set in the November State of the Union.
I couldn't be happier. However, I want to keep this
going. Already for today's updated, I done the following:
Github Integration
The picture of the day is meant for
this part of today's update. I have, for far too long, avoided
this. Why, I have no idea. I'm a software engineer. This is
what I do on the daily basis. I fully understand the
importance of source control. And yet, here we are :)
Well, before getting distracted by SFX
and music, I had said that this was going to be easy, and
easy, it was. Since I'm used to working in Unix environments,
I made sure that my GIT client had a bash interface I could
use, so I could do all GIT related things in there. No fancy
windows explorer plugins or the like for me.
As of now, the repository is public,
but empty. I'll push up my prototype
branch either later tonight, or tomorrow. I'll most
likely make the repo private, which will cost like $7 a month
or something. There's nothing too secret about what I'm doing,
but better to do it now while I'm already doing everything
else...
...Speaking of which...
Trello
...Yep, got started with
Trello as
well, because, why the hell not. For those not in the know,
this is software used to track bugs, issues, featurs, etc. for
projects. I've not put too much effort into it, but at the
very leasy, it'll be a nice visual guide for me as I try to
keep track of all the things I'm working on. I'll continue to
iterate on how I use it, and I'm sure at some point, I'll be
ready to get a job as a project manager :)
What's On The Agenda This
Month
-
Refactor the hell out of the code,
and get it out of "prototype" spaghetti mess into somethig
I would be proud to show others
-
Integrate some cheap unity assets
for units (possibly tiles as well), and try to get some
animations with said cheap unity assets for units
-
Implement actions players can take
during their turns. Right now, you can just move. That's
not so much fun anymore. You need to be able to "Attack",
"Defend", use an ability, item, etc.
-
Enemy AI. I'm not going all Skynet
or anything, but I need the very basics at first, where
they just try to go and attack whatever is closest to
them. Then I'll start implementing a priority list system,
sort of like the
Gambit System from Final
Fantasy XII. Imagine if the enemy is a
healer. They could have something like the following as an
AI list:
Exapnding on the AI a bit, it just goes
down this list until it hits something it can act on. Pretty
simple. Actually, Dragon Age: Origins used a
smiliar system. I know it well, because I had to tweak it to
make sure that party members would use their custom abilities
the base game didn't know about.
Of course, I didn't write the
underlying engine, but I know how it should work. So yeah, I
think I'll roll with what I know, until told otherwise.
Anyway, If I can get these items done,
I'll feel pretty good. I'd like to think December will be like
November, but I don't want to get ahead of myself. Although, I
will be on staycation starting 12/15
until the rest of the year, but I don't want to get too
excited. Here's hoping.
Till tomorrow...
|