Updated: Aug 22, 2019
If the date stamps on these things are any indication, I've been working on Undersea Odyssey for a month and a half now, and I've only just managed to nail down the most important part of the game: the control scheme. This is another huge challenge of mobile development, where you don't have a fancy controller or a whole keyboard worth of buttons to attach functionality to... you've really only got taps and swipes to work with. This can lead to one of a few different types of games: the ones that are nothing but clicking buttons in menus (backed up by fancy visuals, granted), the ones that try to give full controls by adding onscreen control buttons and joysticks or d-pads (bleh), or the ones that make the absolute most of the limited input and build an entire satisfying game around it. Games like Fruit Ninja, Angry Birds, and of course Flappy Bird come to mind for this latter category, and it was the right direction to go for me too. In fact, I had already decided that this would be a mobile-only game because its main gimmick would be using the phone's accelerometer to control the player submarine. So, tilt the phone to move up and down, tap the screen to shoot. Simple enough, right?
When is it ever? One thing I ran into during early playtests is that certain types of obstacles in the game are pretty hard to avoid, namely those that rise or fall from the bottom or top edges of the screen. In the split-second you have while playing, you're pretty much forced to move all the way to the opposite edge, since you don't know exactly how far up or down the obstacle will move, or how long it will take to do so. It would be a lot easier if you could control your sub's horizontal movement as well, so you could speed past or wait until the obstacle was no longer a threat. But I knew I needed to be careful with this, because Undersea Odyssey is not a platformer, and above all, I didn't want players to be able to move backwards. The simplest solution would be to allow player input to control the sub's speed within a range, to make sure it was always moving forwards but could temporarily speed up or slow down for those specific obstacles. Great, but how to implement that?
The first thing I tried was just using the phone's built-in accelerometer again. Using the reading from Input.acceleration.y worked well for up/down, so I tried .x to get left and right. It did not work as expected, and to explain why, I'm going to have to go into some physics. The first thing is that "accelerometer" might be a bit of a misnomer, because I discovered that the readings had nothing to do with how fast I was moving the phone, but how gravity was acting on it. Consider a phone lying face-up on a table. The reading will be (0, 0, -1): the full value of gravity (clamped between -1 and 1) is straight downwards. If you give the phone a spin, so it's rotating around but still face-up, the reading will be (0, 0, -1). See the problem? Gravity is still fully and directly down, and no other movement or "acceleration" is registered. Now, imagine holding the phone up, facing you in landscape format. Can you guess the reading now? It's (0, -1, 0); gravity is now entirely along the phone's negative y-axis (Unity defaults the phone's orientation to landscape, so y is indeed the shorter side). Now, here's the real issue with all of this: if you perform the same "spinning" movement as before, turning the phone clockwise or counter as you're still looking straight at it, the accelerometer reading changes this time. Gravity would no longer be confined to a single axis, so we get component values (along x and y, in this case), which makes sense. But it doesn't help me, because the same movement done by a player results in completely different, and therefore unusable, readings dependent on the starting orientation of the phone.
I'm not using the "spinning" motion in my game, but I had the same problem. This motion:
reads very differently from this one:
Ironically, the third axis works perfectly, and it's because moving the phone that way is actually only a single axis of motion, no matter the starting orientation. A simple calibration on startup is all I needed.
The next challenge was trying to figure out how to describe this problem to go look for help with it. Words like "roll", "pitch", and "yaw" helped a bit, but everyone with information about this sort of thing defines their starting axes and phone orientation differently, not to mention that there are several different coordinate systems in Unity alone, not counting the phone's hardware or its location in real-world space. It's actually why I made the above gifs, so I could literally say "This is what I want to do".
A partial answer revealed to me that I was using the wrong tool entirely. Because the accelerometer only measures gravity, it would never be able to do what I wanted. Instead, I would be better off dealing with the gyroscope, which is supposed to give you a full orientation measurement. You can get a few different numbers from the gyro, but after experimentation, none of them were helpful either! For example, userAcceleration seemed to solve my axis orientation problem, but it was also actually measuring acceleration; if the phone stopped moving at any time, all the numbers went to zero! Attitude was the recommended value by the docs, but it came back as a quaternion, which means it's unusable unless you really, deeply understand this stuff. I followed a blog post which purported to do exactly what I wanted, but after my tests, it seemed to only work for the two axes I didn't need. Both the OP and I were just becoming increasingly more confused; this is definitely a gimbal lock problem, but one which has obviously been solved by the people who made the Wii-mote to go into the Mario Kart steering wheel, and even by the modern PS4 controller, both of which have the exact kind of motion controls I need, without caring about starting orientation.
The closest I came to a solution actually had another problem, which was that when collecting data from the "troublesome" axis, I was noticing a change in the readings for the axis I had working as well. I can only assume that this was a gimbal lock problem as well, but what it signaled to me was that I needed to find a better way to do what I wanted to. Even if I could theoretically read from the accelerometer for up/down control, and the gyro for left/right, a player would not be able to perform precise movements around only one axis at a time...i.e., when trying to move one way, they would also be slightly tilting another way as well. True, this could have added another layer of challenge and uniqueness to the game, but I don't think it's what I wanted at this time. So it was back to the drawing board...
Rather than rush through the explanation of my solution, I'm going to call it here and leave you in suspense for a few days. I'm happy with what I came up with, and I hope you will be too!