itch.io is community of indie game creators and players

Devlogs

DevLog 3

Project Clockwork
A browser game made in HTML5

This dev cycle focused on changing up a lot of the gameplay systems. Enemies did not work how we wanted them to, the movement was way too fast and easy, and an overall balance change.

Player Controller V2 Liam: The player controller from the 2nd playtest was received better than the last but still needed a lot more tuning. It was too easy for the player to build up momentum. They could simply just jump mid-air, crouch, and repeat to build up infinite momentum. This came from the change of accelerating mid-air. It was also too easy for the player to dodge enemies and perform tricks; sliding was super easy to perform, and the requirements were too easily met. Dashing was too fast and recharged too fast, meaning it was easily spammed.

A fix I considered for this was to add a cap to the player's velocity, but I couldn’t think of a good way to solve it. I wanted the player to be able to move really fast. One of the jumping puzzles is a big ramp that sends you over 80M. Capping it would mean finding an arbitrary number to cap it by, potentially limiting myself in the future.

My solution was to make a lot of the movement options stricter.

  • Velocity needed to slide was increased, so just jumping was not enough to start a slide.
  • Dashing was put on a much higher cooldown, but to offset, I made the dash give a lot more velocity.
  • Velocity while sliding reduces a lot faster.

The thought behind this was to make getting velocity harder and more skill-based, requiring a perfectly timed slide-dash or using slopes smartly.

I kept Level 1 as is and modified the numbers of the player around the level. For my initial playtests, it was a lot harder to move but more satisfying to me at the same time. This is something that needs to be playtested a bunch, so I held off on fine-tuning the settings until I find out if people enjoy it.

The last problem to solve was the air control. Missing a jump was too harsh, as air control was extremely limited. So I split up air control into two different types: moving with velocity (i.e., holding W with a forward velocity) and against velocity (i.e., moving in a direction opposite to your current velocity, like holding S with a forward velocity).

Each air control has a different multiplier on how much it affects movement. Moving with the velocity has no control, but against has an increased amount. With this change, a slightly missed jump can be corrected, whilst limiting gaining too much velocity mid-air.

Enemy Balancing Liam: The initial balance of this game did not have many enemies in an arena at once. This was to make it less overwhelming for new players. But from playtesting, people did not like how tedious the enemies were to kill. They were not hard to dodge but instead were bullet sponges.

This iteration, I made the enemies a lot faster, with more of them, but substantially weaker. The intent was to move away from jumping around ~5 enemies to having 20+ enemies on the screen at a time. This pushes the game towards kiting the enemies around the level and less focusing on one enemy until they die.

This change I personally like a lot; it makes the game a lot more challenging but follows the initial design idea more closely and forces the player to use advanced movement mechanics more. This also will need a lot more testing.

Weapon & Upgrade Balancing Liam: The weapons needed some basic tweaks:

  • The rocket launcher was a bit underwhelming, so the fire rate was increased, as well as the blast radius.
  • The shotgun was not really touched except for reducing its range to make it more risk/reward.
  • The Tesla coil is in a good spot and went unchanged.
  • The sniper logic changed: it now only shoots when over an enemy, instead of constantly firing. It also saw a damage increase and starts off being able to pierce one enemy.

To compensate for having a lot more enemies in the game now, upgrades were given out more frequently but remained mostly unchanged.

Tutorial Level James:  In this update, we added a tutorial level. We felt that for newer players to the genre, we needed a tutorial level to teach the movement mechanics. Having it set in its own level allowed for a safe environment where players could learn the basic mechanics stress-free. We left it optional, as it allows more veteran players of the genre to skip the tutorial and figure things out on their own without being handheld.

Art Design: Simplistic Art Design James: Early on, we made the choice to use simple 2D pixel art sprites as it had so many benefits over doing something more complicated like a 3D model or even an animated sprite. The biggest reason was time and resources. In our team of two programmers, we were clearly missing an artist, and as neither of us had much experience in doing 3D/2D art, we had to come up with something achievable and within scope for the 12-week project.

We decided to go for unanimated, silly pixel art monsters for three reasons:

  1. Pixel art, when kept simple, is clear, concise, and easy to create. Its detached nature from the real world allows for lower artistic skill to be perceived as a stylistic choice.
  2. Goofy monsters mean I didn't have to replicate anything real and could just squiggle random shapes, then give them a weapon and a face.
  3. The third reason was how fast it was to produce, which meant I could spend less time on something that I was inefficient at and more time working on programming.

This later had a further benefit of making it possible to add gore and 'electrocuted' systems quickly and easily to the game for great effect.

Overall, we chose simple 2D pixel art for its ease and speed, allowing us to focus on other aspects. The goofy monsters were quick to create, and this approach made it easy to add effects like gore and electrocution.

Upgrade System Plans James:  Much thought and care have gone into redesigning the upgrade system to better fit the gameplay and add an extra layer of depth.

The first thing that will change is simplifying the multiple upgrades per weapon into a linear upgrade path per weapon. For example, the Shotgun:

  • Level 1: dmg +1
  • Level 2: dmg +1, range +1, etc.

There are two major reasons for this change:

  1. Players often chose the most valuable upgrades in order (for the shotgun, that was usually fireRate → damage → range).
  2. Leaving room for more choices of upgrades between weapons and damage type upgrades.

There are four different damage types (Physical, Electric, Fire, and Explosive), and each weapon belongs to a 'damage type.' Each element has its own unique branching upgrade trees that interact with one another to make different builds and unique combos.

Some examples:

  • (Explosive Upgrade): Every time an enemy is damaged by an explosive, reduce the cooldown for explosive weapons by 0.05s.
  • (Fire + Explosive): Enemies that are on fire explode on death.

This could cause a chain reaction and result in a gatling rocket launcher.

The ultimate goal of this upgrade system is to give the player more options to experiment with and figure out a build that 'breaks' the game, resulting in them wanting to go back and try something new.

Scope of the Upgrade System: James: This new system would take a lot of work and time—creating the system, coming up with upgrades, testing, and the biggest time sink of all: balancing. As a result, I believe a system like this would be out of scope and would likely not be implemented within the given time frame. It would have to come at a later date. Perhaps if our team were bigger, it would be possible, but ultimately, it's too much work.

Performance: Liam: This cycle mostly needed me working on design-related stuff, with some minimal programming tweaks to the player movement. I found that when doing non-programming stuff, I get burned out quite easily. One thing I did to stop that was to keep finding creative optimizations to the game.

This involved mainly opening the profiler, isolating the function/script that had the most impact on frame time, and improving it. I'll go through some of the more interesting ones.

The AI was already quite optimized; running 100s of the ranged AI would only take 1–2ms, but surprisingly, most of that time was not spent recalculating paths or doing raycasts. Instead, it was just calculating the distance from the player.

One surprising initial fix to this was to have a static field on the player that held the position vector, set every frame by the player controller. This stopped every enemy from needing to access the player's transform, which in Unity requires communicating with the C++ engine layer, adding a slight overhead.

Even with that fix, calculating distance was just an expensive function, even with the data not needing to be fetched. My final solution to this was to add “distance check LODs.” Basically, if an enemy is 30 units away from the player, they don’t need to update the distance for a few more frames since they won’t move that quickly. So when doing a distance check, the frequency depends on the distance:

  • Enemies that are 5 units away check every frame.
  • Enemies that are 20 units away check every 5 frames.
  • Enemies that are 50+ units away check every 10 frames.

This optimization significantly cut down on the processing time of the enemy AI. Now 1000 enemies can run at around 1–1.2ms.

The next consumer on the AI is now the raycast. I could apply the same optimization as above to it, but since we only see ~60 active enemies at once, it would just be for bragging rights.

Download Project Clockwork
Leave a comment