Saturday, March 19, 2016

Production Post 7 : A Brave New Overworld

A couple days ago I rewrote how the Overworld functions! It extensively changes how the Overworld interacts with DataManager, and how a player navigates. Here's a clip:


All the tutorials are on a big wheel now! With zooming in and zooming out, proper level progression (persistent progression saving included with DataManager), and.... drumroll.... chapter progression! This is a pretty large step forward in development, and puts us significantly closer to a complete build ready for release. Also, it allows Zac to design and implement new chapters seamlessly.

In this post, I'll talk about how it works currently, and how it can be improved.

First off, there are six states the Overworld exists in:


This object-state design pattern is proving to be invaluable. There are different inputs that need to be processed and followed unique events for ZOOMED_IN and ZOOMED_OUT.

ZOOMED_IN recognizes, for now, a single tap to begin zooming out. If the tap was on a node; however, this zoom out event is overridden by the level entering.

ZOOMED_OUT recognizes a single tap which switches over to the ZOOMING_IN state. It also recognizes swipes left and right, and tells the chapter wheel to begin rotating in the correct direction. This instantiates either the next, or the previous (with exception handling for if we are on the first or last chapters and whatnot) chapter, rotated 90 degrees, and positioned either left-ward or right-ward from the wheel axis.

That instantiation makes the new chapter a child of the wheel, allowing me to simply rotate the wheel with the chapters following, without having to worry about moving the chapters at all.

As far as I know, this all works really well! No bugs have come up and everything seems fine from the player's point of view. But, I think the system is flawed and a bit hard to follow for a few reasons. I made this switch about 3/4ths of the way through writing this: I went from reading everything from DataManager to storing localized instances of data. The chapters are destroyed and then instantiated, which means the indices of the List of chapters does not line up with DM's list of chapters (the List of Lists of level IDs I talked about a couple posts ago). If I can change it to sync up perfectly, and hide inactive chapters on the wheel rather than destroy them, it would make saving level progressions a bit more sensical. I have a feeling that this convolution will lead to some minor bugs in the next week, so when those pop up (inevitably when Zac starts adding chapters), I'll redo a bit of how this class works. Until then, who really cares?

Production Post 6 : Data!

A couple weeks ago, the team and I went to implement some little thing. I don't recall what this thing was, but it did uncover a whole slew of issues that urgently needed resolving. We traced the issue back to GameManager, but to fix it we would have to change a lot about how we are saving and loading data. The team and I worked for a few hours to try and hack what was already there together, but it just wasn't working and we all went home at around 1AM. Side note, walking out of the lab with a fundamentally broken game is, like, really disheartening.

So I went home, got some sleep, and death-marched back to campus the next day hell bent on making this work.

Step one was to make an entirely new class: DataManager. DataManager would store all the World and Level data the game needed to operate. All objects, manager, and scripts that needed this data would run through this class. This was no small task: at the time, there was practically no management of this data: there were places that stored data that meant different things than the other places. The code was littered with pseudo saving and loading that sort of did the actual save file writing and loading pretty randomly. The previous SaveLoadManager was this tacked on thing that the game sometimes wrote to, but most of the time did not. It was a nightmare, but not unconquerable.

First thing I did was write make two structs. These structs went through a few iterations, especially with how we managed the completed nodes, but the final structs became:


There are two instances of these structs in DataManager, and EVERYWHERE in the code the previously referenced random half-assed versions of these could now just call a long series of getters and setters defined in DataManager to modify these values. Another neat thing I did to further make DataManager a pleasant black box: in all the setter functions, in addition to just setting the data, I also make a call to the Save() function. This means that absolutely nothing in the code has to deal with saving and loading, DataManager would just do it.

Previously, there were a few calls throughout the code to the Load() function. This now served absolutely no purpose, since the most recent data is always accessible from DataManager: no file-reading required. So, the ONLY place in the code where Load() is called is in DataManager's Awake() function, which only calls once at the start of the application. Beautiful!

Which brings me to the next big change: replacing the non-functioning, platform-dependent data file with Unity's built in PlayerPrefs. PlayerPrefs is essentially a managed data file with a simple Dictionary-style lookup system. It was exactly what we needed. The Save function is as follows:































Simple enough, and totally under the hood, taken care of, and nobody needs to worry about saving or loading. A huge improvement already!

But, PlayerPrefs can only save three data types: ints, floats, and strings (along with nice Dictionary-style lookup keys). So, as you may have noticed, the only data that isn't one of those data types is the List of Lists of integers which stores the CompletedNodes for each chapter. Saving this as a string was the obvious choice, and two functions had to be written to handle converting this list of lists of ints into a string: one to convert from string to List<List<int>>, and one to convert from List<List<int>> to string.

The main part of this was adding two unique identified characters: a '$' to indicate the end of a saved node ID, and a ';' to indicate the end of a chapter's list of node IDs.

Below, I'll post the (nicely commented, as usually) functions:

List<List<int>> to string for saving

string to List<List<int>> for loading
Reasonably elegant, I think, but ya know it doesn't really matter because, yet again, it's completely a black box. The only thing myself and my lovely programming team have to use from DataManager are the getters and setters to access and set data.

In addition to writing DataManager, I had to scroll through every single script in the entire project and make it all work with DataManager. It made a lot of stuff more elegant, and, more importantly, functional, which is something it was previously lacking. The DataManager opens a lot of doors there were previously either closed or really difficult and time-consuming to find.

And that's pretty much all of how DataManager works. So far, it's made the OverWorld possible, and sped up development time on pretty much anything that has to know about World or Level data.

Friday, March 18, 2016

Production Post 5 : I'M DROWNING IN RATIOS

The ocean level. The fucking ocean level. My god, my god, what have I done. There was a point where the ocean level worked fine and nobody really cared that you couldn't change the overall level time. The world was rose-colored and life was worth living. That all changed when we wanted to be able to change the overall level time.

The level functions along the same principles of object-state design I talked about in my last post, with the following managing enum:


The FLOOD states indicate that the waves are rising, and the DRAIN states indicate that the waves are falling. Below is a gif of the ocean level with the level time sped up:


The short rises and falls are what the MINI refers to, while the overall flood or drain are what the FLOOD and DRAIN refer to. I needed to relate the overall level time with the time for each of the above states. So, I made a public variable to decide what percentage of the level the FLOOD would take relative to the DRAIN (see how the drain takes longer than the flood? That was a major pain to accomplish in units of mini rises and falls). Then, I made another public variable to relate the distance a mini fall would cover compared to a mini rise. These variables were made with the FLOOD in mind, because a DRAIN is just an anti-FLOOD in the code (with a few caveats).

I'll post this here, but you shouldn't read it really. It demonstrates the ridiculousness required just to get the correct variables to make this thing work (that was half the battle, I'll admit, and it was very uphill).




































That was fun to work out. All of those variables were absolutely necessary, though. I had to relate every single type of interpolation time and distance to each other using the rations, and make sure the entire monstrosity took exactly <LEVEL_TIME> seconds. It was a labor of love, and totally worth it, because the ocean level actually works with the rest of the game.

I'm not going to post any more code, as it is pretty repetitive going down, but because of that initialize function, the level runs off of just four major state functions: HandleFloodMiniRise(), HandleFloodMiniFall(), HandleDrainMiniRise(), and HandleDrainMiniFall().

I remember writing all this crap: I made a lot of hand gestures to think about these ratios and how they fit into the level as a whole. shudder

Production Post 4 : "Object State" Architecture

The way I've been writing a lot of my scripts has been similar to what a finite state machine accomplishes. A finite state machine usually implies a somewhat complex AI to make those state changing decisions, but for my purposes I've been running these object states on timers.

The problem is that objects often need to execute a series of functions, in order, when timers are completed. This is especially true when we need to interpolate through a series of positions, rotations, scales, colors, etc for polish. I've seen this problem solved by ugly, ugly boolean checks that increase in complexity and decrease in readability very quickly as more states are required. I've been taking advantage of enums to resolve this.

This is a bit hard to explain, but it's actually a very simple concept, and one that has made my code very readable and easy to add and remove functionalities. So, to explain, I'll use the Wind Level as an example:

The important functionality of the wind level is that an invisible wind represented in code by a changing force vector blows the orbs off screen. It was easy enough to get the base wind blowing, but in order to feel like wind I couldn't just have a constant force vector for the wind, I needed to add turbulence. I accomplished this with a series of interpolations that execute in-order. This is the enum that I used:


Then, in the update loop, I have a switch statement that calls the proper functions for the current "turbulence" (object) state (mTS):


Really simple, highly readable, and yes all of these comments were in there to begin with I didn't just write them for this post. So, the HandleNone() function increments a timer, and when the timer runs out, it resets the timer for the BUILDUP state and changes mTS to BUILDUP. Super easy to follow, and with no nasty boolean checks.

I like to use this kind of structure even if I only have two states (which one would typically default to a boolean variable). This is because whenever there are two states, odds are we'll want to add another one later for some polish reason. In this way, the code becomes very modifiable. Maybe we want to make it so the orbs all shoot off a windy particle effect, but only during the COASTING state. Without having to really search through any code, we can accomplish this very easily by going into the HandleCoasting() function and writing some code there!

In short, using enums to manage object states is a really good idea; it makes your code readable, modifiable, and gives you a mini finite state machine to work with in the future!














Friday, February 12, 2016

Production Post 3 : "A link is only as long as your longest strong chain"

Simple, elegant, and intuitive solutions to programming challenges are always ideal. One particular programming challenge we have been faced with throughout the project has been making the tail move properly, and unfortunately an intuitive solution is not possible. There are a lot of caveats that come with making our dragon move, and documenting the current approach should make for a useful blog post.

In the midst of these huge modifications and upgrades to the game to get the game ready for feature lock, fixing the tail has become a lower priority task. But, last night, I stepped Zac through the tail code, explaining a lot of the how's and the why's of the algorithm. It proved to be very valuable, so I'm going to do the same in this post. Specifically, I will focus on the section of the algorithm that deals with the dragon bouncing off the bounds of the world (a circle).

Before we start, I'd like to note a couple things. The tail is broken up into individual node structures, each doubly linked with a parent node and a child node. This structure is contained in the DragonHead class. I have already explained in my previous post how the dragon's head acts as the managing class for the tail, so I won't go into that here.

The Reflect Component of the Algorithm

Let's start with the simplest part: the physical reflection. It is one line of code:







This line of code simply reflects the node off the wall, in the exact opposite way it was facing (without inverting the head's up vector like it used to).

Now, we'll go in-order in code with what happens when the head first hits the wall. The head hits the wall and calls that Reflect() function on itself; the rigidbody also accounts for this reflection.

The head then tells its child (the first tail node behind the head) that it has reflected. It's functionality is described in the comments.






























Without worrying about the math here, the crux of this function is to calculate the time it will take the current node to reach its parent's reflect position given its current velocity. Then, given this reflectionTFinal, the node knows when to reflect itself. A new gameobject with the same transform and velocity as the parent is created, and the node follows that transform for the given time in the same way it would follow its real parent.

The Problem

This is inherently wrong, as the velocity changes due to an acceleration (via rotation), if the parent was not moving perfectly straight at the time of collision. I will address this later. A funny tidbit about this function: when Zac came over we got to this and I was calculating the distance with a standard distance check using Pythagorean distance. But, after changing it to a Vector3.Distance call from Unity's Vector3 library, we saw a significant increase in reflection stability. We were baffled by this: how could this very rudimentary calculation be wrong?? A little while later we realized that, through a slight oversight, I was square rooting the square distance... twice. Of course, though, that was only one factor of why the tail breaks apart so often.

Let's take a look at how the problem here manifests itself in the game:














The first thing I notice here is that the head is the source of the problem. A side effect of how the tail is written is that it corrects itself over time, especially when the dragon moves in circles without hitting the boundary. The head being the source of the problem makes my proposed solution that much more likely to resolve at least most of the tail bugs.

Let's focus on JUST the dragon head and the node behind it, as the dragon head controls rotations and the node behind it is the most screwed up.

Blue: child node
Black Dotted: the initial transform of the parent at time of reflection
Black Solid: parent node final transform at time of child reflection
Grey Dotted: parent node copy initial transform when created by child
Grey Solid: parent node copy final transform at time of child reflection
Green Arrow: Trajectories from initial position to final position






















This works great, but only when the head is not turning at any point in time between the reflection of the head and the reflection of the child tail node. Since most of the game is spent turning, this simple position derivative will not work for changing velocities.

It just creates a copy of the parent node, and makes that transform continue forward until the child reflects. During this time period, the child node does it's usual "RotateTowards" behavior on the head copy. At which point, the parent node copy switches parents back to the real head, and destroys the copy.

The Conclusion

So, my conclusion is that the parent node copy must account for changes in rotation and velocity due to actual parent node turning. There are a few ways to do this, but first let's look at a diagram of what this will look like when rotations are accounted for. Compare the transforms of this diagram with those of the above diagram and it should become apparent why the latter should theoretically work:



















The idea here is simple: Just continue the trajectory as if the head had never hit the wall. This is where we'll have to test and see what happens. The gold trajectory is where I predict the correct final position of the head to be, but the green trajectory is also possible.

The Code Solution to the Conclusion

Theoretically, if in every physics step I set the head copy's trajectory equal to the inverse of the real head, it should work. The child node will reach the point of collision under the EXACT same conditions (with the correct rotation and velocity) that the head experienced when it reflected. As such, the child should reflect in the EXACT same manner as the head did, and the reflection should be successful.

Alternatively, if this slightly more elegant solution doesn't work, I have another. Instead of even creating a copy of the head:
1) Save off the position of the head at the impact point.
1.5) For testing, save off the rotation of the parent at the time of impact as a unit vector. We can use this to check against the actual unit vector of the reflection of the child.

2) Every physics step, obtain a vector from the collision point to the position of the actual head, NOT normalized.

3) Subtract this vector from the reflect position we defined in step 1.
4) We now have the position the dragon head would have been if it hadn't reflected (the gold dotted line)

One problem I haven't completely worked out with these methods is recalculating the time it will take for the child to reach the parent reflect position. Currently, it derives this time from a linear distance, but, as you can see, the distance is not linear. I need to measure the length of that curve. That cannot be one calculation, though, due to the case that the player stops or starts rotating during that time period. So, like the updating position, that time variable will also need to be updated according to the trajectory of the parent node.

Friday, January 29, 2016

Production Post 2 : Moving the Node Dragon

The first (and still ongoing, but coming to a close) tasks for the new programming team have been regarding dragon movement. We wanted 5 different movement schemes (including the legacy rotation scheme) to be changeable at runtime, so we can add new types of playable dragons. Given the relatively large size of the programming team (4), it is crucial that these schemes are highly detailed and polished; and bug free. I couldn't have thought of a better set of tasks to welcome three new programmers to the team than four new movement schemes. It gives us a chance to all work relatively independently on 4 unique sets of code, while requiring that that code be implemented in such a way that it can be brought together easily at runtime.

This hasn't been an easy task by any means, though. Given that the tail functioned in some pretty heinous ways (if it looks polished enough move on to the next thing), and that the tail is the better half of all five movement schemes, getting everything going has been a challenge.


But let's talk about the state we've gotten it to! I'll start with how the head moves, as it is the driving force of the whole system.


Setting Up


We'll start by defining these four variables, publicly modifiable in the editor. These will control the rate at which our movement schemes move.


Next, in our Start() function, we add all five movement scheme scripts to the dragon's head, as components. We immediately disable all of them, and then make a call to the same function that will allow us to change movement type at runtime.



The function that lets us switch movement schemes at runtime. When the previous movement type != the current one, we switch contexts to the new one.


Movement_Behavior abstract class


Now that everything's instantiated, we can get the head moving. I'm only going to post about the base class that our behaviors inherit from:

abstract public class Movement_Behavior : MonoBehaviour

Since all of our movement schemes always move forward (unless paused), we can always move forward in the direction we are facing:

head.velocity = head.transform.forward * mForwardSpeed;

For now, we'll separate the forward motion from the rotation, and just focus on making the tail nodes smoothly look at the node in front of it. A simple LookAt vector can make the dragon function, but making it smoothly function is a bit more difficult.

AngleSigned is a function which returns the angle between two vectors as a value between [-180, 180].
mRigidbody is the dragon's head's Rigidbody component

Currently, adding a meaningful rotational acceleration seems to make the tail separate, and is largely unnoticeable. So, in our current implementation, we just set the acceleration to be higher than the max velocity so it always rotates at the max velocity.

Tail_Behavior

In order for the tail nodes to not slowly separate from each other, we use about the same RotateTowards function above. I've found that if the angular velocity of the tail is shorter than that of the head, then the tail will peel apart. This is because the magnitudes of the tail node velocities are the same, but the directions are not, so this slightly-off direction will in a sense steal some of the velocity's speed. These effects are amplified the further away a tail node is from the head.

We tell each tail node about the node in front of it, like a forward linked list. That parent node's position is used as the seekPos parameter we passed into the function above. Here's a snippit from that:




And that's the core of how the dragon moves!







Saturday, January 23, 2016

Production Post 1 : A New Team

Before now, I hadn't worked with more than one other programmer on a game. Now, halfway through the project with a codebase I wrote, the lovely Abner, Vasily, and Will have joined the team. This makes me the de-facto lead programmer, a role I am not terribly familiar with but am quickly getting used to. What this really means is I have a few more responsibilities, and a bit more work to do than usual.

For one I have to, in certain ways, manage the programming team. I am completely confident in Abner, Vasily, and Will to get their work done on their own, but I have to make sure we all have tasks to be working on, and I have to make sure the tasks are divvied up fairly and according to their skillsets when applicable. Our first task was to make 4 different movement styles for the dragon, all of which are predicted to be in the game. The first thing we did to make this happen was to fix the tail that I had hacked together months ago. It worked "fine," but any change in speed would have to be accompanied by manually changing this variable called "power" through trial-and-error to some arbitrary value so the tail wouldn't drift apart. The first work session went, I think, fairly smoothly for a first work session with an unfamiliar codebase. Will had a fix in mind that I did not agree would work, and I had a fix in mind, so Will and over-the-shoulder Abner worked together on that fix while over-the-shoulder Vasily and I worked on my fix. About an hour later, mine worked and Will's didn't, which was to be expected considering I wrote the code to begin with.

Unfortunately, about an hour of that 2.5 hour session was spent with essentially everyone watching me program since that work was blocking everyone else from working. I see this as a failure on my part, but now I know going into the next session that I need to be sure there is no pipeline block like that.

The next work session went a lot better. We all sat at our own computers, all working in tandem on our respective movement schemes. There was a point where we were all actually trying to solve the same problem: converting touch position in screen pixel coordinates to 3D world coordinates. Abner found a great solution for getting the proper z-axis coordinates, and we all used it.

As far as lead programmer responsibilities go, I am also responsible for making sure we have proper build numbers and making a changelog every week. I also want to make it clear that all four of us have an equal say: if there is dispute over how we should approach a problem we will discuss it and reach a unanimous decision. I'd say we are all about equally competent programmers: the main thing I will be doing as lead programmer is scheduling work sessions, taking large tasks and breaking them up into smaller tasks, making sure everyone always has something to do, and writing up documents. Fun! I'm looking forward to seeing the technical achievements we can accomplish with four programmers!