scripts
#wip
these are scripts that i like to use in all my projects, so i've compiled them here for easy access and of course to provide resources for fellow unity users :B
- animated textures
- spring bones
- spring bone manager
- spring bone collider
- third person controller
animated textures
this uses sprite sheets to animate a texture. how i use this to animate my characters blinking is make the face a separate object and create a 5-frame sprite sheet of the face, with one frame blinking.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//AnimatedTexture
public class AnimatedTexture : MonoBehaviour
{
//vars for the whole sheet
public int colCount = 4;
public int rowCount = 4;
//vars for animation
public int rowNumber = 0; //Zero Indexed
public int colNumber = 0; //Zero Indexed
public int totalCells = 4;
public int fps = 10;
//private vars
private Vector2 offset;
private Renderer renderer;
//Start
void Start() { renderer = this.GetComponent(); }
//Update
void Update() { SetSpriteAnimation(colCount, rowCount, rowNumber, colNumber, totalCells, fps); }
//SetSpriteAnimation
void SetSpriteAnimation(int colCount, int rowCount, int rowNumber, int colNumber, int totalCells, int fps)
{
// Calculate index
int index = (int)(Time.time * fps);
// Repeat when exhausting all cells
index = index % totalCells;
// Size of every cell
float sizeX = 1.0f / colCount;
float sizeY = 1.0f / rowCount;
Vector2 size = new Vector2(sizeX, sizeY);
// split into horizontal and vertical index
var uIndex = index % colCount;
var vIndex = index / colCount;
// build offset
// v coordinate is the bottom of the image in opengl so we need to invert.
float offsetX = (uIndex + colNumber) * size.x;
float offsetY = (1.0f - size.y) - (vIndex + rowNumber) * size.y;
Vector2 offset = new Vector2(offsetX, offsetY);
renderer.material.SetTextureOffset("_MainTex", offset);
renderer.material.SetTextureScale("_MainTex", size);
}
} spring bones
this is taken from the default unity-chan asset, but i isolated it since i just wanted this script :B there are three parts to spring bones - the spring bone manager, the spring bone script itself, and the spring bone collider script. it takes a bit of adjusting of the values to make the spring bone behave nicely:) once you've assigned the bones in your armature, then add it to the spring bone manager on your character.
//SpringBone.cs for unity-chan!
//
//Original Script is here:
//ricopin / SpringBone.cs
//Rocket Jump : http://rocketjump.skr.jp/unity3d/109/
//https://twitter.com/ricopin416
//
//Revised by N.Kobayashi 2014/06/20
//
using UnityEngine;
using System.Collections;
namespace UnityChan
{
public class SpringBone : MonoBehaviour
{
//次のボーン
public Transform child;
//ボーンの向き
public Vector3 boneAxis = new Vector3 (-1.0f, 0.0f, 0.0f);
public float radius = 0.05f;
//各SpringBoneに設定されているstiffnessForceとdragForceを使用するか?
public bool isUseEachBoneForceSettings = false;
//バネが戻る力
public float stiffnessForce = 0.01f;
//力の減衰力
public float dragForce = 0.4f;
public Vector3 springForce = new Vector3 (0.0f, -0.0001f, 0.0f);
public SpringCollider[] colliders;
public bool debug = true;
//Kobayashi:Thredshold Starting to activate activeRatio
public float threshold = 0.01f;
private float springLength;
private Quaternion localRotation;
private Transform trs;
private Vector3 currTipPos;
private Vector3 prevTipPos;
//Kobayashi
private Transform org;
//Kobayashi:Reference for "SpringManager" component with unitychan
private SpringManager managerRef;
private void Awake ()
{
trs = transform;
localRotation = transform.localRotation;
//Kobayashi:Reference for "SpringManager" component with unitychan
// GameObject.Find("unitychan_dynamic").GetComponent<SpringManager>();
managerRef = GetParentSpringManager (transform);
}
private SpringManager GetParentSpringManager (Transform t)
{
var springManager = t.GetComponent<SpringManager> ();
if (springManager != null)
return springManager;
if (t.parent != null) {
return GetParentSpringManager (t.parent);
}
return null;
}
private void Start ()
{
springLength = Vector3.Distance (trs.position, child.position);
currTipPos = child.position;
prevTipPos = child.position;
}
public void UpdateSpring ()
{
//Kobayashi
org = trs;
//回転をリセット
trs.localRotation = Quaternion.identity * localRotation;
float sqrDt = Time.deltaTime * Time.deltaTime;
//stiffness
Vector3 force = trs.rotation * (boneAxis * stiffnessForce) / sqrDt;
//drag
force += (prevTipPos - currTipPos) * dragForce / sqrDt;
force += springForce / sqrDt;
//前フレームと値が同じにならないように
Vector3 temp = currTipPos;
//verlet
currTipPos = (currTipPos - prevTipPos) + currTipPos + (force * sqrDt);
//長さを元に戻す
currTipPos = ((currTipPos - trs.position).normalized * springLength) + trs.position;
//衝突判定
for (int i = 0; i < colliders.Length; i++) {
if (Vector3.Distance (currTipPos, colliders [i].transform.position) <= (radius + colliders [i].radius)) {
Vector3 normal = (currTipPos - colliders [i].transform.position).normalized;
currTipPos = colliders [i].transform.position + (normal * (radius + colliders [i].radius));
currTipPos = ((currTipPos - trs.position).normalized * springLength) + trs.position;
}
}
prevTipPos = temp;
//回転を適用;
Vector3 aimVector = trs.TransformDirection (boneAxis);
Quaternion aimRotation = Quaternion.FromToRotation (aimVector, currTipPos - trs.position);
//original
//trs.rotation = aimRotation * trs.rotation;
//Kobayahsi:Lerp with mixWeight
Quaternion secondaryRotation = aimRotation * trs.rotation;
trs.rotation = Quaternion.Lerp (org.rotation, secondaryRotation, managerRef.dynamicRatio);
}
private void OnDrawGizmos ()
{
if (debug) {
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere (currTipPos, radius);
}
}
}
}spring bone manager
//SpingManager.cs for unity-chan!
//
//Original Script is here:
//ricopin / SpingManager.cs
//Rocket Jump : http://rocketjump.skr.jp/unity3d/109/
//https://twitter.com/ricopin416
//
//Revised by N.Kobayashi 2014/06/24
// Y.Ebata
//
using UnityEngine;
using System.Collections;
namespace UnityChan
{
public class SpringManager : MonoBehaviour
{
//Kobayashi
// DynamicRatio is paramater for activated level of dynamic animation
public float dynamicRatio = 1.0f;
//Ebata
public float stiffnessForce;
public AnimationCurve stiffnessCurve;
public float dragForce;
public AnimationCurve dragCurve;
public SpringBone[] springBones;
void Start ()
{
UpdateParameters ();
}
void Update ()
{
#if UNITY_EDITOR
//Kobayashi
if(dynamicRatio >= 1.0f)
dynamicRatio = 1.0f;
else if(dynamicRatio <= 0.0f)
dynamicRatio = 0.0f;
//Ebata
UpdateParameters();
#endif
}
private void LateUpdate ()
{
//Kobayashi
if (dynamicRatio != 0.0f) {
for (int i = 0; i < springBones.Length; i++) {
if (dynamicRatio > springBones [i].threshold) {
springBones [i].UpdateSpring ();
}
}
}
}
private void UpdateParameters ()
{
UpdateParameter ("stiffnessForce", stiffnessForce, stiffnessCurve);
UpdateParameter ("dragForce", dragForce, dragCurve);
}
private void UpdateParameter (string fieldName, float baseValue, AnimationCurve curve)
{
var start = curve.keys [0].time;
var end = curve.keys [curve.length - 1].time;
//var step = (end - start) / (springBones.Length - 1);
var prop = springBones [0].GetType ().GetField (fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
for (int i = 0; i < springBones.Length; i++) {
//Kobayashi
if (!springBones [i].isUseEachBoneForceSettings) {
var scale = curve.Evaluate (start + (end - start) * i / (springBones.Length - 1));
prop.SetValue (springBones [i], baseValue * scale);
}
}
}
}
}
spring bone collider
//SpringCollider for unity-chan!
//
//Original Script is here:
//ricopin / SpringCollider.cs
//Rocket Jump : http://rocketjump.skr.jp/unity3d/109/
//https://twitter.com/ricopin416
//
using UnityEngine;
using System.Collections;
namespace UnityChan
{
public class SpringCollider : MonoBehaviour
{
//半径
public float radius = 0.5f;
private void OnDrawGizmosSelected ()
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere (transform.position, radius);
}
}
}
third person controller
i recommend taking a look at this youtube tutorial series for how to set this controller up in unity, since these are just the scripts taken from that video :p
you will need to first go to unity's package manager and install the input system. for reference here are how my inputs are set up. please reference the video for setting up the animation controller & camera :3
for now these scripts cover idle->walking->running and handles jumping/falling. the tutorial goes into dodging and slopes which i did not find helpful so they are missing here :[ in the future i plan to add more actions to this script for sitting/interacting with objects etc!
to-do: include visuals for how animation controller/camera is set up >_>
input manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputManager : MonoBehaviour
{
PlayerControls playerControls;
PlayerLocomotion playerLocomotion;
AnimatorManager animatorManager;
public Vector2 movementInput;
public Vector2 cameraInput;
public float cameraInputX;
public float cameraInputY;
public float moveAmount;
public float verticalInput;
public float horizontalInput;
public bool b_Input;
public bool jump_Input;
private void Awake()
{
animatorManager = GetComponent<AnimatorManager>();
playerLocomotion = GetComponent<PlayerLocomotion>();
}
private void OnEnable()
{
if (playerControls == null)
{
playerControls = new PlayerControls();
playerControls.PlayerMovement.Movement.performed += i => movementInput = i.ReadValue<Vector2>();
playerControls.PlayerMovement.Camera.performed += i => cameraInput = i.ReadValue<Vector2>();
playerControls.PlayerActions.B.performed += i => b_Input = true;
playerControls.PlayerActions.B.canceled += i => b_Input = false;
playerControls.PlayerActions.Jump.performed += i => jump_Input = true;
}
playerControls.Enable();
}
private void OnDisable()
{
playerControls.Disable();
}
public void HandleAllInputs()
{
HandleMovementInput();
HandleSprintingInput();
HandleJumpingInput();
//HandleActionInput
}
private void HandleMovementInput()
{
verticalInput = movementInput.y;
horizontalInput = movementInput.x;
cameraInputY = cameraInput.y;
cameraInputX = cameraInput.x;
moveAmount = Mathf.Clamp01(Mathf.Abs(horizontalInput) + Mathf.Abs(verticalInput));
animatorManager.UpdateAnimatorValues(0, moveAmount, playerLocomotion.isSprinting);
}
private void HandleSprintingInput()
{
if (b_Input && moveAmount > 0.5f)
{
playerLocomotion.isSprinting = true;
}
else{
playerLocomotion.isSprinting = false;
}
}
private void HandleJumpingInput()
{
if (jump_Input)
{
jump_Input = false;
playerLocomotion.HandleJumping();
}
}
}
player manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerManager : MonoBehaviour
{
InputManager inputManager;
CameraManager cameraManager;
Animator animator;
PlayerLocomotion playerLocomotion;
public bool isInteracting;
private void Awake()
{
animator = GetComponent<Animator>();
inputManager = GetComponent<InputManager>();
cameraManager = FindObjectOfType<CameraManager>();
playerLocomotion = GetComponent<PlayerLocomotion>();
}
private void Update()
{
inputManager.HandleAllInputs();
}
private void FixedUpdate()
{
playerLocomotion.HandleAllMovement();
}
private void LateUpdate()
{
cameraManager.HandleAllCameraMovement();
isInteracting = animator.GetBool("isInteracting");
playerLocomotion.isJumping = animator.GetBool("isJumping");
animator.SetBool("isGrounded", playerLocomotion.isGrounded);
}
}
player locomotion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerLocomotion : MonoBehaviour
{
PlayerManager playerManager;
AnimatorManager animatorManager;
InputManager inputManager;
Animator animator;
Vector3 moveDirection;
Transform cameraObject;
Rigidbody playerRigidbody;
[Header("Falling")]
public float inAirTimer;
public float leapingVelocity;
public float fallingVelocity;
public float rayCastHeightOffset = 0.5f;
public LayerMask groundLayer;
public float maxDistance = 1;
[Header("Movement Flags")]
public bool isGrounded;
public bool isSprinting;
public bool isJumping;
[Header("Movement Speeds")]
public float walkingSpeed = 1.5f;
public float runningSpeed = 5;
public float sprintingSpeed = 7;
public float rotationSpeed = 15;
[Header("Jump Speeds")]
public float jumpHeight = 3;
public float gravityIntensity = -15;
private void Awake()
{
playerManager = GetComponent<PlayerManager>();
animatorManager = GetComponent<AnimatorManager>();
animator = GetComponent<Animator>();
inputManager = GetComponent<InputManager>();
playerRigidbody = GetComponent<Rigidbody>();
cameraObject = Camera.main.transform;
}
public void HandleAllMovement()
{
HandleFallingAndLanding();
if (playerManager.isInteracting)
return;
HandleMovement();
HandleRotation();
}
private void HandleMovement()
{
if (isJumping)
return;
moveDirection = cameraObject.forward * inputManager.verticalInput; //Movement Input
moveDirection = moveDirection + cameraObject.right * inputManager.horizontalInput;
moveDirection.Normalize();
moveDirection.y = 0;
if (isSprinting)
{
moveDirection = moveDirection * sprintingSpeed;
}
else
{
if (inputManager.moveAmount >= 0.5f)
{
moveDirection = moveDirection * runningSpeed;
}
else{
moveDirection = moveDirection * walkingSpeed;
}
}
moveDirection = moveDirection * runningSpeed;
Vector3 movementVelocity = moveDirection;
playerRigidbody.velocity = movementVelocity;
}
private void HandleRotation()
{
if (isJumping)
return;
Vector3 targetDirection = Vector3.zero;
targetDirection = cameraObject.forward * inputManager.verticalInput;
targetDirection = targetDirection + cameraObject.right * inputManager.horizontalInput;
targetDirection.Normalize();
targetDirection.y = 0;
if (targetDirection == Vector3.zero)
targetDirection = transform.forward;
Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
Quaternion playerRotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
transform.rotation = playerRotation;
}
private void HandleFallingAndLanding()
{
RaycastHit hit;
Vector3 rayCastOrigin = transform.position;
Vector3 targetPosition;
rayCastOrigin.y = rayCastOrigin.y + rayCastHeightOffset;
targetPosition = transform.position;
if (!isGrounded && !isJumping)
{
if (!playerManager.isInteracting)
{
animatorManager.PlayTargetAnimation("falling", true);
animator.SetBool("isGrounded", false);
}
inAirTimer = inAirTimer + Time.deltaTime;
playerRigidbody.AddForce(transform.forward * leapingVelocity);
playerRigidbody.AddForce(-Vector3.up * fallingVelocity * inAirTimer);
}
if (Physics.SphereCast(rayCastOrigin, 0.2f, -Vector3.up, out hit, maxDistance, groundLayer))
{
if(!isGrounded && playerManager.isInteracting)
{
animatorManager.PlayTargetAnimation("landing", true);
animator.SetBool("isGrounded", true);
}
Vector3 rayCastHitPoint = hit.point;
targetPosition.y = rayCastHitPoint.y;
inAirTimer = 0;
isGrounded = true;
}
else
{
isGrounded = false;
}
if (isGrounded && !isJumping)
{
if (playerManager.isInteracting || inputManager.moveAmount > 0)
{
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime / 0.1f);
}
else
{
transform.position = targetPosition;
}
}
}
public void HandleJumping()
{
if (isGrounded)
{
animatorManager.animator.SetBool("isJumping", true);
animatorManager.PlayTargetAnimation("Jump", false);
float jumpingVelocity = Mathf.Sqrt(-2 * gravityIntensity * jumpHeight);
Vector3 playerVelocity = moveDirection;
playerVelocity.y = jumpingVelocity;
playerRigidbody.velocity = playerVelocity;
}
}
}
animation manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimatorManager : MonoBehaviour
{
public Animator animator;
int horizontal;
int vertical;
private void Awake()
{
animator = GetComponent<Animator>();
horizontal = Animator.StringToHash("Horizontal");
vertical = Animator.StringToHash("Vertical");
}
public void PlayTargetAnimation(string targetAnimation, bool isInteracting)
{
animator.SetBool("isInteracting", isInteracting);
animator.CrossFade(targetAnimation, 0.2f);
}
public void UpdateAnimatorValues(float horizontalMovement, float verticalMovement, bool isSprinting)
{
//Animation Snapping
float snappedHorizontal;
float snappedVertical;
#region SnappedHorizontal
if (horizontalMovement > 0 && horizontalMovement < 0.55f)
{
snappedHorizontal = 0.5f;
}
else if (horizontalMovement > 0.55f)
{
snappedHorizontal = 1;
}
else if (horizontalMovement < 0 && horizontalMovement > -0.55f)
{
snappedHorizontal = -0.5f;
}
else if (horizontalMovement < -0.55f)
{
snappedHorizontal = -1;
}
else
{
snappedHorizontal = 0;
}
#endregion
#region SnappedVertical
if (verticalMovement > 0 && verticalMovement < 0.55f)
{
snappedVertical = 0.5f;
}
else if (verticalMovement > 0.55f)
{
snappedVertical = 1;
}
else if (verticalMovement < 0 && verticalMovement > -0.55f)
{
snappedVertical = -0.5f;
}
else if (verticalMovement < -0.55f)
{
snappedVertical = -1;
}
else
{
snappedVertical = 0;
}
#endregion
if (isSprinting)
{
snappedHorizontal = horizontalMovement;
snappedVertical = 2;
}
//animator.SetFloat(horizontal, horizontalMovement, 0.1f, Time.deltaTime);
animator.SetFloat(horizontal, snappedHorizontal, 0.1f, Time.deltaTime);
// animator.SetFloat(vertical, verticalMovement, 0.1f, Time.deltaTime);
animator.SetFloat(vertical, snappedVertical, 0.1f, Time.deltaTime);
}
}
reset bool
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResetBool : StateMachineBehaviour
{
public string isInteractingBool;
public bool isInteractingStatus;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.SetBool(isInteractingBool, isInteractingStatus);
}
}
reset IsJumping
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResetIsJumping : StateMachineBehaviour
{
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.SetBool("isJumping", false);
}
}
camera manager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraManager : MonoBehaviour
{
InputManager inputManager;
public Transform targetTransform; //the object the camera will follow
public Transform cameraPivot; //the object the camera uses to pivot
public Transform cameraTransform; //the transform of the actual camera object in the scene
private float defaultPosition;
private Vector3 cameraFollowVelocity = Vector3.zero;
public LayerMask collisionLayers; // the layers we want our camera to collide with
public float cameraCollisionOffset = 0.2f; //how much camera will jump
public float minimumCollisionOffset = 0.2f;
private Vector3 cameraVectorPosition;
public float cameraFollowSpeed = 0.2f;
public float cameraLookSpeed = 0.2f;
public float cameraPivotSpeed = 0.2f;
public float cameraCollisionRadius = 0.2f;
public float lookAngle; //camera looking up and down
public float pivotAngle; //camera looking left and right
public float minimumPivotAngle = -35;
public float maximumPivotAngle = 35;
private void Awake()
{
inputManager = FindObjectOfType<InputManager>();
targetTransform = FindObjectOfType<PlayerManager>().transform;
cameraTransform = Camera.main.transform;
defaultPosition = cameraTransform.localPosition.z;
}
public void HandleAllCameraMovement()
{
FollowTarget();
RotateCamera();
HandleCameraCollisions();
}
private void FollowTarget()
{
Vector3 targetPosition = Vector3.SmoothDamp(transform.position, targetTransform.position, ref cameraFollowVelocity, cameraFollowSpeed);
transform.position = targetPosition;
}
private void RotateCamera()
{
Vector3 rotation;
Quaternion targetRotation;
lookAngle = lookAngle + (inputManager.cameraInputX * cameraLookSpeed);
pivotAngle = pivotAngle - (inputManager.cameraInputY * cameraPivotSpeed);
pivotAngle = Mathf.Clamp(pivotAngle, minimumPivotAngle, maximumPivotAngle);
rotation = Vector3.zero;
rotation.y = lookAngle;
targetRotation = Quaternion.Euler(rotation);
transform.rotation = targetRotation;
rotation = Vector3.zero;
rotation.x = pivotAngle;
targetRotation = Quaternion.Euler(rotation);
cameraPivot.localRotation = targetRotation;
}
private void HandleCameraCollisions()
{
float targetPosition = defaultPosition;
RaycastHit hit;
Vector3 direction = cameraTransform.position - cameraPivot.position;
direction.Normalize();
if (Physics.SphereCast(cameraPivot.transform.position, cameraCollisionRadius, direction, out hit, Mathf.Abs(targetPosition), collisionLayers))
{
float distance = Vector3.Distance(cameraPivot.position, hit.point);
targetPosition =- (distance - cameraCollisionOffset);
}
if (Mathf.Abs(targetPosition) < minimumCollisionOffset)
{
targetPosition = targetPosition - minimumCollisionOffset;
}
cameraVectorPosition.z = Mathf.Lerp(cameraTransform.localPosition.z, targetPosition, 0.2f);
cameraTransform.localPosition = cameraVectorPosition;
}
}