Welp, I guess we're going this crazy thing now. Onward to 7DFPS with Ed Had a Weird Day.
The Starting Point
So the ingredients in this month's experiment were:
The Character Controller
As anyone who's ever written one well knows, a proper character controller can get pretty complicated, especially if you intend to deal with physics and/or a 3D environment. I'm using Unity, and even the official Unity-provided character controllers have various issues that can cause headaches. So lately I'd been working on creating my own character controller, focusing on side-scroller/platformer types of usage intially, and all of the associated thorny edge cases people expect a modern character controller to handle, like sticking to slopes, sliding cleanly along vertical surfaces, jumping believably, adjusting speed on collisions, and all sorts of nitpicky little things that just have to work or everything feels wrong.
Oh yeah, and not falling through floors or walking through walls.
Since I'd previously written a platformer involving flexible gravity where I was never entirely happy with my character controller, I wrote this controller with an eye towards gravity being a variable, both in strength and in direction. While testing the ability to invert gravity on the fly, I decided to attach the camera to my player's eye just to see what the flip looked like in first person, which led to me setting up a dual-camera system with both the side view and the FPS view visible at the same time, and since I'd just recently heard about 7DFPS coming up, I figured that was the sign I should try making a quick game out of this rig.
As for the character controller itself, I think it still needs some polish, but it can already handle more than I thought I'd be able to get working in a short period.
In case anyone's interested, the basic idea I'm using is that the character controller is broken up into a Controller component and a SweepTestMover component. The Controller component handles movement requests, for which it can be set up to allow player input or to be controlled via scripts to simulate a player's input, so it should accomodate NPCs/bots/whatever down the road. It applies gravity and air control if enabled, watches for adjacent colliders, modifies pending motions to stick to sloped floors, applies and decays a jump force, and probably some things I'm forgetting now, and accumulates all of those into a Vector3 representing the total proposed movement for this frame, then passes that Vector3 to the SweepTestMover component.
The SweepTestMover is a component than can be attached to any object with a RigidBody component. However, to avoid the noodly unpredictability of trying to use RigidBody physics on a character controller, the RigidBody is expected to be flagged as Kinematic. The only purpose the RigidBody serves is to allow the component access to the RigidBody.SweepTest() method, which the SweepTestMover uses to watch for collisions and move its GameObject to the closest possible position it can to where it was asked to move. It will attempt to move on all axes at once, but if it sees a collision it will move as far as it can, then it breaks the gravity-aligned portion of the movement into a separate movement and applies it separately, so if the controller collides with a vertical wall, it will continue to slide down the wall. In the end it reports back the actual movement(s) performed, and it uses BroadcastMessage to alert all components of collision events.
I have some larger ideas here that I'd like to work on later, so this was kind of a test run of those ideas. In order for this game to work, the controls had to work in some acceptable manner in both side-scroller and FPS contexts at the same time, so both views were doing roughly what was expected without the player having to use different controls for each view.
Luckily, the relatively simple control needs for this game made it fairly simple to get the two control schemes working logically together.
- In both views, the space bar jumps and shift runs. Easy enough.
- A and D move left and right in side view space.
- W and S move forward and backward in first person view.
- The mouse is unused in side view, so the right button is used to raise the gun, enabling first person view, mouse movement aims in first person space, and the left button fires.
- To unify the movement controls, when first person view is enabled, the player can't turn around. A and D still move left and right in side view space, but the player keeps facing the same way, to keep the first person view from jumping back and forth.
- W and S always move forward and backward in first person space, regardless of which way the player is facing, so if the player is looking at the first person view while running/jumping, they can use W and space bar the same way they could in a true FPS.
Overall I was really happy with how smoothly this seems to work, and based on the feedback I've gotten I think it lets people move back and forth between the two views and control sets without consciously thinking about it. Very promising, I think.
Artwork, Models, and Other Shamelessly Harvested Content
The various credits are on the title screen of the game, but I had to rely pretty heavily on the creative products of others to get this done on time. Essentially all of the 2D and 3D art is public domain or Creative Commons assets, and the music as usual is the smooth soothing sounds of Ben Freund.
One unexpectedly easy component of the game was the player model itself. I used a public domain mesh, flattened on the Z axis to essentially a 2D character, with the normals recalculated to appear flat (for the record I'm not even using a toon shader - that's just a diffuse shader on the flattened and re-normalized mesh), with a fairly simple Mecanim animation system. The only complication there was having separate animation layers with AvatarMasks applied so the right arm animates separately, which allows the player's arm to stay up with gun drawn while the rest of the mesh goes through the normal walk/run/jump animations.
Having the gun in the side view follow the vertical aim component of the first person view was something that needed to happen, and which I had no idea how to do going in. Luckily it turns out to be much easier than expected - Mecanim animations are applied prior to the LateUpdate() on the object, so in LateUpdate() you can apply relative transformations to individual bones in the armature and get the results you would expect. My Player component just keeps references to the Head and RightShoulder bones of my player armature, then on LateUpdate() it applies a rotation to both bones based on the Y axis of the MouseLook component, and just that easily, the player's right arm and head move up and down with the MouseLook aim. The results aren't perfect, because the MouseLook rotates around the FPS camera while the arm rotates around the shoulder, which introduces error the further up or down you aim, but now that I know that I could base a hybrid FPS/side view rig around the shoulder rotation point and I'm pretty sure I could get it to match exactly, should that need come up in the future.
I feel like I had just finally gotten a handle on this game and started creating some content when I ran out of time and had to wrap it up and get it posted, but I was happy with where it landed, even if there's precious little S in my FPS.
My character controller is shaping up pretty well, I was incredibly happy with how the animation system worked out, particularly with my first foray into script-driven modification of an armature at runtime, and the hybrid sidescroller/FPS gameplay and controls at least resulted in something playable. The exposure on 7DFPS.com got me lots of plays and even some likes and positive comments there and elsewhere, and I've now officially done one of these wacky timed game dev challenges.
So yeah, in summary, this was a good month for game dev.
Note: the following builds are available for testing purposes but I don't have the ability to test them. These are Unity builds so I don't anticipate any serious issues, but I can't vouch for correctness or performance of these builds.