So at this point it's pretty much a given that my real theme each month is "something different." It's also been on my mind lately that I should try to find a project where I'd feel comfortable contributing back to the community by releasing some code
As anyone who's ever used Unity knows, one of the most complicated things to get right is a third-person control and camera rig. There's a long list of things that need to be handled and interacted with and avoided, and the Internet is full of examples that range from incomplete to semi-functional to just plain awful.
While doing research for a potential third-person platformer project, I decided that maybe the best thing I could do would be to churn through all the knowledge and tutorials and examples I could find for creating a third person controller and camera rig, implement as much of it as possible, and then whatever I manage to get done this month, I release all the code.
Since it's almost Halloween and the optional theme for this month is candy, I'll call this a treat for anyone who might be helped by it. ;)
That's Not a Game
You're not wrong. By any reasonable definition I don't think I can describe this as a game, or at least not one that's particularly entertaining since it's a sandbox with no activities except running, jumping, sliding, and playing with a camera. However, after nine months of turning in projects which are all unquestionably games, I figured I could bend the definition a bit in order to release this demo and code as a 1GAM project.
So What Is It?
What I've got here is, I believe, a fairly solid foundation for a general-purpose third-person control system and associated camera control system. Giving credit where it's due, I tested/examined/analyzed/read about plenty of character controller options, and by far the best starting point for someone wanting to roll their own was the 3DBuzz 3rd Person Character System tutorial video series. If you've already worked on something like this, there will be some parts of this series you may want to skim through or skip altogether, but overall this is a great introduction to this kind of project, and I used their code as the initial structure for my controller. There are places where the code is still essentially what is demonstrated in that series, and there are places where I modified, enhanced, or ripped out parts of their system (more specifics on that later). But if you want to understand the theory, that video series is the place to start.
A character controller needs to handle player input, character locomotion, collisions, jumping, gravity, grounding, relevant environmental factors (what is the ground like where you're standing?), and that's just to get basic functionality. On top of that, you need a camera that works for that style of gameplay, which becomes dramatically more complex when you start trying to make your camera intelligently prevent occlusion (in simple terms, getting itself out of situations where an object is between the camera and the thing your player wants to see).
As best I can recall at this point, my character controller currently features:
- Separate walk and run speeds, with autorun option
- Configurable acceleration and stopping power
- Turn or strafe on sidestep, with strafelock option
- Sidestep speed with run sideways option
- Sidestep speed ratio for optional speed penalty when walking or running diagonally
- Jumping, with momentum preservation and configurable jump force
- Air control, configurable by ratio
- Third-person chase camera
- Right-click to mouselook, with option to always mouselook
- Rotate and zoom with mouse controls, smoothed with configurable smooth speeds
- Configurable zoom limits, vertical axis limits, and rotate/zoom speeds
- Rotate character while moving to face camera direction, with configurable speed
- Ability to programmatically rotate character/view, with configurable speed
- Actively attempts to avoid occlusion with a distance/rotation solver:
- Configurable occlusion jump distance, occlusion solver iterations, and minimum camera distance to target
- Exposes occlusion state (distance to nearest occlusion, occluded left/right/top/bottom/center, was occluded, remained occluded)
- If distance solver fails to stop occlusion, a rotation solution is attempted
- Configurable "push" force to rotate camera out of occlusion positions
- Configurable collision layer mask to allow camera to ignore occlusion by transparent/translucent objects
- Exposes ground normal and local ground normal
- Exposes ground slope angles relative to character's facing direction
- Exposes flags indicating whether the ground is sloped left/right and/or forward/back relative to character
- Configurable slope limits in degrees
- Controlled and uncontrollable slides with configurable slide speeds
- Configurable gravity
- Configurable terminal velocity
- Base fall speed setting to "jump start" falling - makes walking off of ledges less "floaty"
- When grounded, exposes ground object and movement/rotation of ground
- Transfers movement and rotation of ground object to character
- When not grounded, exposes altitude (with max altitiude test limit)
- Pusher behavior allows Rigidbodies to push player, attempts to resolve intersections
- PushHandler behavior seeks out nearby Pushers and attempts to push the player to simulate collision without intersection
- Still needs work - some intersection events still possible in some situations, and it should be possible to apply arbitrary temporary pushes to a character for attack responses, explosive forces, etc.
- Events broadcast by Player behavior to all behaviors on its object
- Useful for animation triggers, sound effects, energy consumption, or similar mechanics
- Currently implemented events:
- OnPlayerHitGround(GameObject ground)
- OnPlayerLeftGround(GameObject ground)
- OnPlayerPush(PushEventArgs args)
- Utility methods to simplify setting specific values from character
- Designed to be used with visual animations only, not animations with root motion applied
- Simple 2D blend tree for forward/back and left/right movement
- Forward blend tree blends idle/walk/run
- Left/right blend trees blend shuffle/run
- Simple jump cycle
- Animations are stock - I needed to limit myself to free animations I could include with the example project, and my usual source was giving me some problems, so I had to make do with some demo animations included in Unity Mecanim tutorial projects. As a result, the animations are definitely not great for this purpose, particularly the sidesteps, but I would assume anyone using any of this code would be providing their own model and animations anyway.
Rights, Usage, Etc.
All code available here is either entirely of my own making, or based on the referenced 3DBuzz video series which is freely available, so to my knowledge you should be free to use the included code in whatever way you see fit. I know for my part I reserve no rights to this code, so use it as you will.
Changes From the 3DBuzz Version
As I mentioned, I started from the 3DBuzz tutorial and worked from there. As best I can recall now, the major changes from what you would see in that video series are:
- Implementation of a single master behavior, meant to serve as a central interaction point for external classes, so other objects don't need to know if they need to talk to the motor, or the controller, or the camera, etc.
- Reorganization of several public fields on all behaviors into settings classes, to better categorize them and avoid having a list of dozens of configurable fields in one long unwieldy list
- Several changes to the camera-controller, most notably:
- Addition of a rotation solver to attempt to resolve occlusions that can't be solved by varying the camera distance alone
- Analysis of where the occclusion takes place, and public flags exposing that information
- Ability to rotate camera programmatically, to allow keyboard turn and similar functions
- Addition of configurable occlusion layer mask, to allow camera to ignore occlusion by transparent/translucent objects
- Changes to the motor, including:
- Acceleration and stopping power, separating movement speed from Input settings
- Momentum preservation and air control
- Sideways movement options
- Rotation smoothing
- "Jump start" fall speed option
- Ground position/rotation tracking and transfer of movement/rotation
- Slide implementation discarded entirely and re-written with additional options
- Addition of central event dispatching model and events
- Addition of animation support class to simplify integration with Mecanim
- Addition of Push/Pusher system to allow interation of player and moving rigidbodies.
Aside from the 3DBuzz video series, I also used a character model generated using makehuman with some minimal cleanup editing for my player character, and my sugary sandbox is populated by a variety of public domain and Creative Commons-licensed models provided by members of the BlendSwap community:
- "Doughnuts" by Deltaflyer47
- "Jello Pudding" by MattMump
- "Pumpkin" by Zombiecrackers
- "Low Pollipop" by nathanferguson
- "Candy Cane" by electric5auce
- "Popsicle" by Maika
- "Chocolate" by tadine
By my best estimate, a third person character controller and camera rig like this needs to handle about 400 different things, and I'd say I've got around 100 of them taken care of now. So there's a long way to go, but it's already much, much more capable than many of the samples I found while searching for examples and advice, so I think I'm on the right track, and I might actually end up with a fairly robust controller when I have another block of time to work on it. But for now here it is, and hopefully it's helpful in some way.
Download Unity Scripts Only Download Full Unity Package
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.