Player, player movement, camera control

This commit is contained in:
2024-12-07 21:48:47 +01:00
parent 54fe327198
commit 8969435fda
1052 changed files with 166612 additions and 6768 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 51116ce509f204c439bf161c3492f5f2
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 59c94ba19ccaba442811b429355d9346
timeCreated: 1517755563
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: b6444c446b97c3949a4e91a10723a1db
timeCreated: 1517755562
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,70 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.PlayerCameraCharacterSetup
{
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
}
public void BeforeCharacterUpdate(float deltaTime)
{
// This is called before the motor does anything
}
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
// This is called when the motor wants to know what its rotation should be right now
}
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
// This is called when the motor wants to know what its velocity should be right now
}
public void AfterCharacterUpdate(float deltaTime)
{
// This is called after the motor has finished everything in its update
}
public bool IsColliderValidForCollisions(Collider coll)
{
// This is called after when the motor wants to know if the collider can be collided with (or if we just go through it)
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
// This is called when the motor's ground probing detects a ground hit
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
// This is called when the motor's movement logic detects a hit
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
// This is called after every hit detected in the motor, to give you a chance to modify the HitStabilityReport any way you want
}
public void PostGroundingUpdate(float deltaTime)
{
// This is called after the motor has finished its ground probing, but before PhysicsMover/Velocity/etc.... handling
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
// This is called by the motor when it is detecting a collision that did not result from a "movement hit".
}
}
}

View File

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

View File

@@ -0,0 +1,71 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.PlayerCameraCharacterSetup
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private Vector3 _lookInputVector = Vector3.zero;
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders = Character.GetComponentsInChildren<Collider>().ToList();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw("Mouse Y");
float mouseLookAxisRight = Input.GetAxisRaw("Mouse X");
_lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
_lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis("Mouse ScrollWheel");
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, _lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2500a88107e4f1f469181f22f53c0b3f
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: dcd109c9b01494f49b5a633a2a184afd
timeCreated: 1517665930
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: 577436d930a350c4b98a31b783f58237
timeCreated: 1517665930
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,439 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.MultipleMovementStates
{
public enum CharacterState
{
Default,
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool CrouchDown;
public bool CrouchUp;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
public float MaxStableDistanceFromLedge = 5f;
[Range(0f, 180f)]
public float MaxStableDenivelationAngle = 180f;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Jumping")]
public bool AllowJumpingWhenSliding = false;
public bool AllowDoubleJump = false;
public bool AllowWallJump = false;
public float JumpSpeed = 10f;
public float JumpPreGroundingGraceTime = 0f;
public float JumpPostGroundingGraceTime = 0f;
[Header("Misc")]
public List<Collider> IgnoredColliders = new List<Collider>();
public bool OrientTowardsGravity = false;
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public CharacterState CurrentCharacterState { get; private set; }
private Collider[] _probedColliders = new Collider[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _doubleJumpConsumed = false;
private bool _jumpedThisFrame = false;
private bool _canWallJump = false;
private Vector3 _wallJumpNormal;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
// Handle initial state
TransitionToState(CharacterState.Default);
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
// Clamp input
Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
if (_lookInputVector != Vector3.zero && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
if (OrientTowardsGravity)
{
// Rotate from current up to invert gravity
currentRotation = Quaternion.FromToRotation((currentRotation * Vector3.up), -Gravity) * currentRotation;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
{
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// Handle double jump
if (AllowDoubleJump)
{
if (_jumpConsumed && !_doubleJumpConsumed && (AllowJumpingWhenSliding ? !Motor.GroundingStatus.FoundAnyGround : !Motor.GroundingStatus.IsStableOnGround))
{
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_doubleJumpConsumed = true;
_jumpedThisFrame = true;
}
}
// See if we actually are allowed to jump
if (_canWallJump ||
(!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime)))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (_canWallJump)
{
jumpDirection = _wallJumpNormal;
}
else if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Reset wall jump
_canWallJump = false;
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_doubleJumpConsumed = false;
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
}
else
{
// If no obstructions, uncrouch
MeshRoot.localScale = new Vector3(1f, 1f, 1f);
_isCrouching = false;
}
}
break;
}
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// We can wall jump only if we are not stable on ground and are moving against an obstruction
if (AllowWallJump && !Motor.GroundingStatus.IsStableOnGround && !hitStabilityReport.IsStable)
{
_canWallJump = true;
_wallJumpNormal = hitNormal;
}
break;
}
}
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void PostGroundingUpdate(float deltaTime)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: cdd0dd2408e42ba47b026e5583efd762
timeCreated: 1507247652
licenseType: Store
MonoImporter:
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 KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.MultipleMovementStates
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private const string MouseXInput = "Mouse X";
private const string MouseYInput = "Mouse Y";
private const string MouseScrollInput = "Mouse ScrollWheel";
private const string HorizontalInput = "Horizontal";
private const string VerticalInput = "Vertical";
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders.Clear();
OrbitCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren<Collider>());
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
HandleCharacterInput();
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis(MouseScrollInput);
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
private void HandleCharacterInput()
{
PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
// Build the CharacterInputs struct
characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
characterInputs.CameraRotation = OrbitCamera.Transform.rotation;
characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
// Apply inputs to character
Character.SetInputs(ref characterInputs);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 080c10a3703549c47b115b24b77e16ea
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9ca75fae4cd9a2d478cc927f4e81d91b
timeCreated: 1517767069
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: bfe22d4c784ad9e47a9cdd1582da2c26
timeCreated: 1517767068
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,533 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.ChargingState
{
public enum CharacterState
{
Default,
Charging,
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool CrouchDown;
public bool CrouchUp;
public bool ChargingDown;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
public float MaxStableDistanceFromLedge = 5f;
[Range(0f, 180f)]
public float MaxStableDenivelationAngle = 180f;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Jumping")]
public bool AllowJumpingWhenSliding = false;
public bool AllowDoubleJump = false;
public bool AllowWallJump = false;
public float JumpSpeed = 10f;
public float JumpPreGroundingGraceTime = 0f;
public float JumpPostGroundingGraceTime = 0f;
[Header("Charging")]
public float ChargeSpeed = 15f;
public float MaxChargeTime = 1.5f;
public float StoppedTime = 1f;
[Header("Misc")]
public List<Collider> IgnoredColliders = new List<Collider>();
public bool OrientTowardsGravity = false;
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public CharacterState CurrentCharacterState { get; private set; }
private Collider[] _probedColliders = new Collider[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _doubleJumpConsumed = false;
private bool _jumpedThisFrame = false;
private bool _canWallJump = false;
private Vector3 _wallJumpNormal;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
private Vector3 _currentChargeVelocity;
private bool _isStopped;
private bool _mustStopVelocity = false;
private float _timeSinceStartedCharge = 0;
private float _timeSinceStopped = 0;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
// Handle initial state
TransitionToState(CharacterState.Default);
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
case CharacterState.Charging:
{
_currentChargeVelocity = Motor.CharacterForward * ChargeSpeed;
_isStopped = false;
_timeSinceStartedCharge = 0f;
_timeSinceStopped = 0f;
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
// Handle state transition from input
if (inputs.ChargingDown)
{
TransitionToState(CharacterState.Charging);
}
// Clamp input
Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
break;
}
case CharacterState.Charging:
{
// Update times
_timeSinceStartedCharge += deltaTime;
if (_isStopped)
{
_timeSinceStopped += deltaTime;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
if (_lookInputVector != Vector3.zero && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
if (OrientTowardsGravity)
{
// Rotate from current up to invert gravity
currentRotation = Quaternion.FromToRotation((currentRotation * Vector3.up), -Gravity) * currentRotation;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
{
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// Handle double jump
if (AllowDoubleJump)
{
if (_jumpConsumed && !_doubleJumpConsumed && (AllowJumpingWhenSliding ? !Motor.GroundingStatus.FoundAnyGround : !Motor.GroundingStatus.IsStableOnGround))
{
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_doubleJumpConsumed = true;
_jumpedThisFrame = true;
}
}
// See if we actually are allowed to jump
if (_canWallJump ||
(!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime)))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (_canWallJump)
{
jumpDirection = _wallJumpNormal;
}
else if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Reset wall jump
_canWallJump = false;
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
case CharacterState.Charging:
{
// If we have stopped and need to cancel velocity, do it here
if (_mustStopVelocity)
{
currentVelocity = Vector3.zero;
_mustStopVelocity = false;
}
if (_isStopped)
{
// When stopped, do no velocity handling except gravity
currentVelocity += Gravity * deltaTime;
}
else
{
// When charging, velocity is always constant
float previousY = currentVelocity.y;
currentVelocity = _currentChargeVelocity;
currentVelocity.y = previousY;
currentVelocity += Gravity * deltaTime;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_doubleJumpConsumed = false;
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
}
else
{
// If no obstructions, uncrouch
MeshRoot.localScale = new Vector3(1f, 1f, 1f);
_isCrouching = false;
}
}
break;
}
case CharacterState.Charging:
{
// Detect being stopped by elapsed time
if (!_isStopped && _timeSinceStartedCharge > MaxChargeTime)
{
_mustStopVelocity = true;
_isStopped = true;
}
// Detect end of stopping phase and transition back to default movement state
if (_timeSinceStopped > StoppedTime)
{
TransitionToState(CharacterState.Default);
}
break;
}
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// We can wall jump only if we are not stable on ground and are moving against an obstruction
if (AllowWallJump && !Motor.GroundingStatus.IsStableOnGround && !hitStabilityReport.IsStable)
{
_canWallJump = true;
_wallJumpNormal = hitNormal;
}
break;
}
case CharacterState.Charging:
{
// Detect being stopped by obstructions
if (!_isStopped && !hitStabilityReport.IsStable && Vector3.Dot(-hitNormal, _currentChargeVelocity.normalized) > 0.5f)
{
_mustStopVelocity = true;
_isStopped = true;
}
break;
}
}
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void PostGroundingUpdate(float deltaTime)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}

View File

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

View File

@@ -0,0 +1,95 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.ChargingState
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private const string MouseXInput = "Mouse X";
private const string MouseYInput = "Mouse Y";
private const string MouseScrollInput = "Mouse ScrollWheel";
private const string HorizontalInput = "Horizontal";
private const string VerticalInput = "Vertical";
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders.Clear();
OrbitCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren<Collider>());
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
HandleCharacterInput();
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis(MouseScrollInput);
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
private void HandleCharacterInput()
{
PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
// Build the CharacterInputs struct
characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
characterInputs.CameraRotation = OrbitCamera.Transform.rotation;
characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
characterInputs.ChargingDown = Input.GetKeyDown(KeyCode.Q);
// Apply inputs to character
Character.SetInputs(ref characterInputs);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c51b3a6a6cbf9e478da8e6dc6e68697
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: eeeef78adc5f0f948ae5ce452c42d9bf
timeCreated: 1517767440
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: 60c6db21975c662438028db7f6265ec9
timeCreated: 1517767440
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,501 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.NoClipState
{
public enum CharacterState
{
Default,
NoClip,
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool JumpHeld;
public bool CrouchDown;
public bool CrouchUp;
public bool CrouchHeld;
public bool NoClipDown;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
public float MaxStableDistanceFromLedge = 5f;
[Range(0f, 180f)]
public float MaxStableDenivelationAngle = 180f;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Jumping")]
public bool AllowJumpingWhenSliding = false;
public bool AllowDoubleJump = false;
public bool AllowWallJump = false;
public float JumpSpeed = 10f;
public float JumpPreGroundingGraceTime = 0f;
public float JumpPostGroundingGraceTime = 0f;
[Header("NoClip")]
public float NoClipMoveSpeed = 10f;
public float NoClipSharpness = 15;
[Header("Misc")]
public List<Collider> IgnoredColliders = new List<Collider>();
public bool OrientTowardsGravity = false;
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public CharacterState CurrentCharacterState { get; private set; }
private Collider[] _probedColliders = new Collider[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpInputIsHeld = false;
private bool _crouchInputIsHeld = false;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _doubleJumpConsumed = false;
private bool _jumpedThisFrame = false;
private bool _canWallJump = false;
private Vector3 _wallJumpNormal;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
// Handle initial state
TransitionToState(CharacterState.Default);
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
case CharacterState.NoClip:
{
Motor.SetCapsuleCollisionsActivation(false);
Motor.SetMovementCollisionsSolvingActivation(false);
Motor.SetGroundSolvingActivation(false);
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
case CharacterState.NoClip:
{
Motor.SetCapsuleCollisionsActivation(true);
Motor.SetMovementCollisionsSolvingActivation(true);
Motor.SetGroundSolvingActivation(true);
break;
}
}
}
/// <summary>
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
// Handle state transition from input
if (inputs.NoClipDown)
{
if (CurrentCharacterState == CharacterState.Default)
{
TransitionToState(CharacterState.NoClip);
}
else if (CurrentCharacterState == CharacterState.NoClip)
{
TransitionToState(CharacterState.Default);
}
}
_jumpInputIsHeld = inputs.JumpHeld;
_crouchInputIsHeld = inputs.CrouchHeld;
// Clamp input
Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
case CharacterState.NoClip:
{
_moveInputVector = inputs.CameraRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
case CharacterState.NoClip:
{
if (_lookInputVector != Vector3.zero && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
if (OrientTowardsGravity)
{
// Rotate from current up to invert gravity
currentRotation = Quaternion.FromToRotation((currentRotation * Vector3.up), -Gravity) * currentRotation;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
{
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// Handle double jump
if (AllowDoubleJump)
{
if (_jumpConsumed && !_doubleJumpConsumed && (AllowJumpingWhenSliding ? !Motor.GroundingStatus.FoundAnyGround : !Motor.GroundingStatus.IsStableOnGround))
{
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_doubleJumpConsumed = true;
_jumpedThisFrame = true;
}
}
// See if we actually are allowed to jump
if (_canWallJump ||
(!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime)))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (_canWallJump)
{
jumpDirection = _wallJumpNormal;
}
else if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Reset wall jump
_canWallJump = false;
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
case CharacterState.NoClip:
{
float verticalInput = 0f + (_jumpInputIsHeld ? 1f : 0f) + (_crouchInputIsHeld ? -1f : 0f);
// Smoothly interpolate to target velocity
Vector3 targetMovementVelocity = (_moveInputVector + (Motor.CharacterUp * verticalInput)).normalized * NoClipMoveSpeed;
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-NoClipSharpness * deltaTime));
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_doubleJumpConsumed = false;
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
}
else
{
// If no obstructions, uncrouch
MeshRoot.localScale = new Vector3(1f, 1f, 1f);
_isCrouching = false;
}
}
break;
}
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// We can wall jump only if we are not stable on ground and are moving against an obstruction
if (AllowWallJump && !Motor.GroundingStatus.IsStableOnGround && !hitStabilityReport.IsStable)
{
_canWallJump = true;
_wallJumpNormal = hitNormal;
}
break;
}
}
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void PostGroundingUpdate(float deltaTime)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}

View File

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

View File

@@ -0,0 +1,97 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.NoClipState
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private const string MouseXInput = "Mouse X";
private const string MouseYInput = "Mouse Y";
private const string MouseScrollInput = "Mouse ScrollWheel";
private const string HorizontalInput = "Horizontal";
private const string VerticalInput = "Vertical";
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders.Clear();
OrbitCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren<Collider>());
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
HandleCharacterInput();
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis(MouseScrollInput);
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
private void HandleCharacterInput()
{
PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
// Build the CharacterInputs struct
characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
characterInputs.CameraRotation = OrbitCamera.Transform.rotation;
characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
characterInputs.JumpHeld = Input.GetKey(KeyCode.Space);
characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
characterInputs.CrouchHeld = Input.GetKey(KeyCode.C);
characterInputs.NoClipDown = Input.GetKeyUp(KeyCode.Q);
// Apply inputs to character
Character.SetInputs(ref characterInputs);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd899887b2fc18b46b5bd2b474774a2d
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ad04f42b5c282df45b6cb76c9d5d0ce7
timeCreated: 1517768625
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: 46581c5748209e84fb9336a40afd83c4
timeCreated: 1517768625
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,527 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.SwimmingState
{
public enum CharacterState
{
Default,
Swimming,
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool JumpHeld;
public bool CrouchDown;
public bool CrouchUp;
public bool CrouchHeld;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
public float MaxStableDistanceFromLedge = 5f;
[Range(0f, 180f)]
public float MaxStableDenivelationAngle = 180f;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Jumping")]
public bool AllowJumpingWhenSliding = false;
public bool AllowDoubleJump = false;
public bool AllowWallJump = false;
public float JumpSpeed = 10f;
public float JumpPreGroundingGraceTime = 0f;
public float JumpPostGroundingGraceTime = 0f;
[Header("Swimming")]
public Transform SwimmingReferencePoint;
public LayerMask WaterLayer;
public float SwimmingSpeed = 4f;
public float SwimmingMovementSharpness = 3;
public float SwimmingOrientationSharpness = 2f;
[Header("Misc")]
public List<Collider> IgnoredColliders = new List<Collider>();
public bool OrientTowardsGravity = false;
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public CharacterState CurrentCharacterState { get; private set; }
private Collider[] _probedColliders = new Collider[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpInputIsHeld = false;
private bool _crouchInputIsHeld = false;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _doubleJumpConsumed = false;
private bool _jumpedThisFrame = false;
private bool _canWallJump = false;
private Vector3 _wallJumpNormal;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
private Collider _waterZone;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
// Handle initial state
TransitionToState(CharacterState.Default);
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
Motor.SetGroundSolvingActivation(true);
break;
}
case CharacterState.Swimming:
{
Motor.SetGroundSolvingActivation(false);
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
_jumpInputIsHeld = inputs.JumpHeld;
_crouchInputIsHeld = inputs.CrouchHeld;
// Clamp input
Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
case CharacterState.Swimming:
{
_jumpRequested = inputs.JumpHeld;
_moveInputVector = inputs.CameraRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
// Handle detecting water surfaces
{
// Do a character overlap test to detect water surfaces
if (Motor.CharacterOverlap(Motor.TransientPosition, Motor.TransientRotation, _probedColliders, WaterLayer, QueryTriggerInteraction.Collide) > 0)
{
// If a water surface was detected
if (_probedColliders[0] != null)
{
// If the swimming reference point is inside the box, make sure we are in swimming state
if (Physics.ClosestPoint(SwimmingReferencePoint.position, _probedColliders[0], _probedColliders[0].transform.position, _probedColliders[0].transform.rotation) == SwimmingReferencePoint.position)
{
if (CurrentCharacterState == CharacterState.Default)
{
TransitionToState(CharacterState.Swimming);
_waterZone = _probedColliders[0];
}
}
// otherwise; default state
else
{
if (CurrentCharacterState == CharacterState.Swimming)
{
TransitionToState(CharacterState.Default);
}
}
}
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
case CharacterState.Swimming:
{
if (_lookInputVector != Vector3.zero && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
if (OrientTowardsGravity)
{
// Rotate from current up to invert gravity
currentRotation = Quaternion.FromToRotation((currentRotation * Vector3.up), -Gravity) * currentRotation;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
{
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// Handle double jump
if (AllowDoubleJump)
{
if (_jumpConsumed && !_doubleJumpConsumed && (AllowJumpingWhenSliding ? !Motor.GroundingStatus.FoundAnyGround : !Motor.GroundingStatus.IsStableOnGround))
{
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_doubleJumpConsumed = true;
_jumpedThisFrame = true;
}
}
// See if we actually are allowed to jump
if (_canWallJump ||
(!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime)))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (_canWallJump)
{
jumpDirection = _wallJumpNormal;
}
else if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Reset wall jump
_canWallJump = false;
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
case CharacterState.Swimming:
{
float verticalInput = 0f + (_jumpInputIsHeld ? 1f : 0f) + (_crouchInputIsHeld ? -1f : 0f);
// Smoothly interpolate to target swimming velocity
Vector3 targetMovementVelocity = (_moveInputVector + (Motor.CharacterUp * verticalInput)).normalized * SwimmingSpeed;
Vector3 smoothedVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-SwimmingMovementSharpness * deltaTime));
// See if our swimming reference point would be out of water after the movement from our velocity has been applied
{
Vector3 resultingSwimmingReferancePosition = Motor.TransientPosition + (smoothedVelocity * deltaTime) + (SwimmingReferencePoint.position - Motor.TransientPosition);
Vector3 closestPointWaterSurface = Physics.ClosestPoint(resultingSwimmingReferancePosition, _waterZone, _waterZone.transform.position, _waterZone.transform.rotation);
// if our position would be outside the water surface on next update, project the velocity on the surface normal so that it would not take us out of the water
if (closestPointWaterSurface != resultingSwimmingReferancePosition)
{
Vector3 waterSurfaceNormal = (resultingSwimmingReferancePosition - closestPointWaterSurface).normalized;
smoothedVelocity = Vector3.ProjectOnPlane(smoothedVelocity, waterSurfaceNormal);
// Jump out of water
if (_jumpRequested)
{
smoothedVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
}
}
}
currentVelocity = smoothedVelocity;
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_doubleJumpConsumed = false;
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
}
else
{
// If no obstructions, uncrouch
MeshRoot.localScale = new Vector3(1f, 1f, 1f);
_isCrouching = false;
}
}
break;
}
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// We can wall jump only if we are not stable on ground and are moving against an obstruction
if (AllowWallJump && !Motor.GroundingStatus.IsStableOnGround && !hitStabilityReport.IsStable)
{
_canWallJump = true;
_wallJumpNormal = hitNormal;
}
break;
}
}
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void PostGroundingUpdate(float deltaTime)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}

View File

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

View File

@@ -0,0 +1,96 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.SwimmingState
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private const string MouseXInput = "Mouse X";
private const string MouseYInput = "Mouse Y";
private const string MouseScrollInput = "Mouse ScrollWheel";
private const string HorizontalInput = "Horizontal";
private const string VerticalInput = "Vertical";
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders.Clear();
OrbitCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren<Collider>());
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
HandleCharacterInput();
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis(MouseScrollInput);
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
private void HandleCharacterInput()
{
PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
// Build the CharacterInputs struct
characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
characterInputs.CameraRotation = OrbitCamera.Transform.rotation;
characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
characterInputs.JumpHeld = Input.GetKey(KeyCode.Space);
characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
characterInputs.CrouchHeld = Input.GetKey(KeyCode.C);
// Apply inputs to character
Character.SetInputs(ref characterInputs);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 99217385d36556848a4019cc1e7fa0d8
timeCreated: 1506988233
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bd0fdcc34d728ac4db527844b498d09b
timeCreated: 1517686306
licenseType: Store
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
fileFormatVersion: 2
guid: b0adf4a148ffe70438e744834048a5c0
timeCreated: 1517686306
licenseType: Store
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 4
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
isReadable: 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}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,604 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using System;
namespace KinematicCharacterController.Walkthrough.ClimbingLadders
{
public enum CharacterState
{
Default,
Climbing,
}
public enum ClimbingState
{
Anchoring,
Climbing,
DeAnchoring
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool CrouchDown;
public bool CrouchUp;
public bool ClimbLadder;
}
public class MyCharacterController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[Header("Stable Movement")]
public float MaxStableMoveSpeed = 10f;
public float StableMovementSharpness = 15;
public float OrientationSharpness = 10;
public float MaxStableDistanceFromLedge = 5f;
[Range(0f, 180f)]
public float MaxStableDenivelationAngle = 180f;
[Header("Air Movement")]
public float MaxAirMoveSpeed = 10f;
public float AirAccelerationSpeed = 5f;
public float Drag = 0.1f;
[Header("Jumping")]
public bool AllowJumpingWhenSliding = false;
public bool AllowDoubleJump = false;
public bool AllowWallJump = false;
public float JumpSpeed = 10f;
public float JumpPreGroundingGraceTime = 0f;
public float JumpPostGroundingGraceTime = 0f;
[Header("Ladder Climbing")]
public float ClimbingSpeed = 4f;
public float AnchoringDuration = 0.25f;
public LayerMask InteractionLayer;
[Header("Misc")]
public List<Collider> IgnoredColliders = new List<Collider>();
public bool OrientTowardsGravity = false;
public Vector3 Gravity = new Vector3(0, -30f, 0);
public Transform MeshRoot;
public CharacterState CurrentCharacterState { get; private set; }
private Collider[] _probedColliders = new Collider[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _doubleJumpConsumed = false;
private bool _jumpedThisFrame = false;
private bool _canWallJump = false;
private Vector3 _wallJumpNormal;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
// Ladder vars
private float _ladderUpDownInput;
private MyLadder _activeLadder { get; set; }
private ClimbingState _internalClimbingState;
private ClimbingState _climbingState
{
get
{
return _internalClimbingState;
}
set
{
_internalClimbingState = value;
_anchoringTimer = 0f;
_anchoringStartPosition = Motor.TransientPosition;
_anchoringStartRotation = Motor.TransientRotation;
}
}
private Vector3 _ladderTargetPosition;
private Quaternion _ladderTargetRotation;
private float _onLadderSegmentState = 0;
private float _anchoringTimer = 0f;
private Vector3 _anchoringStartPosition = Vector3.zero;
private Quaternion _anchoringStartRotation = Quaternion.identity;
private Quaternion _rotationBeforeClimbing = Quaternion.identity;
private void Start()
{
// Assign to motor
Motor.CharacterController = this;
// Handle initial state
TransitionToState(CharacterState.Default);
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
case CharacterState.Climbing:
{
_rotationBeforeClimbing = Motor.TransientRotation;
Motor.SetMovementCollisionsSolvingActivation(false);
Motor.SetGroundSolvingActivation(false);
_climbingState = ClimbingState.Anchoring;
// Store the target position and rotation to snap to
_ladderTargetPosition = _activeLadder.ClosestPointOnLadderSegment(Motor.TransientPosition, out _onLadderSegmentState);
_ladderTargetRotation = _activeLadder.transform.rotation;
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
case CharacterState.Climbing:
{
Motor.SetMovementCollisionsSolvingActivation(true);
Motor.SetGroundSolvingActivation(true);
break;
}
}
}
/// <summary>
/// This is called every frame by MyPlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
// Handle ladder transitions
_ladderUpDownInput = inputs.MoveAxisForward;
if (inputs.ClimbLadder)
{
if (Motor.CharacterOverlap(Motor.TransientPosition, Motor.TransientRotation, _probedColliders, InteractionLayer, QueryTriggerInteraction.Collide) > 0)
{
if (_probedColliders[0] != null)
{
// Handle ladders
MyLadder ladder = _probedColliders[0].gameObject.GetComponent<MyLadder>();
if (ladder)
{
// Transition to ladder climbing state
if (CurrentCharacterState == CharacterState.Default)
{
_activeLadder = ladder;
TransitionToState(CharacterState.Climbing);
}
// Transition back to default movement state
else if (CurrentCharacterState == CharacterState.Climbing)
{
_climbingState = ClimbingState.DeAnchoring;
_ladderTargetPosition = Motor.TransientPosition;
_ladderTargetRotation = _rotationBeforeClimbing;
}
}
}
}
}
// Clamp input
Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
_lookInputVector = cameraPlanarDirection;
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
if (_lookInputVector != Vector3.zero && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
if (OrientTowardsGravity)
{
// Rotate from current up to invert gravity
currentRotation = Quaternion.FromToRotation((currentRotation * Vector3.up), -Gravity) * currentRotation;
}
break;
}
case CharacterState.Climbing:
{
switch (_climbingState)
{
case ClimbingState.Climbing:
currentRotation = _activeLadder.transform.rotation;
break;
case ClimbingState.Anchoring:
case ClimbingState.DeAnchoring:
currentRotation = Quaternion.Slerp(_anchoringStartRotation, _ladderTargetRotation, (_anchoringTimer / AnchoringDuration));
break;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
Vector3 targetMovementVelocity = Vector3.zero;
if (Motor.GroundingStatus.IsStableOnGround)
{
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, Motor.GroundingStatus.GroundNormal) * currentVelocity.magnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(Motor.GroundingStatus.GroundNormal, inputRight).normalized * _moveInputVector.magnitude;
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
targetMovementVelocity = _moveInputVector * MaxAirMoveSpeed;
// Prevent climbing on un-stable slopes with air movement
if (Motor.GroundingStatus.FoundAnyGround)
{
Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal);
}
Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity);
currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
{
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// Handle double jump
if (AllowDoubleJump)
{
if (_jumpConsumed && !_doubleJumpConsumed && (AllowJumpingWhenSliding ? !Motor.GroundingStatus.FoundAnyGround : !Motor.GroundingStatus.IsStableOnGround))
{
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (Motor.CharacterUp * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_doubleJumpConsumed = true;
_jumpedThisFrame = true;
}
}
// See if we actually are allowed to jump
if (_canWallJump ||
(!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime)))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (_canWallJump)
{
jumpDirection = _wallJumpNormal;
}
else if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground(0.1f);
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Reset wall jump
_canWallJump = false;
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
case CharacterState.Climbing:
{
currentVelocity = Vector3.zero;
switch (_climbingState)
{
case ClimbingState.Climbing:
currentVelocity = (_ladderUpDownInput * _activeLadder.transform.up).normalized * ClimbingSpeed;
break;
case ClimbingState.Anchoring:
case ClimbingState.DeAnchoring:
Vector3 tmpPosition = Vector3.Lerp(_anchoringStartPosition, _ladderTargetPosition, (_anchoringTimer / AnchoringDuration));
currentVelocity = Motor.GetVelocityForMovePosition(Motor.TransientPosition, tmpPosition, deltaTime);
break;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_doubleJumpConsumed = false;
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, 1f, 0.5f);
}
else
{
// If no obstructions, uncrouch
MeshRoot.localScale = new Vector3(1f, 1f, 1f);
_isCrouching = false;
}
}
break;
}
case CharacterState.Climbing:
{
switch (_climbingState)
{
case ClimbingState.Climbing:
// Detect getting off ladder during climbing
_activeLadder.ClosestPointOnLadderSegment(Motor.TransientPosition, out _onLadderSegmentState);
if (Mathf.Abs(_onLadderSegmentState) > 0.05f)
{
_climbingState = ClimbingState.DeAnchoring;
// If we're higher than the ladder top point
if (_onLadderSegmentState > 0)
{
_ladderTargetPosition = _activeLadder.TopReleasePoint.position;
_ladderTargetRotation = _activeLadder.TopReleasePoint.rotation;
}
// If we're lower than the ladder bottom point
else if (_onLadderSegmentState < 0)
{
_ladderTargetPosition = _activeLadder.BottomReleasePoint.position;
_ladderTargetRotation = _activeLadder.BottomReleasePoint.rotation;
}
}
break;
case ClimbingState.Anchoring:
case ClimbingState.DeAnchoring:
// Detect transitioning out from anchoring states
if (_anchoringTimer >= AnchoringDuration)
{
if (_climbingState == ClimbingState.Anchoring)
{
_climbingState = ClimbingState.Climbing;
}
else if (_climbingState == ClimbingState.DeAnchoring)
{
TransitionToState(CharacterState.Default);
}
}
// Keep track of time since we started anchoring
_anchoringTimer += deltaTime;
break;
}
break;
}
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// We can wall jump only if we are not stable on ground and are moving against an obstruction
if (AllowWallJump && !Motor.GroundingStatus.IsStableOnGround && !hitStabilityReport.IsStable)
{
_canWallJump = true;
_wallJumpNormal = hitNormal;
}
break;
}
}
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
public void PostGroundingUpdate(float deltaTime)
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}

View File

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

View File

@@ -0,0 +1,71 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace KinematicCharacterController.Walkthrough.ClimbingLadders
{
public class MyLadder : MonoBehaviour
{
// Ladder segment
public Vector3 LadderSegmentBottom;
public float LadderSegmentLength;
// Points to move to when reaching one of the extremities and moving off of the ladder
public Transform BottomReleasePoint;
public Transform TopReleasePoint;
// Gets the position of the bottom point of the ladder segment
public Vector3 BottomAnchorPoint
{
get
{
return transform.position + transform.TransformVector(LadderSegmentBottom);
}
}
// Gets the position of the top point of the ladder segment
public Vector3 TopAnchorPoint
{
get
{
return transform.position + transform.TransformVector(LadderSegmentBottom) + (transform.up * LadderSegmentLength);
}
}
public Vector3 ClosestPointOnLadderSegment(Vector3 fromPoint, out float onSegmentState)
{
Vector3 segment = TopAnchorPoint - BottomAnchorPoint;
Vector3 segmentPoint1ToPoint = fromPoint - BottomAnchorPoint;
float pointProjectionLength = Vector3.Dot(segmentPoint1ToPoint, segment.normalized);
// When higher than bottom point
if(pointProjectionLength > 0)
{
// If we are not higher than top point
if (pointProjectionLength <= segment.magnitude)
{
onSegmentState = 0;
return BottomAnchorPoint + (segment.normalized * pointProjectionLength);
}
// If we are higher than top point
else
{
onSegmentState = pointProjectionLength - segment.magnitude;
return TopAnchorPoint;
}
}
// When lower than bottom point
else
{
onSegmentState = pointProjectionLength;
return BottomAnchorPoint;
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.cyan;
Gizmos.DrawLine(BottomAnchorPoint, TopAnchorPoint);
}
}
}

View File

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

View File

@@ -0,0 +1,95 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using KinematicCharacterController;
using KinematicCharacterController.Examples;
using System.Linq;
namespace KinematicCharacterController.Walkthrough.ClimbingLadders
{
public class MyPlayer : MonoBehaviour
{
public ExampleCharacterCamera OrbitCamera;
public Transform CameraFollowPoint;
public MyCharacterController Character;
private const string MouseXInput = "Mouse X";
private const string MouseYInput = "Mouse Y";
private const string MouseScrollInput = "Mouse ScrollWheel";
private const string HorizontalInput = "Horizontal";
private const string VerticalInput = "Vertical";
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Tell camera to follow transform
OrbitCamera.SetFollowTransform(CameraFollowPoint);
// Ignore the character's collider(s) for camera obstruction checks
OrbitCamera.IgnoredColliders.Clear();
OrbitCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren<Collider>());
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
HandleCharacterInput();
}
private void LateUpdate()
{
HandleCameraInput();
}
private void HandleCameraInput()
{
// Create the look input vector for the camera
float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
// Prevent moving the camera while the cursor isn't locked
if (Cursor.lockState != CursorLockMode.Locked)
{
lookInputVector = Vector3.zero;
}
// Input for zooming the camera (disabled in WebGL because it can cause problems)
float scrollInput = -Input.GetAxis(MouseScrollInput);
#if UNITY_WEBGL
scrollInput = 0f;
#endif
// Apply inputs to the camera
OrbitCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
// Handle toggling zoom level
if (Input.GetMouseButtonDown(1))
{
OrbitCamera.TargetDistance = (OrbitCamera.TargetDistance == 0f) ? OrbitCamera.DefaultDistance : 0f;
}
}
private void HandleCharacterInput()
{
PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
// Build the CharacterInputs struct
characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
characterInputs.CameraRotation = OrbitCamera.Transform.rotation;
characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
characterInputs.ClimbLadder = Input.GetKeyUp(KeyCode.E);
// Apply inputs to character
Character.SetInputs(ref characterInputs);
}
}
}

View File

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

View File

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

View File

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

View File

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

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