SimpleTankController

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
m (Text replace - "</javascript>" to "</syntaxhighlight>")
(converted code to C# and removed parent to ground and other extra methods to keep things simple)
 
Line 1: Line 1:
 
== Description ==
 
== Description ==
A non-physics simple tank movement controller that includes acceleration and optional auto-parenting of the object directly below the controlled object.
+
A simple non-physics based tank movement controller.
  
 
== Usage ==
 
== Usage ==
Place this script onto a gameobject. If it currently does not have a CharacterController one will be added. Horizontal/Vertical inputs are used for movement. Tweak various public variables to obtain desired behaviour.  Turning on the Parent To Ground option will enable the controlled object to step on a moving object and begin moving with that object until the controlled object is moved off the moving object.  NOTE: It is important that you remove any colliders from the gameobject being controlled.
+
Attach the script to a game object. A CharacterController component will be automatically added if one does not already exist. The forward and backwards motions is controlled by the vertical input axis, while rotation is controlled by horizontal input axis. Tweak the various variables in the inspector to obtain a desired behaviour.
  
== JavaScript - SimpleTankController.js ==
+
== SimpleTankController.cs ==
<syntaxhighlight lang="javascript">
+
<syntaxhighlight lang="csharp">
enum direction {forward, reverse, stop};
+
using UnityEngine;
  
public var parentToGround : boolean = false; // automatically updates the parent to the object below to allow for moving objects to affect object
+
[RequireComponent (typeof (CharacterController))]
public var groundRayOffset : Vector3 = Vector3.zero; // adjust where the parentToGround ray is cast from
+
public partial class SimpleTankController : MonoBehaviour
public var groundRayCastLength : float = 1.0; // how far does the parentToGround ray cast
+
{
 +
    public enum MoveDirection { Forward, Reverse, Stop }
  
public var topSpeedForward : float = 3.0; // top speed of forward
+
    [Header ("Motion")]
public var topSpeedReverse : float = 1.0; // top speed of reverse
+
public var accelerationRate : float = 3; // rate at which top speed is reached
+
public var decelerationRate : float = 2; // rate at which speed is lost when not accelerating
+
public var brakingDecelerationRate : float = 4; // rate at which speed is lost when braking (input opposite of current direction)
+
public var stoppedTurnRate : float = 2.0; // rate at which object turns when stopped
+
public var topSpeedForwardTurnRate : float = 1.0; // rate at which object turns at top forward speed
+
public var topSpeedReverseTurnRate : float = 2.0; // rate at which object turns at top reverse speed
+
public var gravity = 10.0; // gravity for object
+
public var stickyThrottle : boolean = false; // true to disable loss of speed if no input is provided
+
public var stickyThrottleDelay : float = .35; // delay between change of direction when sticky throttle is enabled
+
  
private var currentSpeed : float = 0.0; // stores current speed
+
    [SerializeField] private float topForwardSpeed = 3.0f;
private var currentTopSpeed : float = topSpeedForward; // stores top speed of current direction
+
    [SerializeField] private float topReverseSpeed = 1.0f;
private var currentDirection : direction = direction.stop; // stores current direction
+
private var isBraking : boolean = false; // true if input is braking
+
private var isAccelerating : boolean = false; // true if input is accelerating
+
private var stickyDelayCount : float = 9999.0; // current sticky delay count
+
private var characterController : CharacterController;
+
  
function Start() {
+
    [Tooltip ("Rate at which top speed is reached.")]
     characterController = GetComponent(CharacterController);
+
     [SerializeField] private float acceleration = 3.0f;
}
+
  
function FixedUpdate() {
+
     [Tooltip ("Rate at which speed is lost when not accelerating.")]
+
    [SerializeField] private float deceleration = 2.0f;
// direction to move this update
+
var moveDirection : Vector3 = Vector3.zero;
+
// direction requested this update
+
var requestedDirection : direction = direction.stop;
+
+
     if(characterController.isGrounded == true) {
+
// simulate loss of turn rate at speed
+
var currentTurnRate = Mathf.Lerp((currentDirection == direction.forward ? topSpeedForwardTurnRate : topSpeedReverseTurnRate), stoppedTurnRate, (1- (currentSpeed/currentTopSpeed)));
+
transform.eulerAngles.y += Input.GetAxis("Horizontal") * currentTurnRate;
+
  
// based on input, determine requested action
+
    [Tooltip ("Rate at which speed is lost when braking (input opposite of current direction).")]
if (Input.GetAxis("Vertical") > 0) { // requesting forward
+
    [SerializeField] private float brakingDeceleration = 4.0f;
requestedDirection = direction.forward;
+
isAccelerating = true;
+
} else if (Input.GetAxis("Vertical") < 0) { // requesting reverse
+
requestedDirection = direction.reverse;
+
isAccelerating = true;
+
} else {
+
requestedDirection = currentDirection;
+
isAccelerating = false;
+
}
+
+
isBraking = false;
+
+
if (currentDirection == direction.stop) { // engage new direction
+
stickyDelayCount += Time.deltaTime;
+
// if we are not sticky throttle or if we have hit the delay then change direction
+
if (!stickyThrottle || stickyDelayCount > stickyThrottleDelay) {
+
// make sure we can go in the requsted direction
+
if (requestedDirection == direction.reverse && topSpeedReverse > 0 ||
+
requestedDirection == direction.forward && topSpeedForward > 0) {
+
+
currentDirection = requestedDirection;
+
}
+
}
+
} else if (currentDirection != requestedDirection) { // requesting a change of direction, but not stopped so we are braking
+
isBraking = true;
+
isAccelerating = false;
+
}
+
+
// setup top speeds and move direction
+
if (currentDirection == direction.forward) {
+
moveDirection = Vector3.forward;
+
currentTopSpeed = topSpeedForward;
+
} else if (currentDirection == direction.reverse) {
+
moveDirection = (-1 * Vector3.forward);
+
currentTopSpeed = topSpeedReverse;
+
} else if (currentDirection == direction.stop) {
+
moveDirection = Vector3.zero;
+
}
+
  
if (isAccelerating) {
+
    [Tooltip ("Maintain speed when no input is provided.")]
//if we havent hit top speed yet, accelerate
+
     [SerializeField] private bool stickyThrottle = false;
  if (currentSpeed < currentTopSpeed){
+
currentSpeed += (accelerationRate * Time.deltaTime);      
+
  }
+
} else {
+
// if we are not accelerating and still have some speed, decelerate
+
if (currentSpeed > 0) {
+
// adjust deceleration for braking and implement sticky throttle
+
var currentDecelerationRate : float = (isBraking ? brakingDecelerationRate : (!stickyThrottle ? decelerationRate : 0));
+
currentSpeed -= (currentDecelerationRate * Time.deltaTime);
+
}
+
}
+
  
// if our speed is below zero, stop and initialize
+
    [Tooltip ("Delay between change of direction when sticky throttle is enabled.")]
if (currentSpeed < 0 || (currentSpeed == 0 && currentDirection != direction.stop)) {
+
     [SerializeField] private float stickyThrottleDelay = 0.35f;
SetStopped();
+
} else if (currentSpeed > currentTopSpeed) { // limit the speed to the current top speed
+
currentSpeed = currentTopSpeed;
+
}
+
+
        moveDirection = transform.TransformDirection(moveDirection);
+
}
+
+
// implement gravity so we can stay grounded
+
     moveDirection.y -= gravity * Time.deltaTime;
+
moveDirection.z = moveDirection.z * (Time.deltaTime * currentSpeed);
+
moveDirection.x = moveDirection.x * (Time.deltaTime * currentSpeed);
+
    characterController.Move(moveDirection);
+
+
if (parentToGround) {
+
var hit : RaycastHit;
+
var down = transform.TransformDirection (-1 * Vector3.up);
+
+
// cast the bumper ray out from bottom and check to see if there is anything below
+
if (Physics.Raycast (transform.TransformPoint(groundRayOffset), down, hit, groundRayCastLength)) {
+
// clamp wanted position to hit position
+
transform.parent = hit.transform;
+
}
+
+
// if we are currently stopped, move just a tad to allow for collisions while parent is moving
+
if (currentDirection == direction.stop) {
+
characterController.SimpleMove(transform.TransformDirection(Vector3.forward) * 0.000000000001);
+
}
+
}
+
+
}
+
  
function GetCurrentSpeed() {
+
    [SerializeField] private float gravity = 10.0f;
return currentSpeed;
+
}
+
  
function GetCurrentTopSpeed() {
+
    [Header ("Rotation")]
return currentTopSpeed;
+
}
+
  
function GetCurrentDirection() {
+
    [SerializeField] private float topForwardTurnRate = 1.0f;
return currentDirection;
+
    [SerializeField] private float topReverseTurnRate = 2.0f;
}
+
    [SerializeField] private float stoppedTurnRate = 2.0f;
  
function GetIsBraking() {
+
    private float currentSpeed;
return isBraking;
+
    private float currentTopSpeed;
}
+
    private MoveDirection currentDirection = MoveDirection.Stop;
 +
    private bool isBraking = false;
 +
    private bool isAccelerating = false;
 +
    private float stickyDelayCount = 9999f;
 +
    private CharacterController m_Controller;
 +
    private Transform m_Transform;
  
function GetIsAccelerating() {
 
return isAccelerating;
 
}
 
  
function SetStopped() {
+
    private void Awake ()
currentSpeed = 0;
+
    {
currentDirection = direction.stop;
+
        // Performance optimization - cache transform reference.
isAccelerating = false;
+
        m_Transform = GetComponent<Transform> ();
isBraking = false;
+
stickyDelayCount = 0;
+
}
+
  
@script RequireComponent(CharacterController)
+
        m_Controller = GetComponent<CharacterController> ();
 +
        currentTopSpeed = topForwardSpeed;
 +
    }
 +
 
 +
 
 +
    private void FixedUpdate ()
 +
    {
 +
        // Direction to move this update.
 +
        Vector3 moveDirection = Vector3.zero;
 +
 
 +
        // Direction requested this update.
 +
        MoveDirection requestedDirection = MoveDirection.Stop;
 +
 
 +
        if (m_Controller.isGrounded)
 +
        {
 +
            // Simulate loss of turn rate at speed.
 +
            float currentTurnRate = Mathf.Lerp (currentDirection == MoveDirection.Forward ?
 +
                topForwardTurnRate : topReverseTurnRate, stoppedTurnRate, 1 - (currentSpeed / currentTopSpeed));
 +
 
 +
            Vector3 angles = m_Transform.eulerAngles;
 +
            angles.y += (Input.GetAxis ("Horizontal") * currentTurnRate);
 +
            m_Transform.eulerAngles = angles;
 +
 
 +
            // Based on input, determine requested action.
 +
            if (Input.GetAxis ("Vertical") > 0) // Requesting forward.
 +
            {
 +
                requestedDirection = MoveDirection.Forward;
 +
                isAccelerating = true;
 +
            }
 +
            else
 +
            {
 +
                if (Input.GetAxis ("Vertical") < 0) // Requesting reverse.
 +
                {
 +
                    requestedDirection = MoveDirection.Reverse;
 +
                    isAccelerating = true;
 +
                }
 +
                else
 +
                {
 +
                    requestedDirection = currentDirection;
 +
                    isAccelerating = false;
 +
                }
 +
            }
 +
 
 +
            isBraking = false;
 +
 
 +
            if (currentDirection == MoveDirection.Stop)
 +
            {
 +
                stickyDelayCount = stickyDelayCount + Time.deltaTime;
 +
 
 +
                // If we are not sticky throttle or if we have hit the delay then change direction.
 +
                if (!stickyThrottle || (stickyDelayCount > stickyThrottleDelay))
 +
                {
 +
                    // Make sure we can go in the requested direction.
 +
                    if (((requestedDirection == MoveDirection.Reverse) && (topReverseSpeed > 0))
 +
                        || ((requestedDirection == MoveDirection.Forward) && (topForwardSpeed > 0)))
 +
                    {
 +
                        currentDirection = requestedDirection;
 +
                    }
 +
                }
 +
            }
 +
            else
 +
            {
 +
                // Requesting a change of direction, but not stopped so we are braking.
 +
                if (currentDirection != requestedDirection)
 +
                {
 +
                    isBraking = true;
 +
                    isAccelerating = false;
 +
                }
 +
            }
 +
 
 +
            // Setup top speeds and move direction.
 +
            if (currentDirection == MoveDirection.Forward)
 +
            {
 +
                moveDirection = Vector3.forward;
 +
                currentTopSpeed = topForwardSpeed;
 +
            }
 +
            else
 +
            {
 +
                if (currentDirection == MoveDirection.Reverse)
 +
                {
 +
                    moveDirection = -1 * Vector3.forward;
 +
                    currentTopSpeed = topReverseSpeed;
 +
                }
 +
                else
 +
                {
 +
                    if (currentDirection == MoveDirection.Stop)
 +
                    {
 +
                        moveDirection = Vector3.zero;
 +
                    }
 +
                }
 +
            }
 +
 
 +
            if (isAccelerating)
 +
            {
 +
                // If we haven't hit top speed yet, accelerate.
 +
                if (currentSpeed < currentTopSpeed)
 +
                {
 +
                    currentSpeed = currentSpeed + (acceleration * Time.deltaTime);
 +
                }
 +
            }
 +
            else
 +
            {
 +
                // If we are not accelerating and still have some speed, decelerate.
 +
                if (currentSpeed > 0)
 +
                {
 +
                    // Adjust deceleration for braking and implement sticky throttle.
 +
                    float currentDecelerationRate = isBraking ? brakingDeceleration : (!stickyThrottle ? deceleration : 0);
 +
                    currentSpeed = currentSpeed - (currentDecelerationRate * Time.deltaTime);
 +
                }
 +
            }
 +
 
 +
            // If our speed is below zero, stop and initialize.
 +
            if ((currentSpeed < 0) || ((currentSpeed == 0) && (currentDirection != MoveDirection.Stop)))
 +
            {
 +
                SetStopped ();
 +
            }
 +
            else
 +
            {
 +
                // Limit the speed to the current top speed.
 +
                if (currentSpeed > currentTopSpeed)
 +
                {
 +
                    currentSpeed = currentTopSpeed;
 +
                }
 +
            }
 +
 
 +
            moveDirection = m_Transform.TransformDirection (moveDirection);
 +
        }
 +
 
 +
        // Implement gravity so we can stay grounded.
 +
        moveDirection.y = moveDirection.y - (gravity * Time.deltaTime);
 +
 
 +
        moveDirection.z = moveDirection.z * (Time.deltaTime * currentSpeed);
 +
        moveDirection.x = moveDirection.x * (Time.deltaTime * currentSpeed);
 +
        m_Controller.Move (moveDirection);
 +
    }
 +
 
 +
 
 +
    private void SetStopped ()
 +
    {
 +
        currentSpeed = 0;
 +
        currentDirection = MoveDirection.Stop;
 +
        isAccelerating = false;
 +
        isBraking = false;
 +
        stickyDelayCount = 0;
 +
    }
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 01:38, 7 September 2018

[edit] Description

A simple non-physics based tank movement controller.

[edit] Usage

Attach the script to a game object. A CharacterController component will be automatically added if one does not already exist. The forward and backwards motions is controlled by the vertical input axis, while rotation is controlled by horizontal input axis. Tweak the various variables in the inspector to obtain a desired behaviour.

[edit] SimpleTankController.cs

using UnityEngine;
 
[RequireComponent (typeof (CharacterController))]
public partial class SimpleTankController : MonoBehaviour
{
    public enum MoveDirection { Forward, Reverse, Stop }
 
    [Header ("Motion")]
 
    [SerializeField] private float topForwardSpeed = 3.0f;
    [SerializeField] private float topReverseSpeed = 1.0f;
 
    [Tooltip ("Rate at which top speed is reached.")]
    [SerializeField] private float acceleration = 3.0f;
 
    [Tooltip ("Rate at which speed is lost when not accelerating.")]
    [SerializeField] private float deceleration = 2.0f;
 
    [Tooltip ("Rate at which speed is lost when braking (input opposite of current direction).")]
    [SerializeField] private float brakingDeceleration = 4.0f;
 
    [Tooltip ("Maintain speed when no input is provided.")]
    [SerializeField] private bool stickyThrottle = false;
 
    [Tooltip ("Delay between change of direction when sticky throttle is enabled.")]
    [SerializeField] private float stickyThrottleDelay = 0.35f;
 
    [SerializeField] private float gravity = 10.0f;
 
    [Header ("Rotation")]
 
    [SerializeField] private float topForwardTurnRate = 1.0f;
    [SerializeField] private float topReverseTurnRate = 2.0f;
    [SerializeField] private float stoppedTurnRate = 2.0f;
 
    private float currentSpeed;
    private float currentTopSpeed;
    private MoveDirection currentDirection = MoveDirection.Stop;
    private bool isBraking = false;
    private bool isAccelerating = false;
    private float stickyDelayCount = 9999f;
    private CharacterController m_Controller;
    private Transform m_Transform;
 
 
    private void Awake ()
    {
        // Performance optimization - cache transform reference.
        m_Transform = GetComponent<Transform> ();
 
        m_Controller = GetComponent<CharacterController> ();
        currentTopSpeed = topForwardSpeed;
    }
 
 
    private void FixedUpdate ()
    {
        // Direction to move this update.
        Vector3 moveDirection = Vector3.zero;
 
        // Direction requested this update.
        MoveDirection requestedDirection = MoveDirection.Stop;
 
        if (m_Controller.isGrounded)
        {
            // Simulate loss of turn rate at speed.
            float currentTurnRate = Mathf.Lerp (currentDirection == MoveDirection.Forward ?
                topForwardTurnRate : topReverseTurnRate, stoppedTurnRate, 1 - (currentSpeed / currentTopSpeed));
 
            Vector3 angles = m_Transform.eulerAngles;
            angles.y += (Input.GetAxis ("Horizontal") * currentTurnRate);
            m_Transform.eulerAngles = angles;
 
            // Based on input, determine requested action.
            if (Input.GetAxis ("Vertical") > 0) // Requesting forward.
            {
                requestedDirection = MoveDirection.Forward;
                isAccelerating = true;
            }
            else
            {
                if (Input.GetAxis ("Vertical") < 0) // Requesting reverse.
                {
                    requestedDirection = MoveDirection.Reverse;
                    isAccelerating = true;
                }
                else
                {
                    requestedDirection = currentDirection;
                    isAccelerating = false;
                }
            }
 
            isBraking = false;
 
            if (currentDirection == MoveDirection.Stop)
            {
                stickyDelayCount = stickyDelayCount + Time.deltaTime;
 
                // If we are not sticky throttle or if we have hit the delay then change direction.
                if (!stickyThrottle || (stickyDelayCount > stickyThrottleDelay))
                {
                    // Make sure we can go in the requested direction.
                    if (((requestedDirection == MoveDirection.Reverse) && (topReverseSpeed > 0))
                        || ((requestedDirection == MoveDirection.Forward) && (topForwardSpeed > 0)))
                    {
                        currentDirection = requestedDirection;
                    }
                }
            }
            else
            {
                // Requesting a change of direction, but not stopped so we are braking.
                if (currentDirection != requestedDirection)
                {
                    isBraking = true;
                    isAccelerating = false;
                }
            }
 
            // Setup top speeds and move direction.
            if (currentDirection == MoveDirection.Forward)
            {
                moveDirection = Vector3.forward;
                currentTopSpeed = topForwardSpeed;
            }
            else
            {
                if (currentDirection == MoveDirection.Reverse)
                {
                    moveDirection = -1 * Vector3.forward;
                    currentTopSpeed = topReverseSpeed;
                }
                else
                {
                    if (currentDirection == MoveDirection.Stop)
                    {
                        moveDirection = Vector3.zero;
                    }
                }
            }
 
            if (isAccelerating)
            {
                // If we haven't hit top speed yet, accelerate.
                if (currentSpeed < currentTopSpeed)
                {
                    currentSpeed = currentSpeed + (acceleration * Time.deltaTime);
                }
            }
            else
            {
                // If we are not accelerating and still have some speed, decelerate.
                if (currentSpeed > 0)
                {
                    // Adjust deceleration for braking and implement sticky throttle.
                    float currentDecelerationRate = isBraking ? brakingDeceleration : (!stickyThrottle ? deceleration : 0);
                    currentSpeed = currentSpeed - (currentDecelerationRate * Time.deltaTime);
                }
            }
 
            // If our speed is below zero, stop and initialize.
            if ((currentSpeed < 0) || ((currentSpeed == 0) && (currentDirection != MoveDirection.Stop)))
            {
                SetStopped ();
            }
            else
            {
                // Limit the speed to the current top speed.
                if (currentSpeed > currentTopSpeed)
                {
                    currentSpeed = currentTopSpeed;
                }
            }
 
            moveDirection = m_Transform.TransformDirection (moveDirection);
        }
 
        // Implement gravity so we can stay grounded.
        moveDirection.y = moveDirection.y - (gravity * Time.deltaTime);
 
        moveDirection.z = moveDirection.z * (Time.deltaTime * currentSpeed);
        moveDirection.x = moveDirection.x * (Time.deltaTime * currentSpeed);
        m_Controller.Move (moveDirection);
    }
 
 
    private void SetStopped ()
    {
        currentSpeed = 0;
        currentDirection = MoveDirection.Stop;
        isAccelerating = false;
        isBraking = false;
        stickyDelayCount = 0;
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox