initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e55a4dcc5bff645d09407f78fffdd2a3
folderAsset: yes
timeCreated: 1434633677
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,208 @@
using UnityEngine;
using System.Collections;
namespace RootMotion {
/// <summary>
/// 3rd person camera controller.
/// </summary>
public class CameraController : MonoBehaviour {
// When to update the camera?
[System.Serializable]
public enum UpdateMode {
Update,
FixedUpdate,
LateUpdate,
FixedLateUpdate
}
public Transform target; // The target Transform to follow
public Transform rotationSpace; // If assigned, will use this Transform's rotation as the rotation space instead of the world space. Useful with spherical planets.
public UpdateMode updateMode = UpdateMode.LateUpdate; // When to update the camera?
public bool lockCursor = true; // If true, the mouse will be locked to screen center and hidden
[Header("Position")]
public bool smoothFollow; // If > 0, camera will smoothly interpolate towards the target
public Vector3 offset = new Vector3(0, 1.5f, 0.5f); // The offset from target relative to camera rotation
public float followSpeed = 10f; // Smooth follow speed
[Header("Rotation")]
public float rotationSensitivity = 3.5f; // The sensitivity of rotation
public float yMinLimit = -20; // Min vertical angle
public float yMaxLimit = 80; // Max vertical angle
public bool rotateAlways = true; // Always rotate to mouse?
public bool rotateOnLeftButton; // Rotate to mouse when left button is pressed?
public bool rotateOnRightButton; // Rotate to mouse when right button is pressed?
public bool rotateOnMiddleButton; // Rotate to mouse when middle button is pressed?
[Header("Distance")]
public float distance = 10.0f; // The current distance to target
public float minDistance = 4; // The minimum distance to target
public float maxDistance = 10; // The maximum distance to target
public float zoomSpeed = 10f; // The speed of interpolating the distance
public float zoomSensitivity = 1f; // The sensitivity of mouse zoom
[Header("Blocking")]
public LayerMask blockingLayers;
public float blockingRadius = 1f;
public float blockingSmoothTime = 0.1f;
public float blockingOriginOffset;
[Range(0f, 1f)] public float blockedOffset = 0.5f;
public float x { get; private set; } // The current x rotation of the camera
public float y { get; private set; } // The current y rotation of the camera
public float distanceTarget { get; private set; } // Get/set distance
private Vector3 targetDistance, position;
private Quaternion rotation = Quaternion.identity;
private Vector3 smoothPosition;
private Camera cam;
private bool fixedFrame;
private float fixedDeltaTime;
private Quaternion r = Quaternion.identity;
private Vector3 lastUp;
private float blockedDistance = 10f, blockedDistanceV;
public void SetAngles(Quaternion rotation)
{
Vector3 euler = rotation.eulerAngles;
this.x = euler.y;
this.y = euler.x;
}
public void SetAngles(float yaw, float pitch)
{
this.x = yaw;
this.y = pitch;
}
// Initiate, set the params to the current transformation of the camera relative to the target
protected virtual void Awake () {
Vector3 angles = transform.eulerAngles;
x = angles.y;
y = angles.x;
distanceTarget = distance;
smoothPosition = transform.position;
cam = GetComponent<Camera>();
lastUp = rotationSpace != null? rotationSpace.up: Vector3.up;
}
protected virtual void Update() {
if (updateMode == UpdateMode.Update) UpdateTransform();
}
protected virtual void FixedUpdate() {
fixedFrame = true;
fixedDeltaTime += Time.deltaTime;
if (updateMode == UpdateMode.FixedUpdate) UpdateTransform();
}
protected virtual void LateUpdate() {
UpdateInput();
if (updateMode == UpdateMode.LateUpdate) UpdateTransform();
if (updateMode == UpdateMode.FixedLateUpdate && fixedFrame) {
UpdateTransform(fixedDeltaTime);
fixedDeltaTime = 0f;
fixedFrame = false;
}
}
// Read the user input
public void UpdateInput() {
if (!cam.enabled) return;
// Cursors
Cursor.lockState = lockCursor? CursorLockMode.Locked: CursorLockMode.None;
Cursor.visible = lockCursor? false: true;
// Should we rotate the camera?
bool rotate = rotateAlways || (rotateOnLeftButton && Input.GetMouseButton(0)) || (rotateOnRightButton && Input.GetMouseButton(1)) || (rotateOnMiddleButton && Input.GetMouseButton(2));
// delta rotation
if (rotate) {
x += Input.GetAxis("Mouse X") * rotationSensitivity;
y = ClampAngle(y - Input.GetAxis("Mouse Y") * rotationSensitivity, yMinLimit, yMaxLimit);
}
// Distance
distanceTarget = Mathf.Clamp(distanceTarget + zoomAdd, minDistance, maxDistance);
}
// Update the camera transform
public void UpdateTransform() {
UpdateTransform(Time.deltaTime);
}
public void UpdateTransform(float deltaTime) {
if (!cam.enabled) return;
// Rotation
rotation = Quaternion.AngleAxis(x, Vector3.up) * Quaternion.AngleAxis(y, Vector3.right);
if (rotationSpace != null) {
r = Quaternion.FromToRotation(lastUp, rotationSpace.up) * r;
rotation = r * rotation;
lastUp = rotationSpace.up;
}
if (target != null) {
// Distance
distance += (distanceTarget - distance) * zoomSpeed * deltaTime;
// Smooth follow
if (!smoothFollow) smoothPosition = target.position;
else smoothPosition = Vector3.Lerp(smoothPosition, target.position, deltaTime * followSpeed);
// Position
Vector3 t = smoothPosition + rotation * offset;
Vector3 f = rotation * -Vector3.forward;
if (blockingLayers != -1)
{
RaycastHit hit;
if (Physics.SphereCast(t - f * blockingOriginOffset, blockingRadius, f, out hit, blockingOriginOffset + distanceTarget - blockingRadius, blockingLayers))
{
blockedDistance = Mathf.SmoothDamp(blockedDistance, hit.distance + blockingRadius * (1f - blockedOffset) - blockingOriginOffset, ref blockedDistanceV, blockingSmoothTime);
}
else blockedDistance = distanceTarget;
distance = Mathf.Min(distance, blockedDistance);
}
position = t + f * distance;
// Translating the camera
transform.position = position;
}
transform.rotation = rotation;
}
// Zoom input
private float zoomAdd {
get {
float scrollAxis = Input.GetAxis("Mouse ScrollWheel");
if (scrollAxis > 0) return -zoomSensitivity;
if (scrollAxis < 0) return zoomSensitivity;
return 0;
}
}
// Clamping Euler angles
private float ClampAngle (float angle, float min, float max) {
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 85ef3e8d5c77f405d8ab70a643d6a2f3
timeCreated: 1444226768
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 10200
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using UnityEngine;
using System.Collections;
namespace RootMotion {
/// <summary>
/// The very simple FPS camera.
/// </summary>
public class CameraControllerFPS: MonoBehaviour {
public float rotationSensitivity = 3f;
public float yMinLimit = -89f;
public float yMaxLimit = 89f;
private float x, y;
void Awake () {
Vector3 angles = transform.eulerAngles;
x = angles.y;
y = angles.x;
}
public void LateUpdate() {
Cursor.lockState = CursorLockMode.Locked;
x += Input.GetAxis("Mouse X") * rotationSensitivity;
y = ClampAngle(y - Input.GetAxis("Mouse Y") * rotationSensitivity, yMinLimit, yMaxLimit);
// Rotation
transform.rotation = Quaternion.AngleAxis(x, Vector3.up) * Quaternion.AngleAxis(y, Vector3.right);
}
// Clamping Euler angles
private float ClampAngle (float angle, float min, float max) {
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2727c46fc0dba4e95bbfe87904846e02
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 677db6436442a4f8aadb9aa0e1f26956
folderAsset: yes
timeCreated: 1434633580
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,77 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// The base abstract class for all character animation controllers.
/// </summary>
public abstract class CharacterAnimationBase: MonoBehaviour {
public bool smoothFollow = true;
public float smoothFollowSpeed = 20f;
protected bool animatePhysics;
private Vector3 lastPosition;
private Vector3 localPosition;
private Quaternion localRotation;
private Quaternion lastRotation;
// Gets the rotation pivot of the character
public virtual Vector3 GetPivotPoint() {
return transform.position;
}
// Is the animator playing the grounded state?
public virtual bool animationGrounded {
get {
return true;
}
}
// Gets angle around y axis from a world space direction
public float GetAngleFromForward(Vector3 worldDirection) {
Vector3 local = transform.InverseTransformDirection(worldDirection);
return Mathf.Atan2 (local.x, local.z) * Mathf.Rad2Deg;
}
protected virtual void Start() {
if (transform.parent.GetComponent<CharacterBase>() == null) {
Debug.LogWarning("Animation controllers should be parented to character controllers!", transform);
}
lastPosition = transform.position;
localPosition = transform.localPosition;
lastRotation = transform.rotation;
localRotation = transform.localRotation;
}
protected virtual void LateUpdate() {
if (animatePhysics) return;
SmoothFollow();
}
// Smooth interpolation of character position. Helps to smooth out hectic rigidbody motion
protected virtual void FixedUpdate() {
if (!animatePhysics) return;
SmoothFollow();
}
private void SmoothFollow() {
if (smoothFollow) {
transform.position = Vector3.Lerp(lastPosition, transform.parent.TransformPoint(localPosition), Time.deltaTime * smoothFollowSpeed);
transform.rotation = Quaternion.Lerp(lastRotation, transform.parent.rotation * localRotation, Time.deltaTime * smoothFollowSpeed);
} else
{
transform.localPosition = localPosition;
transform.localRotation = localRotation;
}
lastPosition = transform.position;
lastRotation = transform.rotation;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 569c0702757374fa699636b2dc4aa1e2
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,40 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// Contols animation for a simple Mecanim character
/// </summary>
public class CharacterAnimationSimple: CharacterAnimationBase {
public CharacterThirdPerson characterController;
public float pivotOffset; // Offset of the rotating pivot point from the root
public AnimationCurve moveSpeed; // The moving speed relative to input forward
private Animator animator;
protected override void Start() {
base.Start();
animator = GetComponentInChildren<Animator>();
}
public override Vector3 GetPivotPoint() {
if (pivotOffset == 0) return transform.position;
return transform.position + transform.forward * pivotOffset;
}
// Update the Animator with the current state of the character controller
void Update() {
float speed = moveSpeed.Evaluate(characterController.animState.moveDirection.z);
// Locomotion
animator.SetFloat("Speed", speed);
// Movement
characterController.Move(characterController.transform.forward * Time.deltaTime * speed, Quaternion.identity);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2209dd700c8b64fc28e9cde6ac3286ba
timeCreated: 1434698904
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// Contols animation for a third person person controller.
/// </summary>
public class CharacterAnimationThirdPerson: CharacterAnimationBase {
public CharacterThirdPerson characterController;
[SerializeField] float turnSensitivity = 0.2f; // Animator turning sensitivity
[SerializeField] float turnSpeed = 5f; // Animator turning interpolation speed
[SerializeField] float runCycleLegOffset = 0.2f; // The offset of leg positions in the running cycle
[Range(0.1f,3f)] [SerializeField] float animSpeedMultiplier = 1; // How much the animation of the character will be multiplied by
protected Animator animator;
private Vector3 lastForward;
private const string groundedDirectional = "Grounded Directional", groundedStrafe = "Grounded Strafe";
private float deltaAngle;
private float jumpLeg;
private bool lastJump;
protected override void Start() {
base.Start();
animator = GetComponent<Animator>();
lastForward = transform.forward;
}
public override Vector3 GetPivotPoint() {
return animator.pivotPosition;
}
// Is the Animator playing the grounded animations?
public override bool animationGrounded {
get {
return animator.GetCurrentAnimatorStateInfo(0).IsName(groundedDirectional) || animator.GetCurrentAnimatorStateInfo(0).IsName(groundedStrafe);
}
}
// Update the Animator with the current state of the character controller
protected virtual void Update() {
if (Time.deltaTime == 0f) return;
animatePhysics = animator.updateMode == AnimatorUpdateMode.AnimatePhysics;
// Jumping
if (characterController.animState.jump) {
if (!lastJump)
{
float runCycle = Mathf.Repeat(animator.GetCurrentAnimatorStateInfo(0).normalizedTime + runCycleLegOffset, 1);
float jumpLeg = (runCycle < 0.5f ? 1 : -1) * characterController.animState.moveDirection.z;
animator.SetFloat("JumpLeg", jumpLeg);
}
}
lastJump = characterController.animState.jump;
// Calculate the angular delta in character rotation
float angle = -GetAngleFromForward(lastForward) - deltaAngle;
deltaAngle = 0f;
lastForward = transform.forward;
angle *= turnSensitivity * 0.01f;
angle = Mathf.Clamp(angle / Time.deltaTime, -1f, 1f);
// Update Animator params
animator.SetFloat("Turn", Mathf.Lerp(animator.GetFloat("Turn"), angle, Time.deltaTime * turnSpeed));
animator.SetFloat("Forward", characterController.animState.moveDirection.z);
animator.SetFloat("Right", characterController.animState.moveDirection.x);
animator.SetBool("Crouch", characterController.animState.crouch);
animator.SetBool("OnGround", characterController.animState.onGround);
animator.SetBool("IsStrafing", characterController.animState.isStrafing);
if (!characterController.animState.onGround) {
animator.SetFloat ("Jump", characterController.animState.yVelocity);
}
if (characterController.doubleJumpEnabled) animator.SetBool("DoubleJump", characterController.animState.doubleJump);
characterController.animState.doubleJump = false;
// the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector
if (characterController.animState.onGround && characterController.animState.moveDirection.z > 0f) {
animator.speed = animSpeedMultiplier;
} else {
// but we don't want to use that while airborne
animator.speed = 1;
}
}
// Call OnAnimatorMove manually on the character controller because it doesn't have the Animator component
void OnAnimatorMove() {
// For not using root rotation in Turn value calculation
Vector3 f = animator.deltaRotation * Vector3.forward;
deltaAngle += Mathf.Atan2(f.x, f.z) * Mathf.Rad2Deg;
if (characterController.fullRootMotion)
{
characterController.transform.position += animator.deltaPosition;
characterController.transform.rotation *= animator.deltaRotation;
}
else
{
characterController.Move(animator.deltaPosition, animator.deltaRotation);
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 44963f0d149a94b76a8afd23095c42df
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// The base abstract class for all character controllers, provides common functionality.
/// </summary>
public abstract class CharacterBase: MonoBehaviour {
[Header("Base Parameters")]
[Tooltip("If specified, will use the direction from the character to this Transform as the gravity vector instead of Physics.gravity. Physics.gravity.magnitude will be used as the magnitude of the gravity vector.")]
public Transform gravityTarget;
[Tooltip("Multiplies gravity applied to the character even if 'Individual Gravity' is unchecked.")]
public float gravityMultiplier = 2f; // gravity modifier - often higher than natural gravity feels right for game characters
public float airborneThreshold = 0.6f; // Height from ground after which the character is considered airborne
public float slopeStartAngle = 50f; // The start angle of velocity dampering on slopes
public float slopeEndAngle = 85f; // The end angle of velocity dampering on slopes
public float spherecastRadius = 0.1f; // The radius of sperecasting
public LayerMask groundLayers; // The walkable layers
private PhysicMaterial zeroFrictionMaterial;
private PhysicMaterial highFrictionMaterial;
protected Rigidbody r;
protected const float half = 0.5f;
protected float originalHeight;
protected Vector3 originalCenter;
protected CapsuleCollider capsule;
public abstract void Move(Vector3 deltaPosition, Quaternion deltaRotation);
protected Vector3 GetGravity() {
if (gravityTarget != null) {
return (gravityTarget.position - transform.position).normalized * Physics.gravity.magnitude;
}
return Physics.gravity;
}
protected virtual void Start() {
capsule = GetComponent<Collider>() as CapsuleCollider;
r = GetComponent<Rigidbody>();
// Store the collider volume
originalHeight = capsule.height;
originalCenter = capsule.center;
// Physics materials
zeroFrictionMaterial = new PhysicMaterial();
zeroFrictionMaterial.dynamicFriction = 0f;
zeroFrictionMaterial.staticFriction = 0f;
zeroFrictionMaterial.frictionCombine = PhysicMaterialCombine.Minimum;
zeroFrictionMaterial.bounciness = 0f;
zeroFrictionMaterial.bounceCombine = PhysicMaterialCombine.Minimum;
highFrictionMaterial = new PhysicMaterial();
// Making sure rigidbody rotation is fixed
r.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;
}
// Spherecast from the root to find ground height
protected virtual RaycastHit GetSpherecastHit() {
Vector3 up = transform.up;
Ray ray = new Ray (r.position + up * airborneThreshold, -up);
RaycastHit h = new RaycastHit();
h.point = transform.position - transform.transform.up * airborneThreshold;
h.normal = transform.up;
Physics.SphereCast(ray, spherecastRadius, out h, airborneThreshold * 2f, groundLayers);
return h;
}
// Gets angle around y axis from a world space direction
public float GetAngleFromForward(Vector3 worldDirection) {
Vector3 local = transform.InverseTransformDirection(worldDirection);
return Mathf.Atan2 (local.x, local.z) * Mathf.Rad2Deg;
}
// Rotate a rigidbody around a point and axis by angle
protected void RigidbodyRotateAround(Vector3 point, Vector3 axis, float angle) {
Quaternion rotation = Quaternion.AngleAxis(angle, axis);
Vector3 d = transform.position - point;
r.MovePosition(point + rotation * d);
r.MoveRotation(rotation * transform.rotation);
}
// Scale the capsule collider to 'mlp' of the initial value
protected void ScaleCapsule (float mlp) {
if (capsule.height != originalHeight * mlp) {
capsule.height = Mathf.MoveTowards (capsule.height, originalHeight * mlp, Time.deltaTime * 4);
capsule.center = Vector3.MoveTowards (capsule.center, originalCenter * mlp, Time.deltaTime * 2);
}
}
// Set the collider to high friction material
protected void HighFriction() {
capsule.material = highFrictionMaterial;
}
// Set the collider to zero friction material
protected void ZeroFriction() {
capsule.material = zeroFrictionMaterial;
}
// Get the damper of velocity on the slopes
protected float GetSlopeDamper(Vector3 velocity, Vector3 groundNormal) {
float angle = 90f - Vector3.Angle(velocity, groundNormal);
angle -= slopeStartAngle;
float range = slopeEndAngle - slopeStartAngle;
return 1f - Mathf.Clamp(angle / range, 0f, 1f);
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2869ec4c764fa4c24b4c2b1654fd316d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,470 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// Third person character controller. This class is based on the ThirdPersonCharacter.cs of the Unity Exmaple Assets.
/// </summary>
public class CharacterThirdPerson : CharacterBase {
// Is the character always rotating to face the move direction or is he strafing?
[System.Serializable]
public enum MoveMode {
Directional,
Strafe
}
// Animation state
public struct AnimState {
public Vector3 moveDirection; // the forward speed
public bool jump; // should the character be jumping?
public bool crouch; // should the character be crouching?
public bool onGround; // is the character grounded
public bool isStrafing; // should the character always rotate to face the move direction or strafe?
public float yVelocity; // y velocity of the character
public bool doubleJump;
}
[Header("References")]
public CharacterAnimationBase characterAnimation; // the animation controller
public UserControlThirdPerson userControl; // user input
public CameraController cam; // Camera controller (optional). If assigned will update the camera in LateUpdate only if character moves
[Header("Movement")]
public MoveMode moveMode; // Is the character always rotating to face the move direction or is he strafing?
public bool smoothPhysics = true; // If true, will use interpolation to smooth out the fixed time step.
public float smoothAccelerationTime = 0.2f; // The smooth acceleration of the speed of the character (using Vector3.SmoothDamp)
public float linearAccelerationSpeed = 3f; // The linear acceleration of the speed of the character (using Vector3.MoveTowards)
public float platformFriction = 7f; // the acceleration of adapting the velocities of moving platforms
public float groundStickyEffect = 4f; // power of 'stick to ground' effect - prevents bumping down slopes.
public float maxVerticalVelocityOnGround = 3f; // the maximum y velocity while the character is grounded
public float velocityToGroundTangentWeight = 0f; // the weight of rotating character velocity vector to the ground tangent
[Header("Rotation")]
public bool lookInCameraDirection; // should the character be looking in the same direction that the camera is facing
public float turnSpeed = 5f; // additional turn speed added when the player is moving (added to animation root rotation)
public float stationaryTurnSpeedMlp = 1f; // additional turn speed added when the player is stationary (added to animation root rotation)
[Header("Jumping and Falling")]
public bool smoothJump = true; // If true, adds jump force over a few fixed time steps, not in a single step
public float airSpeed = 6f; // determines the max speed of the character while airborne
public float airControl = 2f; // determines the response speed of controlling the character while airborne
public float jumpPower = 12f; // determines the jump force applied when jumping (and therefore the jump height)
public float jumpRepeatDelayTime = 0f; // amount of time that must elapse between landing and being able to jump again
public bool doubleJumpEnabled;
public float doubleJumpPowerMlp = 1f;
[Header("Wall Running")]
public LayerMask wallRunLayers; // walkable vertical surfaces
public float wallRunMaxLength = 1f; // max duration of a wallrun
public float wallRunMinMoveMag = 0.6f; // the minumum magnitude of the user control input move vector
public float wallRunMinVelocityY = -1f; // the minimum vertical velocity of doing a wall run
public float wallRunRotationSpeed = 1.5f; // the speed of rotating the character to the wall normal
public float wallRunMaxRotationAngle = 70f; // max angle of character rotation
public float wallRunWeightSpeed = 5f; // the speed of blending in/out the wall running effect
[Header("Crouching")]
public float crouchCapsuleScaleMlp = 0.6f; // the capsule collider scale multiplier while crouching
/// <summary>
/// Enable this while playing an animation that should be driven 100% by root motion, such as climbing walls
/// </summary>
public bool fullRootMotion { get; set; }
public bool onGround { get; private set; }
public AnimState animState = new AnimState();
protected Vector3 moveDirection; // The current move direction of the character in Strafe move mode
private Animator animator;
private Vector3 normal, platformVelocity, platformAngularVelocity;
private RaycastHit hit;
private float jumpLeg, jumpEndTime, forwardMlp, groundDistance, lastAirTime, stickyForce;
private Vector3 wallNormal = Vector3.up;
private Vector3 moveDirectionVelocity;
private float wallRunWeight;
private float lastWallRunWeight;
private float fixedDeltaTime;
private Vector3 fixedDeltaPosition;
private Quaternion fixedDeltaRotation = Quaternion.identity;
private bool fixedFrame;
private float wallRunEndTime;
private Vector3 gravity;
private Vector3 verticalVelocity;
private float velocityY;
private bool doubleJumped;
private bool jumpReleased;
// Use this for initialization
protected override void Start () {
base.Start();
animator = GetComponent<Animator>();
if (animator == null) animator = characterAnimation.GetComponent<Animator>();
wallNormal = -gravity.normalized;
onGround = true;
animState.onGround = true;
if (cam != null) cam.enabled = false;
}
void OnAnimatorMove() {
Move (animator.deltaPosition, animator.deltaRotation);
}
// When the Animator moves
public override void Move(Vector3 deltaPosition, Quaternion deltaRotation) {
// Accumulate delta position, update in FixedUpdate to maintain consitency
fixedDeltaTime += Time.deltaTime;
fixedDeltaPosition += deltaPosition;
fixedDeltaRotation *= deltaRotation;
}
void FixedUpdate() {
gravity = fullRootMotion? Vector3.zero: GetGravity();
verticalVelocity = V3Tools.ExtractVertical(r.velocity, gravity, 1f);
velocityY = verticalVelocity.magnitude;
if (Vector3.Dot(verticalVelocity, gravity) > 0f) velocityY = -velocityY;
// Smoothing out the fixed time step
r.interpolation = smoothPhysics? RigidbodyInterpolation.Interpolate: RigidbodyInterpolation.None;
characterAnimation.smoothFollow = smoothPhysics;
// Move
MoveFixed(fixedDeltaPosition);
fixedDeltaTime = 0f;
fixedDeltaPosition = Vector3.zero;
r.MoveRotation(transform.rotation * fixedDeltaRotation);
fixedDeltaRotation = Quaternion.identity;
Rotate();
GroundCheck (); // detect and stick to ground
// Friction
if (userControl.state.move == Vector3.zero && groundDistance < airborneThreshold * 0.5f) HighFriction();
else ZeroFriction();
bool stopSlide = !fullRootMotion && onGround && userControl.state.move == Vector3.zero && r.velocity.magnitude < 0.5f && groundDistance < airborneThreshold * 0.5f;
// Individual gravity
if (gravityTarget != null) {
r.useGravity = false;
if (!stopSlide) r.AddForce(gravity);
}
if (stopSlide) {
r.useGravity = false;
r.velocity = Vector3.zero;
} else if (gravityTarget == null) r.useGravity = true;
if (onGround) {
// Jumping
animState.jump = Jump();
jumpReleased = false;
doubleJumped = false;
} else {
if (!userControl.state.jump) jumpReleased = true;
//r.AddForce(gravity * gravityMultiplier);
if (jumpReleased && userControl.state.jump && !doubleJumped && doubleJumpEnabled) {
jumpEndTime = Time.time + 0.1f;
animState.doubleJump = true;
Vector3 jumpVelocity = userControl.state.move * airSpeed;
r.velocity = jumpVelocity;
r.velocity += transform.up * jumpPower * doubleJumpPowerMlp;
doubleJumped = true;
}
}
// Scale the capsule colllider while crouching
ScaleCapsule(userControl.state.crouch? crouchCapsuleScaleMlp: 1f);
fixedFrame = true;
}
protected virtual void Update() {
// Fill in animState
animState.onGround = onGround;
animState.moveDirection = GetMoveDirection();
animState.yVelocity = Mathf.Lerp(animState.yVelocity, velocityY, Time.deltaTime * 10f);
animState.crouch = userControl.state.crouch;
animState.isStrafing = moveMode == MoveMode.Strafe;
}
protected virtual void LateUpdate() {
if (cam == null) return;
cam.UpdateInput();
if (!fixedFrame && r.interpolation == RigidbodyInterpolation.None) return;
// Update camera only if character moves
cam.UpdateTransform(r.interpolation == RigidbodyInterpolation.None? Time.fixedDeltaTime: Time.deltaTime);
fixedFrame = false;
}
private void MoveFixed(Vector3 deltaPosition) {
// Process horizontal wall-running
WallRun();
Vector3 velocity = fixedDeltaTime > 0f? deltaPosition / fixedDeltaTime: Vector3.zero;
// Add velocity of the rigidbody the character is standing on
if (!fullRootMotion)
{
velocity += V3Tools.ExtractHorizontal(platformVelocity, gravity, 1f);
if (onGround)
{
// Rotate velocity to ground tangent
if (velocityToGroundTangentWeight > 0f)
{
Quaternion rotation = Quaternion.FromToRotation(transform.up, normal);
velocity = Quaternion.Lerp(Quaternion.identity, rotation, velocityToGroundTangentWeight) * velocity;
}
}
else
{
// Air move
//Vector3 airMove = new Vector3 (userControl.state.move.x * airSpeed, 0f, userControl.state.move.z * airSpeed);
Vector3 airMove = V3Tools.ExtractHorizontal(userControl.state.move * airSpeed, gravity, 1f);
velocity = Vector3.Lerp(r.velocity, airMove, Time.deltaTime * airControl);
}
if (onGround && Time.time > jumpEndTime && !r.isKinematic)
{
r.velocity = r.velocity - transform.up * stickyForce * Time.deltaTime;
}
// Vertical velocity
Vector3 verticalVelocity = V3Tools.ExtractVertical(r.velocity, gravity, 1f);
Vector3 horizontalVelocity = V3Tools.ExtractHorizontal(velocity, gravity, 1f);
if (onGround)
{
if (Vector3.Dot(verticalVelocity, gravity) < 0f)
{
verticalVelocity = Vector3.ClampMagnitude(verticalVelocity, maxVerticalVelocityOnGround);
}
}
r.velocity = horizontalVelocity + verticalVelocity;
} else
{
r.velocity = velocity;
}
// Dampering forward speed on the slopes (Not working since Unity 2017.2)
//float slopeDamper = !onGround? 1f: GetSlopeDamper(-deltaPosition / Time.deltaTime, normal);
//forwardMlp = Mathf.Lerp(forwardMlp, slopeDamper, Time.deltaTime * 5f);
forwardMlp = 1f;
}
// Processing horizontal wall running
private void WallRun() {
bool canWallRun = CanWallRun();
// Remove flickering in and out of wall-running
if (wallRunWeight > 0f && !canWallRun) wallRunEndTime = Time.time;
if (Time.time < wallRunEndTime + 0.5f) canWallRun = false;
wallRunWeight = Mathf.MoveTowards(wallRunWeight, (canWallRun? 1f: 0f), Time.deltaTime * wallRunWeightSpeed);
if (wallRunWeight <= 0f) {
// Reset
if (lastWallRunWeight > 0f) {
Vector3 frw = V3Tools.ExtractHorizontal(transform.forward, gravity, 1f);
transform.rotation = Quaternion.LookRotation(frw, -gravity);
wallNormal = -gravity.normalized;
}
}
lastWallRunWeight = wallRunWeight;
if (wallRunWeight <= 0f) return;
// Make sure the character won't fall down
if (onGround && velocityY < 0f) r.velocity = V3Tools.ExtractHorizontal(r.velocity, gravity, 1f);
// transform.forward flattened
Vector3 f = V3Tools.ExtractHorizontal(transform.forward, gravity, 1f);
// Raycasting to find a walkable wall
RaycastHit velocityHit = new RaycastHit();
velocityHit.normal = -gravity.normalized;
Physics.Raycast(onGround? transform.position: capsule.bounds.center, f, out velocityHit, 3f, wallRunLayers);
// Finding the normal to rotate to
wallNormal = Vector3.Lerp(wallNormal, velocityHit.normal, Time.deltaTime * wallRunRotationSpeed);
// Clamping wall normal to max rotation angle
wallNormal = Vector3.RotateTowards(-gravity.normalized, wallNormal, wallRunMaxRotationAngle * Mathf.Deg2Rad, 0f);
// Get transform.forward ortho-normalized to the wall normal
Vector3 fW = transform.forward;
Vector3 nW = wallNormal;
Vector3.OrthoNormalize(ref nW, ref fW);
// Rotate from upright to wall normal
transform.rotation = Quaternion.Slerp(Quaternion.LookRotation(f, -gravity), Quaternion.LookRotation(fW, wallNormal), wallRunWeight);
}
// Should the character be enabled to do a wall run?
private bool CanWallRun() {
if (fullRootMotion) return false;
if (Time.time < jumpEndTime - 0.1f) return false;
if (Time.time > jumpEndTime - 0.1f + wallRunMaxLength) return false;
if (velocityY < wallRunMinVelocityY) return false;
if (userControl.state.move.magnitude < wallRunMinMoveMag) return false;
return true;
}
// Get the move direction of the character relative to the character rotation
private Vector3 GetMoveDirection() {
switch(moveMode) {
case MoveMode.Directional:
moveDirection = Vector3.SmoothDamp(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), ref moveDirectionVelocity, smoothAccelerationTime);
moveDirection = Vector3.MoveTowards(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), Time.deltaTime * linearAccelerationSpeed);
return moveDirection * forwardMlp;
case MoveMode.Strafe:
moveDirection = Vector3.SmoothDamp(moveDirection, userControl.state.move, ref moveDirectionVelocity, smoothAccelerationTime);
moveDirection = Vector3.MoveTowards(moveDirection, userControl.state.move, Time.deltaTime * linearAccelerationSpeed);
return transform.InverseTransformDirection(moveDirection);
}
return Vector3.zero;
}
// Rotate the character
protected virtual void Rotate() {
if (gravityTarget != null) r.MoveRotation (Quaternion.FromToRotation(transform.up, transform.position - gravityTarget.position) * transform.rotation);
if (platformAngularVelocity != Vector3.zero) r.MoveRotation (Quaternion.Euler(platformAngularVelocity) * transform.rotation);
float angle = GetAngleFromForward(GetForwardDirection());
if (userControl.state.move == Vector3.zero) angle *= (1.01f - (Mathf.Abs(angle) / 180f)) * stationaryTurnSpeedMlp;
// Rotating the character
//RigidbodyRotateAround(characterAnimation.GetPivotPoint(), transform.up, angle * Time.deltaTime * turnSpeed);
r.MoveRotation(Quaternion.AngleAxis(angle * Time.deltaTime * turnSpeed, transform.up) * r.rotation);
}
// Which way to look at?
private Vector3 GetForwardDirection() {
bool isMoving = userControl.state.move != Vector3.zero;
switch (moveMode) {
case MoveMode.Directional:
if (isMoving) return userControl.state.move;
return lookInCameraDirection? userControl.state.lookPos - r.position: transform.forward;
case MoveMode.Strafe:
if (isMoving) return userControl.state.lookPos - r.position;
return lookInCameraDirection? userControl.state.lookPos - r.position: transform.forward;
}
return Vector3.zero;
}
protected virtual bool Jump() {
// check whether conditions are right to allow a jump:
if (!userControl.state.jump) return false;
if (userControl.state.crouch) return false;
if (!characterAnimation.animationGrounded) return false;
if (Time.time < lastAirTime + jumpRepeatDelayTime) return false;
// Jump
onGround = false;
jumpEndTime = Time.time + 0.1f;
Vector3 jumpVelocity = userControl.state.move * airSpeed;
jumpVelocity += transform.up * jumpPower;
if (smoothJump)
{
StopAllCoroutines();
StartCoroutine(JumpSmooth(jumpVelocity - r.velocity));
} else
{
r.velocity = jumpVelocity;
}
return true;
}
// Add jump velocity smoothly to avoid puppets launching to space when unpinned during jump acceleration
private IEnumerator JumpSmooth(Vector3 jumpVelocity)
{
int steps = 0;
int stepsToTake = 3;
while (steps < stepsToTake)
{
r.AddForce((jumpVelocity) / stepsToTake, ForceMode.VelocityChange);
steps++;
yield return new WaitForFixedUpdate();
}
}
// Is the character grounded?
private void GroundCheck () {
Vector3 platformVelocityTarget = Vector3.zero;
platformAngularVelocity = Vector3.zero;
float stickyForceTarget = 0f;
// Spherecasting
hit = GetSpherecastHit();
//normal = hit.normal;
normal = transform.up;
//groundDistance = r.position.y - hit.point.y;
groundDistance = Vector3.Project(r.position - hit.point, transform.up).magnitude;
// if not jumping...
bool findGround = Time.time > jumpEndTime && velocityY < jumpPower * 0.5f;
if (findGround) {
bool g = onGround;
onGround = false;
// The distance of considering the character grounded
float groundHeight = !g? airborneThreshold * 0.5f: airborneThreshold;
//Vector3 horizontalVelocity = r.velocity;
Vector3 horizontalVelocity = V3Tools.ExtractHorizontal(r.velocity, gravity, 1f);
float velocityF = horizontalVelocity.magnitude;
if (groundDistance < groundHeight) {
// Force the character on the ground
stickyForceTarget = groundStickyEffect * velocityF * groundHeight;
// On moving platforms
if (hit.rigidbody != null) {
platformVelocityTarget = hit.rigidbody.GetPointVelocity(hit.point);
platformAngularVelocity = Vector3.Project(hit.rigidbody.angularVelocity, transform.up);
}
// Flag the character grounded
onGround = true;
}
}
// Interpolate the additive velocity of the platform the character might be standing on
platformVelocity = Vector3.Lerp(platformVelocity, platformVelocityTarget, Time.deltaTime * platformFriction);
if (fullRootMotion) stickyForce = 0f;
stickyForce = stickyForceTarget;//Mathf.Lerp(stickyForce, stickyForceTarget, Time.deltaTime * 5f);
// remember when we were last in air, for jump delay
if (!onGround) lastAirTime = Time.time;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d8f8cb876d2c742a9a5d9bfa03990ada
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,141 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
// The simplest multi-purpose locomotion controller for demo purposes. Can use root motion, simple procedural motion or the CharacterController
public class SimpleLocomotion : MonoBehaviour {
// The character rotation mode
[System.Serializable]
public enum RotationMode {
Smooth,
Linear
}
[Tooltip("The component that updates the camera.")]
public CameraController cameraController;
[Tooltip("Acceleration of movement.")]
public float accelerationTime = 0.2f;
[Tooltip("Turning speed.")]
public float turnTime = 0.2f;
[Tooltip("If true, will run on left shift, if not will walk on left shift.")]
public bool walkByDefault = true;
[Tooltip("Smooth or linear rotation.")]
public RotationMode rotationMode;
[Tooltip("Procedural motion speed (if not using root motion).")]
public float moveSpeed = 3f;
// Is the character grounded (using very simple y < something here for simplicity's sake)?
public bool isGrounded { get; private set; }
private Animator animator;
private float speed;
private float angleVel;
private float speedVel;
private Vector3 linearTargetDirection;
private CharacterController characterController;
void Start() {
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
cameraController.enabled = false;
}
void Update() {
// Very basic planar method, should use collision events
isGrounded = transform.position.y < 0.1f;
Rotate();
Move();
}
void LateUpdate() {
// Update the camera last
cameraController.UpdateInput();
cameraController.UpdateTransform();
}
private void Rotate() {
if (!isGrounded) return;
// Updating the rotation of the character
Vector3 inputVector = GetInputVector();
if (inputVector == Vector3.zero) return;
Vector3 forward = transform.forward;
switch(rotationMode) {
case RotationMode.Smooth:
Vector3 targetDirection = cameraController.transform.rotation * inputVector;
float angleForward = Mathf.Atan2(forward.x, forward.z) * Mathf.Rad2Deg;
float angleTarget = Mathf.Atan2(targetDirection.x, targetDirection.z) * Mathf.Rad2Deg;
// Smoothly rotating the character
float angle = Mathf.SmoothDampAngle(angleForward, angleTarget, ref angleVel, turnTime);
transform.rotation = Quaternion.AngleAxis(angle, Vector3.up);
break;
case RotationMode.Linear:
Vector3 inputVectorRaw = GetInputVectorRaw();
if (inputVectorRaw != Vector3.zero) linearTargetDirection = cameraController.transform.rotation * inputVectorRaw;
forward = Vector3.RotateTowards(forward, linearTargetDirection, Time.deltaTime * (1f /turnTime), 1f);
forward.y = 0f;
transform.rotation = Quaternion.LookRotation(forward);
break;
}
}
private void Move() {
// Speed interpolation
float speedTarget = walkByDefault? (Input.GetKey(KeyCode.LeftShift)? 1f: 0.5f): (Input.GetKey(KeyCode.LeftShift)? 0.5f: 1f);
speed = Mathf.SmoothDamp(speed, speedTarget, ref speedVel, accelerationTime);
// Moving the character by root motion
float s = GetInputVector().magnitude * speed;
animator.SetFloat("Speed", s);
// Procedural motion if we don't have root motion
bool proceduralMotion = !animator.hasRootMotion && isGrounded;
if (proceduralMotion) {
Vector3 move = transform.forward * s * moveSpeed;
if (characterController != null) {
characterController.SimpleMove(move);
} else {
transform.position += move * Time.deltaTime;
}
}
}
// Reads the Input to get the movement direction.
private Vector3 GetInputVector() {
Vector3 d = new Vector3(
Input.GetAxis("Horizontal"),
0f,
Input.GetAxis("Vertical")
);
d.z += Mathf.Abs(d.x) * 0.05f;
d.x -= Mathf.Abs(d.z) * 0.05f;
return d;
}
private Vector3 GetInputVectorRaw() {
return new Vector3(
Input.GetAxisRaw("Horizontal"),
0f,
Input.GetAxisRaw("Vertical")
);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: eb82cec62d812495d878b167e9cdf49c
timeCreated: 1434633542
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,55 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// User input for an AI controlled character controller.
/// </summary>
public class UserControlAI : UserControlThirdPerson {
public Transform moveTarget;
public float stoppingDistance = 0.5f;
public float stoppingThreshold = 1.5f;
public Navigator navigator;
protected override void Start()
{
base.Start();
navigator.Initiate(transform);
}
protected override void Update () {
float moveSpeed = walkByDefault? 0.5f: 1f;
// If using Unity Navigation
if (navigator.activeTargetSeeking)
{
navigator.Update(moveTarget.position);
state.move = navigator.normalizedDeltaPosition * moveSpeed;
}
// No navigation, just move straight to the target
else
{
Vector3 direction = moveTarget.position - transform.position;
float distance = direction.magnitude;
Vector3 normal = transform.up;
Vector3.OrthoNormalize(ref normal, ref direction);
float sD = state.move != Vector3.zero ? stoppingDistance : stoppingDistance * stoppingThreshold;
state.move = distance > sD ? direction * moveSpeed : Vector3.zero;
state.lookPos = moveTarget.position;
}
}
// Visualize the navigator
void OnDrawGizmos()
{
if (navigator.activeTargetSeeking) navigator.Visualize();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c65b1ab317b6940d29112cc6fa332c1d
timeCreated: 1438773513
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// User input for a third person character controller.
/// </summary>
public class UserControlThirdPerson : MonoBehaviour {
// Input state
public struct State {
public Vector3 move;
public Vector3 lookPos;
public bool crouch;
public bool jump;
public int actionIndex;
}
public bool walkByDefault; // toggle for walking state
public bool canCrouch = true;
public bool canJump = true;
public State state = new State(); // The current state of the user input
protected Transform cam; // A reference to the main camera in the scenes transform
protected virtual void Start () {
// get the transform of the main camera
cam = Camera.main.transform;
}
protected virtual void Update () {
// read inputs
state.crouch = canCrouch && Input.GetKey(KeyCode.C);
state.jump = canJump && Input.GetButton("Jump");
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
// calculate move direction
Vector3 move = cam.rotation * new Vector3(h, 0f, v).normalized;
// Flatten move vector to the character.up plane
if (move != Vector3.zero) {
Vector3 normal = transform.up;
Vector3.OrthoNormalize(ref normal, ref move);
state.move = move;
} else state.move = Vector3.zero;
bool walkToggle = Input.GetKey(KeyCode.LeftShift);
// We select appropriate speed based on whether we're walking by default, and whether the walk/run toggle button is pressed:
float walkMultiplier = (walkByDefault ? walkToggle ? 1 : 0.5f : walkToggle ? 0.5f : 1);
state.move *= walkMultiplier;
// calculate the head look target position
state.lookPos = transform.position + cam.forward * 100f;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bc555ffe423d44c2f983615b1af3cef4
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 1f8e444f617e246809eb51310e80dfe4
folderAsset: yes
timeCreated: 1434644189
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
// Safely getting out of full screen desktop builds
public class ApplicationQuit : MonoBehaviour {
void Update () {
if (Input.GetKeyDown(KeyCode.Q) || Input.GetKeyDown(KeyCode.Escape)) Application.Quit();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b2b7e342c712748b785bd6f84ed05e68
timeCreated: 1452867515
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,30 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.Demos {
/// <summary>
/// Going slow motion on user input
/// </summary>
public class SlowMo : MonoBehaviour {
public KeyCode[] keyCodes;
public bool mouse0;
public bool mouse1;
public float slowMoTimeScale = 0.3f;
void Update () {
Time.timeScale = IsSlowMotion()? slowMoTimeScale: 1f;
}
private bool IsSlowMotion() {
if (mouse0 && Input.GetMouseButton(0)) return true;
if (mouse1 && Input.GetMouseButton(1)) return true;
for (int i = 0; i < keyCodes.Length; i++) {
if (Input.GetKey(keyCodes[i])) return true;
}
return false;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1a3904f1ea7c846159c7892541ba85cd
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: