完善A星添加角色系统

This commit is contained in:
SnowShow 2025-04-25 17:40:38 +08:00
parent 3dcaf8b4de
commit 7070c9bf7d
3742 changed files with 1463132 additions and 328161 deletions

View File

@ -9490,7 +9490,7 @@ Transform:
m_GameObject: {fileID: 285986652131527170}
serializedVersion: 2
m_LocalRotation: {x: 0.70710677, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 88.27699, y: 6.3215823, z: -0.047535613}
m_LocalPosition: {x: 85.52, y: 6.3215823, z: -0.047535613}
m_LocalScale: {x: 1.81862, y: 1.81862, z: 1.81862}
m_ConstrainProportionsScale: 0
m_Children:
@ -9565,7 +9565,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
_isRunning: 0
_shelfToolActionType: 0
_shelfToolActionType: 2
_currentBoxactor: {fileID: 0}
_treeOwner: {fileID: 0}
_targetPositionBox: {fileID: 0}
@ -84370,6 +84370,7 @@ GameObject:
m_Component:
- component: {fileID: 4309968558015114303}
- component: {fileID: 8202619528938505423}
- component: {fileID: 3570133681296783813}
m_Layer: 0
m_Name: actor_Warehouseshelf
m_TagString: Untagged
@ -84409,6 +84410,27 @@ MonoBehaviour:
m_EditorClassIdentifier:
_targetBoxId: 0
_tool: {fileID: 0}
--- !u!65 &3570133681296783813
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2895222871435310345}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 165.03473, y: 52.36976, z: 13.39}
m_Center: {x: -1.0754242, y: 26.128292, z: 0}
--- !u!1 &2896999456890700578
GameObject:
m_ObjectHideFlags: 0
@ -232173,7 +232195,7 @@ Transform:
m_GameObject: {fileID: 8386615510630291625}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -0.24234578, y: 0.001337206, z: 2.2394881}
m_LocalPosition: {x: -0.24234578, y: 0.001337206, z: 2.796}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5ef555787d020214999b728e64154995
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,10 @@
using TEngine;
using UnityEngine;
namespace GameLogic
{
public class BaseCharacter : EntityLogic
{
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 44b5cd5e4befaa14fbb7642695a1c7f9

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c8ee8fd7dadbdaf48b7c3e25472f3aca
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using TEngine;
using UnityEngine;
namespace GameLogic
{
public class RobotAI : EnemyBase
{
protected override void OnInit(object userData)
{
base.OnInit(userData);
Log.Debug("初始化RobotAI");
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f63f0d04665826e49bce7babd7c49fea

View File

@ -0,0 +1,12 @@
using UnityEngine;
namespace GameLogic
{
public class EnemyBase : BaseCharacter
{
protected override void OnInit(object userData)
{
base.OnInit(userData);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e67158861e312b046824ebf7dd0bc104

View File

@ -0,0 +1,9 @@
using UnityEngine;
namespace GameLogic
{
public class PlayBase : BaseCharacter
{
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a794deeda4fe3fd4ca43b767021b59f7

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b19fbd67cbcf4f429dd48ded3b0bb38
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
using UnityEngine;
namespace GameLogic
{
public class Player : PlayBase
{
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4496005bdb534ef43a55cd73750993dd

View File

@ -12,33 +12,42 @@ namespace GameLogic
[ShowInInspector]private Dictionary<int,BoxActor> _boxes = new Dictionary<int,BoxActor>();
protected override void OnLoad()
{
//Init().Forget();
var array = transform.Find("BoxGroup").childCount;
for (int i = 0; i < array; i++)
{
_boxes.Add(i,transform.Find("BoxGroup").GetChild(i).GetComponent<BoxActor>());
}
// foreach (var box in _boxes)
// //Init().Forget();
// var array = transform.Find("BoxGroup").childCount;
// for (int i = 0; i < array; i++)
// {
//
// _boxes.Add(i,transform.Find("BoxGroup").GetChild(i).GetComponent<BoxActor>());
// }
// // foreach (var box in _boxes)
// // {
// //
// // }
base.OnLoad();
}
private async void Start()
{
// Init().Forget();
Init().Forget();
}
private async UniTaskVoid Init()
{
// await GeneratorBox();
GameModuleExtension.Entity.AddEntityGroup("boxGroup",
-1,
30000,
99999999,
0);
await GeneratorBox();
}
private async UniTask GeneratorBox()
{
IEntityGroup group = GameModuleExtension.Entity.GetEntityGroup("boxGroup");
var boxArray = GameObject.FindGameObjectsWithTag(TagSetting.Box);
GameObject boxGroup = new GameObject();
boxGroup.name = "BoxGroup";
@ -46,11 +55,12 @@ namespace GameLogic
Material boxMat = GameModule.Resource.LoadAsset<Material>(LocationSetting.Mat_ActorBox);
for (int i = 0; i < boxArray.Length; i++)
{
var boxActor = BoxFactory.CreatBox(i, boxArray[i].transform,boxGroup.transform);
var boxActor = BoxFactory.CreatBoxGameModule(i, boxArray[i].transform,boxGroup.transform,group);
//boxActor.SetMaterial(boxMat);
_boxes.Add(i,boxActor);
Log.Info($"Box Generator (物流纸箱生成进度) :{i/(boxArray.Length*1.0f) * 100} %");
}
Log.Info("实体数量"+group.EntityCount);
await UniTask.Yield();
}

View File

@ -1,3 +1,4 @@
using System;
using TEngine;
using UnityEngine;
@ -8,7 +9,10 @@ namespace GameLogic
public int index;
private void Awake()
{
OnInit(null);
}
public void SetMaterial(Material material)
{

View File

@ -1,14 +1,20 @@
using TEngine;
using UnityEngine;
namespace GameLogic
{
public static class BoxFactory
{
public static BoxActor CreatBoxGameModule(int index,Transform origin,Transform parent)
public static BoxActor CreatBoxGameModule(int index,Transform origin,Transform parent,IEntityGroup group)
{
var boxGo = GameModule.Resource.LoadGameObject(LocationSetting.BoxActor);
boxGo.SetTransform(origin);
boxGo.SetParent(parent);
var box = boxGo.GetComponent<BoxActor>();
box.index = index;
return boxGo.GetComponent<BoxActor>();

View File

@ -0,0 +1,10 @@
using TEngine;
using UnityEngine;
namespace GameLogic
{
public class GameModuleExtension
{
public static EntityModule Entity => GameObject.FindAnyObjectByType<EntityModule>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a0ec0e72ffc4da8a6bae9cd193d623a
timeCreated: 1745560001

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 916c2cc0a94e68b47957485da44d162c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: caa01241d67e9c147907e1d816ba28ed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88be720759cf79a4d9b14514fcf82a0d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,144 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2750169105938585654
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3690873532784470854}
- component: {fileID: 5486110000465605157}
- component: {fileID: 2604934662711577773}
m_Layer: 0
m_Name: Blank Character
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3690873532784470854
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 2084248668550000093}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5486110000465605157
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 78cff8dc757190641a7708af786fcf21, type: 3}
m_Name:
m_EditorClassIdentifier:
is2D: 0
bodyType: 1
bodySize: {x: 1, y: 2}
mass: 50
--- !u!114 &2604934662711577773
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4f199f35221ef584d909e881238cba74, type: 3}
m_Name:
m_EditorClassIdentifier:
tagsAndLayersProfile: {fileID: 0}
slopeLimit: 60
stepOffset: 0.5
stepUpSpeed: 0.5
stepDownDistance: 0.5
stepDownSpeed: 0.1
edgeCompensation: 0
alwaysNotGrounded: 0
verticalAlignmentSettings:
alignmentReference: {fileID: 0}
referenceMode: 1
alignmentDirection: {x: 0, y: 1, z: 0}
supportDynamicGround: 1
rotateForwardDirection: 1
applyWeightToGround: 1
weightGravity: 9.8
filterWeightRigidbodiesByTag: 1
addExternalVelocity: 1
ignoreStaticObstacles: 1
ignoreKinematicRigidbodies: 1
--- !u!1 &5532968350065946407
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7399720800942972954}
m_Layer: 0
m_Name: Your animated character
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7399720800942972954
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5532968350065946407}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2084248668550000093}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &6415588977015373110
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2084248668550000093}
m_Layer: 0
m_Name: Graphics
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2084248668550000093
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6415588977015373110}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 7399720800942972954}
m_Father: {fileID: 3690873532784470854}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

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

View File

@ -0,0 +1,378 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1218552866658402712
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4842251681767870480}
m_Layer: 0
m_Name: Arrow
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4842251681767870480
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1218552866658402712}
m_LocalRotation: {x: -0.49999714, y: -0.4999999, z: -0.5000029, w: -0.5000001}
m_LocalPosition: {x: 0, y: 2.288558, z: -0.20655823}
m_LocalScale: {x: 0.17285192, y: 0.17285194, z: 0.17285194}
m_Children:
- {fileID: 2308672894763730565}
m_Father: {fileID: 2161635890232759230}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: -270, z: 90.00001}
--- !u!1 &1326713445091067784
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2161635890232759230}
- component: {fileID: 8235324090581383526}
m_Layer: 0
m_Name: Capsule root
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2161635890232759230
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1326713445091067784}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 4172487410879816610}
- {fileID: 4842251681767870480}
m_Father: {fileID: 2084248668550000093}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8235324090581383526
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1326713445091067784}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5e93c8610dacb85428134b9b32dec22a, type: 3}
m_Name:
m_EditorClassIdentifier:
scaleHeightComponent: 1
--- !u!1 &1523431370174691967
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2308672894763730565}
- component: {fileID: 2729997595277447576}
- component: {fileID: 4087739539510225864}
m_Layer: 0
m_Name: Mesh1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2308672894763730565
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1523431370174691967}
m_LocalRotation: {x: 0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -0.33, y: -0.85, z: -0}
m_LocalScale: {x: 1.7093, y: 1.7093, z: 1.7093}
m_Children: []
m_Father: {fileID: 4842251681767870480}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &2729997595277447576
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1523431370174691967}
m_Mesh: {fileID: 4300000, guid: a21dd1b7fbbe2d744aa9bc81a1c7a04b, type: 3}
--- !u!23 &4087739539510225864
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1523431370174691967}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 0278e8512b313d54ba7e4b2b109c2796, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!1 &2750169105938585654
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3690873532784470854}
- component: {fileID: 5486110000465605157}
- component: {fileID: 2604934662711577773}
m_Layer: 8
m_Name: Capsule Blank Character
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3690873532784470854
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 2084248668550000093}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5486110000465605157
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 78cff8dc757190641a7708af786fcf21, type: 3}
m_Name:
m_EditorClassIdentifier:
is2D: 0
bodySize: {x: 1, y: 2}
mass: 50
--- !u!114 &2604934662711577773
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2750169105938585654}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4f199f35221ef584d909e881238cba74, type: 3}
m_Name:
m_EditorClassIdentifier:
interpolateActor: 1
useContinuousCollisionDetection: 1
UseRootMotion: 0
UpdateRootPosition: 1
rootMotionVelocityType: 0
UpdateRootRotation: 1
rootMotionRotationType: 1
oneWayPlatformsLayerMask:
serializedVersion: 2
m_Bits: 0
oneWayPlatformsValidArc: 175
useGroundTrigger: 0
alwaysNotGrounded: 0
forceGroundedAtStart: 1
stableLayerMask:
serializedVersion: 2
m_Bits: 4294967295
slopeLimit: 60
useStableEdgeWhenLanding: 1
stepUpDistance: 0.6
stepDownDistance: 0.5
edgeCompensation: 0
detectGroundWhileAscending: 0
maxUnstableUpwardsVelocity: 7
preventBadSteps: 1
supportDynamicGround: 1
rotateForwardDirection: 1
maxGroundVelocityChange: 30
inheritedGroundPlanarVelocityThreshold: 2
inheritedGroundPlanarVelocityMultiplier: 1
inheritedGroundVerticalVelocityThreshold: 2
inheritedGroundVerticalVelocityMultiplier: 1
movementReference:
movementReferenceMode: 0
externalReference: {fileID: 0}
slideOnWalls: 1
stablePostSimulationVelocity: 2
unstablePostSimulationVelocity: 2
sizeReferenceType: 2
sizeLerpSpeed: 8
constraintRotation: 1
upDirectionReference: {fileID: 0}
upDirectionReferenceMode: 1
constraintUpDirection: {x: 0, y: 1, z: 0}
canPushDynamicRigidbodies: 1
pushableRigidbodyLayerMask:
serializedVersion: 2
m_Bits: 4294967295
applyWeightToGround: 1
weightGravity: 9.8
--- !u!1 &6173809583089036531
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4172487410879816610}
- component: {fileID: 4064914065884037380}
- component: {fileID: 6907179781620337088}
m_Layer: 0
m_Name: Capsule
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4172487410879816610
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6173809583089036531}
m_LocalRotation: {x: -0, y: -0, z: -0.000000014901161, w: 1}
m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2161635890232759230}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &4064914065884037380
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6173809583089036531}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &6907179781620337088
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6173809583089036531}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 0278e8512b313d54ba7e4b2b109c2796, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!1 &6415588977015373110
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2084248668550000093}
m_Layer: 0
m_Name: Graphics
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2084248668550000093
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6415588977015373110}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 2161635890232759230}
m_Father: {fileID: 3690873532784470854}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 36306ba9866cb5b46b38bc07bf5f031f
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b7ee4dfaa2510534f8e447d3e92f889b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: e405849a2c57a92459447d4f1af4c839
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 9c5347bdfa0951540849bd28ba7a5443
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ed94cbf3a2dee724f9d20a2f51d124cc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ae920956a7e616747a90f9aae8cf02d9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,44 @@
using UnityEngine;
namespace Lightbug.CharacterControllerPro.Core
{
[RequireComponent(typeof(Animator))]
public class AnimatorLink : MonoBehaviour
{
Animator _animator;
bool _resetIKWeightsFlag = false;
public event System.Action OnAnimatorMoveEvent;
public event System.Action<int> OnAnimatorIKEvent;
public void ResetIKWeights() => _resetIKWeightsFlag = true;
#region Messages
void Awake()
{
_animator = GetComponent<Animator>();
}
void OnAnimatorMove() => OnAnimatorMoveEvent?.Invoke();
void OnAnimatorIK(int layerIndex)
{
if (_resetIKWeightsFlag)
{
_resetIKWeightsFlag = false;
_animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 0f);
_animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 0f);
_animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0f);
_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0f);
}
OnAnimatorIKEvent?.Invoke(layerIndex);
}
#endregion
}
}

View File

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

View File

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

View File

@ -0,0 +1,83 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace Lightbug.CharacterControllerPro.Core
{
[CustomEditor(typeof(CharacterActor))]
public class CharacterActorEditor : Editor
{
CharacterActor characterActor = null;
void OnEnable()
{
characterActor = target as CharacterActor;
}
void OnDisable()
{
Tools.hidden = false;
}
public override void OnInspectorGUI()
{
if (characterActor.CharacterBody.Is2D)
{
if (Physics2D.simulationMode != SimulationMode2D.FixedUpdate)
EditorGUILayout.HelpBox("(Physics2D) Only FixedUpdate simulation mode is supported!", MessageType.Error);
}
else
{
#if UNITY_6000_0_OR_NEWER
if (Physics.simulationMode != SimulationMode.FixedUpdate)
EditorGUILayout.HelpBox("(Physics) Only FixedUpdate simulation mode is supported!", MessageType.Error);
#endif
}
EditorGUIUtility.labelWidth += 75;
DrawDefaultInspector();
EditorGUIUtility.labelWidth -= 75;
}
void OnSceneGUI()
{
if (!Application.isPlaying)
return;
if (Tools.current == Tool.Move)
{
Tools.hidden = true;
EditorGUI.BeginChangeCheck();
Vector3 targetPosition = Handles.PositionHandle(characterActor.transform.position, characterActor.transform.rotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(characterActor, "Change character position");
characterActor.Teleport(targetPosition);
}
}
else if (Tools.current == Tool.Rotate)
{
Tools.hidden = true;
EditorGUI.BeginChangeCheck();
Quaternion targetRotation = Handles.RotationHandle(characterActor.transform.rotation, characterActor.transform.position);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(characterActor, "Change character rotation");
characterActor.Teleport(characterActor.Position, targetRotation);
}
}
else
{
Tools.hidden = false;
}
}
}
}
#endif

View File

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

View File

@ -0,0 +1,11 @@
//#define CCP_DEBUG
namespace Lightbug.CharacterControllerPro.Core
{
public enum CharacterActorState
{
NotGrounded,
StableGrounded,
UnstableGrounded
}
}

View File

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

View File

@ -0,0 +1,94 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
public enum CharacterBodyType
{
Sphere,
Capsule
}
/// <summary>
/// This component exposes all the character body properties, such as width, height, body shape, physics, etc.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Body")]
public class CharacterBody : MonoBehaviour
{
[HelpBox("This component will automatically assign a Rigidbody component and a CapsuleCollider component at runtime.", HelpBoxMessageType.Info)]
[SerializeField, BooleanButton("Physics", "3D", "2D", false)]
bool is2D = false;
[SerializeField, BreakVector2("Width", "Height")]
Vector2 bodySize = new Vector2(1f, 2f);
[SerializeField]
float mass = 50f;
/// <summary>
/// Returns true if the character is governed by 2D Physics, false otherwise.
/// </summary>
public bool Is2D => is2D;
/// <summary>
/// Gets the RigidbodyComponent component associated to the character.
/// </summary>
public RigidbodyComponent RigidbodyComponent { get; private set; }
/// <summary>
/// Gets the ColliderComponent component associated to the character.
/// </summary>
public ColliderComponent ColliderComponent { get; private set; }
/// <summary>
/// Gets the mass of the character.
/// </summary>
public float Mass => mass;
/// <summary>
/// Gets the body size of the character (width and height).
/// </summary>
public Vector2 BodySize => bodySize;
/// <summary>
/// Initializes the body properties and components.
/// </summary>
void Awake()
{
if (Is2D)
{
ColliderComponent = gameObject.AddComponent<CapsuleColliderComponent2D>();
RigidbodyComponent = gameObject.AddComponent<RigidbodyComponent2D>();
}
else
{
ColliderComponent = gameObject.AddComponent<CapsuleColliderComponent3D>();
RigidbodyComponent = gameObject.AddComponent<RigidbodyComponent3D>();
}
}
CharacterActor characterActor = null;
void OnValidate()
{
if (characterActor == null)
characterActor = GetComponent<CharacterActor>();
bodySize = new Vector2(
Mathf.Max(bodySize.x, 0f),
Mathf.Max(bodySize.y, bodySize.x + CharacterConstants.ColliderMinBottomOffset)
);
if (characterActor != null)
characterActor.OnValidate();
}
}
}

View File

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

View File

@ -0,0 +1,91 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Lightbug.CharacterControllerPro.Core
{
[CustomEditor(typeof(CharacterBody), true), CanEditMultipleObjects]
public class CharacterBodyEditor : Editor
{
SerializedProperty is2D = null;
SerializedProperty bodySize = null;
CharacterBody monoBehaviour;
void OnEnable()
{
monoBehaviour = (CharacterBody)target;
is2D = serializedObject.FindProperty("is2D");
bodySize = serializedObject.FindProperty("bodySize");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
if (monoBehaviour.transform.localScale != Vector3.one)
EditorGUILayout.HelpBox("Transform local scale must be <1,1,1> !!!", MessageType.Error);
serializedObject.ApplyModifiedProperties();
}
CapsuleBoundsHandle capsuleHandle = new CapsuleBoundsHandle();
void OnSceneGUI()
{
if (monoBehaviour == null)
return;
if (is2D.boolValue)
{
Transform handlesTransform = monoBehaviour.transform;
// handlesTransform.rotation = Quaternion.identity;
Handles.matrix = handlesTransform.localToWorldMatrix;
capsuleHandle.radius = bodySize.vector2Value.x / 2f;
capsuleHandle.height = bodySize.vector2Value.y;
capsuleHandle.center = Vector3.up * capsuleHandle.height / 2f;
capsuleHandle.axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y;
capsuleHandle.heightAxis = CapsuleBoundsHandle.HeightAxis.Y;
capsuleHandle.DrawHandle();
Handles.matrix = Matrix4x4.identity;
}
else
{
Handles.matrix = monoBehaviour.transform.localToWorldMatrix;
capsuleHandle.radius = bodySize.vector2Value.x / 2f;
capsuleHandle.height = bodySize.vector2Value.y;
capsuleHandle.center = Vector3.up * capsuleHandle.height / 2f;
capsuleHandle.axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y | PrimitiveBoundsHandle.Axes.Z;
capsuleHandle.heightAxis = CapsuleBoundsHandle.HeightAxis.Y;
capsuleHandle.DrawHandle();
Handles.matrix = Matrix4x4.identity;
}
}
}
}
#endif

View File

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

View File

@ -0,0 +1,173 @@
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This struct contains all the character info related to collision, that is, collision flags and external components. All the internal fields are updated frame by frame, and can
/// can be accessed by using public properties from the CharacterActor component.
/// </summary>
public struct CharacterCollisionInfo
{
// Ground
public Vector3 groundContactPoint;
public Vector3 groundContactNormal;
public Vector3 groundStableNormal;
public float groundSlopeAngle;
// Head
public bool headCollision;
public Contact headContact;
public float headAngle;
// Wall
public bool wallCollision;
public Contact wallContact;
public float wallAngle;
// Edge
public bool isOnEdge;
public float edgeAngle;
// Object
public GameObject groundObject;
public int groundLayer;
public Collider groundCollider3D;
public Collider2D groundCollider2D;
public Rigidbody groundRigidbody3D;
public Rigidbody2D groundRigidbody2D;
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
/// <summary>
/// Resets all the fields to default.
/// </summary>
public void Reset()
{
ResetGroundInfo();
ResetWallInfo();
ResetHeadInfo();
}
/// <summary>
/// Resets the wall contact related info.
/// </summary>
public void ResetWallInfo()
{
wallCollision = false;
wallContact = new Contact();
wallAngle = 0f;
}
public void SetWallInfo(in Contact contact, CharacterActor characterActor)
{
wallCollision = true;
wallAngle = Vector3.Angle(characterActor.Up, contact.normal);
wallContact = contact;
}
/// <summary>
/// Resets the head contact related info.
/// </summary>
public void ResetHeadInfo()
{
headCollision = false;
headContact = new Contact();
headAngle = 0f;
}
public void SetHeadInfo(in Contact contact, CharacterActor characterActor)
{
headCollision = true;
headAngle = Vector3.Angle(characterActor.Up, headContact.normal);
headContact = contact;
}
public void SetGroundInfo(in CollisionInfo collisionInfo, CharacterActor characterActor)
{
if (collisionInfo.hitInfo.hit)
{
isOnEdge = collisionInfo.isAnEdge;
edgeAngle = collisionInfo.edgeAngle;
groundContactNormal = collisionInfo.contactSlopeAngle < 90f ? collisionInfo.hitInfo.normal : characterActor.Up;
groundContactPoint = collisionInfo.hitInfo.point;
groundStableNormal = characterActor.GetGroundSlopeNormal(collisionInfo);
groundSlopeAngle = Vector3.Angle(characterActor.Up, groundStableNormal);
groundObject = collisionInfo.hitInfo.transform.gameObject;
groundLayer = groundObject.layer;
groundCollider2D = collisionInfo.hitInfo.collider2D;
groundCollider3D = collisionInfo.hitInfo.collider3D;
groundRigidbody2D = collisionInfo.hitInfo.rigidbody2D;
groundRigidbody3D = collisionInfo.hitInfo.rigidbody3D;
// Ground contact
Vector3 pointVelocity = Vector3.zero;
if (collisionInfo.hitInfo.rigidbody2D != null)
pointVelocity = collisionInfo.hitInfo.rigidbody2D.GetPointVelocity(groundContactPoint);
else if (collisionInfo.hitInfo.rigidbody3D != null)
pointVelocity = collisionInfo.hitInfo.rigidbody3D.GetPointVelocity(groundContactPoint);
Vector3 relativeVelocity = characterActor.Velocity - pointVelocity;
Contact groundContact = new Contact(groundContactPoint, groundContactNormal, pointVelocity, relativeVelocity);
characterActor.GroundContacts.Add(groundContact);
}
else
{
ResetGroundInfo();
}
}
void SetGroundContact(in CollisionInfo collisionInfo, CharacterActor characterActor)
{
if (!collisionInfo.hitInfo.hit)
return;
Vector3 point = collisionInfo.hitInfo.point;
Vector3 normal = collisionInfo.hitInfo.normal;
Vector3 pointVelocity = Vector3.zero;
if (collisionInfo.hitInfo.rigidbody2D != null)
pointVelocity = collisionInfo.hitInfo.rigidbody2D.GetPointVelocity(point);
else if (collisionInfo.hitInfo.rigidbody3D != null)
pointVelocity = collisionInfo.hitInfo.rigidbody3D.GetPointVelocity(point);
Vector3 relativeVelocity = characterActor.Velocity - pointVelocity;
Contact groundContact = new Contact(point, normal, pointVelocity, relativeVelocity);
characterActor.GroundContacts.Add(groundContact);
}
/// <summary>
/// Resets the ground contact related info.
/// </summary>
public void ResetGroundInfo()
{
groundContactPoint = Vector3.zero;
groundContactNormal = Vector3.up;
groundStableNormal = Vector3.up;
groundSlopeAngle = 0f;
isOnEdge = false;
edgeAngle = 0f;
groundObject = null;
groundLayer = 0;
groundCollider3D = null;
groundCollider2D = null;
}
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
}
}

View File

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

View File

@ -0,0 +1,240 @@
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
public abstract class CharacterCollisions : MonoBehaviour
{
public HitInfo[] HitsBuffer { get; protected set; } = new HitInfo[20];
protected CharacterActor CharacterActor { get; private set; }
public PhysicsComponent PhysicsComponent { get; protected set; }
readonly CollisionInfo _collisionInfo = new CollisionInfo();
float BackstepDistance => 2f * ContactOffset;
public abstract float ContactOffset { get; }
public abstract float CollisionRadius { get; }
protected Transform Transform;
public static CharacterCollisions CreateInstance(GameObject gameObject)
{
Rigidbody2D rigidbody2D = gameObject.GetComponent<Rigidbody2D>();
Rigidbody rigidbody3D = gameObject.GetComponent<Rigidbody>();
if (rigidbody2D != null)
return gameObject.GetOrAddComponent<CharacterCollisions2D>();
else if (rigidbody3D != null)
return gameObject.GetOrAddComponent<CharacterCollisions3D>();
return null;
}
/// <summary>
/// Checks vertically for the ground using a CastSphere.
/// </summary>
public CollisionInfo CheckForGround(Vector3 position, float stepOffset, float stepDownDistance, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null)
{
float preDistance = stepOffset + BackstepDistance;
Vector3 displacement = CustomUtilities.Multiply(-CharacterActor.Up, Mathf.Max(CharacterConstants.GroundCheckDistance, stepDownDistance));
Vector3 origin = CharacterActor.GetBottomCenter(position, preDistance);
Vector3 castDisplacement = displacement + CustomUtilities.Multiply(Vector3.Normalize(displacement), preDistance + ContactOffset);
PhysicsComponent.SphereCast(
out HitInfo hitInfo,
origin,
CollisionRadius,
castDisplacement,
in hitInfoFilter,
false,
hitFilter
);
UpdateCollisionInfo(_collisionInfo, position, in hitInfo, castDisplacement, preDistance, true, in hitInfoFilter);
return _collisionInfo;
}
/// <summary>
/// Checks vertically for the ground using a CastRay.
/// </summary>
public CollisionInfo CheckForGroundRay(Vector3 position, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null)
{
float preDistance = CharacterActor.BodySize.x / 2f;
Vector3 origin = CharacterActor.GetBottomCenter(position);
Vector3 displacement = CustomUtilities.Multiply(-CharacterActor.Up, Mathf.Max(CharacterConstants.GroundCheckDistance, CharacterActor.stepDownDistance));
Vector3 castDisplacement = displacement + CustomUtilities.Multiply(Vector3.Normalize(displacement), preDistance);
PhysicsComponent.Raycast(
out HitInfo hitInfo,
origin,
castDisplacement,
in hitInfoFilter,
false,
hitFilter
);
UpdateCollisionInfo(_collisionInfo, position, in hitInfo, castDisplacement, preDistance, false, in hitInfoFilter);
return _collisionInfo;
}
/// <summary>
/// Cast the current body shape a get the closest hit.
/// </summary>
public CollisionInfo CastBody(Vector3 position, Vector3 displacement, float bottomOffset, in HitInfoFilter hitInfoFilter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null)
{
float preDistance = BackstepDistance;
Vector3 direction = Vector3.Normalize(displacement);
Vector3 bottom = CharacterActor.GetBottomCenter(position, bottomOffset);
Vector3 top = CharacterActor.GetTopCenter(position);
bottom -= CustomUtilities.Multiply(direction, preDistance);
top -= CustomUtilities.Multiply(direction, preDistance);
Vector3 castDisplacement = displacement + CustomUtilities.Multiply(Vector3.Normalize(displacement), preDistance + ContactOffset);
float radius = CharacterActor.BodySize.x / 2f;
PhysicsComponent.CapsuleCast(
out HitInfo hitInfo,
bottom,
top,
radius,
castDisplacement,
in hitInfoFilter,
allowOverlaps,
hitFilter
);
UpdateCollisionInfo(_collisionInfo, position, in hitInfo, castDisplacement, preDistance, false, in hitInfoFilter);
return _collisionInfo;
}
/// <summary>
/// Performs an overlap test at a given position.
/// </summary>
[System.Obsolete("Use CheckOverlap instead.")]
public bool CheckOverlapWithLayerMask(Vector3 position, float bottomOffset, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null) =>
CheckOverlap(position, bottomOffset, in hitInfoFilter, hitFilter);
/// <summary>
/// Performs an overlap test at a given position.
/// </summary>
/// <param name="position">Target position.</param>
/// <param name="bottomOffset">Bottom offset of the capsule.</param>
/// <param name="hitInfoFilter">Overlap filter.</param>
/// <returns>True if the overlap test detects some obstacle.</returns>
public bool CheckOverlap(Vector3 position, float bottomOffset, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null)
{
Vector3 bottom = CharacterActor.GetBottomCenter(position, bottomOffset);
Vector3 top = CharacterActor.GetTopCenter(position);
float radius = CharacterActor.BodySize.x / 2f - CharacterConstants.SkinWidth;
bool overlap = PhysicsComponent.OverlapCapsule(
bottom,
top,
radius,
in hitInfoFilter,
hitFilter
);
return overlap;
}
/// <summary>
/// Checks if the character fits at a specific location.
/// </summary>
public bool CheckBodySize(Vector3 size, Vector3 position, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null)
{
Vector3 bottom = CharacterActor.GetBottomCenter(position, size);
float radius = size.x / 2f;
// BottomCenterToTopCenter = Up displacement
Vector3 castDisplacement = CharacterActor.GetBottomCenterToTopCenter(size);
PhysicsComponent.SphereCast(
out HitInfo hitInfo,
bottom,
radius,
castDisplacement,
in hitInfoFilter,
false,
hitFilter
);
bool overlap = hitInfo.hit;
return !overlap;
}
/// <summary>
/// Checks if the character fits in place.
/// </summary>
public bool CheckBodySize(Vector3 size, in HitInfoFilter hitInfoFilter, HitFilterDelegate hitFilter = null) => CheckBodySize(size, CharacterActor.Position, in hitInfoFilter, hitFilter);
public void UpdateCollisionInfo(
CollisionInfo collisionInfo,
Vector3 position,
in HitInfo hitInfo,
Vector3 castDisplacement,
float preDistance,
bool calculateEdge = true,
in HitInfoFilter hitInfoFilter = new HitInfoFilter())
{
if (hitInfo.hit)
{
Vector3 castDirection = Vector3.Normalize(castDisplacement);
float closestDistance = hitInfo.distance - preDistance - ContactOffset;
var displacement = castDirection * closestDistance;
if (calculateEdge)
{
Vector3 edgeCenterReference = CharacterActor.GetBottomCenter(position + displacement, 0f);
PerformEdgeDetectionRaycasts(in edgeCenterReference, in hitInfo.point, in hitInfoFilter, out HitInfo upperHitInfo, out HitInfo lowerHitInfo);
collisionInfo.SetData(in hitInfo, CharacterActor.Up, displacement, in upperHitInfo, in lowerHitInfo);
}
else
{
collisionInfo.SetData(in hitInfo, CharacterActor.Up, displacement);
}
}
else
{
collisionInfo.Reset();
}
}
void PerformEdgeDetectionRaycasts(in Vector3 edgeCenterReference, in Vector3 contactPoint, in HitInfoFilter hitInfoFilter, out HitInfo upperHitInfo, out HitInfo lowerHitInfo)
{
Vector3 castDirection = Vector3.Normalize(contactPoint - edgeCenterReference);
Vector3 castDisplacement = CustomUtilities.Multiply(castDirection, CharacterConstants.EdgeRaysCastDistance);
Vector3 upperHitPosition = edgeCenterReference + CustomUtilities.Multiply(CharacterActor.Up, CharacterConstants.EdgeRaysSeparation);
Vector3 lowerHitPosition = edgeCenterReference - CustomUtilities.Multiply(CharacterActor.Up, CharacterConstants.EdgeRaysSeparation);
PhysicsComponent.Raycast(
out upperHitInfo,
upperHitPosition,
castDisplacement,
in hitInfoFilter
);
PhysicsComponent.Raycast(
out lowerHitInfo,
lowerHitPosition,
castDisplacement,
in hitInfoFilter
);
}
protected virtual void Awake()
{
CharacterActor = GetComponent<CharacterActor>();
if (CharacterActor == null)
Debug.Log("CharacterCollisions: CharacterComponent missing");
}
}
}

View File

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

View File

@ -0,0 +1,18 @@
using Lightbug.Utilities;
using UnityEngine;
namespace Lightbug.CharacterControllerPro.Core
{
public class CharacterCollisions2D : CharacterCollisions
{
protected override void Awake()
{
base.Awake();
PhysicsComponent = gameObject.AddComponent<PhysicsComponent2D>();
}
public override float ContactOffset => Physics2D.defaultContactOffset;
public override float CollisionRadius => CharacterActor.BodySize.x / 2f;
}
}

View File

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

View File

@ -0,0 +1,18 @@
using Lightbug.Utilities;
using UnityEngine;
namespace Lightbug.CharacterControllerPro.Core
{
public class CharacterCollisions3D : CharacterCollisions
{
protected override void Awake()
{
base.Awake();
PhysicsComponent = gameObject.AddComponent<PhysicsComponent3D>();
}
public override float ContactOffset => Physics.defaultContactOffset;
public override float CollisionRadius => CharacterActor.BodySize.x / 2f;
}
}

View File

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

View File

@ -0,0 +1,103 @@
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This class contain all the constants used for collision detection, steps detection, ground probing, etc. All the values were carefully chosen, this means that it is not recommended to modify these values at all,
/// however if you need to do it do so at your own risk (make a backup before).
/// </summary>
public class CharacterConstants
{
/// <summary>
/// Offset (towards the ground) applied to the ground trigger.
/// </summary>
public const float GroundTriggerOffset = 0.05f;
/// <summary>
/// This value represents the time (in seconds) that a jumping character can remain unstable while touching the ground. This becomes useful to prevent the character
/// to be stuck in this unstable state for too long.
/// </summary>
public const float MaxUnstableGroundContactTime = 0.25f;
/// <summary>
/// Distance between the origins of the upper and lower edge detection rays.
/// </summary>
public const float EdgeRaysSeparation = 0.005f;
/// <summary>
/// Cast distance used for the raycasts in the edge detection algorithm.
/// </summary>
public const float EdgeRaysCastDistance = 2f;
/// <summary>
/// Space between the collider and the collision shape (used by physics queries).
/// </summary>
public const float SkinWidth = 0.005f;
/// <summary>
/// Minimum offset applied to the bottom of the capsule (upwards) to avoid contact with the ground.
/// </summary>
public const float ColliderMinBottomOffset = 0.1f;
/// <summary>
/// Minimum angle between upper and lower normals (from the edge detector) that defines an edge.
/// </summary>
public const float MinEdgeAngle = 0.5f;
/// <summary>
/// Maximum angle between upper and lower normals (from the edge detector) that defines an edge.
/// </summary>
public const float MaxEdgeAngle = 170f;
/// <summary>
/// Minimum angle between upper and lower normals (from the edge detector) that defines a step.
/// </summary>
public const float MinStepAngle = 85f;
/// <summary>
/// Maximum angle between upper and lower normals (from the edge detector) that defines a step.
/// </summary>
public const float MaxStepAngle = 95f;
/// <summary>
/// Base distance used for ground probing.
/// </summary>
public const float GroundCheckDistance = 0.1f;
/// <summary>
/// Maximum number of iterations available for the collide and slide algorithm.
/// </summary>
public const int MaxSlideIterations = 3;
/// <summary>
/// Maximum number of iterations available for the collide and slide algorithm used after the simulation (dynamic ground processing).
/// </summary>
public const int MaxPostSimulationSlideIterations = 2;
/// <summary>
/// The default gravity value used by the weight function.
/// </summary>
public const float DefaultGravity = 9.8f;
/// <summary>
/// Minimum angle value considered when choosing the "head contact". The angle is measured between the contact normal and the "Up" vector.
/// The valid range goes from "MinHeadContactAngle" to 180 degrees.
/// </summary>
public const float HeadContactMinAngle = 100f;
/// <summary>
/// Tolerance value considered when choosing the "wall contact". The angle is measured between the contact normal and the "Up" vector.
/// The valid range goes from 90 - "WallContactAngleTolerance" to 90 degrees.
/// </summary>
public const float WallContactAngleTolerance = 10f;
/// <summary>
/// Distance used to predict the ground below the character.
/// </summary>
public const float GroundPredictionDistance = 10f;
}
}

View File

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

View File

@ -0,0 +1,113 @@
using UnityEngine;
using UnityEngine.UI;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This class is used for debug purposes, mainly to print information on screen about the collision flags, certain values and/or triggering events.
/// </summary>
[DefaultExecutionOrder(ExecutionOrder.CharacterActorOrder)]
[AddComponentMenu("Character Controller Pro/Core/Character Debug")]
public class CharacterDebug : MonoBehaviour
{
[SerializeField]
CharacterActor characterActor = null;
[Header("Character Info")]
[SerializeField]
Text text = null;
[Header("Events")]
[SerializeField]
bool printEvents = false;
[Header("Stability")]
[SerializeField]
Renderer stabilityIndicator;
[Condition("stabilityIndicator", ConditionAttribute.ConditionType.IsNotNull, ConditionAttribute.VisibilityType.NotEditable)]
[SerializeField]
Color stableColor = new Color(0f, 1f, 0f, 0.5f);
[Condition("stabilityIndicator", ConditionAttribute.ConditionType.IsNotNull, ConditionAttribute.VisibilityType.NotEditable)]
[SerializeField]
Color unstableColor = new Color(1f, 0f, 0f, 0.5f);
int colorID = Shader.PropertyToID("_Color");
float time = 0f;
void UpdateCharacterInfoText()
{
if (text == null)
return;
if (time > 0.2f)
{
text.text = characterActor.GetCharacterInfo();
time = 0f;
}
else
{
time += Time.deltaTime;
}
}
void OnWallHit(Contact contact) => Debug.Log("OnWallHit");
void OnGroundedStateEnter(Vector3 localVelocity) => Debug.Log("OnEnterGroundedState, localVelocity : " + localVelocity.ToString("F3"));
void OnGroundedStateExit() => Debug.Log("OnExitGroundedState");
void OnStableStateEnter(Vector3 localVelocity) => Debug.Log("OnStableStateEnter, localVelocity : " + localVelocity.ToString("F3"));
void OnStableStateExit() => Debug.Log("OnStableStateExit");
void OnHeadHit(Contact contact) => Debug.Log("OnHeadHit");
void OnTeleportation(Vector3 position, Quaternion rotation) => Debug.Log("OnTeleportation, position : " + position.ToString("F3") + " and rotation : " + rotation.ToString("F3"));
#region Messages
void FixedUpdate()
{
if (characterActor == null)
{
enabled = false;
return;
}
UpdateCharacterInfoText();
}
void Update()
{
if (stabilityIndicator != null)
stabilityIndicator.material.SetColor(colorID, characterActor.IsStable ? stableColor : unstableColor);
}
void OnEnable()
{
if (!printEvents)
return;
characterActor.OnHeadHit += OnHeadHit;
characterActor.OnWallHit += OnWallHit;
characterActor.OnGroundedStateEnter += OnGroundedStateEnter;
characterActor.OnGroundedStateExit += OnGroundedStateExit;
characterActor.OnStableStateEnter += OnStableStateEnter;
characterActor.OnStableStateExit += OnStableStateExit;
characterActor.OnTeleport += OnTeleportation;
}
void OnDisable()
{
if (!printEvents)
return;
characterActor.OnHeadHit -= OnHeadHit;
characterActor.OnWallHit -= OnWallHit;
characterActor.OnGroundedStateEnter -= OnGroundedStateEnter;
characterActor.OnGroundedStateExit -= OnGroundedStateExit;
characterActor.OnStableStateEnter += OnStableStateEnter;
characterActor.OnStableStateExit += OnStableStateExit;
characterActor.OnTeleport -= OnTeleportation;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,117 @@
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This class contains the collision information gathered from of all the different physics queries (raycast, capsuleCast, etc). This information is very important
/// for movement prediction.
/// </summary>
public class CollisionInfo
{
/// <summary>
/// The physics query data.
/// </summary>
public HitInfo hitInfo;
/// <summary>
/// The available displacement obtained as the result of the collision test. By adding this vector to the character position, the result will represent the closest possible position to the hit surface.
/// </summary>
public Vector3 displacement;
/// <summary>
/// The angle between the contact normal and the character up vector.
/// </summary>
public float contactSlopeAngle;
/// <summary>
/// Flag that indicates if the character is standing on an edge or not.
/// </summary>
public bool isAnEdge;
/// <summary>
/// Flag that indicates if the character is standing on an step or not.
/// </summary>
public bool isAStep;
/// <summary>
/// Normal vector obtained from the edge detector upper ray.
/// </summary>
public Vector3 edgeUpperNormal;
/// <summary>
/// Normal vector obtained from the edge detector lower ray.
/// </summary>
public Vector3 edgeLowerNormal;
/// <summary>
/// Angle between the character up vector and the edge detector upper normal.
/// </summary>
public float edgeUpperSlopeAngle;
/// <summary>
/// Angle between the character up vector and the edge detector lower normal.
/// </summary>
public float edgeLowerSlopeAngle;
/// <summary>
/// Angle between the edge detector upper normal and the edge detector lower normal.
/// </summary>
public float edgeAngle;
/// <summary>
/// Resets all the fields to their default values.
/// </summary>
public void Reset()
{
this.hitInfo = default;
this.displacement = default;
this.contactSlopeAngle = default;
this.edgeUpperNormal = default;
this.edgeLowerNormal = default;
this.edgeUpperSlopeAngle = default;
this.edgeLowerSlopeAngle = default;
this.edgeAngle = default;
this.isAnEdge = default;
this.isAStep = default;
}
/// <summary>
/// Sets the fields values based on the character movement.
/// </summary>
public void SetData(in HitInfo hitInfo, Vector3 upDirection, Vector3 displacement)
{
this.hitInfo = hitInfo;
this.displacement = displacement;
this.contactSlopeAngle = Vector3.Angle(upDirection, hitInfo.normal);
}
/// <summary>
/// Sets the fields values based on the character movement.
/// </summary>
public void SetData(
in HitInfo hitInfo,
Vector3 upDirection,
Vector3 displacement,
in HitInfo upperHitInfo,
in HitInfo lowerHitInfo
)
{
SetData(in hitInfo, upDirection, displacement);
this.edgeUpperNormal = upperHitInfo.normal;
this.edgeLowerNormal = lowerHitInfo.normal;
this.edgeUpperSlopeAngle = Vector3.Angle(edgeUpperNormal, upDirection);
this.edgeLowerSlopeAngle = Vector3.Angle(edgeLowerNormal, upDirection);
this.edgeAngle = Vector3.Angle(edgeUpperNormal, edgeLowerNormal);
this.isAnEdge = CustomUtilities.isBetween(edgeAngle, CharacterConstants.MinEdgeAngle, CharacterConstants.MaxEdgeAngle, true);
this.isAStep = CustomUtilities.isBetween(edgeAngle, CharacterConstants.MinStepAngle, CharacterConstants.MaxStepAngle, true);
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 939c76a9b1746ed4ba9f3d184f7b544c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using Lightbug.Utilities;
using UnityEngine;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// The root abstract class for all the graphics-based components.
/// </summary>
public abstract class CharacterGraphics : MonoBehaviour
{
protected CharacterActor CharacterActor { get; private set; }
protected CharacterBody CharacterBody => CharacterActor.CharacterBody;
protected virtual void OnValidate()
{
CharacterActor = this.GetComponentInBranch<CharacterActor>();
if (CharacterActor == null)
{
Debug.Log("Warning: No CharacterActor component detected in this hierarchy.");
}
}
protected virtual void Awake()
{
CharacterActor = this.GetComponentInBranch<CharacterActor>();
if (CharacterActor == null)
{
Debug.Log("Warning: No CharacterActor component detected in this hierarchy.");
enabled = false;
return;
}
//RootController = CharacterActor.GetComponentInChildren<CharacterGraphicsRootController>();
}
}
}

View File

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

View File

@ -0,0 +1,123 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component can be used to rotate a 2D character based on its forward direction. For 2D, characters usually have its forward direction pointing towards Vector3.forward (or negtive).
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/2D Rotator (Obsolete)")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder)]
public class CharacterGraphics2DRotator : CharacterGraphics
{
[HelpBox("This component is obsolete and has been replaced with the CharacterSpriteScaler component, which is a simplified version.", HelpBoxMessageType.Warning)]
[Tooltip("Scale: it will flip the sprite along the horizontal axis (localScale). This works only with sprites!\nRotation: it will rotate the object towards the facing direction.")]
public FacingDirectionMode facingDirectionMode = FacingDirectionMode.Rotation;
[Condition("facingDirectionMode", ConditionAttribute.ConditionType.IsEqualTo, ConditionAttribute.VisibilityType.Hidden, (int)FacingDirectionMode.Scale)]
[SerializeField]
VectorComponent scaleAffectedComponent = VectorComponent.X;
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Vector3 initialScale = Vector3.zero;
Vector3 initialForward;
enum VectorComponent
{
X,
Y,
Z
}
/// <summary>
/// The method used by the CharacterGraphics component to orient the graphics object towards the facing direction vector.
/// </summary>
public enum FacingDirectionMode
{
Rotation,
Scale
}
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
void HandleRotation(float dt)
{
if (facingDirectionMode == FacingDirectionMode.Scale)
{
transform.rotation = Quaternion.LookRotation(initialForward, CharacterActor.Up);
float signedAngle = Vector3.SignedAngle(CharacterActor.Forward, CharacterActor.Up, Vector3.forward);
bool shouldBeFacingRight = signedAngle > 0f;
switch (scaleAffectedComponent)
{
case VectorComponent.X:
transform.localScale = new Vector3(
shouldBeFacingRight ? initialScale.x : -initialScale.x,
transform.localScale.y,
transform.localScale.z
);
break;
case VectorComponent.Y:
transform.localScale = new Vector3(
transform.localScale.x,
shouldBeFacingRight ? initialScale.y : -initialScale.y,
transform.localScale.z
);
break;
case VectorComponent.Z:
transform.localScale = new Vector3(
transform.localScale.x,
transform.localScale.y,
shouldBeFacingRight ? initialScale.z : -initialScale.z
);
break;
}
}
}
protected override void OnValidate()
{
base.OnValidate();
if (!CharacterBody.Is2D)
Debug.Log("Warning: CharacterBody is not 2D. This component is intended to be used with a 2D physics character.");
SkinnedMeshRenderer skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
if (skinnedMeshRenderer != null && facingDirectionMode == FacingDirectionMode.Scale)
Debug.Log("Warning: \"Scale\" facing direction mode is intended to work with sprites, not with humanoid characters, choose \"Rotation\" instead.");
}
protected override void Awake()
{
base.Awake();
if (!CharacterBody.Is2D)
enabled = false;
}
void Start()
{
initialScale = transform.localScale;
initialForward = transform.forward;
}
void LateUpdate()
{
float dt = Time.deltaTime;
HandleRotation(dt);
}
}
}

View File

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

View File

@ -0,0 +1,174 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component is responsible for smoothing out the graphics-related elements (under the root object) based on the character movement and rotation.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/Graphics Root Controller (obsolete)")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder)]
public class CharacterGraphicsRootController : CharacterGraphics
{
[HelpBox("This component is obsolete. It has been separated into two new components: Step Lerper and Rotation Lerper.", HelpBoxMessageType.Warning)]
[Tooltip("Whether or not interpolate the rotation of the character.")]
[SerializeField]
bool lerpRotation = false;
[Condition("lerpRotation", ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.VisibilityType.NotEditable)]
[SerializeField]
float rotationLerpSpeed = 25f;
[Space(10f)]
[Tooltip("Whether or not to interpolate the vertical displacement change of the character. A vertical displacement happens everytime the character " +
"increase/decrease its vertical position (slopes, step up, step down, etc.). This feature does not work with rigidbodies (if this is required use the new VerticalDisplacementLerper component instead).")]
[SerializeField]
bool lerpVerticalDisplacement = true;
[Tooltip("How fast the step up interpolation is going to be.")]
[SerializeField]
float positiveDisplacementSpeed = 10f;
[Tooltip("How fast the step down interpolation is going to be.")]
[SerializeField]
float negativeDisplacementSpeed = 40f;
[Tooltip("Having a character that is being interpolated all the time is not ideal, especially when walking on slopes, being not grounded, or maybe using a moving platform. " +
"For those cases, the character should be allowed to smoothly go back to its original local position over time. This field represents the duration of this process (in seconds).")]
[SerializeField]
float recoveryDuration = 1f;
[Tooltip("The maximum speed used for the recovery process (see recoveryDuration tooltip).")]
[SerializeField]
float maxRecoverySpeed = 200f;
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Vector3 previousPosition = default(Vector3);
Quaternion previousRotation = default(Quaternion);
Vector3 initialLocalForward = default(Vector3);
protected override void OnValidate()
{
base.OnValidate();
CustomUtilities.SetPositive(ref rotationLerpSpeed);
CustomUtilities.SetPositive(ref positiveDisplacementSpeed);
CustomUtilities.SetPositive(ref negativeDisplacementSpeed);
}
void Start()
{
initialLocalForward = CharacterActor.transform.InverseTransformDirection(transform.forward);
previousPosition = transform.position;
previousRotation = transform.rotation;
}
void OnEnable()
{
CharacterActor.OnTeleport += OnTeleport;
}
void OnDisable()
{
CharacterActor.OnTeleport -= OnTeleport;
}
bool teleportFlag = false;
void OnTeleport(Vector3 position, Quaternion rotation)
{
teleportFlag = true;
}
void Update()
{
if (CharacterActor == null)
{
this.enabled = false;
return;
}
float dt = Time.deltaTime;
HandleRotation(dt);
HandleVerticalDisplacement(dt);
if (teleportFlag)
teleportFlag = false;
}
float recoveryTimer = 0f;
void HandleVerticalDisplacement(float dt)
{
if (!lerpVerticalDisplacement)
return;
if (teleportFlag)
{
previousPosition = transform.position;
transform.position = CharacterActor.Position;
return;
}
Vector3 planarDisplacement = Vector3.ProjectOnPlane(CharacterActor.transform.position - previousPosition, CharacterActor.Up);
Vector3 verticalDisplacement = Vector3.Project(CharacterActor.transform.position - previousPosition, CharacterActor.Up);
float groundProbingDisplacement = CharacterActor.transform.InverseTransformVectorUnscaled(CharacterActor.GroundProbingDisplacement).y;
if (Mathf.Abs(groundProbingDisplacement) < 0.01f)
recoveryTimer += dt;
else
recoveryTimer = 0f;
// Choose between positive and negative displacement speed based on the vertical displacement.
bool upwardsLerpDirection = CharacterActor.transform.InverseTransformVectorUnscaled(verticalDisplacement).y >= 0f;
float displacementTSpeed = upwardsLerpDirection ? positiveDisplacementSpeed : negativeDisplacementSpeed;
// Calculate the lerp t speed as a function of the recovery timer.
float lerpTSpeedOutput = Mathf.Min(displacementTSpeed + ((maxRecoverySpeed - displacementTSpeed) / recoveryDuration) * recoveryTimer, maxRecoverySpeed);
transform.position = previousPosition + planarDisplacement + Vector3.Lerp(Vector3.zero, verticalDisplacement, lerpTSpeedOutput * dt);
previousPosition = transform.position;
}
void HandleRotation(float dt)
{
if (!lerpRotation)
return;
if (teleportFlag)
{
transform.localRotation = Quaternion.identity;
previousRotation = transform.rotation;
return;
}
transform.rotation = Quaternion.Slerp(previousRotation, CharacterActor.Rotation, rotationLerpSpeed * dt);
previousRotation = transform.rotation;
}
}
}

View File

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

View File

@ -0,0 +1,84 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component can be used to make a Transform change its scale based on the character actor height.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/Scaler")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder + 1)]
public class CharacterGraphicsScaler : CharacterGraphics
{
[SerializeField]
VectorComponent scaleHeightComponent = VectorComponent.Y;
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
enum VectorComponent
{
X,
Y,
Z
}
Vector3 initialLocalScale = Vector3.one;
void Start()
{
initialLocalScale = transform.localScale;
}
void Update()
{
if (!CharacterActor.enabled)
return;
Vector3 scale = Vector3.one;
switch (scaleHeightComponent)
{
case VectorComponent.X:
scale = new Vector3(
initialLocalScale.x * (CharacterActor.BodySize.y / CharacterActor.DefaultBodySize.y),
initialLocalScale.y * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x),
initialLocalScale.z * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x)
);
break;
case VectorComponent.Y:
scale = new Vector3(
initialLocalScale.x * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x),
initialLocalScale.y * (CharacterActor.BodySize.y / CharacterActor.DefaultBodySize.y),
initialLocalScale.z * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x)
);
break;
case VectorComponent.Z:
scale = new Vector3(
initialLocalScale.x * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x),
initialLocalScale.y * (CharacterActor.BodySize.x / CharacterActor.DefaultBodySize.x),
initialLocalScale.z * (CharacterActor.BodySize.y / CharacterActor.DefaultBodySize.y)
);
break;
}
transform.localScale = scale;
}
}
}

View File

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

View File

@ -0,0 +1,76 @@
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component can be used to smooth out the graphics object rotation.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/Rotation Lerper")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder)]
public class CharacterRotationLerper : CharacterGraphics
{
[Condition("lerpRotation", ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.VisibilityType.NotEditable)]
[SerializeField]
float rotationLerpSpeed = 25f;
Quaternion previousRotation = default;
bool teleportFlag = false;
#region Messages
protected override void OnValidate()
{
base.OnValidate();
CustomUtilities.SetPositive(ref rotationLerpSpeed);
}
void Start()
{
previousRotation = transform.rotation;
}
void OnEnable() => CharacterActor.OnTeleport += OnTeleport;
void OnDisable() => CharacterActor.OnTeleport -= OnTeleport;
void Update()
{
if (CharacterActor == null)
{
enabled = false;
return;
}
float dt = Time.deltaTime;
HandleRotation(dt);
if (teleportFlag)
teleportFlag = false;
}
#endregion
void OnTeleport(Vector3 position, Quaternion rotation) => teleportFlag = true;
void HandleRotation(float dt)
{
if (teleportFlag)
{
transform.localRotation = Quaternion.identity;
previousRotation = transform.rotation;
return;
}
transform.rotation = Quaternion.Slerp(previousRotation, CharacterActor.Rotation, rotationLerpSpeed * dt);
previousRotation = transform.rotation;
}
}
}

View File

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

View File

@ -0,0 +1,104 @@
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component can be used to rotate a 2D sprite based on the character forward direction.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/Sprite Rotator")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder)]
public class CharacterSpriteRotator : CharacterGraphics
{
[SerializeField]
VectorComponent scaleAffectedComponent = VectorComponent.X;
enum VectorComponent
{
X,
Y,
Z
}
Vector3 initialScale;
Vector3 initialForward;
void HandleRotation(float dt)
{
transform.rotation = Quaternion.LookRotation(initialForward, CharacterActor.Up);
float signedAngle = Vector3.SignedAngle(CharacterActor.Forward, CharacterActor.Up, Vector3.forward);
bool shouldBeFacingRight = signedAngle > 0f;
switch (scaleAffectedComponent)
{
case VectorComponent.X:
transform.localScale = new Vector3(
shouldBeFacingRight ? initialScale.x : -initialScale.x,
transform.localScale.y,
transform.localScale.z
);
break;
case VectorComponent.Y:
transform.localScale = new Vector3(
transform.localScale.x,
shouldBeFacingRight ? initialScale.y : -initialScale.y,
transform.localScale.z
);
break;
case VectorComponent.Z:
transform.localScale = new Vector3(
transform.localScale.x,
transform.localScale.y,
shouldBeFacingRight ? initialScale.z : -initialScale.z
);
break;
}
}
#region Messages
protected override void OnValidate()
{
base.OnValidate();
if (!CharacterBody.Is2D)
Debug.Log("Warning: CharacterBody is not 2D. This component is intended to be used with a 2D physics character.");
SkinnedMeshRenderer skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
if (skinnedMeshRenderer != null)
Debug.Log("Warning: \"Scale\" facing direction mode is intended to work with sprites, not with humanoid characters, choose \"Rotation\" instead.");
}
protected override void Awake()
{
base.Awake();
if (!CharacterBody.Is2D)
enabled = false;
}
void Start()
{
initialScale = transform.localScale;
initialForward = transform.forward;
}
void LateUpdate()
{
float dt = Time.deltaTime;
HandleRotation(dt);
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,113 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component is responsible for smoothing out the graphics-related elements (under the root object) based on the character movement (CharacterActor).
/// It allows you to modify the position and rotation accordingly, producing a great end result.
/// </summary>
[AddComponentMenu("Character Controller Pro/Core/Character Graphics/Step Lerper")]
[DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder)]
public class CharacterStepLerper : CharacterGraphics
{
[Tooltip("How fast the step up interpolation is going to be.")]
[SerializeField]
float positiveDisplacementSpeed = 20f;
[Tooltip("How fast the step down interpolation is going to be.")]
[SerializeField]
float negativeDisplacementSpeed = 40f;
[Tooltip("Having a character that is being interpolated all the time is not ideal, especially when walking on slopes, being not grounded, or maybe using a moving platform. " +
"For those cases, the character should be allowed to smoothly go back to its original local position over time. This field represents the duration of this process (in seconds).")]
[SerializeField]
float recoveryDuration = 1f;
[Tooltip("The maximum speed used for the recovery process (see recoveryDuration tooltip).")]
[SerializeField]
float maxRecoverySpeed = 200f;
Vector3 previousPosition = default;
bool teleportFlag = false;
float recoveryTimer = 0f;
#region Messages
protected override void OnValidate()
{
base.OnValidate();
CustomUtilities.SetPositive(ref positiveDisplacementSpeed);
CustomUtilities.SetPositive(ref negativeDisplacementSpeed);
}
void Start()
{
previousPosition = transform.position;
}
void OnEnable() => CharacterActor.OnTeleport += OnTeleport;
void OnDisable() => CharacterActor.OnTeleport -= OnTeleport;
void Update()
{
if (CharacterActor == null)
{
enabled = false;
return;
}
float dt = Time.deltaTime;
HandleVerticalDisplacement(dt);
if (teleportFlag)
teleportFlag = false;
}
#endregion
void OnTeleport(Vector3 position, Quaternion rotation) => teleportFlag = true;
void HandleVerticalDisplacement(float dt)
{
if (teleportFlag)
{
previousPosition = transform.position;
transform.position = CharacterActor.Position;
return;
}
Vector3 planarDisplacement = Vector3.ProjectOnPlane(CharacterActor.transform.position - previousPosition, CharacterActor.Up);
Vector3 verticalDisplacement = Vector3.Project(CharacterActor.transform.position - previousPosition, CharacterActor.Up);
float groundProbingDisplacement = CharacterActor.transform.InverseTransformVectorUnscaled(CharacterActor.GroundProbingDisplacement).y;
if (Mathf.Abs(groundProbingDisplacement) < 0.01f)
recoveryTimer += dt;
else
recoveryTimer = 0f;
// Choose between positive and negative displacement speed based on the vertical displacement.
bool upwardsLerpDirection = CharacterActor.transform.InverseTransformVectorUnscaled(verticalDisplacement).y >= 0f;
float displacementTSpeed = upwardsLerpDirection ? positiveDisplacementSpeed : negativeDisplacementSpeed;
// Calculate the lerp t speed as a function of the recovery timer.
float lerpTSpeedOutput = Mathf.Min(displacementTSpeed + ((maxRecoverySpeed - displacementTSpeed) / recoveryDuration) * recoveryTimer, maxRecoverySpeed);
transform.position = previousPosition + planarDisplacement + Vector3.Lerp(Vector3.zero, verticalDisplacement, lerpTSpeedOutput * dt);
previousPosition = transform.position;
}
}
}

View File

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

View File

@ -0,0 +1,811 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// A physics-based actor that represents a custom 2D/3D interpolated rigidbody.
/// </summary>
public abstract class PhysicsActor : MonoBehaviour
{
[Header("Rigidbody")]
[Tooltip("Interpolates the Transform component associated with this actor during Update calls. This is a custom implementation, the actor " +
"does not use Unity's default interpolation.")]
public bool interpolateActor = true;
[Tooltip("Whether or not to use continuous collision detection (rigidbody property). " +
"This won't affect character vs static obstacles interactions, but it will affect character vs dynamic rigidbodies.")]
public bool useContinuousCollisionDetection = true;
[Header("Root motion")]
[Tooltip("This option activates root motion for the character. With root motion enabled, position and rotation are handled exclusively by the animation system.")]
public bool UseRootMotion = false;
[Tooltip("Whether or not to transfer position data from the root motion animation to the character.")]
[Condition("UseRootMotion", ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.VisibilityType.NotEditable)]
public bool UpdateRootPosition = true;
[Tooltip("How the root velocity data is going to be applied to the actor.")]
[Condition(
new string[] { "UpdateRootPosition", "UseRootMotion" },
new ConditionAttribute.ConditionType[] { ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.ConditionType.IsTrue },
new float[] { 0f, 0f },
ConditionAttribute.VisibilityType.NotEditable)]
public RootMotionVelocityType rootMotionVelocityType = RootMotionVelocityType.SetVelocity;
[Tooltip("Whether or not to transfer rotation data from the root motion animation to the character.")]
[Condition("UseRootMotion", ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.VisibilityType.NotEditable)]
public bool UpdateRootRotation = true;
[Tooltip("How the root velocity data is going to be applied to the actor.")]
// [Condition( "UpdateRootRotation" , ConditionAttribute.ConditionType.IsTrue , ConditionAttribute.VisibilityType.NotEditable )]
[Condition(
new string[] { "UpdateRootRotation", "UseRootMotion" },
new ConditionAttribute.ConditionType[] { ConditionAttribute.ConditionType.IsTrue, ConditionAttribute.ConditionType.IsTrue },
new float[] { 0f, 0f },
ConditionAttribute.VisibilityType.NotEditable)]
public RootMotionRotationType rootMotionRotationType = RootMotionRotationType.AddRotation;
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
/// <summary>
/// Defines how the root velocity data is going to be applied to the actor.
/// </summary>
public enum RootMotionVelocityType
{
/// <summary>
/// The root motion velocity will be applied as velocity.
/// </summary>
SetVelocity,
/// <summary>
/// The root motion velocity will be applied as planar velocity.
/// </summary>
SetPlanarVelocity,
/// <summary>
/// The root motion velocity will be applied as vertical velocity.
/// </summary>
SetVerticalVelocity,
}
/// <summary>
/// Defines how the root rotation data is going to be applied to the actor.
/// </summary>
public enum RootMotionRotationType
{
/// <summary>
/// The root motion rotation will override the current rotation.
/// </summary>
SetRotation,
/// <summary>
/// The root motion rotation will be added to the current rotation.
/// </summary>
AddRotation
}
/// <summary>
/// This event is called prior to the physics simulation.
/// </summary>
public event System.Action<float> OnPreSimulation;
/// <summary>
/// This event is called after the physics simulation.
/// </summary>
public event System.Action<float> OnPostSimulation;
Vector3 startingPosition;
Vector3 targetPosition;
Quaternion startingRotation;
Quaternion targetRotation;
Coroutine postSimulationUpdateCoroutine;
AnimatorLink animatorLink = null;
bool wasInterpolatingActor = false;
/// <summary>
/// Gets the RigidbodyComponent component associated with the character.
/// </summary>
public abstract RigidbodyComponent RigidbodyComponent { get; }
/// <summary>
/// Gets the Animator component associated with the actor.
/// </summary>
public Animator Animator { get; private set; }
/// <summary>
/// Gets/Sets the rigidbody velocity.
/// </summary>
public Vector3 Velocity
{
get => RigidbodyComponent.Velocity;
set => RigidbodyComponent.Velocity = value;
}
/// <summary>
/// Gets/Sets the rigidbody velocity projected onto a plane formed by its up direction.
/// </summary>
public Vector3 PlanarVelocity
{
get => transform.TransformDirection(LocalPlanarVelocity);
set => LocalPlanarVelocity = transform.InverseTransformDirection(value);
}
/// <summary>
/// Gets/Sets the rigidbody velocity projected onto its up direction.
/// </summary>
public Vector3 VerticalVelocity
{
get => transform.TransformDirection(LocalVerticalVelocity);
set => LocalVerticalVelocity = transform.InverseTransformDirection(value);
}
/// <summary>
/// Gets/Sets the rigidbody local velocity.
/// </summary>
public Vector3 LocalVelocity
{
get => transform.InverseTransformDirection(RigidbodyComponent.Velocity);
set => RigidbodyComponent.Velocity = transform.TransformDirection(value);
}
/// <summary>
/// Gets/Sets the rigidbody local planar velocity.
/// </summary>
public Vector3 LocalPlanarVelocity
{
get
{
Vector3 localVelocity = LocalVelocity;
localVelocity.y = 0f;
return localVelocity;
}
set
{
value.y = 0f;
LocalVelocity = value + LocalVerticalVelocity;
}
}
/// <summary>
/// Gets/Sets the rigidbody local vertical velocity.
/// </summary>
public Vector3 LocalVerticalVelocity
{
get
{
Vector3 localVelocity = LocalVelocity;
localVelocity.x = localVelocity.z = 0f;
return localVelocity;
}
set
{
value.x = value.z = 0f;
LocalVelocity = LocalPlanarVelocity + value;
}
}
/// <summary>
/// Returns true if the character local vertical velocity is less than zero.
/// </summary>
public bool IsFalling => LocalVelocity.y < 0f;
/// <summary>
/// Returns true if the character local vertical velocity is greater than zero.
/// </summary>
public bool IsAscending => LocalVelocity.y > 0f;
/// <summary>
/// Gets the CharacterBody component associated with this character actor.
/// </summary>
public bool Is2D => RigidbodyComponent.Is2D;
/// <summary>
/// Gets/Sets the current rigidbody position. This action will produce an "interpolation reset", meaning that (visually) the object will move instantly to the target.
/// </summary>
public Vector3 Position
{
get => RigidbodyComponent.Position;
set
{
RigidbodyComponent.Position = value;
targetPosition = value;
}
}
/// <summary>
/// Gets/Sets the current rigidbody rotation. This action will produce an "interpolation reset", meaning that (visually) the object will rotate instantly to the target.
/// </summary>
public Quaternion Rotation
{
get => transform.rotation;
set
{
transform.rotation = value;
targetRotation = value;
}
}
public bool IsKinematic
{
get => RigidbodyComponent.IsKinematic;
set => RigidbodyComponent.IsKinematic = value;
}
/// <summary>
/// Sets the rigidbody velocity based on a target position. The same can be achieved by setting the velocity value manually.
/// </summary>
public void Move(Vector3 position) => RigidbodyComponent.Move(position);
public event System.Action<Vector3, Quaternion> OnTeleport;
public event System.Action OnAnimatorMoveEvent;
public event System.Action<int> OnAnimatorIKEvent;
/// <summary>
/// Sets the teleportation position.
/// The character will move/rotate internally using its own internal logic.
/// </summary>
public void Teleport(Vector3 position) => Teleport(position, Rotation);
/// <summary>
/// Sets the teleportation position and rotation using an external Transform reference.
/// The character will move/rotate internally using its own internal logic.
/// </summary>
public void Teleport(Transform reference) => Teleport(reference.position, reference.rotation);
/// <summary>
/// Sets the teleportation position and rotation.
/// The character will move/rotate internally using its own internal logic.
/// </summary>
public void Teleport(Vector3 position, Quaternion rotation)
{
Position = position;
Rotation = rotation;
ResetInterpolationPosition();
ResetInterpolationRotation();
OnTeleport?.Invoke(Position, Rotation);
}
/// <summary>
/// Gets the current up direction based on the rigidbody rotation (not necessarily transform.up).
/// </summary>
public virtual Vector3 Up
{
get
{
return Rotation * Vector3.up;
}
set
{
Quaternion deltaRotation = Quaternion.FromToRotation(Up, value);
Rotation = deltaRotation * Rotation;
}
}
/// <summary>
/// Gets/Sets the current forward direction based on the rigidbody rotation (not necessarily transform.forward).
/// </summary>
public virtual Vector3 Forward
{
get
{
return Is2D ? Rotation * Vector3.right : Rotation * Vector3.forward;
}
set
{
Quaternion deltaRotation = Quaternion.FromToRotation(Forward, value);
Rotation = deltaRotation * Rotation;
}
}
/// <summary>
/// Gets the current up direction based on the rigidbody rotation (not necessarily transform.right)
/// </summary>
public virtual Vector3 Right
{
get
{
return Is2D ? Rotation * Vector3.forward : Rotation * Vector3.right;
}
set
{
Quaternion deltaRotation = Quaternion.FromToRotation(Right, value);
Rotation = deltaRotation * Rotation;
}
}
#region Rotation
/// <summary>
/// Sets a rotation based on "forward" and "up". This is equivalent to Quaternion.LookRotation.
/// </summary>
public virtual void SetRotation(Vector3 forward, Vector3 up)
{
Rotation = Quaternion.LookRotation(forward, up);
}
/// <summary>
/// Rotates the character by doing yaw rotation (around its "up" axis).
/// </summary>
public virtual void RotateAround(Quaternion deltaRotation, Vector3 pivot)
{
Vector3 preReferenceToPivot = pivot - Position;
Rotation = deltaRotation * Rotation;
Vector3 postReferenceToPivot = deltaRotation * preReferenceToPivot;
Position += preReferenceToPivot - postReferenceToPivot;
}
/// <summary>
/// Rotates the character by doing yaw rotation (around its "up" axis) based on a given "forward" vector.
/// </summary>
/// <param name="angle">The desired forward vector.</param>
public virtual void SetYaw(Vector3 forward)
{
Rotation = Quaternion.AngleAxis(Vector3.SignedAngle(Forward, forward, Up), Up) * Rotation;
}
[System.Obsolete]
/// <summary>
/// Rotates the character by doing yaw rotation (around its "up" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
public void SetYaw(float angle)
{
Rotation = Quaternion.AngleAxis(angle, Up) * Rotation;
}
/// <summary>
/// Rotates the character by doing yaw rotation (around its "up" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
public virtual void RotateYaw(float angle)
{
Rotation = Quaternion.AngleAxis(angle, Up) * Rotation;
}
/// <summary>
/// Rotates the character by doing yaw rotation (around its "up" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
/// <param name="pivot">The rotation pivot in space.</param>
public virtual void RotateYaw(float angle, Vector3 pivot)
{
Quaternion deltaRotation = Quaternion.AngleAxis(angle, Up);
RotateAround(deltaRotation, pivot);
}
/// <summary>
/// Rotates the character by doing pitch rotation (around its "right" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
public virtual void RotatePitch(float angle)
{
Rotation = Quaternion.AngleAxis(angle, Right) * Rotation;
}
/// <summary>
/// Rotates the character by doing pitch rotation (around its "right" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
/// <param name="pivot">The rotation pivot in space.</param>
public virtual void RotatePitch(float angle, Vector3 pivot)
{
Quaternion deltaRotation = Quaternion.AngleAxis(angle, Right);
RotateAround(deltaRotation, pivot);
}
/// <summary>
/// Rotates the character by doing roll rotation (around its "forward" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
public virtual void RotateRoll(float angle)
{
Rotation = Quaternion.AngleAxis(angle, Forward) * Rotation;
}
/// <summary>
/// Rotates the character by doing roll rotation (around its "forward" axis).
/// </summary>
/// <param name="angle">The angle in degrees.</param>
/// <param name="pivot">The rotation pivot in space.</param>
public virtual void RotateRoll(float angle, Vector3 pivot)
{
Quaternion deltaRotation = Quaternion.AngleAxis(angle, Forward);
RotateAround(deltaRotation, pivot);
}
/// <summary>
/// Rotates the character by performing 180 degrees of yaw rotation (around its vertical axis). Also, interpolation (rotation) gets automatically reset
/// just to prevent weird visual artifacts.
/// </summary>
/// <param name="angle">The angle in degrees.</param>
public virtual void TurnAround()
{
ResetInterpolationRotation();
RotateYaw(180f);
}
#endregion
/// <summary>
/// Configures all the animation-related components based on a given Animator component. The Animator provides root motion data along
/// </summary>
[System.Obsolete]
public void InitializeAnimation()
{
if (Animator == null)
return;
#if UNITY_2023_1_OR_NEWER
Animator.updateMode = AnimatorUpdateMode.Fixed;
#else
Animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
#endif
if (!Animator.TryGetComponent(out animatorLink))
animatorLink = Animator.gameObject.AddComponent<AnimatorLink>();
}
public void SetAnimator(Animator animator)
{
if (animator == null)
return;
Animator = animator;
#if UNITY_2023_1_OR_NEWER
Animator.updateMode = AnimatorUpdateMode.Fixed;
#else
Animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
#endif
if (!Animator.TryGetComponent(out animatorLink))
animatorLink = Animator.gameObject.AddComponent<AnimatorLink>();
}
public void ResetIKWeights()
{
if (animatorLink != null)
animatorLink.ResetIKWeights();
}
protected virtual void PreSimulationUpdate(float dt) { }
protected virtual void PostSimulationUpdate(float dt) { }
protected virtual void UpdateKinematicRootMotionPosition()
{
if (!UpdateRootPosition)
return;
Position += Animator.deltaPosition;
}
protected virtual void UpdateKinematicRootMotionRotation()
{
if (!UpdateRootRotation)
return;
if (rootMotionRotationType == RootMotionRotationType.AddRotation)
Rotation *= Animator.deltaRotation;
else
Rotation = Animator.rootRotation;
}
protected virtual void UpdateDynamicRootMotionPosition()
{
if (!UpdateRootPosition)
return;
RigidbodyComponent.Move(Position + Animator.deltaPosition);
}
protected virtual void UpdateDynamicRootMotionRotation()
{
if (!UpdateRootRotation)
return;
if (rootMotionRotationType == RootMotionRotationType.AddRotation)
Rotation *= Animator.deltaRotation;
else
Rotation = Animator.rootRotation;
}
void PreSimulationRootMotionUpdate()
{
if (RigidbodyComponent.IsKinematic)
{
if (UpdateRootPosition)
UpdateKinematicRootMotionPosition();
if (UpdateRootRotation)
UpdateKinematicRootMotionRotation();
}
else
{
if (UpdateRootPosition)
UpdateDynamicRootMotionPosition();
if (UpdateRootRotation)
UpdateDynamicRootMotionRotation();
}
}
void OnAnimatorIKLinkMethod(int layerIndex) => OnAnimatorIKEvent?.Invoke(layerIndex);
#region Interpolation
bool internalResetFlag = true;
bool IsReadyForInterpolation()
{
if (!interpolateActor)
return false;
if (Is2D)
{
return Physics2D.simulationMode == SimulationMode2D.FixedUpdate;
}
else
{
#if UNITY_2022_2_OR_NEWER
return Physics.simulationMode == SimulationMode.FixedUpdate;
#else
return true;
#endif
}
}
public void SyncBody()
{
if (!IsReadyForInterpolation())
return;
if (!wasInterpolatingActor)
return;
// Since the Transform component has been modified during Update calls due to the interpolation process, this will affect the body position/rotation (Rigidbody).
// It is important to re-define the rigidbody properties with the previous physics frame targets data.
Position = startingPosition = targetPosition;
Rotation = startingRotation = targetRotation;
if (internalResetFlag)
{
internalResetFlag = false;
resetPositionFlag = false;
resetRotationFlag = false;
}
}
public void InterpolateBody()
{
if (!IsReadyForInterpolation())
return;
if (wasInterpolatingActor)
{
float interpolationFactor = (Time.time - Time.fixedTime) / Time.fixedDeltaTime;
transform.SetPositionAndRotation(
resetPositionFlag ? targetPosition : Vector3.Lerp(startingPosition, targetPosition, interpolationFactor),
resetRotationFlag ? targetRotation : Quaternion.Slerp(startingRotation, targetRotation, interpolationFactor)
);
}
else // interpolation has been enabled
{
ResetInterpolationPosition();
ResetInterpolationRotation();
}
}
public void UpdateInterpolationTargets()
{
if (!IsReadyForInterpolation())
return;
targetPosition = Position;
targetRotation = Rotation;
if (resetPositionFlag)
{
startingPosition = targetPosition;
}
if (resetRotationFlag)
{
startingRotation = targetRotation;
}
}
bool resetPositionFlag = false;
bool resetRotationFlag = false;
/// <summary>
/// Prevents the body from getting its position interpolated during one physics update.
/// </summary>
public void ResetInterpolationPosition() => resetPositionFlag = true;
/// <summary>
/// Prevents the body from getting its rotation interpolated during one physics update.
/// </summary>
public void ResetInterpolationRotation() => resetRotationFlag = true;
/// <summary>
/// Prevents the body from getting interpolated during one physics update.
/// </summary>
public void ResetInterpolation()
{
ResetInterpolationPosition();
ResetInterpolationRotation();
}
#endregion
/// <summary>
/// Checks if the Animator component associated with the character is valid or not. An Animator is valid if it
/// is active and its internal references are not null.
/// </summary>
/// <returns>True if the Animator is valid, false otherwise.</returns>
public bool IsAnimatorValid()
{
if (Animator == null)
return false;
if (Animator.runtimeAnimatorController == null)
return false;
if (!Animator.gameObject.activeSelf)
return false;
return true;
}
#region Messages
public virtual void OnValidate() { }
protected virtual void Awake()
{
gameObject.GetOrAddComponent<PhysicsActorSync>();
var animator = this.GetComponentInBranch<CharacterActor, Animator>();
SetAnimator(animator);
}
protected virtual void OnEnable()
{
postSimulationUpdateCoroutine ??= StartCoroutine(PostSimulationUpdate());
if (animatorLink != null)
{
animatorLink.OnAnimatorMoveEvent += OnAnimatorMoveLinkMethod;
animatorLink.OnAnimatorIKEvent += OnAnimatorIKLinkMethod;
}
startingPosition = targetPosition = transform.position;
startingRotation = targetRotation = transform.rotation;
ResetInterpolationPosition();
ResetInterpolationRotation();
}
protected virtual void OnDisable()
{
if (postSimulationUpdateCoroutine != null)
{
StopCoroutine(postSimulationUpdateCoroutine);
postSimulationUpdateCoroutine = null;
}
if (animatorLink != null)
{
animatorLink.OnAnimatorMoveEvent -= OnAnimatorMoveLinkMethod;
animatorLink.OnAnimatorIKEvent -= OnAnimatorIKLinkMethod;
}
}
protected virtual void Start()
{
RigidbodyComponent.ContinuousCollisionDetection = useContinuousCollisionDetection;
RigidbodyComponent.UseInterpolation = false;
// Interpolation
targetPosition = startingPosition = transform.position;
targetRotation = startingRotation = transform.rotation;
}
void Update()
{
InterpolateBody();
wasInterpolatingActor = interpolateActor;
internalResetFlag = true;
}
void OnAnimatorMoveLinkMethod()
{
if (!enabled)
return;
if (!UseRootMotion)
return;
float dt = Time.deltaTime;
OnAnimatorMoveEvent?.Invoke();
PreSimulationRootMotionUpdate();
PreSimulationUpdate(dt);
OnPreSimulation?.Invoke(dt);
// 2D Physics (Box2D) requires transform.forward to be Vector3.forward/back, otherwise the simulation will ignore
// the body due to the thinness of it.
if (Is2D)
{
if (Right.z > 0f)
Right = Vector3.forward;
else
Right = Vector3.back;
}
// Manual sync in case the Transform component is "dirty".
transform.SetPositionAndRotation(Position, Rotation);
}
void FixedUpdate()
{
if (UseRootMotion)
return;
float dt = Time.deltaTime;
PreSimulationUpdate(dt);
OnPreSimulation?.Invoke(dt);
// 2D Physics (Box2D) requires transform.forward to be Vector3.forward/back, otherwise the simulation will ignore
// the body due to the thinness of it.
if (Is2D)
{
if (Right.z > 0f)
Right = Vector3.forward;
else
Right = Vector3.back;
}
// Manual sync in case the Transform component is "dirty".
transform.SetPositionAndRotation(Position, Rotation);
}
IEnumerator PostSimulationUpdate()
{
YieldInstruction waitForFixedUpdate = new WaitForFixedUpdate();
while (true)
{
yield return waitForFixedUpdate;
float dt = Time.deltaTime;
if (enabled)
{
PostSimulationUpdate(dt);
OnPostSimulation?.Invoke(dt);
UpdateInterpolationTargets();
}
}
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,32 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This component is responsible for initializing the interpolation data associated with the physics actor.
/// </summary>
[DefaultExecutionOrder(int.MinValue)]
[RequireComponent(typeof(PhysicsActor))]
public class PhysicsActorSync : MonoBehaviour
{
PhysicsActor physicsActor = null;
void Awake()
{
physicsActor = GetComponent<PhysicsActor>();
}
void FixedUpdate()
{
if (!physicsActor.enabled)
return;
// This instruction runs before anything else and it makes sure the rigidbody data is always in "sync" with the interpolation data (physics actor).
physicsActor.SyncBody();
}
}
}

View File

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

View File

@ -0,0 +1,104 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Lightbug.CharacterControllerPro.Core
{
[System.Serializable]
public class VerticalAlignmentSettings
{
[Tooltip("By assigning this object, the character up direction will be automatically calculated based on it. " +
"A null value means that the character up direction will be the one defined in the \"alignment direction\" field")]
public Transform alignmentReference = null;
[Tooltip("The mode defines how the up direction is calculated (alignment reference not null).")]
public VerticalReferenceMode referenceMode = VerticalReferenceMode.Away;
[Tooltip("The desired up direction (null alignment reference).")]
public Vector3 alignmentDirection = Vector3.up;
public enum VerticalReferenceMode
{
Towards,
Away
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(VerticalAlignmentSettings))]
public class VerticalAlignmentSettingsPropertyDrawer : PropertyDrawer
{
const float verticalSpaceMultiplier = 1.1f;
SerializedProperty alignmentDirection = null;
SerializedProperty alignmentReference = null;
SerializedProperty referenceMode = null;
void FindProperties(SerializedProperty property)
{
alignmentReference = property.FindPropertyRelative("alignmentReference");
alignmentDirection = property.FindPropertyRelative("alignmentDirection");
referenceMode = property.FindPropertyRelative("referenceMode");
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
FindProperties(property);
Rect fieldRect = position;
fieldRect.height = EditorGUIUtility.singleLineHeight;
property.isExpanded = true;
EditorGUI.PropertyField(fieldRect, alignmentReference);
fieldRect.y += verticalSpaceMultiplier * fieldRect.height;
if (alignmentReference.objectReferenceValue != null)
{
EditorGUI.PropertyField(fieldRect, referenceMode);
}
else
{
EditorGUI.PropertyField(fieldRect, alignmentDirection);
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float output = 2f * verticalSpaceMultiplier * EditorGUIUtility.singleLineHeight;
// if( property.isExpanded )
// {
// FindProperties( property );
// if( alignmentReference.objectReferenceValue != null )
// {
// output += verticalSpaceMultiplier * EditorGUIUtility.singleLineHeight;
// }
// }
return output;
}
}
#endif
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 872bdfb8908144a41bc0a2e31c77d828
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,121 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
[AddComponentMenu("Character Controller Pro/Core/Dynamic One Way Platform")]
public class DynamicOneWayPlatform : MonoBehaviour
{
public LayerMask characterLayerMask = -1;
protected Vector3 preSimulationPosition;
Coroutine postSimulationUpdateCoroutine = null;
protected Dictionary<Transform, CharacterActor> characters = new Dictionary<Transform, CharacterActor>();
ColliderComponent colliderComponent;
PhysicsComponent physicsComponent;
RigidbodyComponent rigidbodyComponent;
void Awake()
{
colliderComponent = ColliderComponent.CreateInstance(gameObject);
physicsComponent = PhysicsComponent.CreateInstance(gameObject);
rigidbodyComponent = RigidbodyComponent.CreateInstance(gameObject);
}
void OnEnable()
{
postSimulationUpdateCoroutine ??= StartCoroutine(PostSimulationUpdate());
}
void OnDisable()
{
if (postSimulationUpdateCoroutine != null)
{
StopCoroutine(PostSimulationUpdate());
postSimulationUpdateCoroutine = null;
}
}
protected List<HitInfo> CastPlatformBody(Vector3 castDisplacement)
{
float backstepDistance = 0.1f;
float skinWidth = 0f;
Vector3 castDirection = castDisplacement.normalized;
castDisplacement += backstepDistance * skinWidth * castDirection;
Vector3 origin = preSimulationPosition + colliderComponent.Offset - castDirection * backstepDistance;
HitInfoFilter filter = new HitInfoFilter(characterLayerMask, false, true);
return physicsComponent.BoxCast(
origin,
colliderComponent.BoundsSize - Vector3.one * skinWidth,
castDisplacement,
rigidbodyComponent.Rotation,
in filter
);
}
protected bool ValidateOWPCollision(CharacterActor characterActor, Vector3 contactPoint) =>
characterActor.CheckOneWayPlatformCollision(contactPoint, characterActor.Position);
void FixedUpdate() => preSimulationPosition = rigidbodyComponent.Position;
IEnumerator PostSimulationUpdate()
{
YieldInstruction waitForFixedUpdate = new WaitForFixedUpdate();
while (true)
{
yield return waitForFixedUpdate;
UpdatePlatform();
}
}
void UpdatePlatform()
{
Vector3 castDisplacement = rigidbodyComponent.Position - preSimulationPosition;
var hitsList = CastPlatformBody(castDisplacement);
if (hitsList == null)
return;
for (int i = 0; i < hitsList.Count; i++)
{
var hitInfo = physicsComponent.HitsBuffer[i];
if (hitInfo.distance == 0f)
continue;
var characterActor = characters.GetOrRegisterValue(hitInfo.transform);
if (characterActor == null)
continue;
// Ignore the character collider (hitInfo contains the collider information).
physicsComponent.IgnoreCollision(in hitInfo, true);
if (!characterActor.IsGrounded)
{
// Check if the collision is valid
bool isValidCollision = ValidateOWPCollision(characterActor, hitInfo.point);
// If so, then move the character
if (isValidCollision)
{
// How much the actor needs to move
Vector3 actorCastDisplacement = castDisplacement.normalized * (castDisplacement.magnitude - hitInfo.distance);
Vector3 destination = characterActor.Position + actorCastDisplacement;
// Set the collision filter
HitInfoFilter filter = new HitInfoFilter(characterActor.ObstaclesWithoutOWPLayerMask, false, true);
characterActor.SweepAndTeleport(destination, in filter);
characterActor.ForceGrounded();
}
}
}
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90dcb45485fb8f14ea8b9937e819ade1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace Lightbug.CharacterControllerPro.Core
{
public class AboutWindow : CharacterControllerProWindow
{
const float Width = 200f;
const float Height = 100f;
protected override void OnEnable()
{
this.position = new Rect((Screen.width - Width) / 2f, (Screen.height - Height) / 2f, Width, Height);
this.maxSize = this.minSize = this.position.size;
this.titleContent = new GUIContent("About");
}
void OnGUI()
{
EditorGUILayout.SelectableLabel("Version: 1.4.12", GUILayout.Height(15f));
EditorGUILayout.SelectableLabel("Author : Juan Sálice (Lightbug)", GUILayout.Height(15f));
EditorGUILayout.SelectableLabel("Email : lightbug14@gmail.com", GUILayout.Height(15f));
}
}
}
#endif

View File

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

View File

@ -0,0 +1,24 @@
#if UNITY_EDITOR
using UnityEditor;
namespace Lightbug.CharacterControllerPro.Core
{
class CCPAssetPostprocessor : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
var rootPath = CharacterControllerProEditor.GetAssetRootFolder();
foreach (string importedAsset in importedAssets)
{
if (importedAsset.Equals(rootPath))
{
WelcomeWindow window = EditorWindow.GetWindow<WelcomeWindow>(true, "Welcome");
}
}
}
}
}
#endif

View File

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

View File

@ -0,0 +1,234 @@
#if UNITY_EDITOR
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Presets;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
public class CharacterControllerProEditor : Editor
{
public static string GetAssetRootFolder()
{
// Find the assembly definition file
string[] guids = AssetDatabase.FindAssets("com.lightbug.character-controller-pro");
if (guids.Length > 0)
{
// Look for a parent directory called "Character Controller Pro"
string asmdefFilePath = AssetDatabase.GUIDToAssetPath(guids[0]);
return GetParentPathByFile("Character Controller Pro", asmdefFilePath);
}
return null;
}
#region Utilities
public static string GetParentPathByFile(string targetFolderName, string initialFilePath)
{
// Start by finding the assembly definition file
string[] guids = AssetDatabase.FindAssets("com.lightbug.character-controller-pro");
if (guids.Length > 1)
{
Debug.Log("Multiple \"com.lightbug.character-controller-pro\" assembly definition files found.");
}
else if (guids.Length > 0)
{
// ──────────────────────────────────────────────────────────────────────────
// path = "FolderA/FolderB/file.txt"
// Path.GetFileName(path) --> "file.txt"
// Path.GetDirectoryName(path) --> "FolderA/FolderB"
// ──────────────────────────────────────────────────────────────────────────
string currentDirectory = Path.GetDirectoryName(initialFilePath);
while (!string.IsNullOrEmpty(currentDirectory))
{
string folderName = Path.GetFileName(currentDirectory);
if (folderName == targetFolderName)
return currentDirectory;
else
currentDirectory = Path.GetDirectoryName(currentDirectory);
}
}
else // No file? :(
{
Debug.Log("No assembly definition file found. Have you deleted the file?.");
}
Debug.Log($"Error: Parent folder \"{targetFolderName}\" not found");
return null;
}
public static void MoveFolderToFolder(string sourceFolderPath, string destinationFolderPath)
{
// Source folder
if (!Directory.Exists(sourceFolderPath))
{
Debug.Log($"Error: Source directory {sourceFolderPath} doesn't exist");
return;
}
// Find the meta file
string sourceMetaPath = sourceFolderPath + ".meta";
if (!File.Exists(sourceMetaPath))
{
Debug.Log($"Error: Meta file {sourceMetaPath} doesn't exist");
return;
}
// Destination folder
if (!Directory.Exists(destinationFolderPath))
{
Directory.CreateDirectory(destinationFolderPath);
}
Directory.Move(sourceFolderPath, $"{destinationFolderPath}/{Path.GetFileName(sourceFolderPath)}");
File.Move(sourceMetaPath, $"{destinationFolderPath}/{Path.GetFileName(sourceMetaPath)}");
AssetDatabase.Refresh();
}
public static void MoveFileToFolder(string sourceFilePath, string destinationFolderPath)
{
// Source folder
if (!File.Exists(sourceFilePath))
{
Debug.Log($"Error: Source file {sourceFilePath} doesn't exist");
return;
}
// Find the meta file
string sourceMetaPath = sourceFilePath + ".meta";
if (!File.Exists(sourceMetaPath))
{
Debug.Log($"Error: Meta file {sourceMetaPath} doesn't exist");
return;
}
// Destination folder
if (!Directory.Exists(destinationFolderPath))
{
Directory.CreateDirectory(destinationFolderPath);
}
File.Move(sourceFilePath, $"{destinationFolderPath}/{Path.GetFileName(sourceFilePath)}");
File.Move(sourceMetaPath, $"{destinationFolderPath}/{Path.GetFileName(sourceMetaPath)}");
AssetDatabase.Refresh();
}
#endregion
[MenuItem("Window/Character Controller Pro/Welcome", false, 0)]
public static void WelcomeMessage()
{
WelcomeWindow window = EditorWindow.GetWindow<WelcomeWindow>(true, "Welcome");
}
[MenuItem("Window/Character Controller Pro/Documentation", false, 1)]
public static void Documentation()
{
Application.OpenURL("https://lightbug14.gitbook.io/ccp/");
}
[MenuItem("Window/Character Controller Pro/API Reference", false, 2)]
public static void APIReference()
{
Application.OpenURL("https://lightbug14.github.io/lightbug-web/character-controller-pro/Documentation/html/index.html");
}
[MenuItem("Window/Character Controller Pro/About", false, 100)]
public static void About()
{
EditorWindow.GetWindow<AboutWindow>(true, "About");
}
/*public static void UseOldStructure(string rootPath)
{
if (!Directory.Exists($"{rootPath}/Main"))
{
Debug.Log("Error: The current folder structure is the old one.");
return;
}
MoveFolderToFolder($"{rootPath}/Main/Core", $"{rootPath}");
MoveFolderToFolder($"{rootPath}/Main/Documentation", $"{rootPath}");
MoveFolderToFolder($"{rootPath}/Main/Implementation", $"{rootPath}");
MoveFolderToFolder($"{rootPath}/Main/Utilities", $"{rootPath}");
MoveFileToFolder($"{rootPath}/Main/com.lightbug.character-controller-pro.asmdef", $"{rootPath}");
Directory.Delete($"{rootPath}/Main");
File.Delete($"{rootPath}/Main.meta");
AssetDatabase.Refresh();
}
[MenuItem("Window/Character Controller Pro/Use old (1.4.10 or lower) folder structure", false, 0)]
static void UseOldStructure()
{
if (!IsCurrentStructureNew())
{
EditorUtility.DisplayDialog(
"Old folder structure detected",
"The current folder structure your project is using corresponds to the old one",
"Ok"
);
return;
}
bool result = EditorUtility.DisplayDialog(
"Folder structure",
"This will" +
"\n\nFrom 1.4.11 onwards, all the demo content locates outside the main assembly by separating the asset into \"Main\" and \"Demo\". " +
"Do you want this tool to update this structure for you?",
"Yes (recommended)",
"No"
);
if (result)
{
var rootPath = GetAssetRootFolder();
UseOldStructure(rootPath);
}
}*/
/*static void UseNewStructure(string rootPath)
{
if (Directory.Exists($"{rootPath}/Main"))
{
Debug.Log("Error: The current folder structure is the new one.");
return;
}
MoveFolderToFolder($"{rootPath}/Core", $"{rootPath}/Main");
MoveFolderToFolder($"{rootPath}/Documentation", $"{rootPath}/Main");
MoveFolderToFolder($"{rootPath}/Implementation", $"{rootPath}/Main");
MoveFolderToFolder($"{rootPath}/Utilities", $"{rootPath}/Main");
MoveFileToFolder($"{rootPath}/com.lightbug.character-controller-pro.asmdef", $"{rootPath}/Main");
}
[MenuItem("Window/Character Controller Pro/Use new (1.4.11 or higher) folder structure", false, 0)]
static void UseNewStructure()
{
var rootPath = GetAssetRootFolder();
UseNewStructure(rootPath);
}*/
public static bool IsCurrentStructureNew(string rootPath) => Directory.Exists($"{rootPath}/Main");
public static bool IsCurrentStructureNew() => IsCurrentStructureNew(GetAssetRootFolder());
}
}
#endif

View File

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

View File

@ -0,0 +1,42 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace Lightbug.CharacterControllerPro.Core
{
public abstract class CharacterControllerProWindow : EditorWindow
{
protected GUIStyle titleStyle = new GUIStyle();
protected GUIStyle subtitleStyle = new GUIStyle();
protected GUIStyle descriptionStyle = new GUIStyle();
protected virtual void OnEnable()
{
titleStyle.fontSize = 50;
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.padding.top = 4;
titleStyle.padding.bottom = 4;
titleStyle.normal.textColor = EditorGUIUtility.isProSkin ? Color.white : Color.black;
subtitleStyle.fontSize = 18;
subtitleStyle.alignment = TextAnchor.MiddleCenter;
subtitleStyle.padding.top = 4;
subtitleStyle.padding.bottom = 4;
subtitleStyle.normal.textColor = EditorGUIUtility.isProSkin ? Color.white : Color.black;
descriptionStyle.fontSize = 15;
descriptionStyle.wordWrap = true;
descriptionStyle.padding.left = 10;
descriptionStyle.padding.right = 10;
descriptionStyle.padding.top = 4;
descriptionStyle.padding.bottom = 4;
descriptionStyle.richText = true;
descriptionStyle.normal.textColor = EditorGUIUtility.isProSkin ? Color.white : Color.black;
}
}
}
#endif

View File

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

View File

@ -0,0 +1,113 @@
using System.Collections.Generic;
using UnityEngine;
using Lightbug.Utilities;
namespace Lightbug.CharacterControllerPro.Core
{
public abstract class CharacterDetector : MonoBehaviour
{
protected Dictionary<Transform, CharacterActor> characters = new Dictionary<Transform, CharacterActor>();
protected List<int> onEnterDirtyTransforms = new List<int>();
protected List<int> onStayDirtyTransforms = new List<int>();
protected List<int> onExitDirtyTransforms = new List<int>();
protected virtual void ProcessEnterAction(CharacterActor characterActor) { }
protected virtual void ProcessStayAction(CharacterActor characterActor) { }
protected virtual void ProcessExitAction(CharacterActor characterActor) { }
/// <summary>
/// Gets the number of characters (CharacterActor) currently inside this trigger.
/// </summary>
public int CharactersNumber { get; private set; }
void FixedUpdate()
{
if (onEnterDirtyTransforms.Count != 0)
onEnterDirtyTransforms.Clear();
if (onStayDirtyTransforms.Count != 0)
onStayDirtyTransforms.Clear();
if (onExitDirtyTransforms.Count != 0)
onExitDirtyTransforms.Clear();
}
void ProcessAction(Transform transform, List<int> characterActorsIDList, System.Action<CharacterActor> Action)
{
if (!enabled)
return;
CharacterActor characterActor = characters.GetOrRegisterValue(transform);
if (characterActor == null)
return;
// We don't want to trigger the logic more than once due to multiple colliders interacting with the trigger.
int characterActorID = characterActor.GetInstanceID();
if (characterActorsIDList.Contains(characterActorID))
return;
// The actor is not registered, add the ID to the list.
characterActorsIDList.Add(characterActorID);
CharactersNumber++;
Action?.Invoke(characterActor);
}
void OnTriggerEnter(Collider collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onEnterDirtyTransforms, ProcessEnterAction);
}
void OnTriggerEnter2D(Collider2D collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onEnterDirtyTransforms, ProcessEnterAction);
}
void OnTriggerStay(Collider collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onStayDirtyTransforms, ProcessStayAction);
}
void OnTriggerStay2D(Collider2D collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onStayDirtyTransforms, ProcessStayAction);
}
void OnTriggerExit(Collider collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onExitDirtyTransforms, ProcessExitAction);
}
void OnTriggerExit2D(Collider2D collider)
{
var characterRigidbody = collider.attachedRigidbody;
if (characterRigidbody == null)
return;
ProcessAction(characterRigidbody.transform, onExitDirtyTransforms, ProcessExitAction);
}
}
}

View File

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

View File

@ -0,0 +1,13 @@
namespace Lightbug.CharacterControllerPro.Core
{
/// <summary>
/// This class contains constant values related to execution order values. These values are used by the Core components to define its own execution order inside Unity.
/// </summary>
public static class ExecutionOrder
{
public const int CharacterActorOrder = 10;
public const int CharacterGraphicsOrder = CharacterActorOrder + 10;
}
}

View File

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

View File

@ -0,0 +1,95 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace Lightbug.CharacterControllerPro.Core
{
public class WelcomeWindow : CharacterControllerProWindow
{
protected override void OnEnable()
{
base.OnEnable();
this.position = new Rect(10f, 10f, 700f, 700f);
this.maxSize = this.minSize = this.position.size;
}
[MenuItem("Window/Character Controller Pro/Upgrade guide 1.4.x", false, 0)]
public static void OpenUpgradeGuide()
{
string[] results = AssetDatabase.FindAssets("Upgrade guide 1.4.x");
if (results.Length == 0)
return;
OpenProjectFile(results[0]);
}
static void OpenProjectFile(string guid)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var projectPathWithoutAssets = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
Application.OpenURL(projectPathWithoutAssets + path);
}
void OnGUI()
{
GUILayout.Label("Character Controller Pro", titleStyle);
GUILayout.Space(20f);
GUILayout.BeginVertical("Box");
GUILayout.Label("<b>Upgrade to 1.4.X</b>", subtitleStyle);
GUILayout.Label("Are you upgrading to 1.4.x?\nPlease read the <b>upgrade guide</b> (<i>Character Controller Pro/Documentation/Upgrade guide 1.4.0.pdf</i>)", descriptionStyle);
if (GUILayout.Button("Upgrade guide"))
OpenUpgradeGuide();
GUILayout.EndVertical();
GUILayout.Space(10f);
GUILayout.BeginVertical("Box");
GUILayout.Label("<b>Demo setup</b>", subtitleStyle);
GUILayout.Label(
"In order to play the demo scenes that come with the asset, you must include some specific inputs (old InputManager), " +
"tags and layers to your project. <b>This is required for demo purposes only. The character controller (core + implementation) does " +
"not require any previous setup to work.</b>", descriptionStyle);
GUILayout.Space(10f);
GUILayout.Label(
"1. Open the <b>Input manager settings</b>.\n" +
"2. Load <i><color=yellow>Preset_Inputs.preset</color></i>.\n" +
"3. Open the <b>Tags and Layers settings</b>.\n" +
"4. Load <i><color=yellow>Preset_TagsAndLayers.preset</color></i>.\n", descriptionStyle);
GUILayout.EndVertical();
GUILayout.Space(10f);
GUILayout.BeginVertical("Box");
GUILayout.Label("<b>Known issues (editor)</b>", subtitleStyle);
GUILayout.Label("Please check the \"Known issues\" section if you are experiencing " +
"problems while testing the demo content. These are (most of the time) issues related to the Unity editor itself.", descriptionStyle);
if (GUILayout.Button("Known issues", EditorStyles.miniButton))
{
Application.OpenURL("https://lightbug14.gitbook.io/ccp/package/known-issues");
}
GUILayout.EndVertical();
GUILayout.Space(10f);
GUILayout.Space(10f);
GUILayout.Label("You can open this window from the top menu: \n<i>Window/Character Controller Pro/Welcome</i>", descriptionStyle);
}
}
}
#endif

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 145fa8bb62a46f449a41f0a1ed96473a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: da7a4f81de6c4464c8f6d99c1d02278a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1107 &-7394464091379382199
AnimatorStateMachine:
serializedVersion: 5
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 192492146393855169}
m_Position: {x: 340, y: 110, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 192492146393855169}
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Animated Platform
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -7394464091379382199}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &192492146393855169
AnimatorState:
serializedVersion: 5
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Demo animation
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: b2a701607db4bf445be618698899ff61, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:

Some files were not shown because too many files have changed in this diff Show More