- Home /
Modify movement script for continuous movement
(2D Game) How can I let the character always running into a value and with the left and right arrows only slightly reduce and increase the speed of the character. It would be very difficult to make the character slip with this controller?
THIS IS https://github.com/prime31/CharacterController2D
And why he dont trigger any events? he only debug but dont do any action.
using UnityEngine;
using System.Collections;
public class PhysicsPlayerTester : MonoBehaviour
{
// movement config
public float gravity = -25f;
public float runSpeed = 8f;
public float groundDamping = 20f; // how fast do we change direction? higher means faster
public float inAirDamping = 5f;
public float jumpHeight = 3f;
[HideInInspector]
private float normalizedHorizontalSpeed = 0;
private CharacterController2D _controller;
private Animator _animator;
private RaycastHit2D _lastControllerColliderHit;
private Vector3 _velocity;
// input
private bool _right;
private bool _left;
private bool _up;
void Awake()
{
_animator = GetComponent<Animator>();
_controller = GetComponent<CharacterController2D>();
// listen to some events for illustration purposes
_controller.onControllerCollidedEvent += onControllerCollider;
_controller.onTriggerEnterEvent += onTriggerEnterEvent;
_controller.onTriggerExitEvent += onTriggerExitEvent;
}
#region Event Listeners
void onControllerCollider( RaycastHit2D hit )
{
// logs any collider hits if uncommented. it gets noisy so it is commented out for the demo
//Debug.Log( "flags: " + _controller.collisionState + ", hit.normal: " + hit.normal );
}
void onTriggerEnterEvent( Collider2D col )
{
Debug.Log( "onTriggerEnterEvent: " + col.gameObject.name );
}
void onTriggerExitEvent( Collider2D col )
{
Debug.Log( "onTriggerExitEvent: " + col.gameObject.name );
}
#endregion
// the Update loop only gathers input. Actual movement is handled in FixedUpdate because we are using the Physics system for movement
void Update()
{
// a minor bit of trickery here. FixedUpdate sets _up to false so to ensure we never miss any jump presses we leave _up
// set to true if it was true the previous frame
_up = _up || Input.GetKeyDown( KeyCode.UpArrow );
_right = Input.GetKey( KeyCode.RightArrow );
_left = Input.GetKey( KeyCode.LeftArrow );
}
void FixedUpdate()
{
// grab our current _velocity to use as a base for all calculations
_velocity = _controller.velocity;
if( _controller.isGrounded )
_velocity.y = 0;
if( _right )
{
normalizedHorizontalSpeed = 1;
if( transform.localScale.x < 0f )
transform.localScale = new Vector3( -transform.localScale.x, transform.localScale.y, transform.localScale.z );
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Run" ) );
}
else if( _left )
{
normalizedHorizontalSpeed = -1;
if( transform.localScale.x > 0f )
transform.localScale = new Vector3( -transform.localScale.x, transform.localScale.y, transform.localScale.z );
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Run" ) );
}
else
{
normalizedHorizontalSpeed = 0;
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Idle" ) );
}
// we can only jump whilst grounded
if( _controller.isGrounded && _up )
{
_velocity.y = Mathf.Sqrt( 2f * jumpHeight * -gravity );
_animator.Play( Animator.StringToHash( "Jump" ) );
}
// apply horizontal speed smoothing it
var smoothedMovementFactor = _controller.isGrounded ? groundDamping : inAirDamping; // how fast do we change direction?
_velocity.x = Mathf.Lerp( _velocity.x, normalizedHorizontalSpeed * runSpeed, Time.fixedDeltaTime * smoothedMovementFactor );
// apply gravity before moving
_velocity.y += gravity * Time.fixedDeltaTime;
_controller.move( _velocity * Time.fixedDeltaTime );
// reset input
_up = false;
}
}
define DEBUG_CC2D_RAYS
sing UnityEngine; using System; using System.Collections.Generic;
[RequireComponent( typeof( BoxCollider2D ), typeof( Rigidbody2D ) )] public class CharacterController2D : MonoBehaviour { #region internal types
private struct CharacterRaycastOrigins
{
public Vector3 topRight;
public Vector3 topLeft;
public Vector3 bottomRight;
public Vector3 bottomLeft;
}
public class CharacterCollisionState2D
{
public bool right;
public bool left;
public bool above;
public bool below;
public bool becameGroundedThisFrame;
public bool movingDownSlope;
public float slopeAngle;
public bool hasCollision()
{
return below || right || left || above;
}
public void reset()
{
right = left = above = below = becameGroundedThisFrame = movingDownSlope = false;
slopeAngle = 0f;
}
public override string ToString()
{
return string.Format( "[CharacterCollisionState2D] r: {0}, l: {1}, a: {2}, b: {3}, movingDownSlope: {4}, angle: {5}",
right, left, above, below, movingDownSlope, slopeAngle );
}
}
#endregion
#region events, properties and fields
public event Action<RaycastHit2D> onControllerCollidedEvent;
public event Action<Collider2D> onTriggerEnterEvent;
public event Action<Collider2D> onTriggerStayEvent;
public event Action<Collider2D> onTriggerExitEvent;
/// <summary>
/// toggles if the RigidBody2D methods should be used for movement or if Transform.Translate will be used. All the usual Unity rules for physics based movement apply when true
/// such as getting your input in Update and only calling move in FixedUpdate amonst others.
/// </summary>
public bool usePhysicsForMovement = false;
[SerializeField]
[Range( 0.001f, 0.3f )]
private float _skinWidth = 0.02f;
/// <summary>
/// defines how far in from the edges of the collider rays are cast from. If cast with a 0 extent it will often result in ray hits that are
/// not desired (for example a foot collider casting horizontally from directly on the surface can result in a hit)
/// </summary>
public float skinWidth
{
get { return _skinWidth; }
set
{
_skinWidth = value;
recalculateDistanceBetweenRays();
}
}
/// <summary>
/// mask with all layers that the player should interact with
/// </summary>
public LayerMask platformMask = 0;
/// <summary>
/// mask with all layers that should act as one-way platforms. Note that one-way platforms should always be EdgeCollider2Ds. This is private because it does not support being
/// updated anytime outside of the inspector for now.
/// </summary>
[SerializeField]
private LayerMask oneWayPlatformMask = 0;
/// <summary>
/// the max slope angle that the CC2D can climb
/// </summary>
/// <value>The slope limit.</value>
[Range( 0, 90f )]
public float slopeLimit = 30f;
/// <summary>
/// curve for multiplying speed based on slope (negative = down slope and positive = up slope)
/// </summary>
public AnimationCurve slopeSpeedMultiplier = new AnimationCurve( new Keyframe( -90, 1.5f ), new Keyframe( 0, 1 ), new Keyframe( 90, 0 ) );
[Range( 2, 20 )]
public int totalHorizontalRays = 8;
[Range( 2, 20 )]
public int totalVerticalRays = 4;
/// <summary>
/// this is used to calculate the downward ray that is cast to check for slopes. We use the somewhat arbitray value 75 degrees
/// to calcuate the lenght of the ray that checks for slopes.
/// </summary>
private float _slopeLimitTangent = Mathf.Tan( 75f * Mathf.Deg2Rad );
/// <summary>
/// if true, a new GameObject named CC2DTriggerHelper will be created in Awake and latched on via a DistanceJoint2D
/// to the player so that trigger messages can be received
/// </summary>
public bool createTriggerHelperGameObject = false;
[Range( 0.8f, 0.999f )]
public float triggerHelperBoxColliderScale = 0.95f;
[HideInInspector]
public new Transform transform;
[HideInInspector]
public BoxCollider2D boxCollider;
[HideInInspector]
public Rigidbody2D rigidBody2D;
[HideInInspector]
[NonSerialized]
public CharacterCollisionState2D collisionState = new CharacterCollisionState2D();
[HideInInspector]
[NonSerialized]
public Vector3 velocity;
public bool isGrounded { get { return collisionState.below; } }
private const float kSkinWidthFloatFudgeFactor = 0.001f;
#endregion
/// <summary>
/// holder for our raycast origin corners (TR, TL, BR, BL)
/// </summary>
private CharacterRaycastOrigins _raycastOrigins;
/// <summary>
/// stores our raycast hit during movement
/// </summary>
private RaycastHit2D _raycastHit;
/// <summary>
/// stores any raycast hits that occur this frame. we have to store them in case we get a hit moving
/// horizontally and vertically so that we can send the events after all collision state is set
/// </summary>
private List<RaycastHit2D> _raycastHitsThisFrame = new List<RaycastHit2D>( 2 );
// horizontal/vertical movement data
private float _verticalDistanceBetweenRays;
private float _horizontalDistanceBetweenRays;
#region Monobehaviour
void Awake()
{
// add our one-way platforms to our normal platform mask so that we can land on them from above
platformMask |= oneWayPlatformMask;
// cache some components
transform = GetComponent<Transform>();
boxCollider = GetComponent<BoxCollider2D>();
rigidBody2D = GetComponent<Rigidbody2D>();
// we dont have a bounds property until Unity 4.5+ so we really don't need the BoxCollider2D to be active since we just use
// it for it's size and center properties so might as well remove it from play
boxCollider.enabled = false;
if( createTriggerHelperGameObject )
createTriggerHelper();
// here, we trigger our properties that have setters with bodies
skinWidth = _skinWidth;
}
public void OnTriggerEnter2D( Collider2D col )
{
if( onTriggerEnterEvent != null )
onTriggerEnterEvent( col );
}
public void OnTriggerStay2D( Collider2D col )
{
if( onTriggerStayEvent != null )
onTriggerStayEvent( col );
}
public void OnTriggerExit2D( Collider2D col )
{
if( onTriggerExitEvent != null )
onTriggerExitEvent( col );
}
#endregion
[System.Diagnostics.Conditional( "DEBUG_CC2D_RAYS" )]
private void DrawRay( Vector3 start, Vector3 dir, Color color )
{
Debug.DrawRay( start, dir, color );
}
#region Public
public void move( Vector3 deltaMovement )
{
// save off our current grounded state
var wasGroundedBeforeMoving = collisionState.below;
// clear our state
collisionState.reset();
_raycastHitsThisFrame.Clear();
primeRaycastOrigins();
// first, we check for a slope below us before moving
// only check slopes if we are going down and grounded
if( deltaMovement.y < 0 && wasGroundedBeforeMoving )
handleVerticalSlope( ref deltaMovement );
// now we check movement in the horizontal dir
if( deltaMovement.x != 0 )
moveHorizontally( ref deltaMovement );
// next, check movement in the vertical dir
if( deltaMovement.y != 0 )
moveVertically( ref deltaMovement );
// move then update our state
if( usePhysicsForMovement )
{
if UNITY_4_5 || UNITY_4_6
rigidbody2D.MovePosition( transform.position + deltaMovement );
else
rigidbody2D.velocity = deltaMovement / Time.fixedDeltaTime;
endif
velocity = rigidbody2D.velocity;
}
else
{
transform.Translate( deltaMovement, Space.World );
// only calculate velocity if we have a non-zero deltaTime
if( Time.deltaTime > 0 )
velocity = deltaMovement / Time.deltaTime;
}
// set our becameGrounded state based on the previous and current collision state
if( !wasGroundedBeforeMoving && collisionState.below )
collisionState.becameGroundedThisFrame = true;
// send off the collision events if we have a listener
if( onControllerCollidedEvent != null )
{
for( var i = 0; i < _raycastHitsThisFrame.Count; i++ )
onControllerCollidedEvent( _raycastHitsThisFrame[i] );
}
}
/// <summary>
/// this should be called anytime you have to modify the BoxCollider2D at runtime. It will recalculate the distance between the rays used for collision detection.
/// It is also used in the skinWidth setter in case it is changed at runtime.
/// </summary>
public void recalculateDistanceBetweenRays()
{
// figure out the distance between our rays in both directions
// horizontal
var colliderUseableHeight = boxCollider.size.y * Mathf.Abs( transform.localScale.y ) - ( 2f * _skinWidth );
_verticalDistanceBetweenRays = colliderUseableHeight / ( totalHorizontalRays - 1 );
// vertical
var colliderUseableWidth = boxCollider.size.x * Mathf.Abs( transform.localScale.x ) - ( 2f * _skinWidth );
_horizontalDistanceBetweenRays = colliderUseableWidth / ( totalVerticalRays - 1 );
}
/// <summary>
/// this is called internally if createTriggerHelperGameObject is true. It is provided as a public method
/// in case you want to grab a handle on the GO created to modify it in some way. Note that by default only
/// collisions with triggers will be allowed to pass through and fire the events.
/// </summary>
public GameObject createTriggerHelper()
{
var go = new GameObject( "PlayerTriggerHelper" );
go.hideFlags = HideFlags.HideInHierarchy;
go.layer = gameObject.layer;
// scale is slightly less so that we don't get trigger messages when colliding with non-triggers
go.transform.localScale = transform.localScale * triggerHelperBoxColliderScale;
go.AddComponent<CC2DTriggerHelper>().setParentCharacterController( this );
var rb = go.AddComponent<Rigidbody2D>();
rb.mass = 0f;
rb.gravityScale = 0f;
var bc = go.AddComponent<BoxCollider2D>();
bc.size = boxCollider.size;
bc.isTrigger = true;
var joint = go.AddComponent<DistanceJoint2D>();
joint.connectedBody = rigidbody2D;
joint.distance = 0f;
return go;
}
#endregion
#region Private Movement Methods
/// <summary>
/// resets the raycastOrigins to the current extents of the box collider inset by the skinWidth. It is inset
/// to avoid casting a ray from a position directly touching another collider which results in wonky normal data.
/// </summary>
private void primeRaycastOrigins()
{
var scaledColliderSize = new Vector2( boxCollider.size.x * Mathf.Abs( transform.localScale.x ), boxCollider.size.y * Mathf.Abs( transform.localScale.y ) ) / 2;
var scaledCenter = new Vector2( boxCollider.center.x * transform.localScale.x, boxCollider.center.y * transform.localScale.y );
_raycastOrigins.topRight = transform.position + new Vector3( scaledCenter.x + scaledColliderSize.x, scaledCenter.y + scaledColliderSize.y );
_raycastOrigins.topRight.x -= _skinWidth;
_raycastOrigins.topRight.y -= _skinWidth;
_raycastOrigins.topLeft = transform.position + new Vector3( scaledCenter.x - scaledColliderSize.x, scaledCenter.y + scaledColliderSize.y );
_raycastOrigins.topLeft.x += _skinWidth;
_raycastOrigins.topLeft.y -= _skinWidth;
_raycastOrigins.bottomRight = transform.position + new Vector3( scaledCenter.x + scaledColliderSize.x, scaledCenter.y -scaledColliderSize.y );
_raycastOrigins.bottomRight.x -= _skinWidth;
_raycastOrigins.bottomRight.y += _skinWidth;
_raycastOrigins.bottomLeft = transform.position + new Vector3( scaledCenter.x - scaledColliderSize.x, scaledCenter.y -scaledColliderSize.y );
_raycastOrigins.bottomLeft.x += _skinWidth;
_raycastOrigins.bottomLeft.y += _skinWidth;
}
/// <summary>
/// we have to use a bit of trickery in this one. The rays must be cast from a small distance inside of our
/// collider (skinWidth) to avoid zero distance rays which will get the wrong normal. Because of this small offset
/// we have to increase the ray distance skinWidth then remember to remove skinWidth from deltaMovement before
/// actually moving the player
/// </summary>
private void moveHorizontally( ref Vector3 deltaMovement )
{
var isGoingRight = deltaMovement.x > 0;
var rayDistance = Mathf.Abs( deltaMovement.x ) + _skinWidth;
var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;
var initialRayOrigin = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft;
for( var i = 0; i < totalHorizontalRays; i++ )
{
var ray = new Vector2( initialRayOrigin.x, initialRayOrigin.y + i * _verticalDistanceBetweenRays );
DrawRay( ray, rayDirection * rayDistance, Color.red );
_raycastHit = Physics2D.Raycast( ray, rayDirection, rayDistance, platformMask & ~oneWayPlatformMask );
if( _raycastHit )
{
// the bottom ray can hit slopes but no other ray can so we have special handling for those cases
if( i == 0 && handleHorizontalSlope( ref deltaMovement, Vector2.Angle( _raycastHit.normal, Vector2.up ), isGoingRight ) )
{
_raycastHitsThisFrame.Add( _raycastHit );
break;
}
// set our new deltaMovement and recalculate the rayDistance taking it into account
deltaMovement.x = _raycastHit.point.x - ray.x;
rayDistance = Mathf.Abs( deltaMovement.x );
// remember to remove the skinWidth from our deltaMovement
if( isGoingRight )
{
deltaMovement.x -= _skinWidth;
collisionState.right = true;
}
else
{
deltaMovement.x += _skinWidth;
collisionState.left = true;
}
_raycastHitsThisFrame.Add( _raycastHit );
// we add a small fudge factor for the float operations here. if our rayDistance is smaller
// than the width + fudge bail out because we have a direct impact
if( rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor )
break;
}
}
}
private bool handleHorizontalSlope( ref Vector3 deltaMovement, float angle, bool isGoingRight )
{
Debug.Log( "angle: " + angle );
// disregard 90 degree angles (walls)
if( Mathf.RoundToInt( angle ) == 90 )
return false;
// if we can walk on slopes and our angle is small enough we need to move up
if( angle < slopeLimit )
{
// we only need to adjust the y movement if we are not jumping
// TODO: this uses a magic number which isn't ideal!
if( deltaMovement.y < 0.07f )
{
// apply the slopeModifier to slow our movement up the slope
var slopeModifier = slopeSpeedMultiplier.Evaluate( angle );
deltaMovement.x *= slopeModifier;
// we dont set collisions on the sides for this since a slope is not technically a side collision
if( isGoingRight )
{
deltaMovement.x -= _skinWidth;
}
else
{
deltaMovement.x += _skinWidth;
}
// smooth y movement when we climb. we make the y movement equivalent to the actual y location that corresponds
// to our new x location using our good friend Pythagoras
deltaMovement.y = Mathf.Abs( Mathf.Tan( angle * Mathf.Deg2Rad ) * deltaMovement.x );
collisionState.below = true;
}
}
else // too steep. get out of here
{
Debug.Log( "TOO STEEP" );
deltaMovement.x = 0;
}
return true;
}
private void moveVertically( ref Vector3 deltaMovement )
{
var isGoingUp = deltaMovement.y > 0;
var rayDistance = Mathf.Abs( deltaMovement.y ) + _skinWidth;
var rayDirection = isGoingUp ? Vector2.up : -Vector2.up;
var initialRayOrigin = isGoingUp ? _raycastOrigins.topLeft : _raycastOrigins.bottomLeft;
// apply our horizontal deltaMovement here so that we do our raycast from the actual position we would be in if we had moved
initialRayOrigin.x += deltaMovement.x;
// if we are moving up, we should ignore the layers in oneWayPlatformMask
var mask = platformMask;
if( isGoingUp )
mask &= ~oneWayPlatformMask;
for( var i = 0; i < totalVerticalRays; i++ )
{
var ray = new Vector2( initialRayOrigin.x + i * _horizontalDistanceBetweenRays, initialRayOrigin.y );
DrawRay( ray, rayDirection * rayDistance, Color.red );
_raycastHit = Physics2D.Raycast( ray, rayDirection, rayDistance, mask );
if( _raycastHit )
{
// set our new deltaMovement and recalculate the rayDistance taking it into account
deltaMovement.y = _raycastHit.point.y - ray.y;
rayDistance = Mathf.Abs( deltaMovement.y );
// remember to remove the skinWidth from our deltaMovement
if( isGoingUp )
{
deltaMovement.y -= _skinWidth;
collisionState.above = true;
}
else
{
deltaMovement.y += _skinWidth;
collisionState.below = true;
}
_raycastHitsThisFrame.Add( _raycastHit );
// we add a small fudge factor for the float operations here. if our rayDistance is smaller
// than the width + fudge bail out because we have a direct impact
if( rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor )
return;
}
}
}
/// <summary>
/// checks the center point under the BoxCollider2D for a slope. If it finds one then the deltaMovement is adjusted so that
/// the player stays grounded and the slopeSpeedModifier is taken into account to speed up movement.
/// </summary>
/// <param name="deltaMovement">Delta movement.</param>
private void handleVerticalSlope( ref Vector3 deltaMovement )
{
// slope check from the center of our collider
var centerOfCollider = ( _raycastOrigins.bottomLeft.x + _raycastOrigins.bottomRight.x ) * 0.5f;
var rayDirection = -Vector2.up;
// the ray distance is based on our slopeLimit
var slopeCheckRayDistance = _slopeLimitTangent * ( _raycastOrigins.bottomRight.x - centerOfCollider );
var slopeRay = new Vector2( centerOfCollider, _raycastOrigins.bottomLeft.y );
DrawRay( slopeRay, rayDirection * slopeCheckRayDistance, Color.yellow );
_raycastHit = Physics2D.Raycast( slopeRay, rayDirection, slopeCheckRayDistance, platformMask );
if( _raycastHit )
{
// bail out if we have no slope
var angle = Vector2.Angle( _raycastHit.normal, Vector2.up );
if( angle == 0 )
return;
// we are moving down the slope if our normal and movement direction are in the same x direction
var isMovingDownSlope = Mathf.Sign( _raycastHit.normal.x ) == Mathf.Sign( deltaMovement.x );
if( isMovingDownSlope )
{
// going down we want to speed up in most cases so the slopeSpeedMultiplier curve should be > 1 for negative angles
var slopeModifier = slopeSpeedMultiplier.Evaluate( -angle );
deltaMovement.y = _raycastHit.point.y - slopeRay.y;
deltaMovement.x *= slopeModifier;
collisionState.movingDownSlope = true;
collisionState.slopeAngle = angle;
}
}
}
#endregion
}
I'm sorry but I do not know programming, I'm learning, I tried tinkering for hours and when I was getting close to something, gave error. If someone could help me I appreciate it.
Answer by Swaggre · May 02, 2014 at 08:02 PM
Do you mean Stick Run style controls?
I did not test this but it should work. And it's not perfect, could be a lot shorter :S
var Character : GameObject;
private var speed : float;
var minSpeed : float;
var maxSpeed : float;
var accelSpeed : float = 0.02;
// Use this for initialization
function Start () {
speed = (maxSpeed + minSpeed) * 0.5;
}
// Update is called once per frame
function Update () {
Character.transform.Translate(speed,0,0);
if(Input.GetKey(KeyCode.RightArrow)){//increase speed
if(speed < maxSpeed){
speed += accelSpeed;
}
}
if(Input.GetKey(KeyCode.LeftArrow)){//decrease speed
if(speed > minSpeed){
speed -= accelSpeed;
}
}
}
I think on line 9 should be brackets for working it properly and on line 23 should be $$anonymous$$Speed ins$$anonymous$$d of max.
Thank you for your help, already cleared quite how to continue the script. I tsted it and when the character jumps we can change the direction, I will try to solve it.
Your answer
Follow this Question
Related Questions
Problem detecting ground with collision flags and controller.move (vector3.down) 1 Answer
Character Controller with Relative Gravity? 1 Answer
Face Direction Movement using CharacterController 1 Answer
Very Simple Left and Right Movement Script [OR] disable dravity on CharacterController? 2 Answers
Disable diagonal movement on 2D character controller 1 Answer