Build Update 8 – Controller Support

Whenever we start to work on something, we like to think about the possible challenges that may come our way and plan accordingly. You know, doing some planning and thinking ahead of time can save a lot of effort in the long run. It’s usually better to put together a plan of attack then move forward than try to rush things and have to adjust mid-flight.

But what happens when the plan doesn’t hold up? Well, life gets hard. The controller support was such a project for us and we’re more than happy to share with you all the struggle we went through.

Past Experience

It’s not the first time our team has added controller support to a game. We actually did it a couple times before and it went smoothly enough. I think this past experience led us to build some confidence in our ability while, at the same time, take a couple things for granted.

On top of that, we had integrated some controller support to the game early on but eventually decided to focus on keyboard/mouse only to avoid spending time supporting multiple input schemes when we were not sure if a feature would stay in the game or not long term. Here we can interpret this reality in 2 different ways when considering the task ahead: we could think “well if we dropped it, it was probably somewhat challenging to support”. Unfortunately, we took the other route: “oh we already have some stuff in place, it will probably save us time”.

With our hearts filled with confidence, we decided to dive in. This was our first mistake. We didn’t challenge much our approach early on, which made things harder in the long run.

Aiming

This is where resides most of the challenges, and they are many. Sending a projectile at the right place in a game like Towers of Thana is not as easy as it seems because of the camera perspective. Here’s a side by side picture of the same projectile, seen from 2 different angles. I added a sphere to indicate the trajectory of the projectile:

As you can see, the sphere and the cursor are on top of each other in the left image, which is the perspective seen by the player. But in the right image, you can see that the sphere is actually above the ground. This set of images showcases fairly well the solution we went with. The cursor is always on the ground, we then identify the position of the sphere in the world by using a projection of the cursor on the ground and the height of the projectile. Then the projectile travels in the direction of the sphere.

This setup is not exactly new and has actually been in the game for a long time. We did have a couple issues with it though where the VFX may not match the collider (mostly visible with melee attacks), or where the projectile may fly up in the air instead of following the ground. Those were fixed as well in the latest update.

Moving With The Left Stick

In our implementation, the player can move around with the left stick. Optionally, they can use the right stick to aim. Using the right stick to aim should be fairly straight forward since it’s mostly the same than shooting with the mouse. You just do the same perspective tricks with the sphere we saw above and you’re good to go.

But what happens when you simply move to the right by holding the left joystick in the correct direction, leaving the right one alone, and want to shoot at the same time? You’d expect to shoot perfectly to the right. What actually happens?

In the previous image, the yellow arrow indicates the direction the character is moving.

You end up shooting in diagonal. Looks like our strategy of sending the projectile in the sphere’s direction doesn’t hold up anymore! So what do you do? You discard the sphere and simply shoot in the direction the player is facing. This results in the projectile NOT going through the cursor, but it’s a small price to pay for the projectile to go where you actually aim at. Plus, the player isn’t really aiming with the cursor in that situation, so in the end it feels quite natural.

Notice on the next image how the sphere is no longer on top of the cursor.

Camera Pan

Sometimes you implement a feature in a quick and dirty way. You know you’re playing the lottery. If it’s a win, it didn’t take a long time, it’s self contained and it works well. If you lose, the shortcuts you took bite you back. This is what happened with the Camera Pan. I don’t know if you’ve noticed, but the camera moves slightly as you move your cursor around, allowing you to see further away on the map. This is what I am referring to here.

This feature was not using the cursor’s world position but instead the raw mouse positions on the screen. Since there’s no mouse involved when you play with a gamepad, the feature wasn’t working at all. Luckily, it was not a big time sink to move things around so that it uses the cursor position instead, along with fixing a few other things along the way, but it was a friendly reminder of what tends to happen with the quick and dirty implementations.

Translation VS Stick Mirrors

I think that this one we should have seen coming, had we spent a bit more time thinking through about what a proper controller support meant. When you use your right joystick to aim, how do you want the cursor to behave? Should the cursor be dragged in the direction you’re pushing? Meaning that if you release the joystick, the cursor should stay there. Or maybe you want the cursor to be as snappy as possible and be locked within a circle around the player. You move the stick half way to the left? The cursor goes there instantly and stays there. You release the stick? It moves back to the center.

As it turns out, we needed both functionalities. When you aim to shoot, the second option feels just right. It’s snappy and allows you to shoot left and right at will. But when you want to place a tower on the ground, activate a combo or chose a teleport location, you want to be precise. The “dragging” behaviour is a lot more forgiving and allows you to find the exact spot you want, albeit slower.

Icons

In theory, that one should have been really easy to implement. This is THE controller-related feature we made sure we kept around throughout development. Early on, we created a small tool to show input icons. It’s quite nice really: if we ever change our input configuration (let’s say I want you to attack with Space Bar instead of Right Click), I just change the general implementation and don’t touch the icons. They should all just work!

On top of that, we had already implemented dynamic input switching. Meaning that if you were using your keyboard/mouse combo and picked up your Xbox controller mid-game instead, all the icons would switch automatically. A really neat tool that made this process a lot easier! Until…

The Unity Bug

Once we plugged in a PS4 and a Switch Pro controller, we ended up having a really weird behaviour. Turns out that there is currently a bug in Unity’s implementation with these gamepads (and probably others). If I plug in a PS4 controller, Unity will tell me that I have a 2 gamepads connected: a PS4 and an Xbox. If I press a button on my gamepad, Unity will fire an event for both.

This is highly frustrating and, as far as we can tell, there is no way for us to reliably tell if we are in a scenario where the Xbox controller firing event is a ghost/duplicated PS4 controller, or if it’s really just an Xbox controller. There are probably some strategy we could implement to try to figure it out, but since this is a bug on Unity’s side and that it will probably end up being resolved at some point, we opted to move forward with a workaround.

The Workaround

Because of the previously mentioned bug, we can’t reliably differentiate between a real or a ghost Xbox controller. For that reason, we decided to let the player decide what icon set they want to use when a gamepad is in use.

If you are using your keyboard and mouse combo, we’ll show you the correct icons. But if you use a gamepad, no matter what we detect, we’ll use the configuration you set there. Simple enough!

That’s it for this update! It was a lot more work than we anticipated, but in the end we’re really happy to finally get it out there for you to try. As always, let us know if you encounter any issues! You can find us on Discord.