This commit is contained in:
2025-05-11 00:46:26 +08:00
parent 366f9e95ec
commit 618f75f911
2404 changed files with 154475 additions and 924730 deletions

8
Assets/ECM2.meta Normal file
View File

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

3
Assets/ECM2/Editor.meta Normal file
View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b4ae63dacfed40dd8a3675e13b58068c
timeCreated: 1702783925

View File

@@ -0,0 +1,46 @@
using UnityEditor;
using UnityEngine;
namespace ECM2.Editor
{
public static class CharacterFactoryEditor
{
private const string PATH = "GameObject/ECM2/";
private const int PRIORITY = 1;
private static void InitPhysicsBody(GameObject go)
{
Rigidbody rb = go.GetComponent<Rigidbody>();
rb.linearDamping = 0.0f;
rb.angularDamping = 0.0f;
rb.useGravity = false;
rb.isKinematic = true;
rb.interpolation = RigidbodyInterpolation.Interpolate;
CapsuleCollider capsuleCollider = go.GetComponent<CapsuleCollider>();
capsuleCollider.center = new Vector3(0f, 1f, 0f);
capsuleCollider.radius = 0.5f;
capsuleCollider.height = 2.0f;
}
[MenuItem(PATH + "Character", false, PRIORITY)]
public static void CreateCharacter()
{
// Create an initialize a new Character GameObject
GameObject go = new GameObject("Character", typeof(Rigidbody), typeof(CapsuleCollider),
typeof(CharacterMovement), typeof(Character));
InitPhysicsBody(go);
// Focus the newly created character
Undo.RegisterCreatedObjectUndo(go, "Create " + go.name);
Selection.activeGameObject = go;
SceneView.FrameLastActiveSceneView();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 229efc90fc94436ca2b1a73d23c044c6
timeCreated: 1702783950

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9f3fcc8c4084496b897827d8e50634a
timeCreated: 1700450016

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 902a39e6598617c4596fd0bbc3ff7667
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 57066912f8758f14b9bf7d2ece477e69
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 112000000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: ac5ca879376bdb94e9f4867550fde697
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
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: 1
seamlessCubemap: 1
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 2
aniso: 0
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
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: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- 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: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2fe07f4fb0c44cf9a8cb190a16e0c4e8
timeCreated: 1700523469

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 82dc065f9f6f4cb4953fe304639ea0ea
timeCreated: 1700707754

View File

@@ -0,0 +1,47 @@
using ECM2.Examples.FirstPerson;
using UnityEngine;
namespace ECM2.Examples.FirstPersonFly
{
/// <summary>
/// Regular First Person Character Input. Shows how to handle movement while flying.
/// In this case, we allow to fly towards our view direction, allowing to freely move through the air.
/// </summary>
public class FirstPersonFlyInput : FirstPersonInput
{
protected override void HandleInput()
{
// Call base method implementation
base.HandleInput();
if (character.IsFlying())
{
// Movement when Flying
Vector2 movementInput = GetMovementInput();
Vector3 movementDirection = Vector3.zero;
// Strafe
movementDirection += character.GetRightVector() * movementInput.x;
// Forward, along camera view direction (if any) or along character's forward if camera not found
Vector3 forward = character.camera
? character.cameraTransform.forward
: character.GetForwardVector();
movementDirection += forward * movementInput.y;
// Vertical movement
if (character.jumpInputPressed)
movementDirection += Vector3.up;
character.SetMovementDirection(movementDirection);
}
}
}
}

View File

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

View File

@@ -0,0 +1,85 @@
using UnityEngine;
namespace ECM2.Examples.FirstPersonFly
{
/// <summary>
/// This example shows how to extend a Character (through composition) and use
/// its Flying movement mode to implement a fly ability.
///
/// Flying movement mode needs to be manually enabled / disabled as needed.
/// </summary>
public class FlyAbility : MonoBehaviour
{
public bool canEverFly = true;
private Character _character;
/// <summary>
/// Determines if the Character is able to fly in its current state.
/// </summary>
private bool IsFlyAllowed()
{
return canEverFly && _character.IsFalling();
}
/// <summary>
/// Determines if the character should enter flying movement mode.
/// </summary>
protected virtual bool CanFly()
{
bool isFlyAllowed = IsFlyAllowed();
if (isFlyAllowed)
{
// If Fly is allowed, determine if is falling down otherwise its a jump!
Vector3 worldUp = -_character.GetGravityDirection();
float verticalSpeed = Vector3.Dot(_character.GetVelocity(), worldUp);
isFlyAllowed = verticalSpeed < 0.0f;
}
return isFlyAllowed;
}
private void OnCollided(ref CollisionResult collisionResult)
{
// If flying and collided with walkable ground, exit flying state.
// I.e: Change to Falling movement mode as this is managed based on grounding status.
if (_character.IsFlying() && collisionResult.isWalkable)
_character.SetMovementMode(Character.MovementMode.Falling);
}
private void OnBeforeSimulationUpdated(float deltaTime)
{
// Attempts to enter Flying movement mode
bool isFlying = _character.IsFlying();
bool wantsToFly = _character.jumpInputPressed;
if (!isFlying && wantsToFly && CanFly())
_character.SetMovementMode(Character.MovementMode.Flying);
}
private void Awake()
{
_character = GetComponent<Character>();
}
private void OnEnable()
{
_character.Collided += OnCollided;
_character.BeforeSimulationUpdated += OnBeforeSimulationUpdated;
}
private void OnDisable()
{
_character.Collided -= OnCollided;
_character.BeforeSimulationUpdated -= OnBeforeSimulationUpdated;
}
}
}

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 815661453b364202904d788f5432336a
timeCreated: 1700523481

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5f6e4d053a8307d4c820c4452d96fc36
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 66684442958e47e4bcf3c569fe375813
timeCreated: 1700707789

View File

@@ -0,0 +1,62 @@
using ECM2.Examples.FirstPerson;
using UnityEngine;
namespace ECM2.Examples.FirstPersonSwim
{
/// <summary>
/// This example shows how to handle movement while swimming.
/// Here, we allow to swim towards our view direction, allowing to freely move through the water.
///
/// Swimming is automatically enabled / disabled when Water Physics Volume is used,
/// otherwise it must be enabled / disabled as needed.
/// </summary>
public class FirstPersonSwimInput : FirstPersonInput
{
protected override void HandleInput()
{
// Call base method implementation
base.HandleInput();
if (character.IsSwimming())
{
// Handle movement when swimming
// Strafe
Vector2 movementInput = GetMovementInput();
Vector3 movementDirection = Vector3.zero;
movementDirection += character.GetRightVector() * movementInput.x;
// Forward, along camera view direction (if any) or along character's forward if camera not found
Vector3 forward =
character.camera ? character.cameraTransform.forward : character.GetForwardVector();
movementDirection += forward * movementInput.y;
// Vertical movement
if (character.jumpInputPressed)
{
// Use immersion depth to check if we are at top of water line,
// if yes, jump of water
float depth = character.CalcImmersionDepth();
if (depth > 0.65f)
movementDirection += character.GetUpVector();
else
{
// Jump out of water
character.SetMovementMode(Character.MovementMode.Falling);
character.LaunchCharacter(character.GetUpVector() * 9.0f, true);
}
}
character.SetMovementDirection(movementDirection);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 492ff1da6c6e3884bbc194a74442b56b

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
using UnityEngine;
namespace ECM2.Examples.FirstPerson
{
/// <summary>
/// This example extends a Character (through inheritance), implementing a First Person control.
/// </summary>
public class FirstPersonCharacter : Character
{
[Tooltip("The first person camera parent.")]
public GameObject cameraParent;
private float _cameraPitch;
/// <summary>
/// Add input (affecting Yaw).
/// This is applied to the Character's rotation.
/// </summary>
public virtual void AddControlYawInput(float value)
{
if (value != 0.0f)
AddYawInput(value);
}
/// <summary>
/// Add input (affecting Pitch).
/// This is applied to the cameraParent's local rotation.
/// </summary>
public virtual void AddControlPitchInput(float value, float minPitch = -80.0f, float maxPitch = 80.0f)
{
if (value != 0.0f)
_cameraPitch = MathLib.ClampAngle(_cameraPitch + value, minPitch, maxPitch);
}
/// <summary>
/// Update cameraParent local rotation applying current _cameraPitch value.
/// </summary>
protected virtual void UpdateCameraParentRotation()
{
cameraParent.transform.localRotation = Quaternion.Euler(_cameraPitch, 0.0f, 0.0f);
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void LateUpdate()
{
UpdateCameraParentRotation();
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected override void Reset()
{
// Call base method implementation
base.Reset();
// Disable character's rotation,
// it is handled by the AddControlYawInput method
SetRotationMode(RotationMode.None);
}
}
}

View File

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

View File

@@ -0,0 +1,114 @@
using UnityEngine;
using UnityEngine.InputSystem;
namespace ECM2.Examples.FirstPerson
{
/// <summary>
/// First person character input.
/// Extends the default CharacterInput component adding support for typical first person controls.
/// </summary>
public class FirstPersonInput : CharacterInput
{
[Space(15.0f)]
public bool invertLook = true;
[Tooltip("Look sensitivity")]
public Vector2 sensitivity = new Vector2(0.05f, 0.05f);
[Space(15.0f)]
[Tooltip("How far in degrees can you move the camera down.")]
public float minPitch = -80.0f;
[Tooltip("How far in degrees can you move the camera up.")]
public float maxPitch = 80.0f;
/// <summary>
/// Cached FirstPersonCharacter.
/// </summary>
public FirstPersonCharacter firstPersonCharacter { get; private set; }
/// <summary>
/// Movement InputAction.
/// </summary>
public InputAction lookInputAction { get; set; }
/// <summary>
/// Polls look InputAction (if any).
/// Return its current value or zero if no valid InputAction found.
/// </summary>
public Vector2 GetLookInput()
{
return lookInputAction?.ReadValue<Vector2>() ?? Vector2.zero;
}
/// <summary>
/// Initialize player InputActions (if any).
/// E.g. Subscribe to input action events and enable input actions here.
/// </summary>
protected override void InitPlayerInput()
{
base.InitPlayerInput();
// Look input action (no handler, this is polled, e.g. GetLookInput())
lookInputAction = inputActionsAsset.FindAction("Look");
lookInputAction?.Enable();
}
/// <summary>
/// Unsubscribe from input action events and disable input actions.
/// </summary>
protected override void DeinitPlayerInput()
{
base.DeinitPlayerInput();
// Unsubscribe from input action events and disable input actions
if (lookInputAction != null)
{
lookInputAction.Disable();
lookInputAction = null;
}
}
protected override void Awake()
{
base.Awake();
firstPersonCharacter = character as FirstPersonCharacter;
}
protected virtual void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
protected override void HandleInput()
{
// Move
Vector2 movementInput = GetMovementInput();
Vector3 movementDirection = Vector3.zero;
movementDirection += Vector3.forward * movementInput.y;
movementDirection += Vector3.right * movementInput.x;
movementDirection =
movementDirection.relativeTo(firstPersonCharacter.cameraTransform, firstPersonCharacter.GetUpVector());
firstPersonCharacter.SetMovementDirection(movementDirection);
// Look
Vector2 lookInput = GetLookInput() * sensitivity;
firstPersonCharacter.AddControlYawInput(lookInput.x);
firstPersonCharacter.AddControlPitchInput(invertLook ? -lookInput.y : lookInput.y, minPitch, maxPitch);
}
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
using UnityEngine;
namespace ECM2.Examples.Glide
{
/// <summary>
/// This example shows how to extend a Character (through composition) implementing a Glide mechanic.
/// </summary>
public class GlideAbility : MonoBehaviour
{
public bool canEverGlide = true;
public float maxFallSpeedGliding = 1.0f;
private Character _character;
protected bool _glideInputPressed;
protected bool _isGliding;
public bool glideInputPressed => _glideInputPressed;
/// <summary>
/// Is the Character gliding?
/// </summary>
public virtual bool IsGliding()
{
return _isGliding;
}
/// <summary>
/// Request to start a glide.
/// </summary>
public virtual void Glide()
{
_glideInputPressed = true;
}
/// <summary>
/// Request to stop gliding.
/// </summary>
public virtual void StopGliding()
{
_glideInputPressed = false;
}
/// <summary>
/// Determines if the character is able to perform a glide in its current state.
/// </summary>
protected virtual bool IsGlideAllowed()
{
return canEverGlide && _character.IsFalling();
}
/// <summary>
/// Determines if the character can perform a requested glide.
/// </summary>
protected virtual bool CanGlide()
{
bool isGlideAllowed = IsGlideAllowed();
if (isGlideAllowed)
{
Vector3 worldUp = -_character.GetGravityDirection();
float verticalSpeed = Vector3.Dot(_character.GetVelocity(), worldUp);
isGlideAllowed = verticalSpeed < 0.0f;
}
return isGlideAllowed;
}
/// <summary>
/// Start / Stop a requested glide.
/// </summary>
protected virtual void CheckGlideInput()
{
if (!_isGliding && _glideInputPressed && CanGlide())
{
_isGliding = true;
_character.maxFallSpeed = maxFallSpeedGliding;
}
else if (_isGliding && (!_glideInputPressed || !CanGlide()))
{
_isGliding = false;
_character.maxFallSpeed = 40.0f;
}
}
private void OnBeforeCharacterSimulationUpdated(float deltaTime)
{
CheckGlideInput();
}
private void Awake()
{
_character = GetComponent<Character>();
}
private void OnEnable()
{
_character.BeforeSimulationUpdated += OnBeforeCharacterSimulationUpdated;
}
private void OnDisable()
{
_character.BeforeSimulationUpdated -= OnBeforeCharacterSimulationUpdated;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d97f3b8906b7417e88698c51eb7e04c8
timeCreated: 1700291875

View File

@@ -0,0 +1,38 @@
using UnityEngine.InputSystem;
namespace ECM2.Examples.Glide
{
/// <summary>
/// Extends default Character Input to handle GlideAbility Input.
/// </summary>
public class GlideInput : CharacterInput
{
private GlideAbility _glideAbility;
/// <summary>
/// Extend OnJump handler to add GlideAbility input support.
/// </summary>
public override void OnJump(InputAction.CallbackContext context)
{
// Call base method implementation (handle jump)
base.OnJump(context);
if (context.started)
_glideAbility.Glide();
else if (context.canceled)
_glideAbility.StopGliding();
}
protected override void Awake()
{
base.Awake();
// Cache Glide Ability
_glideAbility = GetComponent<GlideAbility>();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 49ae0493228344b5b3bdfc1bf5545e57
timeCreated: 1700292768

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,413 @@
using UnityEngine;
namespace ECM2.Examples.Jump
{
/// <summary>
/// This example shows how to extend a Character (through composition) implementing a jump ability.
/// This is the exact jump available in the Character class before v1.4.
/// </summary>
public class JumpAbility : MonoBehaviour
{
[Space(15f)]
[Tooltip("Is the character able to jump ?")]
[SerializeField]
private bool _canEverJump;
[Tooltip("Can jump while crouching ?")]
[SerializeField]
private bool _jumpWhileCrouching;
[Tooltip("The max number of jumps the Character can perform.")]
[SerializeField]
private int _jumpMaxCount;
[Tooltip("Initial velocity (instantaneous vertical velocity) when jumping.")]
[SerializeField]
private float _jumpImpulse;
[Tooltip("The maximum time (in seconds) to hold the jump. eg: Variable height jump.")]
[SerializeField]
private float _jumpMaxHoldTime;
[Tooltip("How early before hitting the ground you can trigger a jump (in seconds).")]
[SerializeField]
private float _jumpPreGroundedTime;
[Tooltip("How long after leaving the ground you can trigger a jump (in seconds).")]
[SerializeField]
private float _jumpPostGroundedTime;
private Character _character;
protected bool _jumpButtonPressed;
protected float _jumpButtonHeldDownTime;
protected float _jumpHoldTime;
protected int _jumpCount;
protected bool _isJumping;
/// <summary>
/// Is the character able to jump ?
/// </summary>
public bool canEverJump
{
get => _canEverJump;
set => _canEverJump = value;
}
/// <summary>
/// Can jump while crouching ?
/// </summary>
public bool jumpWhileCrouching
{
get => _jumpWhileCrouching;
set => _jumpWhileCrouching = value;
}
/// <summary>
/// The max number of jumps the Character can perform.
/// </summary>
public int jumpMaxCount
{
get => _jumpMaxCount;
set => _jumpMaxCount = Mathf.Max(1, value);
}
/// <summary>
/// Initial velocity (instantaneous vertical velocity) when jumping.
/// </summary>
public float jumpImpulse
{
get => _jumpImpulse;
set => _jumpImpulse = Mathf.Max(0.0f, value);
}
/// <summary>
/// The maximum time (in seconds) to hold the jump. eg: Variable height jump.
/// </summary>
public float jumpMaxHoldTime
{
get => _jumpMaxHoldTime;
set => _jumpMaxHoldTime = Mathf.Max(0.0f, value);
}
/// <summary>
/// How early before hitting the ground you can trigger a jump (in seconds).
/// </summary>
public float jumpPreGroundedTime
{
get => _jumpPreGroundedTime;
set => _jumpPreGroundedTime = Mathf.Max(0.0f, value);
}
/// <summary>
/// How long after leaving the ground you can trigger a jump (in seconds).
/// </summary>
public float jumpPostGroundedTime
{
get => _jumpPostGroundedTime;
set => _jumpPostGroundedTime = Mathf.Max(0.0f, value);
}
/// <summary>
/// True is _jumpButtonPressed is true, false otherwise.
/// </summary>
public bool jumpButtonPressed => _jumpButtonPressed;
/// <summary>
/// This is the time (in seconds) that the player has held the jump button.
/// </summary>
public float jumpButtonHeldDownTime => _jumpButtonHeldDownTime;
/// <summary>
/// Tracks the current number of jumps performed.
/// </summary>
public int jumpCount => _jumpCount;
/// <summary>
/// This is the time (in seconds) that the player has been holding the jump.
/// Eg: Variable height jump.
/// </summary>
public float jumpHoldTime => _jumpHoldTime;
/// <summary>
/// Is the Character jumping ?
/// </summary>
public virtual bool IsJumping()
{
return _isJumping;
}
/// <summary>
/// Start a jump.
/// Call this from an input event (such as a button 'down' event).
/// </summary>
public void Jump()
{
_jumpButtonPressed = true;
}
/// <summary>
/// Stop the Character from jumping.
/// Call this from an input event (such as a button 'up' event).
/// </summary>
public void StopJumping()
{
// Input state
_jumpButtonPressed = false;
_jumpButtonHeldDownTime = 0.0f;
// Jump holding state
_isJumping = false;
_jumpHoldTime = 0.0f;
}
/// <summary>
/// Returns the current jump count.
/// </summary>
public virtual int GetJumpCount()
{
return _jumpCount;
}
/// <summary>
/// Determines if the Character is able to perform the requested jump.
/// </summary>
public virtual bool CanJump()
{
// Is character even able to jump ?
if (!canEverJump)
return false;
// Can jump while crouching ?
if (_character.IsCrouched() && !jumpWhileCrouching)
return false;
// Cant jump if no jumps available
if (jumpMaxCount == 0 || _jumpCount >= jumpMaxCount)
return false;
// Is fist jump ?
if (_jumpCount == 0)
{
// On first jump,
// can jump if is walking or is falling BUT withing post grounded time
bool canJump = _character.IsWalking() ||
_character.IsFalling() && jumpPostGroundedTime > 0.0f && _character.fallingTime < jumpPostGroundedTime;
// Missed post grounded time ?
if (_character.IsFalling() && !canJump)
{
// Missed post grounded time,
// can jump if have any 'in-air' jumps but the first jump counts as the in-air jump
canJump = jumpMaxCount > 1;
if (canJump)
_jumpCount++;
}
return canJump;
}
// In air jump conditions...
return _character.IsFalling();
}
/// <summary>
/// Determines the jump impulse vector.
/// </summary>
protected virtual Vector3 CalcJumpImpulse()
{
Vector3 worldUp = -_character.GetGravityDirection();
float verticalSpeed = Vector3.Dot(_character.GetVelocity(), worldUp);
float actualJumpImpulse = Mathf.Max(verticalSpeed, jumpImpulse);
return worldUp * actualJumpImpulse;
}
/// <summary>
/// Attempts to perform a requested jump.
/// </summary>
protected virtual void DoJump(float deltaTime)
{
// Update held down timer
if (_jumpButtonPressed)
_jumpButtonHeldDownTime += deltaTime;
// Wants to jump and not already jumping..
if (_jumpButtonPressed && !IsJumping())
{
// If jumpPreGroundedTime is enabled,
// allow to jump only if held down time is less than tolerance
if (jumpPreGroundedTime > 0.0f)
{
bool canJump = _jumpButtonHeldDownTime <= jumpPreGroundedTime;
if (!canJump)
return;
}
// Can perform the requested jump ?
if (CanJump())
{
// Jump!
_character.SetMovementMode(Character.MovementMode.Falling);
_character.PauseGroundConstraint();
_character.LaunchCharacter(CalcJumpImpulse(), true);
_jumpCount++;
_isJumping = true;
}
}
}
/// <summary>
/// Handle jumping state.
/// Eg: check input, perform jump hold (jumpMaxHoldTime > 0), etc.
/// </summary>
protected virtual void Jumping(float deltaTime)
{
// Is character allowed to jump ?
if (!canEverJump)
{
// If not allowed but was jumping, stop jump
if (IsJumping())
StopJumping();
return;
}
// Check jump input state and attempts to do the requested jump
DoJump(deltaTime);
// Perform jump hold, applies an opposite gravity force proportional to _jumpHoldTime.
if (IsJumping() && _jumpButtonPressed && jumpMaxHoldTime > 0.0f && _jumpHoldTime < jumpMaxHoldTime)
{
Vector3 actualGravity = _character.GetGravityVector();
float actualGravityMagnitude = actualGravity.magnitude;
Vector3 actualGravityDirection = actualGravityMagnitude > 0.0f
? actualGravity / actualGravityMagnitude
: Vector3.zero;
float jumpProgress = Mathf.InverseLerp(0.0f, jumpMaxHoldTime, _jumpHoldTime);
float proportionalForce = Mathf.LerpUnclamped(actualGravityMagnitude, 0.0f, jumpProgress);
Vector3 proportionalJumpForce = -actualGravityDirection * proportionalForce;
_character.AddForce(proportionalJumpForce);
_jumpHoldTime += deltaTime;
}
}
protected virtual void OnMovementModeChanged(Character.MovementMode prevMovementMode, int prevCustomMode)
{
if (_character.IsWalking())
_jumpCount = 0;
else if (_character.IsFlying() || _character.IsSwimming())
StopJumping();
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void Reset()
{
_canEverJump = true;
_jumpWhileCrouching = true;
_jumpMaxCount = 1;
_jumpImpulse = 5.0f;
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void OnValidate()
{
jumpMaxCount = _jumpMaxCount;
jumpImpulse = _jumpImpulse;
jumpMaxHoldTime = _jumpMaxHoldTime;
jumpPreGroundedTime = _jumpPreGroundedTime;
jumpPostGroundedTime = _jumpPostGroundedTime;
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void Awake()
{
_character = GetComponent<Character>();
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void OnEnable()
{
_character.MovementModeChanged += OnMovementModeChanged;
_character.BeforeSimulationUpdated += Jumping;
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void OnDisable()
{
_character.BeforeSimulationUpdated -= Jumping;
_character.MovementModeChanged -= OnMovementModeChanged;
}
/// <summary>
/// If overriden, base method MUST be called.
/// </summary>
protected virtual void Start()
{
// Disable Character built-in jump
_character.canEverJump = false;
}
}
}

View File

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

View File

@@ -0,0 +1,36 @@
using UnityEngine.InputSystem;
namespace ECM2.Examples.Jump
{
/// <summary>
/// Extends default Character Input to handle JumpAbility Input.
/// </summary>
public class JumpInput : CharacterInput
{
// The jump ability
private JumpAbility _jumpAbility;
/// <summary>
/// Extend OnJump handler to manage JumpAbility input.
/// </summary>
public override void OnJump(InputAction.CallbackContext context)
{
if (context.started)
_jumpAbility.Jump();
else if (context.canceled)
_jumpAbility.StopJumping();
}
protected override void Awake()
{
base.Awake();
// Cache JumpAbility
_jumpAbility = GetComponent<JumpAbility>();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3b75fe96fbf5426a80316e1303c9fa5c
timeCreated: 1700900486

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4769c4722c8844c49d187dbe2b9c7bad
timeCreated: 1700523532

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 593d3397361c48ab9f2a4688ec660199
timeCreated: 1700524424

View File

@@ -0,0 +1,76 @@
using UnityEngine;
namespace ECM2.Examples.Ladders
{
public sealed class Ladder : MonoBehaviour
{
#region EDITOR EXPOSED FIELDS
[Header("Ladder Path")]
public float PathLength = 10.0f;
public Vector3 PathOffset = new Vector3(0f, 0f, -0.5f);
[Header("Anchor Points")]
public Transform TopPoint;
public Transform BottomPoint;
#endregion
#region PROPERTIES
public Vector3 bottomAnchorPoint => transform.position + transform.TransformVector(PathOffset);
public Vector3 topAnchorPoint => bottomAnchorPoint + transform.up * PathLength;
#endregion
#region METHODS
public Vector3 ClosestPointOnPath(Vector3 position, out float pathPosition)
{
Vector3 path = topAnchorPoint - bottomAnchorPoint;
Vector3 pathToPoint = position - bottomAnchorPoint;
float height = Vector3.Dot(pathToPoint, path.normalized);
if (height > 0.0f)
{
// If we are below top point
if (height <= path.magnitude)
{
pathPosition = 0;
return bottomAnchorPoint + path.normalized * height;
}
// If we are higher than top point
pathPosition = height - path.magnitude;
return topAnchorPoint;
}
// Below bottom point
pathPosition = height;
return bottomAnchorPoint;
}
#endregion
#region MONOBEHAVIOUR
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
Gizmos.DrawLine(bottomAnchorPoint, topAnchorPoint);
if (BottomPoint == null || TopPoint == null)
return;
Gizmos.DrawWireCube(BottomPoint.position, Vector3.one * 0.25f);
Gizmos.DrawWireCube(TopPoint.position, Vector3.one * 0.25f);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,287 @@
using UnityEngine;
namespace ECM2.Examples.Ladders
{
/// <summary>
/// This example shows how to extend a Character (through composition) adding a custom movement mode.
/// Here we implement a ladder climbing ability using a Climbing custom movement mode.
/// </summary>
public class LadderClimbAbility : MonoBehaviour
{
public enum CustomMovementMode
{
Climbing = 1
}
public enum ClimbingState
{
None,
Grabbing,
Grabbed,
Releasing
}
public float climbingSpeed = 5.0f;
public float grabbingTime = 0.25f;
public LayerMask ladderMask;
private Character _character;
private Ladder _activeLadder;
private float _ladderPathPosition;
private Vector3 _ladderStartPosition;
private Vector3 _ladderTargetPosition;
private Quaternion _ladderStartRotation;
private Quaternion _ladderTargetRotation;
private float _ladderTime;
private ClimbingState _climbingState;
private Character.RotationMode _previousRotationMode;
/// <summary>
/// True if the Character is in Climbing custom movement mode, false otherwise.
/// </summary>
public bool IsClimbing()
{
bool isClimbing = _character.movementMode == Character.MovementMode.Custom &&
_character.customMovementMode == (int)CustomMovementMode.Climbing;
return isClimbing;
}
/// <summary>
/// Determines if the Character is able to climb.
/// </summary>
private bool CanClimb()
{
// Do not allow to climb if crouching
if (_character.IsCrouched())
return false;
// Attempt to find a ladder
CharacterMovement characterMovement = _character.characterMovement;
var overlappedColliders =
characterMovement.OverlapTest(ladderMask, QueryTriggerInteraction.Collide, out int overlapCount);
if (overlapCount == 0)
return false;
// Is a ladder ?
if (!overlappedColliders[0].TryGetComponent(out Ladder ladder))
return false;
// Found a ladder, make it active ladder and return
_activeLadder = ladder;
return true;
}
/// <summary>
/// Start a climb.
/// Call this from an input event (such as a button 'down' event).
/// </summary>
public void Climb()
{
if (IsClimbing() || !CanClimb())
return;
_character.SetMovementMode(Character.MovementMode.Custom, (int) CustomMovementMode.Climbing);
_ladderStartPosition = _character.GetPosition();
_ladderTargetPosition = _activeLadder.ClosestPointOnPath(_ladderStartPosition, out _ladderPathPosition);
_ladderStartRotation = _character.GetRotation();
_ladderTargetRotation = _activeLadder.transform.rotation;
}
/// <summary>
/// Stop the Character from climbing.
/// Call this from an input event (such as a button 'up' event).
/// </summary>
public void StopClimbing()
{
if (!IsClimbing() || _climbingState != ClimbingState.Grabbed)
return;
_climbingState = ClimbingState.Releasing;
_ladderStartPosition = _character.GetPosition();
_ladderStartRotation = _character.GetRotation();
_ladderTargetPosition = _ladderStartPosition;
_ladderTargetRotation = _activeLadder.BottomPoint.rotation;
}
/// <summary>
/// Perform climbing movement.
/// </summary>
private void ClimbingMovementMode(float deltaTime)
{
Vector3 velocity = Vector3.zero;
switch (_climbingState)
{
case ClimbingState.Grabbing:
case ClimbingState.Releasing:
{
_ladderTime += deltaTime;
if (_ladderTime <= grabbingTime)
{
Vector3 interpolatedPosition = Vector3.Lerp(_ladderStartPosition, _ladderTargetPosition, _ladderTime / grabbingTime);
velocity = (interpolatedPosition - transform.position) / deltaTime;
}
else
{
// If target has been reached, change ladder phase
_ladderTime = 0.0f;
if (_climbingState == ClimbingState.Grabbing )
{
// Switch to ladder climb phase
_climbingState = ClimbingState.Grabbed;
}
else if (_climbingState == ClimbingState.Releasing)
{
// Exit climbing state (change to falling movement mode)
_character.SetMovementMode(Character.MovementMode.Falling);
}
}
break;
}
case ClimbingState.Grabbed:
{
// Get the path position from character's current position
_activeLadder.ClosestPointOnPath(_character.GetPosition(), out _ladderPathPosition);
if (Mathf.Abs(_ladderPathPosition) < 0.05f)
{
// Move the character along the ladder path
Vector3 movementInput = _character.GetMovementDirection();
velocity = _activeLadder.transform.up * (movementInput.z * climbingSpeed);
}
else
{
// If reached on of the ladder path extremes, change to releasing phase
_climbingState = ClimbingState.Releasing;
_ladderStartPosition = _character.GetPosition();
_ladderStartRotation = _character.GetRotation();
if (_ladderPathPosition > 0.0f)
{
// Above ladder path top point
_ladderTargetPosition = _activeLadder.TopPoint.position;
_ladderTargetRotation = _activeLadder.TopPoint.rotation;
}
else if (_ladderPathPosition < 0.0f)
{
// Below ladder path bottom point
_ladderTargetPosition = _activeLadder.BottomPoint.position;
_ladderTargetRotation = _activeLadder.BottomPoint.rotation;
}
}
break;
}
}
// Update character's velocity
_character.SetVelocity(velocity);
}
private void OnMovementModeChanged(Character.MovementMode prevMovementMode, int prevCustomMovementMode)
{
// Enter Climbing movement mode
if (IsClimbing())
{
_climbingState = ClimbingState.Grabbing;
_character.StopJumping();
_character.EnableGroundConstraint(false);
_previousRotationMode = _character.rotationMode;
_character.SetRotationMode(Character.RotationMode.Custom);
}
// Exit Climbing movement mode
bool wasClimbing = prevMovementMode == Character.MovementMode.Custom &&
prevCustomMovementMode == (int)CustomMovementMode.Climbing;
if (wasClimbing)
{
_climbingState = ClimbingState.None;
_character.EnableGroundConstraint(true);
_character.SetRotationMode(_previousRotationMode);
}
}
private void OnCustomMovementModeUpdated(float deltaTime)
{
if (IsClimbing())
ClimbingMovementMode(deltaTime);
}
private void OnCustomRotationModeUpdated(float deltaTime)
{
if (IsClimbing() && (_climbingState == ClimbingState.Grabbing || _climbingState == ClimbingState.Releasing))
{
Quaternion rotation =
Quaternion.Slerp(_ladderStartRotation, _ladderTargetRotation, _ladderTime / grabbingTime);
_character.SetRotation(rotation);
}
}
private void Awake()
{
_character = GetComponent<Character>();
}
private void OnEnable()
{
_character.MovementModeChanged += OnMovementModeChanged;
_character.CustomMovementModeUpdated += OnCustomMovementModeUpdated;
_character.CustomRotationModeUpdated += OnCustomRotationModeUpdated;
}
private void OnDisable()
{
_character.MovementModeChanged -= OnMovementModeChanged;
_character.CustomMovementModeUpdated -= OnCustomMovementModeUpdated;
_character.CustomRotationModeUpdated -= OnCustomRotationModeUpdated;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5ef7d6f6890f479fac1591d410e31408
timeCreated: 1700524463

View File

@@ -0,0 +1,74 @@
using UnityEngine.InputSystem;
namespace ECM2.Examples.Ladders
{
/// <summary>
/// Extends default Character Input to handle LadderClimbInput Input.
/// </summary>
public class LadderClimbInput : CharacterInput
{
private LadderClimbAbility _ladderClimbAbility;
/// <summary>
/// Interact InputAction.
/// </summary>
public InputAction interactInputAction { get; set; }
/// <summary>
/// Jump InputAction handler.
/// </summary>
public virtual void OnInteract(InputAction.CallbackContext context)
{
if (context.started)
_ladderClimbAbility.Climb();
else if (context.canceled)
_ladderClimbAbility.StopClimbing();
}
protected override void InitPlayerInput()
{
// Call base method implementation
base.InitPlayerInput();
// Setup Interact input action handlers
interactInputAction = inputActionsAsset.FindAction("Interact");
if (interactInputAction != null)
{
interactInputAction.started += OnInteract;
interactInputAction.canceled += OnInteract;
interactInputAction.Enable();
}
}
protected override void DeinitPlayerInput()
{
base.DeinitPlayerInput();
// Unsubscribe from input action events and disable input actions
if (interactInputAction != null)
{
interactInputAction.started -= OnInteract;
interactInputAction.canceled -= OnInteract;
interactInputAction.Disable();
interactInputAction = null;
}
}
protected override void Awake()
{
base.Awake();
// Cache Ladder Climb Ability
_ladderClimbAbility = GetComponent<LadderClimbAbility>();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57e07038965948caa8713a162db0f2be
timeCreated: 1700530516

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 491c3a228ce9f5240bba52a7a7803b4c
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 632c6e9af7bb79949a32cabf0120ff1d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 702cd3842e1f48b78516f11a721bb0bb
timeCreated: 1700621484

View File

@@ -0,0 +1,127 @@
using UnityEngine;
namespace ECM2.Examples.OrientToGround
{
/// <summary>
/// This example extends a Character (through composition) to adjust a Character's rotation
/// to follow a 'terrain' contour.
/// </summary>
public class CharacterOrientToGround : MonoBehaviour, IColliderFilter
{
public float maxSlopeAngle = 30.0f;
public float alignRate = 10.0f;
public float rayOffset = 0.1f;
public LayerMask groundMask = 1;
[Space(15f)]
public bool drawRays = true;
private readonly RaycastHit[] _hits = new RaycastHit[8];
private Character _character;
// Implement IColliderFilter.
// Ignore character's capsule collider.
public bool Filter(Collider otherCollider)
{
CharacterMovement characterMovement = _character.GetCharacterMovement();
if (otherCollider == characterMovement.collider)
return true;
return false;
}
/// <summary>
/// Computes the average normal sampling a 3x3 area, each ray is a rayOffset distance of other.
/// </summary>
private Vector3 ComputeAverageNormal()
{
CharacterMovement characterMovement = _character.GetCharacterMovement();
Vector3 worldUp = Vector3.up;
Vector3 castOrigin = _character.GetPosition() + worldUp * (characterMovement.height * 0.5f);
Vector3 castDirection = -worldUp;
float castDistance = characterMovement.height;
LayerMask castLayerMask = groundMask;
Vector3 avgNormal = Vector3.zero;
float x = -rayOffset;
float z = -rayOffset;
int hitCount = 0;
for (int i = 0; i < 3; i++)
{
z = -rayOffset;
for (int j = 0; j < 3; j++)
{
bool hit = CollisionDetection.Raycast(castOrigin + new Vector3(x, 0.0f, z), castDirection,
castDistance, castLayerMask, QueryTriggerInteraction.Ignore, out RaycastHit hitResult, _hits, this) > 0;
if (hit)
{
float angle = Vector3.Angle(hitResult.normal, worldUp);
if (angle < maxSlopeAngle)
{
avgNormal += hitResult.normal;
if (drawRays)
Debug.DrawRay(hitResult.point, hitResult.normal, Color.yellow);
hitCount ++;
}
}
z += rayOffset;
}
x += rayOffset;
}
if (hitCount > 0)
avgNormal /= hitCount;
else
avgNormal = worldUp;
if (drawRays)
Debug.DrawRay(_character.GetPosition(), avgNormal * 2f, Color.green);
return avgNormal;
}
private void OnAfterSimulationUpdated(float deltaTime)
{
Vector3 avgNormal = _character.IsWalking() ? ComputeAverageNormal() : Vector3.up;
Quaternion characterRotation = _character.GetRotation();
Vector3 characterUp = characterRotation * Vector3.up;
Quaternion slopeRotation = Quaternion.FromToRotation(characterUp, avgNormal);
characterRotation = Quaternion.Slerp(characterRotation, slopeRotation * characterRotation, alignRate * deltaTime);
_character.SetRotation(characterRotation);
}
private void Awake()
{
_character = GetComponent<Character>();
}
private void OnEnable()
{
_character.AfterSimulationUpdated += OnAfterSimulationUpdated;
}
private void OnDisable()
{
_character.AfterSimulationUpdated -= OnAfterSimulationUpdated;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ddceac5c369948b4b1f56559e4d41796
timeCreated: 1700621503

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3938dc9601b64b80aad092fdd3b438bf
timeCreated: 1700523966

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5baa8109e4051dd4980b7e59de338ef3
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b200f749d8db4954d8599e6772165940
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 112000000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 23407264829349547ae7a870aa3ebae8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
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: 1
seamlessCubemap: 1
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 2
aniso: 0
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
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: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- 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: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
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: f02f1530dd241694b817ab613b519138
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
using ECM2.Examples.ThirdPerson;
using UnityEngine;
namespace ECM2.Examples.PlanetWalk
{
/// <summary>
/// This example extends a Character (through inheritance) adjusting its gravity and orientation
/// to follow a planet curvature similar to the Mario Galaxy game.
/// </summary>
public class PlayerCharacter : ThirdPersonCharacter
{
[Space(15f)]
public Transform planetTransform;
// Current camera forward, perpendicular to target's up vector.
private Vector3 _cameraForward = Vector3.forward;
public override void AddControlYawInput(float value)
{
// Rotate our forward along follow target's up axis
Vector3 targetUp = followTarget.transform.up;
_cameraForward = Quaternion.Euler(targetUp * value) * _cameraForward;
}
protected override void UpdateCameraRotation()
{
// Make sure camera forward vector is perpendicular to Character's current up vector
Vector3 targetUp = followTarget.transform.up;
Vector3.OrthoNormalize(ref targetUp, ref _cameraForward);
// Computes final Camera rotation from yaw and pitch
cameraTransform.rotation =
Quaternion.LookRotation(_cameraForward, targetUp) * Quaternion.Euler(_cameraPitch, 0.0f, 0.0f);
}
protected override void UpdateRotation(float deltaTime)
{
// Call base method (i.e: rotate towards movement direction)
base.UpdateRotation(deltaTime);
// Adjust gravity direction (ie: a vector pointing from character position to planet's center)
Vector3 toPlanet = planetTransform.position - GetPosition();
SetGravityVector(toPlanet.normalized * GetGravityMagnitude());
// Adjust Character's rotation following the new world-up (defined by gravity direction)
Vector3 worldUp = GetGravityDirection() * -1.0f;
Quaternion newRotation = Quaternion.FromToRotation(GetUpVector(), worldUp) * GetRotation();
SetRotation(newRotation);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af237fa0d4164de3b8bbb1640d85f70b
timeCreated: 1700798056

View File

@@ -0,0 +1,35 @@
using UnityEngine;
namespace ECM2.Examples.SideScrolling
{
/// <summary>
/// This example shows how to implement a typical side-scrolling movement with side to side rotation snap.
/// </summary>
public class SideScrollingInput : CharacterInput
{
protected override void Awake()
{
// Call base method implementation
base.Awake();
// Disable Character rotation, well handle it here (snap move direction)
character.SetRotationMode(Character.RotationMode.None);
}
protected override void HandleInput()
{
// Add horizontal movement (in world space)
Vector2 movementInput = GetMovementInput();
character.SetMovementDirection(Vector3.right * movementInput.x);
// Snap side to side rotation
if (movementInput.x != 0)
character.SetYaw(movementInput.x * 90.0f);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8f138cba3d2dc7146817f32230b4f5b5

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9d6613720d4108f4fa955b37a378bbdc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@@ -0,0 +1,216 @@
using UnityEngine;
namespace ECM2.Examples.Slide
{
/// <summary>
/// This example extends a Character (through inheritance) implementing a slide mechanic.
/// </summary>
public class PlayerCharacter : Character
{
[Space(15.0f)]
public float slideImpulse = 20.0f;
public float slideDownAcceleration = 20.0f;
/// <summary>
/// Our custom movement mode(s) id(s).
/// </summary>
enum ECustomMovementMode
{
Sliding = 1
}
/// <summary>
/// If sliding, return max walk speed as our speed limit.
/// </summary>
public override float GetMaxSpeed()
{
return IsSliding() ? maxWalkSpeed : base.GetMaxSpeed();
}
/// <summary>
/// If sliding, limit acceleration.
/// </summary>
public override float GetMaxAcceleration()
{
return IsSliding() ? maxAcceleration * 0.1f : base.GetMaxAcceleration();
}
/// <summary>
/// Override IsWalking (aka: grounded movement mode) to add Sliding movement mode support,
/// otherwise crouch, jump will fail while sliding due its conditions checking if IsWalking.
/// </summary>
public override bool IsWalking()
{
return IsSliding() || base.IsWalking();
}
/// <summary>
/// Is the Character sliding?
/// </summary>
public bool IsSliding()
{
return movementMode == MovementMode.Custom && customMovementMode == (int)ECustomMovementMode.Sliding;
}
/// <summary>
/// Determines if the character can slide in its current state.
/// </summary>
protected virtual bool CanSlide()
{
// Slide is tied to crouch, if not crouching cant slide!
if (!IsGrounded())
return false;
// Check allowed slide speed threshold
float sqrSpeed = velocity.sqrMagnitude;
float slideSpeedThreshold = maxWalkSpeedCrouched * maxWalkSpeedCrouched;
return sqrSpeed >= slideSpeedThreshold * 1.02f;
}
/// <summary>
/// Calculate sliding direction vector.
/// </summary>
protected virtual Vector3 CalcSlideDirection()
{
Vector3 slideDirection = GetMovementDirection();
if (slideDirection.isZero())
slideDirection = GetVelocity();
else if (slideDirection.isZero())
slideDirection = GetForwardVector();
slideDirection = ConstrainInputVector(slideDirection);
return slideDirection.normalized;
}
/// <summary>
/// Attempts to perform a requested slide or
/// stop it if requested or cant continue sliding.
/// </summary>
protected virtual void CheckSlideInput()
{
bool isSliding = IsSliding();
bool wantsToSlide = crouchInputPressed;
if (!isSliding && wantsToSlide && CanSlide())
{
SetMovementMode(MovementMode.Custom, (int)ECustomMovementMode.Sliding);
}
else if (isSliding && (!wantsToSlide || !CanSlide()))
{
SetMovementMode(MovementMode.Walking);
}
}
/// <summary>
/// Handle Sliding enter / exit.
/// </summary>
protected override void OnMovementModeChanged(MovementMode prevMovementMode, int prevCustomMode)
{
// Call base method implementation
base.OnMovementModeChanged(prevMovementMode, prevCustomMode);
// Enter sliding movement mode...
if (IsSliding())
{
// Apply initial slide impulse
Vector3 slideDirection = CalcSlideDirection();
characterMovement.velocity += slideDirection * slideImpulse;
// Disable Character rotation
SetRotationMode(RotationMode.None);
}
// Exit sliding movement mode...
bool wasSliding = prevMovementMode == MovementMode.Custom &&
prevCustomMode == (int)ECustomMovementMode.Sliding;
if (wasSliding)
{
// Re-enable Character rotation
SetRotationMode(RotationMode.OrientRotationToMovement);
// If falling, make sure its velocity do not exceed maxWalkSpeed
if (IsFalling())
{
Vector3 worldUp = -GetGravityDirection();
Vector3 verticalVelocity = Vector3.Project(velocity, worldUp);
Vector3 lateralVelocity = Vector3.ClampMagnitude(velocity - verticalVelocity, maxWalkSpeed);
characterMovement.velocity = lateralVelocity + verticalVelocity;
}
}
}
protected override void OnBeforeSimulationUpdate(float deltaTime)
{
// Call base method implementation
base.OnBeforeSimulationUpdate(deltaTime);
// Attempts to do a requested slide
CheckSlideInput();
}
/// <summary>
/// Update Character's velocity while SLIDING on walkable surfaces.
/// </summary>
protected virtual void SlidingMovementMode(float deltaTime)
{
// Limit input to lateral movement only (strafing)
Vector3 desiredVelocity = Vector3.Project(GetDesiredVelocity(), GetRightVector());
// Calculate new velocity
characterMovement.velocity =
CalcVelocity(characterMovement.velocity, desiredVelocity, groundFriction * 0.2f, false, deltaTime);
// Apply slide down acceleration
Vector3 slideDownDirection =
Vector3.ProjectOnPlane(GetGravityDirection(), characterMovement.groundNormal).normalized;
characterMovement.velocity += slideDownAcceleration * deltaTime * slideDownDirection;
// Apply downwards force
if (applyStandingDownwardForce)
ApplyDownwardsForce();
}
protected override void CustomMovementMode(float deltaTime)
{
// Call base method implementation
base.CustomMovementMode(deltaTime);
// Sliding custom movement mode
if (customMovementMode == (int)ECustomMovementMode.Sliding)
SlidingMovementMode(deltaTime);
}
}
}

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