Start by getting the asset from the AssetStore. In this case, we are using the "Robot Kyle | URP" asset from the AssetStore.
Now, look through the asset and build a rough* plan of Managed behaviors you want to build interop for. (or already have) E.g. this project:
Supported | Unsupported |
---|---|
- Animator - Animation Events - Input System (C# generated) |
- Cinemachine - UI - Input System (Input Action on MB) |
Also look out for any simplifications you can make to the asset. Make it just do what you need it to do. Nothing more.
To fulfill this first step, the following changes were made to the project:
- Removed the Mobile UI.
- Removed the
URPWizard
. - Removed ability to push objects.
- Commited to only using the new Input System.
- Changed the Input System to use the generated C# code version.
- Character should only contain
Animator
on GameObjects that are separate to the root.- Kyle normally has 2
Animator
s, one for the character, and one for the underlying model on the player
- Kyle normally has 2
- Removed duplicate URP settings.
- Removed Cinemachine.
- Moved kyle scene to the root of the project. (and fixed lighting)
- Ensure build settings are correct. (Make a build to test)
When building any GameObject to Entities interop, you need to consider which case you're dealing with:
- A. Your Managed Behaviour is heavily tied to a specific object (e.g.
Animator
) - B. Your Managed Behaviour is heavily tied to the scene (e.g.
Input System
orUI
)
I generally recommend having your ECS Systems be the owner of the behavior. This means that the system is responsible for updating the behavior (like activating an Animation), and the behavior is only partly responsible for providing the data the system needs to act on.
When pulling Managed data from an ECS System, you need the managed reference first. This is the main crux of the problem. And where the approach differs between A and B.
Now, I won't be covering B this time, but generally static
's or ECS Singletons work well for this. (See Episode 3 for an example of this in action.)
A is a bit more complex than B, as you need to consider how you're going to handle tying an Entity to a GameObject.
Luckily, Entities has managed components. Not only are IComponentData
on a class a managed component, but so is any Unity Object that inherits from UnityEngine.Object
. This means that you can have any GameObject
component on an Entity.
That said, you still need to get the GameObject
to the Entity
in the first place.
Now, SubScenes can only store entities in the SubScene hierarchy.
This is a problem, because you can't have a GameObject
in a SubScene, and you can't store a reference outside of the SubScene.
One exception to this rule is UnityObjectRef<T>
which is a unmanaged reference to a managed UnityEngine.Object
that can be stored in a SubScene. Importantly for us, it can store a reference to a GameObject prefab.
So, here's the plan:
- Create a
GameObject
prefab that contains theAnimator
component, along with the mesh it affects. - Create an empty object that gets baked in the SubScene that will contain a
UnityObjectRef<GameObject>
that references the prefab. - Create a PlayMode System that will
GameObject.Instantiate
the prefab, and attach theAnimator
to theEntity
that the system is working on. - Sync the game object's transform to the entity's transform.
- Use a cleanup component to destroy the
GameObject
when the entity is destroyed. - Realize edit mode has no visual representation of the
GameObject
prefab. - Create an EditMode System (for more detail Episode 7) that will create an entity representation of the
GameObject
prefab. - Realize the EditMode visualization is not the same as the PlayMode visualization. He's laying down, but he should be standing up.
- Create a shader that supports Entities Graphics drawing SkinnedMeshes correctly. Put it on Kyle.
Result of all this, let's you write code like this:
var animator = SystemAPI.ManagedAPI.GetComponent<Animator>(characterData.AnimationEntity);
animator.SetBool(AnimIDJump, characterInput.Jump);
3. Importing the com.unity.charactercontroller
package
Set aside the animation interop for now, and focus on the character controller. Start by importing the package. And the sample files. Then, the samples simply give a small example implementing the package. So let's look at the sample files and see what you can do to make it work, and start moving. In this case, we need to:
- Use all the prefabs found in
/Samples/Character Controller/1.1.0-exp.10/Standard Characters/ThirdPerson/Prefabs
, by putting them in the subscene. - Assign the
ThirdPersonCharacter
prefab to the matching field onThirdPersonPlayer
prefab. - Assign the
OrbitCamera
prefab to the matching field onThirdPersonPlayer
prefab. - Sync the Main Camera to the
OrbitCamera
prefab. By adding the componentMainGameObjectCamera
to the camera, andMainEntityCameraAuthor ig
to theOrbitCamera
prefab. - Press play and see the character controller in action.
The result of following those steps can be seen here.
For this project I also chose to clean up the project a bit more to make it easier to follow along with the stream. This includes:
- Delete FirstPerson assets
- Jobs made Main Thread (easier to explain on stream)
ThirdPersonPlayer
becomes one withThirdPersonCharacterData
, and simplified camera sync down to one scriptSyncWithEntityOrbitCamera
- Removed some usages of
WithEntityAccess
- Renamed
Aspect.CharacterComponent
toAspect.CharacterData
- Renamed
CharacterControl
toCharacterInput
andThirdPersonCharacterControl
toThirdPersonCharacterInput
All of the above changes don't change the functionality of the project, but make it easier to follow along with the stream.
Now that we have both the character controller and the animation interop working, we can start merging the two projects. First, made a few tweaks to the character controller to build confidence in how the character controller works:
- Locked the mouse cursor and hid it.
- Implemented sprint and changed to use the C# generated input system.
- Made orbit camera ignores the pole.
Then, I started merging the two projects by:
- Deleting the capsule from the
ThirdPersonCharacter
prefab. - Moving the empty game object that contains the
UnityObjectRef<GameObject>
to theThirdPersonCharacter
prefab. - Voila! The character controller is now moving the character with the model, that has an
Animator
attached to it.
Implementing animation is then as simple as this commit.
Connecting animation events is as simple as this commit.
Written by: Dani K Andersen (@dani485b)