Added KCC

This commit is contained in:
nothke
2024-08-17 00:50:47 +02:00
parent 7099af3fe2
commit 6720e05750
42 changed files with 5061 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 220667f043f505a4eb52d0208e14818e
folderAsset: yes
timeCreated: 1504492780
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "KCC.Editor",
"references": [
"GUID:1bf68a6e05395544c9eb0c8d1ab94588"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8b7c931d99dfb09428a98748ecfd6169
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace KinematicCharacterController
{
[CustomEditor(typeof(KinematicCharacterMotor))]
public class KinematicCharacterMotorEditor : Editor
{
protected virtual void OnSceneGUI()
{
KinematicCharacterMotor motor = (target as KinematicCharacterMotor);
if (motor)
{
Vector3 characterBottom = motor.transform.position + (motor.Capsule.center + (-Vector3.up * (motor.Capsule.height * 0.5f)));
Handles.color = Color.yellow;
Handles.CircleHandleCap(
0,
characterBottom + (motor.transform.up * motor.MaxStepHeight),
Quaternion.LookRotation(motor.transform.up, motor.transform.forward),
motor.Capsule.radius + 0.1f,
EventType.Repaint);
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7db3ed6d74cbf2b489cab5e1586bed7a
timeCreated: 1518400757
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using UnityEngine;
using UnityEditor;
namespace KinematicCharacterController
{
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyPropertyDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0d4351ab88853824b85a8f458928d825
timeCreated: 1504492796
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace KinematicCharacterController
{
public interface ICharacterController
{
/// <summary>
/// This is called when the motor wants to know what its rotation should be right now
/// </summary>
void UpdateRotation(ref Quaternion currentRotation, float deltaTime);
/// <summary>
/// This is called when the motor wants to know what its velocity should be right now
/// </summary>
void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime);
/// <summary>
/// This is called before the motor does anything
/// </summary>
void BeforeCharacterUpdate(float deltaTime);
/// <summary>
/// This is called after the motor has finished its ground probing, but before PhysicsMover/Velocity/etc.... handling
/// </summary>
void PostGroundingUpdate(float deltaTime);
/// <summary>
/// This is called after the motor has finished everything in its update
/// </summary>
void AfterCharacterUpdate(float deltaTime);
/// <summary>
/// This is called after when the motor wants to know if the collider can be collided with (or if we just go through it)
/// </summary>
bool IsColliderValidForCollisions(Collider coll);
/// <summary>
/// This is called when the motor's ground probing detects a ground hit
/// </summary>
void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport);
/// <summary>
/// This is called when the motor's movement logic detects a hit
/// </summary>
void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport);
/// <summary>
/// This is called after every move hit, to give you an opportunity to modify the HitStabilityReport to your liking
/// </summary>
void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport);
/// <summary>
/// This is called when the character detects discrete collisions (collisions that don't result from the motor's capsuleCasts when moving)
/// </summary>
void OnDiscreteCollisionDetected(Collider hitCollider);
}
}

View File

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

View File

@@ -0,0 +1,14 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace KinematicCharacterController
{
public interface IMoverController
{
/// <summary>
/// This is called to let you tell the PhysicsMover where it should be right now
/// </summary>
void UpdateMovement(out Vector3 goalPosition, out Quaternion goalRotation, float deltaTime);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ae7eceec45f1be4c8450edc46c0ad36
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"name": "KCC"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1bf68a6e05395544c9eb0c8d1ab94588
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace KinematicCharacterController
{
[CreateAssetMenu]
public class KCCSettings : ScriptableObject
{
/// <summary>
/// Determines if the system simulates automatically.
/// If true, the simulation is done on FixedUpdate
/// </summary>
[Tooltip("Determines if the system simulates automatically. If true, the simulation is done on FixedUpdate")]
public bool AutoSimulation = true;
/// <summary>
/// Should interpolation of characters and PhysicsMovers be handled
/// </summary>
[Tooltip("Should interpolation of characters and PhysicsMovers be handled")]
public bool Interpolate = true;
/// <summary>
/// Initial capacity of the system's list of Motors (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)
/// </summary>
[Tooltip("Initial capacity of the system's list of Motors (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)")]
public int MotorsListInitialCapacity = 100;
/// <summary>
/// Initial capacity of the system's list of Movers (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)
/// </summary>
[Tooltip("Initial capacity of the system's list of Movers (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)")]
public int MoversListInitialCapacity = 100;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d1bc5515e3ab954e80599c538834774
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace KinematicCharacterController
{
/// <summary>
/// The system that manages the simulation of KinematicCharacterMotor and PhysicsMover
/// </summary>
[DefaultExecutionOrder(-100)]
public class KinematicCharacterSystem : MonoBehaviour
{
private static KinematicCharacterSystem _instance;
public static List<KinematicCharacterMotor> CharacterMotors = new List<KinematicCharacterMotor>();
public static List<PhysicsMover> PhysicsMovers = new List<PhysicsMover>();
private static float _lastCustomInterpolationStartTime = -1f;
private static float _lastCustomInterpolationDeltaTime = -1f;
public static KCCSettings Settings;
/// <summary>
/// Creates a KinematicCharacterSystem instance if there isn't already one
/// </summary>
public static void EnsureCreation()
{
if (_instance == null)
{
GameObject systemGameObject = new GameObject("KinematicCharacterSystem");
_instance = systemGameObject.AddComponent<KinematicCharacterSystem>();
systemGameObject.hideFlags = HideFlags.NotEditable;
_instance.hideFlags = HideFlags.NotEditable;
Settings = ScriptableObject.CreateInstance<KCCSettings>();
GameObject.DontDestroyOnLoad(systemGameObject);
}
}
/// <summary>
/// Gets the KinematicCharacterSystem instance if any
/// </summary>
/// <returns></returns>
public static KinematicCharacterSystem GetInstance()
{
return _instance;
}
/// <summary>
/// Sets the maximum capacity of the character motors list, to prevent allocations when adding characters
/// </summary>
/// <param name="capacity"></param>
public static void SetCharacterMotorsCapacity(int capacity)
{
if (capacity < CharacterMotors.Count)
{
capacity = CharacterMotors.Count;
}
CharacterMotors.Capacity = capacity;
}
/// <summary>
/// Registers a KinematicCharacterMotor into the system
/// </summary>
public static void RegisterCharacterMotor(KinematicCharacterMotor motor)
{
CharacterMotors.Add(motor);
}
/// <summary>
/// Unregisters a KinematicCharacterMotor from the system
/// </summary>
public static void UnregisterCharacterMotor(KinematicCharacterMotor motor)
{
CharacterMotors.Remove(motor);
}
/// <summary>
/// Sets the maximum capacity of the physics movers list, to prevent allocations when adding movers
/// </summary>
/// <param name="capacity"></param>
public static void SetPhysicsMoversCapacity(int capacity)
{
if (capacity < PhysicsMovers.Count)
{
capacity = PhysicsMovers.Count;
}
PhysicsMovers.Capacity = capacity;
}
/// <summary>
/// Registers a PhysicsMover into the system
/// </summary>
public static void RegisterPhysicsMover(PhysicsMover mover)
{
PhysicsMovers.Add(mover);
mover.Rigidbody.interpolation = RigidbodyInterpolation.None;
}
/// <summary>
/// Unregisters a PhysicsMover from the system
/// </summary>
public static void UnregisterPhysicsMover(PhysicsMover mover)
{
PhysicsMovers.Remove(mover);
}
// This is to prevent duplicating the singleton gameobject on script recompiles
private void OnDisable()
{
Destroy(this.gameObject);
}
private void Awake()
{
_instance = this;
}
private void FixedUpdate()
{
if (Settings.AutoSimulation)
{
float deltaTime = Time.deltaTime;
if (Settings.Interpolate)
{
PreSimulationInterpolationUpdate(deltaTime);
}
Simulate(deltaTime, CharacterMotors, PhysicsMovers);
if (Settings.Interpolate)
{
PostSimulationInterpolationUpdate(deltaTime);
}
}
}
private void LateUpdate()
{
if (Settings.Interpolate)
{
CustomInterpolationUpdate();
}
}
/// <summary>
/// Remembers the point to interpolate from for KinematicCharacterMotors and PhysicsMovers
/// </summary>
public static void PreSimulationInterpolationUpdate(float deltaTime)
{
// Save pre-simulation poses and place transform at transient pose
for (int i = 0; i < CharacterMotors.Count; i++)
{
KinematicCharacterMotor motor = CharacterMotors[i];
motor.InitialTickPosition = motor.TransientPosition;
motor.InitialTickRotation = motor.TransientRotation;
motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
}
for (int i = 0; i < PhysicsMovers.Count; i++)
{
PhysicsMover mover = PhysicsMovers[i];
mover.InitialTickPosition = mover.TransientPosition;
mover.InitialTickRotation = mover.TransientRotation;
mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
mover.Rigidbody.position = mover.TransientPosition;
mover.Rigidbody.rotation = mover.TransientRotation;
}
}
/// <summary>
/// Ticks characters and/or movers
/// </summary>
public static void Simulate(float deltaTime, List<KinematicCharacterMotor> motors, List<PhysicsMover> movers)
{
int characterMotorsCount = motors.Count;
int physicsMoversCount = movers.Count;
#pragma warning disable 0162
// Update PhysicsMover velocities
for (int i = 0; i < physicsMoversCount; i++)
{
movers[i].VelocityUpdate(deltaTime);
}
// Character controller update phase 1
for (int i = 0; i < characterMotorsCount; i++)
{
motors[i].UpdatePhase1(deltaTime);
}
// Simulate PhysicsMover displacement
for (int i = 0; i < physicsMoversCount; i++)
{
PhysicsMover mover = movers[i];
mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
mover.Rigidbody.position = mover.TransientPosition;
mover.Rigidbody.rotation = mover.TransientRotation;
}
// Character controller update phase 2 and move
for (int i = 0; i < characterMotorsCount; i++)
{
KinematicCharacterMotor motor = motors[i];
motor.UpdatePhase2(deltaTime);
motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
}
#pragma warning restore 0162
}
/// <summary>
/// Initiates the interpolation for KinematicCharacterMotors and PhysicsMovers
/// </summary>
public static void PostSimulationInterpolationUpdate(float deltaTime)
{
_lastCustomInterpolationStartTime = Time.time;
_lastCustomInterpolationDeltaTime = deltaTime;
// Return interpolated roots to their initial poses
for (int i = 0; i < CharacterMotors.Count; i++)
{
KinematicCharacterMotor motor = CharacterMotors[i];
motor.Transform.SetPositionAndRotation(motor.InitialTickPosition, motor.InitialTickRotation);
}
for (int i = 0; i < PhysicsMovers.Count; i++)
{
PhysicsMover mover = PhysicsMovers[i];
if (mover.MoveWithPhysics)
{
mover.Rigidbody.position = mover.InitialTickPosition;
mover.Rigidbody.rotation = mover.InitialTickRotation;
mover.Rigidbody.MovePosition(mover.TransientPosition);
mover.Rigidbody.MoveRotation(mover.TransientRotation);
}
else
{
mover.Rigidbody.position = (mover.TransientPosition);
mover.Rigidbody.rotation = (mover.TransientRotation);
}
}
}
/// <summary>
/// Handles per-frame interpolation
/// </summary>
private static void CustomInterpolationUpdate()
{
float interpolationFactor = Mathf.Clamp01((Time.time - _lastCustomInterpolationStartTime) / _lastCustomInterpolationDeltaTime);
// Handle characters interpolation
for (int i = 0; i < CharacterMotors.Count; i++)
{
KinematicCharacterMotor motor = CharacterMotors[i];
motor.Transform.SetPositionAndRotation(
Vector3.Lerp(motor.InitialTickPosition, motor.TransientPosition, interpolationFactor),
Quaternion.Slerp(motor.InitialTickRotation, motor.TransientRotation, interpolationFactor));
}
// Handle PhysicsMovers interpolation
for (int i = 0; i < PhysicsMovers.Count; i++)
{
PhysicsMover mover = PhysicsMovers[i];
mover.Transform.SetPositionAndRotation(
Vector3.Lerp(mover.InitialTickPosition, mover.TransientPosition, interpolationFactor),
Quaternion.Slerp(mover.InitialTickRotation, mover.TransientRotation, interpolationFactor));
Vector3 newPos = mover.Transform.position;
Quaternion newRot = mover.Transform.rotation;
mover.PositionDeltaFromInterpolation = newPos - mover.LatestInterpolationPosition;
mover.RotationDeltaFromInterpolation = Quaternion.Inverse(mover.LatestInterpolationRotation) * newRot;
mover.LatestInterpolationPosition = newPos;
mover.LatestInterpolationRotation = newRot;
}
}
}
}

View File

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

View File

@@ -0,0 +1,261 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace KinematicCharacterController
{
/// <summary>
/// Represents the entire state of a PhysicsMover that is pertinent for simulation.
/// Use this to save state or revert to past state
/// </summary>
[System.Serializable]
public struct PhysicsMoverState
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Velocity;
public Vector3 AngularVelocity;
}
/// <summary>
/// Component that manages the movement of moving kinematic rigidbodies for
/// proper interaction with characters
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class PhysicsMover : MonoBehaviour
{
/// <summary>
/// The mover's Rigidbody
/// </summary>
[ReadOnly]
public Rigidbody Rigidbody;
/// <summary>
/// Determines if the platform moves with rigidbody.MovePosition (when true), or with rigidbody.position (when false)
/// </summary>
public bool MoveWithPhysics = true;
/// <summary>
/// Index of this motor in KinematicCharacterSystem arrays
/// </summary>
[NonSerialized]
public IMoverController MoverController;
/// <summary>
/// Remembers latest position in interpolation
/// </summary>
[NonSerialized]
public Vector3 LatestInterpolationPosition;
/// <summary>
/// Remembers latest rotation in interpolation
/// </summary>
[NonSerialized]
public Quaternion LatestInterpolationRotation;
/// <summary>
/// The latest movement made by interpolation
/// </summary>
[NonSerialized]
public Vector3 PositionDeltaFromInterpolation;
/// <summary>
/// The latest rotation made by interpolation
/// </summary>
[NonSerialized]
public Quaternion RotationDeltaFromInterpolation;
/// <summary>
/// Index of this motor in KinematicCharacterSystem arrays
/// </summary>
public int IndexInCharacterSystem { get; set; }
/// <summary>
/// Remembers initial position before all simulation are done
/// </summary>
public Vector3 Velocity { get; protected set; }
/// <summary>
/// Remembers initial position before all simulation are done
/// </summary>
public Vector3 AngularVelocity { get; protected set; }
/// <summary>
/// Remembers initial position before all simulation are done
/// </summary>
public Vector3 InitialTickPosition { get; set; }
/// <summary>
/// Remembers initial rotation before all simulation are done
/// </summary>
public Quaternion InitialTickRotation { get; set; }
/// <summary>
/// The mover's Transform
/// </summary>
public Transform Transform { get; private set; }
/// <summary>
/// The character's position before the movement calculations began
/// </summary>
public Vector3 InitialSimulationPosition { get; private set; }
/// <summary>
/// The character's rotation before the movement calculations began
/// </summary>
public Quaternion InitialSimulationRotation { get; private set; }
private Vector3 _internalTransientPosition;
/// <summary>
/// The mover's rotation (always up-to-date during the character update phase)
/// </summary>
public Vector3 TransientPosition
{
get
{
return _internalTransientPosition;
}
private set
{
_internalTransientPosition = value;
}
}
private Quaternion _internalTransientRotation;
/// <summary>
/// The mover's rotation (always up-to-date during the character update phase)
/// </summary>
public Quaternion TransientRotation
{
get
{
return _internalTransientRotation;
}
private set
{
_internalTransientRotation = value;
}
}
private void Reset()
{
ValidateData();
}
private void OnValidate()
{
ValidateData();
}
/// <summary>
/// Handle validating all required values
/// </summary>
public void ValidateData()
{
Rigidbody = gameObject.GetComponent<Rigidbody>();
Rigidbody.centerOfMass = Vector3.zero;
Rigidbody.maxAngularVelocity = Mathf.Infinity;
Rigidbody.maxDepenetrationVelocity = Mathf.Infinity;
Rigidbody.isKinematic = true;
Rigidbody.interpolation = RigidbodyInterpolation.None;
}
private void OnEnable()
{
KinematicCharacterSystem.EnsureCreation();
KinematicCharacterSystem.RegisterPhysicsMover(this);
}
private void OnDisable()
{
KinematicCharacterSystem.UnregisterPhysicsMover(this);
}
private void Awake()
{
Transform = this.transform;
ValidateData();
TransientPosition = Rigidbody.position;
TransientRotation = Rigidbody.rotation;
InitialSimulationPosition = Rigidbody.position;
InitialSimulationRotation = Rigidbody.rotation;
LatestInterpolationPosition = Transform.position;
LatestInterpolationRotation = Transform.rotation;
}
/// <summary>
/// Sets the mover's position directly
/// </summary>
public void SetPosition(Vector3 position)
{
Transform.position = position;
Rigidbody.position = position;
InitialSimulationPosition = position;
TransientPosition = position;
}
/// <summary>
/// Sets the mover's rotation directly
/// </summary>
public void SetRotation(Quaternion rotation)
{
Transform.rotation = rotation;
Rigidbody.rotation = rotation;
InitialSimulationRotation = rotation;
TransientRotation = rotation;
}
/// <summary>
/// Sets the mover's position and rotation directly
/// </summary>
public void SetPositionAndRotation(Vector3 position, Quaternion rotation)
{
Transform.SetPositionAndRotation(position, rotation);
Rigidbody.position = position;
Rigidbody.rotation = rotation;
InitialSimulationPosition = position;
InitialSimulationRotation = rotation;
TransientPosition = position;
TransientRotation = rotation;
}
/// <summary>
/// Returns all the state information of the mover that is pertinent for simulation
/// </summary>
public PhysicsMoverState GetState()
{
PhysicsMoverState state = new PhysicsMoverState();
state.Position = TransientPosition;
state.Rotation = TransientRotation;
state.Velocity = Velocity;
state.AngularVelocity = AngularVelocity;
return state;
}
/// <summary>
/// Applies a mover state instantly
/// </summary>
public void ApplyState(PhysicsMoverState state)
{
SetPositionAndRotation(state.Position, state.Rotation);
Velocity = state.Velocity;
AngularVelocity = state.AngularVelocity;
}
/// <summary>
/// Caches velocity values based on deltatime and target position/rotations
/// </summary>
public void VelocityUpdate(float deltaTime)
{
InitialSimulationPosition = TransientPosition;
InitialSimulationRotation = TransientRotation;
MoverController.UpdateMovement(out _internalTransientPosition, out _internalTransientRotation, deltaTime);
if (deltaTime > 0f)
{
Velocity = (TransientPosition - InitialSimulationPosition) / deltaTime;
Quaternion rotationFromCurrentToGoal = TransientRotation * (Quaternion.Inverse(InitialSimulationRotation));
AngularVelocity = (Mathf.Deg2Rad * rotationFromCurrentToGoal.eulerAngles) / deltaTime;
}
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
using UnityEngine;
namespace KinematicCharacterController
{
public class ReadOnlyAttribute : PropertyAttribute
{
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 63e8828991cf67f42bfeb498686aad0b
timeCreated: 1504493014
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: