Update 7 added one of my favorite new features to the game, speed. It does exactly what it sounds like it does, it determines how fast you move along the timeline. However, ever since its addition to the game, speed has become a big hurdle to overcome anytime I want to make any changes to it. This dev-log (which is a semi-continuation of Dev-Log #3) explains why this is and how it works exactly.
I have had the idea of speed since the beginning of the game’s development. I thought it would be a neat mechanic to add. I wanted to add it to the first version of the game but couldn’t figure out how to make it work the way I wanted to. At the time, I was too inexperienced and just getting started so I decided to worry about the basics first and see if I could add it later. It wasn’t until Update 7 where I started to add more stats that I thought it was about time for me to try again to add speed to the game.
This part gets technical, but I will try to simplify it as much as possible. Let me explain how speed works and how it got to where we are now.
Initially, I thought that the only things I would need to change were the rate at which the arrow moves across the timeline and the internal timer that kept the timeline in place. The arrow moved one pixel every frame, and the timeline value subtracted one (out of thirty) every frame. So it looked a bit like this…
arrow.x = arrow.x +1;
timeline = timeline -.1;
What I thought I would have to do was simply add the speed to these values. The speed value that you see in the stat would be multiplied by .001 to get the speed value for the back-end of the game to make speed less drastic. For example…
//Speed of 10
display_speed = 10; //Speed displayed in the game. Overworld stat
player_speed = display_speed * .001; //What the game sees it as (so it's less dramatic)
In this example, the back-end speed would end up being 0.01. So, I thought all I would have to do is add speed like this…
arrow.x = arrow.x + (1+player_speed);
timeline = timeline – (.1+player_speed);
After some testing, I changed a few things and got everything working. I thought I was done. That was until I tried to use an attack. I quickly realized the future arrow was misaligned with where the attack landed. This should have been a quick and easy fix, but this is where the problems arose. Long story short, after countless hours of troubleshooting, I decided to redesign the way the attacks are calculated to accommodate speed from the ground up.
The basic structure of the combat works with three elements: an attack selection script, an attack time script, and an attack execution script. Selection is how the attack was initially selected from the menu, data calculated here goes into the time script. The time script determines how much time must pass before an attack is used. It also manages the order to make sure attacks are not used out of turn. Finally, execution determines what happens when the attack hits. This is where any special properties of the attack are determined.
What I had to do was change the selection and time script, the execution script stayed relatively unchanged. I had to find some way to determine where the attack should land and give it to the time script while incorporating speed. I chose to use the future arrow to determine where the attack will hit. This way, no matter what (technically), you should always attack where you are supposed to attack. So, the plan was to make a new calculation for the future arrow and give the location on the timeline to the time script to execute the attack.
When I initially did my testing, I had completely forgotten about the enemy. The enemy also has speed that I had to take that into consideration whenever I calculated the value for the future arrow. What I needed to find was the difference between the enemy and the player and meet in the middle. After days and weeks of testing and experimenting, I finally made the calculation that I worked consistently. Here it is and I’ll explain what it means…
arrow.x = (((attack_time*5)-(((attack_time-speed_conversion)*5-((attack_time-enemy_speed_conversion) * 5)
Speed conversion was the key to all this that I was missing. Speed conversion is how many extra pixels are added for every 1 timeline value (out of 30). Speed conversion is calculated when a player/enemy enters the battle. I multiply it by 5 is due to every 1 timeline unit being equal to 5 pixels; so all these calculations are done in pixels. The equation finds the difference between every 5 pixels (which is 1 timeline unit) between the enemy and player then subtracted it from the amount of time the attack takes (once again multiplied by 5). This gives me an output in pixels that I can then add directly to the future arrow x-axis.
After this, I send the predicted future arrow x-axis to the time script to know when to execute the attack. After a few changes there, I had everything working perfectly. The only issue was that visually, the arrow could be inconsistent. However, functionally, it seemed to work perfectly.
I relaxed and rejoiced that I had fixed the problem. This was until I added the speed buff. I gave someone plus 100 speed to make it obvious to see when the speed buff was working. However, this made it evident that something was wrong. The future arrow would be placed, but the attack would execute after the attack phase has finished. This meant that, once again, the future arrow calculation was wrong, and since the whole system now depends on the future arrow, I needed to fix this or else I would have to redo the entire selection and time scripts again.
This is where I ended the last dev-log, on a cliffhanger of sorts. Ever since then, I have been working hard on fixing the issue without having to remake everything. However, I am happy to report that I have fixed the issue. Let me explain.
The problem lies within the future arrow x-axis calculation from earlier. When someone has the speed stat of 100 plus whatever their speed stat was before, their speed conversion turns out to be about 0.50. With this number, I was overshooting the mark, meaning I had to figure out a way to subtract a bigger number from the attack time without affecting the lower-value speeds. The answer was multiplication…
arrow.x = (attack_time – ((attack_time*speed_conversion)-(attack_time*enemy_speed_conversion))) * 5
Up to this point, I had only considered addition and subtraction. I had tried multiplication, but I guess I did it wrong because I don’t remember it working well. This new calculation takes the attack time multiplied by the speed conversion for both the enemy and player and then subtracts them. After that, it subtracts that number from the attack time and converts it to pixels to add to the x-axis. The only issue now is that if the enemy’s speed is greater than the players, you get a negative number. We have to adjust for this, otherwise, the arrow would move in the wrong direction on the x-axis. This is the solution…
speed_calc = ((attack_time*speed_conversion)-(attack_time*enemy_speed_conversion));
speed_calc = speed_calc * sign(speed_calc); //Sign can either be 1 or -1
arrow.x = (attack_time – speed_calc) * 5;
Basically, this splits up the calculation to two different parts. The first part does the speed conversion calculation and makes sure it is always positive. This number is stored in speed_calc to use in the second calculation that adds it to the arrow.x. Here is an example of how this works compared to the old system…
//STATS
attack_time = 12;
player_speed_conversion = .012; //(for low conversion) .5 (for high conversion)
enemy_speed_conversion = .01;
//OLD SYSTEM
//IF SPEED IS LOW
arrow.x = (((12*5)-(((12-.012) * 5-((12-.01) * 5); //= 59.96 pixels to the right
//IF SPEED IT HIGH
arrow.x = (((12*5)-(((12-.5) * 5-((12-.01) * 5); //= 62.45 pixels to the right
//NEW SYSTEM
//IF SPEED IS LOW
speed_calc = ((12*.012)-(12*.01)) //This equals .024;
speed_calc = .024 * sign(.024); //Sign can either be 1 or -1 (in this case 1 because .024 is positive), In case speed_calc was negative, this makes positive.
arrow.x = (12 – .24) * 5; //= 58.8 pixels to the right.
//IF SPEED IT HIGH
speed_calc = ((12*.5)-(12*.01)); //This equals 5.88
speed_calc = 5.88* sign(5.88); //Sign can either be 1 or -1 (in this case 1 because 5.88 is positive), In case speed_calc was negative, this makes positive.
arrow.x = (12 – 5.88)*5; //= 30 pixels to the right.
Let me point out the significant differences between the two. For reference, the difference in speed between the two stats is plus 100! This means logically that the higher the speed, the lower the final output number should be. However, in the first calculation, the exact opposite happens. The calculation goes up! Also, pay attention to how similar the first set of low-speed numbers are between the two equations. It not only explains why I didn’t catch this earlier, but also the behavior of the first equation. The higher the number, the farther from the correct value it is. In the second equation, you can see the big difference between the final numbers. The high-speed output in the correct equation is greater than half of the old one! No wonder it was wrong. The sign function simply takes the number you input and spits out a 1 or -1 depending if the value is poisitive or negative. To make any number positive, you can just multiply it by the sign of itself, and I take advantage of that here. If you want to mess around and put in your own numbers, go right ahead, the equations are there. It is not only fun to see how speed can drastically affect things, but also how wrong I was before!
Now whatever the output number is will be added to the future arrow x-axis. Wherever the x-axis lands is the location that will be sent to the time script. From there, it will execute the attack when your timeline arrow reaches that point in the timeline. This (technically) should lead to a attack system that is 100% accurate, given that all the data and calculations are accurate.
That is how the future arrow system works, why it is so complicated and why it can break the game! To avoid too much confusion, I left out a few other elements that contribute to this system working, like what happens if the future arrow goes over the timeline and loops to the other side. Hopefully you learned something new and found the technical aspects of Phases interesting. Writing this dev-log was a lot of fun and helped me really fact-check what I was doing. I hope to do more of these technical dev-logs in the future, so stay tuned.
Thanks for reading,
Andy