Created
August 12, 2018 23:17
-
-
Save juaxix/bada8c3a436a6d7595c4edd674db21b3 to your computer and use it in GitHub Desktop.
OutOfSpace- LudumDare 42
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
using System.Collections.Generic; | |
using System.Linq; | |
using DG.Tweening; | |
using UnityEngine.Events; | |
using Random = UnityEngine.Random; | |
public class Game : MonoBehaviour | |
{ | |
public Transform[] personSpawnPoints; | |
private static Game instance; | |
public GameObject[] peoplePrefabs; | |
public int peopleToGenerate = 30; | |
[Range(0.1f,100f)] | |
[Tooltip("In seconds")] | |
public float frequenceToGeneratePeople = 0.4f; | |
public Player _player; | |
private List<Person> _persons = new List<Person>(); | |
private bool _allpersonsspawned; | |
public bool AllPersonsAreSpawned { get { return _allpersonsspawned; } } | |
[SerializeField] private UnityEvent OnUIGameOverCalled = null; | |
[SerializeField] private UnityEngine.UI.Text _GameOverText = null; | |
[SerializeField] private UnityEngine.UI.Text _StatusText = null; | |
[SerializeField] private UnityEvent OnUIPersonArrested = null; | |
private Camera _camera; | |
private Transform _cameraTransform; | |
public Camera PlayerCamera {get { return _camera; }} | |
public Transform PlayerCameraTransform { get { return _cameraTransform; }} | |
private static bool firstRun = true; | |
public static Game Instance | |
{ | |
get { return instance != null ? instance : (instance = FindObjectOfType<Game>()); } | |
} | |
void Awake() | |
{ | |
instance = this; | |
if (_player==null) _player = FindObjectOfType<Player>(); | |
_camera = Camera.main; | |
_cameraTransform = _camera.GetComponent<Transform>(); | |
GetComponent<AudioSource>(); | |
#if !UNITY_EDITOR | |
if (firstRun) | |
{ | |
firstRun = false; | |
_audioSource.Play(); | |
} | |
#endif | |
} | |
public void GeneratePerson() | |
{ | |
if (peoplePrefabs.Length == 0) | |
{ | |
Debug.Log("No people prefabs"); | |
return; | |
} | |
int randomIndex = Random.Range(0, peoplePrefabs.Length); | |
GameObject goperson = Instantiate(peoplePrefabs[randomIndex]) as GameObject; | |
if (goperson==null) | |
{ | |
Debug.LogError("Error creating person with prefab index " + randomIndex); | |
return; | |
} | |
_persons.Add(goperson.GetComponent<Person>()); | |
_persons.Last().ChangeState(Person.PersonalSpaceState.WALKING); | |
} | |
void AllPersonsSpawned() | |
{ | |
_allpersonsspawned = true; | |
} | |
public void OnPlayerEnteringPersonPrivateSpace(Player player,Person person, Vector3 position) | |
{ | |
switch (person._state) | |
{ | |
case Person.PersonalSpaceState.IDLE: | |
break; | |
case Person.PersonalSpaceState.WALKING: | |
break; | |
case Person.PersonalSpaceState.AFFRAID: | |
break; | |
case Person.PersonalSpaceState.INJURIED: | |
break; | |
case Person.PersonalSpaceState.ATTACKING: | |
break; | |
case Person.PersonalSpaceState.ARRESTED: | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
} | |
public void OnPlayerExitingPersonPrivateSpace(Player player, Person person, Vector3 point) | |
{ | |
} | |
void Start() | |
{ | |
DOVirtual.DelayedCall(0.4f, GeneratePerson) | |
.SetLoops(peopleToGenerate) | |
.SetDelay(frequenceToGeneratePeople) | |
.OnComplete(AllPersonsSpawned); | |
} | |
void Update() | |
{ | |
for (int i = 0; i < _persons.Count; i++) | |
{ | |
Person person = _persons[i]; | |
person.UpdateAggroLevel(); | |
person.UpdateAnimationSpeed(); | |
switch (person._state) | |
{ | |
case Person.PersonalSpaceState.IDLE: | |
person.ChangeState(Person.PersonalSpaceState.WALKING); | |
break; | |
case Person.PersonalSpaceState.WALKING: | |
if (person._navMeshAgent.velocity.magnitude <= 0.1f && | |
Vector3.Distance(person._transform.position, person._navMeshAgent.destination) < 0.2f) | |
{ | |
person.ChangeState(Person.PersonalSpaceState.IDLE); | |
} | |
break; | |
case Person.PersonalSpaceState.AFFRAID: | |
break; | |
case Person.PersonalSpaceState.INJURIED: | |
break; | |
case Person.PersonalSpaceState.ATTACKING: | |
break; | |
case Person.PersonalSpaceState.ARRESTED: | |
break; | |
} | |
} | |
} | |
public void OnPlayerMadeNormalPersonInjuried(Person person) | |
{ | |
if (_GameOverText != null) | |
{ | |
_GameOverText.text = "Invaded the personal space of an innocent!"; | |
} | |
if (OnUIGameOverCalled != null) | |
{ | |
OnUIGameOverCalled.Invoke(); | |
} | |
} | |
public void OnPlayerArrestedPerson(Person person) | |
{ | |
if (_StatusText != null) | |
{ | |
_StatusText.text = "Person arrested"; | |
} | |
if (OnUIPersonArrested != null) | |
{ | |
OnUIPersonArrested.Invoke(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
/// MouseLook rotates the transform based on the mouse delta. | |
/// Minimum and Maximum values can be used to constrain the possible rotation | |
/// To make an FPS style character: | |
/// - Create a capsule. | |
/// - Add the MouseLook script to the capsule. | |
/// -> Set the mouse look to use LookX. (You want to only turn character but not tilt it) | |
/// - Add FPSInputController script to the capsule | |
/// -> A CharacterMotor and a CharacterController component will be automatically added. | |
/// - Create a camera. Make the camera a child of the capsule. Reset it's transform. | |
/// - Add a MouseLook script to the camera. | |
/// -> Set the mouse look to use LookY. (You want the camera to tilt up and down like a head. The character already turns.) | |
[AddComponentMenu("Camera-Control/Mouse Look")] | |
public class MouseLook : MonoBehaviour { | |
public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 } | |
public RotationAxes axes = RotationAxes.MouseXAndY; | |
public float sensitivityX = 15F; | |
public float sensitivityY = 15F; | |
public float minimumX = -360F; | |
public float maximumX = 360F; | |
public float minimumY = -60F; | |
public float maximumY = 60F; | |
float rotationY = 0F; | |
void Update () | |
{ | |
if (axes == RotationAxes.MouseXAndY) | |
{ | |
float rotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivityX; | |
rotationY += Input.GetAxis("Mouse Y") * sensitivityY; | |
rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); | |
transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0); | |
} | |
else if (axes == RotationAxes.MouseX) | |
{ | |
transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0); | |
} | |
else | |
{ | |
rotationY += Input.GetAxis("Mouse Y") * sensitivityY; | |
rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); | |
transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0); | |
} | |
} | |
void Start () | |
{ | |
// Make the rigid body not change rotation | |
if (GetComponent<Rigidbody>()) | |
GetComponent<Rigidbody>().freezeRotation = true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
using System.Runtime.CompilerServices; | |
using Random = UnityEngine.Random; | |
[assembly: InternalsVisibleTo("Game")] | |
[RequireComponent(typeof(NavMeshAgent))] | |
[RequireComponent(typeof(Animator))] | |
[RequireComponent(typeof(AudioSource))] | |
public class Person : MonoBehaviour | |
{ | |
[SerializeField] | |
private Animator _animator; | |
[SerializeField] | |
internal NavMeshAgent _navMeshAgent; | |
[SerializeField] | |
internal GameObject _FearAura = null; | |
[SerializeField] | |
internal GameObject _AngryAura = null; | |
[SerializeField] | |
internal GameObject _NormalAura = null; | |
[SerializeField] | |
internal GameObject _GoodAura = null; | |
[SerializeField] | |
internal AudioSource _audioSource; | |
[SerializeField] | |
internal AudioClip _fearSound = null; | |
[SerializeField] | |
internal float _walkSpeed = 1.55f; | |
[SerializeField] | |
internal float _minimumDistanceToWalk = 40.0f; | |
[SerializeField] | |
internal float _walkAcceleration = 8.0f; | |
[SerializeField] | |
internal float _runSpeed = 5.0f; | |
[SerializeField] | |
internal float _runAcceleration = 9.0f; | |
[Tooltip("Increment each frame the personal space is entered by the player")] | |
[SerializeField] | |
internal float _aggroIncrement = 0.15f; | |
internal float _aggroLevel; | |
public Transform _aggroSliderGroup = null; | |
public Transform _aggroSliderQuad = null; | |
//[SerializeField] | |
//internal float _aggroSliderDistanceToCamera = 3.0f; | |
public enum TypesOfPersons | |
{ | |
NORMAL, | |
CRIMINAL | |
} | |
public enum PersonalSpaceState | |
{ | |
IDLE, | |
WALKING, | |
AFFRAID, | |
INJURIED, | |
ATTACKING, | |
ARRESTED | |
} | |
[SerializeField] | |
internal PersonalSpaceState _state = PersonalSpaceState.IDLE; | |
private TypesOfPersons _type = TypesOfPersons.NORMAL; | |
internal bool _invaded; | |
internal Transform _transform; | |
void Awake () | |
{ | |
if (_animator == null) _animator = GetComponent<Animator>(); | |
if (_navMeshAgent == null) _navMeshAgent = GetComponent<NavMeshAgent>(); | |
if (_audioSource == null) _audioSource = GetComponent<AudioSource>(); | |
_state = PersonalSpaceState.IDLE; | |
_transform = GetComponent<Transform>(); | |
_type = (Random.Range(1, 6) >= 5) ? TypesOfPersons.CRIMINAL : TypesOfPersons.NORMAL; | |
if (_type == TypesOfPersons.CRIMINAL) _aggroIncrement *= 2.0f; | |
SetActiveAura(null); | |
SetAggroLevel(0.0f); | |
WarpToRandomPosition(); | |
enabled = false; | |
} | |
internal void SetAggroLevel(float aggrolevel) | |
{ | |
_aggroLevel = Mathf.Clamp01(aggrolevel); | |
_aggroSliderQuad.localScale = new Vector3(_aggroLevel, 0.1f, 1.0f); | |
if (aggrolevel >= 1.0f) | |
{ | |
_aggroLevel = 0.0f; | |
switch (_type) | |
{ | |
case TypesOfPersons.NORMAL: | |
switch (_state) | |
{ | |
case PersonalSpaceState.WALKING: | |
ChangeState(PersonalSpaceState.AFFRAID); | |
break; | |
case PersonalSpaceState.AFFRAID: | |
ChangeState(PersonalSpaceState.INJURIED); | |
break; | |
} | |
break; | |
case TypesOfPersons.CRIMINAL: | |
switch (_state) | |
{ | |
case PersonalSpaceState.WALKING: | |
ChangeState(PersonalSpaceState.AFFRAID); | |
break; | |
case PersonalSpaceState.AFFRAID: | |
ChangeState(PersonalSpaceState.ATTACKING); | |
break; | |
case PersonalSpaceState.ATTACKING: | |
ChangeState(PersonalSpaceState.ARRESTED); | |
break; | |
} | |
break; | |
} | |
} else if (aggrolevel == 0.0f) | |
{ | |
switch (_type) | |
{ | |
case TypesOfPersons.NORMAL: | |
switch (_state) | |
{ | |
case PersonalSpaceState.IDLE: | |
break; | |
case PersonalSpaceState.WALKING: | |
break; | |
case PersonalSpaceState.AFFRAID: | |
break; | |
case PersonalSpaceState.INJURIED: | |
break; | |
case PersonalSpaceState.ATTACKING: | |
break; | |
case PersonalSpaceState.ARRESTED: | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
break; | |
case TypesOfPersons.CRIMINAL: | |
switch (_state) | |
{ | |
case PersonalSpaceState.IDLE: | |
break; | |
case PersonalSpaceState.WALKING: | |
break; | |
case PersonalSpaceState.AFFRAID: | |
break; | |
case PersonalSpaceState.INJURIED: | |
break; | |
case PersonalSpaceState.ATTACKING: | |
break; | |
case PersonalSpaceState.ARRESTED: | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
break; | |
} | |
} | |
} | |
void WarpToRandomPosition() | |
{ | |
int startIndex = Random.Range(0, Game.Instance.personSpawnPoints.Length); | |
_transform.position = Game.Instance.personSpawnPoints[startIndex].position; | |
_navMeshAgent.Warp(_transform.position); | |
} | |
bool GoToRandomPosition() | |
{ | |
int endIndex; | |
do | |
{ | |
endIndex = Random.Range(0, Game.Instance.personSpawnPoints.Length); | |
} while (Vector3.Distance(Game.Instance.personSpawnPoints[endIndex].position, _transform.position) > | |
_minimumDistanceToWalk); | |
return _navMeshAgent.SetDestination(Game.Instance.personSpawnPoints[endIndex].position); | |
} | |
internal void UpdateAnimationSpeed() | |
{ | |
_animator.SetFloat("speed",_navMeshAgent.velocity.magnitude); | |
} | |
internal void UpdateAggroLevel() | |
{ | |
if (_invaded) | |
{ | |
SetAggroLevel(_aggroIncrement * Time.deltaTime + _aggroLevel); | |
} | |
else | |
{ | |
if (_aggroLevel > 0.0f) | |
SetAggroLevel(_aggroLevel - _aggroIncrement * Time.deltaTime); | |
} | |
_aggroSliderGroup.rotation = Quaternion.LookRotation(Game.Instance.PlayerCameraTransform.position - _aggroSliderGroup.position); | |
} | |
public void ChangeState(PersonalSpaceState newState) | |
{ | |
if (newState == _state) return; | |
switch (newState) | |
{ | |
case PersonalSpaceState.WALKING: | |
_navMeshAgent.speed = _walkSpeed; | |
_navMeshAgent.acceleration = _walkAcceleration; | |
bool result = GoToRandomPosition(); | |
if (!result) | |
{ | |
ChangeState(PersonalSpaceState.IDLE); | |
} | |
break; | |
case PersonalSpaceState.AFFRAID: | |
SetActiveAura(_FearAura); | |
if (_type == TypesOfPersons.CRIMINAL) | |
{ | |
GoToRandomPosition(); | |
_navMeshAgent.speed = _runSpeed; | |
_navMeshAgent.acceleration = _walkAcceleration; | |
} | |
else | |
{ | |
_navMeshAgent.Stop(true); | |
} | |
_audioSource.PlayOneShot(_fearSound); | |
break; | |
case PersonalSpaceState.ATTACKING: | |
SetActiveAura(_AngryAura); | |
_navMeshAgent.Stop(true); | |
_animator.SetBool("attacking", true); | |
break; | |
case PersonalSpaceState.INJURIED: | |
SetActiveAura(_GoodAura); | |
_navMeshAgent.Stop(true); | |
Game.Instance.OnPlayerMadeNormalPersonInjuried(this); | |
break; | |
case PersonalSpaceState.ARRESTED: | |
SetActiveAura(_FearAura); | |
_navMeshAgent.Stop(true); | |
Game.Instance.OnPlayerArrestedPerson(this); | |
break; | |
} | |
_state = newState; | |
} | |
private void SetActiveAura(GameObject aura) | |
{ | |
_FearAura.SetActive(aura == _FearAura); | |
_AngryAura.SetActive(aura == _AngryAura); | |
_NormalAura.SetActive(aura == _NormalAura); | |
_GoodAura.SetActive(aura == _GoodAura); | |
} | |
public void OnTriggerEnter(Collider otherCollider) | |
{ | |
Debug.Log("I am invaded " + otherCollider.name); | |
if (otherCollider.GetComponent<Player>()) | |
{ | |
Game.Instance.OnPlayerEnteringPersonPrivateSpace( | |
otherCollider.GetComponent<Player>(), | |
this, | |
otherCollider.ClosestPointOnBounds(_transform.position) | |
); | |
_invaded = true; | |
} | |
} | |
public void OnTriggerExit(Collider otherCollider) | |
{ | |
if (otherCollider.GetComponent<Player>()) | |
{ | |
Game.Instance.OnPlayerExitingPersonPrivateSpace( | |
otherCollider.GetComponent<Player>(), | |
this, | |
otherCollider.ClosestPointOnBounds(_transform.position) | |
); | |
_invaded = false; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
[RequireComponent (typeof(CharacterMotor))] | |
[AddComponentMenu ("Character/Platform Input Controller")] | |
public class PlatformInputController : MonoBehaviour | |
{ | |
// This makes the character turn to face the current movement speed per default. | |
public bool autoRotate = true; | |
public float maxRotationSpeed = 360.0f; | |
private CharacterMotor motor; | |
void Awake () { | |
motor = GetComponent<CharacterMotor>(); | |
} | |
void Update () { | |
// Get the input vector from keyboard or analog stick | |
Vector3 directionVector = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0); | |
if (directionVector != Vector3.zero) { | |
// Get the length of the directon vector and then normalize it | |
// Dividing by the length is cheaper than normalizing when we already have the length anyway | |
float directionLength = directionVector.magnitude; | |
directionVector = directionVector / directionLength; | |
// Make sure the length is no bigger than 1 | |
directionLength = Mathf.Min(1, directionLength); | |
// Make the input vector more sensitive towards the extremes and less sensitive in the middle | |
// This makes it easier to control slow speeds when using analog sticks | |
directionLength = directionLength * directionLength; | |
// Multiply the normalized direction vector by the modified length | |
directionVector = directionVector * directionLength; | |
} | |
// Rotate the input vector into camera space so up is camera's up and right is camera's right | |
directionVector = Camera.main.transform.rotation * directionVector; | |
// Rotate input vector to be perpendicular to character's up vector | |
Quaternion camToCharacterSpace = Quaternion.FromToRotation(-Camera.main.transform.forward, transform.up); | |
directionVector = (camToCharacterSpace * directionVector); | |
// Apply the direction to the CharacterMotor | |
motor.inputMoveDirection = directionVector; | |
motor.inputJump = Input.GetButton("Jump"); | |
// Set rotation to the move direction | |
if (autoRotate && directionVector.sqrMagnitude > 0.01) | |
{ | |
Vector3 newForward = ConstantSlerp( | |
transform.forward, | |
directionVector, | |
maxRotationSpeed * Time.deltaTime | |
); | |
newForward = ProjectOntoPlane(newForward, transform.up); | |
transform.rotation = Quaternion.LookRotation(newForward, transform.up); | |
} | |
} | |
Vector3 ProjectOntoPlane (Vector3 v, Vector3 normal ) | |
{ | |
return v - Vector3.Project(v, normal); | |
} | |
public Vector3 ConstantSlerp (Vector3 from , Vector3 to , float angle) { | |
float value = Mathf.Min(1, angle / Vector3.Angle(from, to)); | |
return Vector3.Slerp(from, to, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
public class Player : MonoBehaviour | |
{ | |
public ThirdPersonController playerController; | |
public ThirdPersonCamera playerCamera; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
public class ThirdPersonCamera : MonoBehaviour | |
{ | |
public Transform cameraTransform = null; | |
private Transform _target = null; | |
// The distance in the x-z plane to the target | |
public float distance = 7.0f; | |
// the height we want the camera to be above the target | |
public float height = 3.0f; | |
public float angularSmoothLag = 0.3f; | |
public float angularMaxSpeed = 15.0f; | |
public float heightSmoothLag = 0.3f; | |
public float snapSmoothLag = 0.2f; | |
public float snapMaxSpeed = 720.0f; | |
public float clampHeadPositionScreenSpace = 0.75f; | |
public float lockCameraTimeout = 0.2f; | |
private Vector3 headOffset = Vector3.zero; | |
private Vector3 centerOffset = Vector3.zero; | |
private float heightVelocity = 0.0f; | |
private float angleVelocity = 0.0f; | |
private bool snap = false; | |
private ThirdPersonController controller; | |
private float targetHeight = 100000.0f; | |
void Awake () | |
{ | |
if(!cameraTransform && Camera.main) | |
cameraTransform = Camera.main.transform; | |
if(!cameraTransform) { | |
Debug.Log("Please assign a camera to the ThirdPersonCamera script."); | |
enabled = false; | |
} | |
_target = transform; | |
if (_target) | |
{ | |
controller = _target.GetComponent<ThirdPersonController>(); | |
} | |
if (controller) | |
{ | |
Collider characterController = _target.GetComponent<Collider>(); | |
centerOffset = characterController.bounds.center - _target.position; | |
headOffset = centerOffset; | |
headOffset.y = characterController.bounds.max.y - _target.position.y; | |
} | |
else | |
Debug.Log("Please assign a target to the camera that has a ThirdPersonController script attached."); | |
Cut(_target, centerOffset); | |
} | |
public void DebugDrawStuff () | |
{ | |
Debug.DrawLine(_target.position, _target.position + headOffset); | |
} | |
public float AngleDistance (float a, float b) | |
{ | |
a = Mathf.Repeat(a, 360); | |
b = Mathf.Repeat(b, 360); | |
return Mathf.Abs(b - a); | |
} | |
public void Apply (Transform dummyTarget,Vector3 dummyCenter) | |
{ | |
// Early out if we don't have a target | |
if (!controller) | |
return; | |
Vector3 targetCenter = _target.position + centerOffset; | |
Vector3 targetHead = _target.position + headOffset; | |
// DebugDrawStuff(); | |
// Calculate the current & target rotation angles | |
float originalTargetAngle = _target.eulerAngles.y; | |
float currentAngle = cameraTransform.eulerAngles.y; | |
// Adjust real target angle when camera is locked | |
float targetAngle = originalTargetAngle; | |
// When pressing Fire2 (alt) the camera will snap to the target direction real quick. | |
// It will stop snapping when it reaches the target | |
if (Input.GetButton("Fire2")) | |
snap = true; | |
if (snap) | |
{ | |
// We are close to the target, so we can stop snapping now! | |
if (AngleDistance (currentAngle, originalTargetAngle) < 3.0) | |
snap = false; | |
currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angleVelocity, snapSmoothLag, snapMaxSpeed); | |
} | |
// Normal camera motion | |
else | |
{ | |
if (controller.GetLockCameraTimer () < lockCameraTimeout) | |
{ | |
targetAngle = currentAngle; | |
} | |
// Lock the camera when moving backwards! | |
// * It is really confusing to do 180 degree spins when turning around. | |
if (AngleDistance (currentAngle, targetAngle) > 160 && controller.IsMovingBackwards ()) | |
targetAngle += 180; | |
currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angleVelocity, angularSmoothLag, angularMaxSpeed); | |
} | |
// When jumping don't move camera upwards but only down! | |
if (controller.IsJumping ()) | |
{ | |
// We'd be moving the camera upwards, do that only if it's really high | |
float newTargetHeight = targetCenter.y + height; | |
if (newTargetHeight < targetHeight || newTargetHeight - targetHeight > 5) | |
targetHeight = targetCenter.y + height; | |
} | |
// When walking always update the target height | |
else | |
{ | |
targetHeight = targetCenter.y + height; | |
} | |
// Damp the height | |
float currentHeight = cameraTransform.position.y; | |
currentHeight = Mathf.SmoothDamp (currentHeight, targetHeight, ref heightVelocity, heightSmoothLag); | |
// Convert the angle into a rotation, by which we then reposition the camera | |
Quaternion currentRotation = Quaternion.Euler (0f, currentAngle, 0f); | |
// Set the position of the camera on the x-z plane to: | |
// distance meters behind the target | |
cameraTransform.position = targetCenter; | |
cameraTransform.position += currentRotation * Vector3.back * distance; | |
// Set the height of the camera | |
cameraTransform.position = new Vector3(cameraTransform.position.x, currentHeight, cameraTransform.position.z); | |
// Always look at the target | |
SetUpRotation(targetCenter, targetHead); | |
} | |
void LateUpdate () | |
{ | |
Apply (transform, Vector3.zero); | |
} | |
public void Cut (Transform dummyTarget,Vector3 dummyCenter ) | |
{ | |
float oldHeightSmooth = heightSmoothLag; | |
float oldSnapMaxSpeed = snapMaxSpeed; | |
float oldSnapSmooth = snapSmoothLag; | |
snapMaxSpeed = 10000f; | |
snapSmoothLag = 0.001f; | |
heightSmoothLag = 0.001f; | |
snap = true; | |
Apply (transform, Vector3.zero); | |
heightSmoothLag = oldHeightSmooth; | |
snapMaxSpeed = oldSnapMaxSpeed; | |
snapSmoothLag = oldSnapSmooth; | |
} | |
public void SetUpRotation (Vector3 centerPos,Vector3 headPos) | |
{ | |
// Now it's getting hairy. The devil is in the details here, the big issue is jumping of course. | |
// * When jumping up and down we don't want to center the guy in screen space. | |
// This is important to give a feel for how high you jump and avoiding large camera movements. | |
// | |
// * At the same time we dont want him to ever go out of screen and we want all rotations to be totally smooth. | |
// | |
// So here is what we will do: | |
// | |
// 1. We first find the rotation around the y axis. Thus he is always centered on the y-axis | |
// 2. When grounded we make him be centered | |
// 3. When jumping we keep the camera rotation but rotate the camera to get him back into view if his head is above some threshold | |
// 4. When landing we smoothly interpolate towards centering him on screen | |
Vector3 cameraPos = cameraTransform.position; | |
Vector3 offsetToCenter = centerPos - cameraPos; | |
// Generate base rotation only around y-axis | |
Quaternion yRotation = Quaternion.LookRotation(new Vector3(offsetToCenter.x, 0, offsetToCenter.z)); | |
Vector3 relativeOffset = Vector3.forward * distance + Vector3.down * height; | |
cameraTransform.rotation = yRotation * Quaternion.LookRotation(relativeOffset); | |
// Calculate the projected center position and top position in world space | |
Ray centerRay = cameraTransform.GetComponent<Camera>().ViewportPointToRay(new Vector3(.5f, 0.5f, 1f)); | |
Ray topRay = cameraTransform.GetComponent<Camera>().ViewportPointToRay(new Vector3(.5f, clampHeadPositionScreenSpace, 1)); | |
Vector3 centerRayPos = centerRay.GetPoint(distance); | |
Vector3 topRayPos = topRay.GetPoint(distance); | |
float centerToTopAngle = Vector3.Angle(centerRay.direction, topRay.direction); | |
float heightToAngle = centerToTopAngle / (centerRayPos.y - topRayPos.y); | |
float extraLookAngle = heightToAngle * (centerRayPos.y - centerPos.y); | |
if (extraLookAngle < centerToTopAngle) | |
{ | |
extraLookAngle = 0.0f; | |
} | |
else | |
{ | |
extraLookAngle = extraLookAngle - centerToTopAngle; | |
cameraTransform.rotation *= Quaternion.Euler(-extraLookAngle, 0, 0); | |
} | |
} | |
public Vector3 GetCenterOffset () | |
{ | |
return centerOffset; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
[RequireComponent(typeof(CharacterController))] | |
public class ThirdPersonController : MonoBehaviour | |
{ | |
public float walkMaxAnimationSpeed = 0.75f; | |
public float trotMaxAnimationSpeed = 1.0f; | |
public float runMaxAnimationSpeed = 1.0f; | |
public float jumpAnimationSpeed = 1.15f; | |
public float landAnimationSpeed = 1.0f; | |
private int personLayer = 0; | |
private Animator _animator; | |
public enum CharacterState { | |
Idle = 0, | |
Walking = 1, | |
Trotting = 2, | |
Running = 3, | |
Jumping = 4, | |
} | |
private CharacterState _characterState; | |
// The speed when walking | |
public float walkSpeed = 2.0f; | |
public float trotSpeed = 4.0f; | |
public float runSpeed = 6.0f; | |
public float inAirControlAcceleration = 3.0f; | |
public float jumpHeight = 0.5f; | |
public float gravity = 20.0f; | |
public float speedSmoothing = 10.0f; | |
public float rotateSpeed = 500.0f; | |
public float trotAfterSeconds = 3.0f; | |
public bool canJump = true; | |
private float jumpRepeatTime = 0.05f; | |
private float jumpTimeout = 0.15f; | |
private float groundedTimeout = 0.25f; | |
// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around. | |
private float lockCameraTimer = 0.0f; | |
// The current move direction in x-z | |
private Vector3 moveDirection = Vector3.zero; | |
// The current vertical speed | |
private float verticalSpeed = 0.0f; | |
// The current x-z move speed | |
private float moveSpeed = 0.0f; | |
// The last collision flags returned from controller.Move | |
private CollisionFlags collisionFlags; | |
// Are we jumping? (Initiated with jump button and not grounded yet) | |
private bool jumping = false; | |
private bool jumpingReachedApex = false; | |
// Are we moving backwards (This locks the camera to not do a 180 degree spin) | |
private bool movingBack = false; | |
// Is the user pressing any keys? | |
private bool isMoving = false; | |
// When did the user start walking (Used for going into trot after a while) | |
private float walkTimeStart = 0.0f; | |
// Last time the jump button was clicked down | |
private float lastJumpButtonTime = -10.0f; | |
// Last time we performed a jump | |
private float lastJumpTime = -1.0f; | |
// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.) | |
private float lastJumpStartHeight = 0.0f; | |
private Vector3 inAirVelocity = Vector3.zero; | |
private float lastGroundedTime = 0.0f; | |
private bool isControllable = true; | |
private CharacterController controller = null; | |
void Awake () | |
{ | |
personLayer = LayerMask.NameToLayer("Person"); | |
moveDirection = transform.TransformDirection(Vector3.forward); | |
_animator = GetComponent<Animator>(); | |
if(!_animator) | |
Debug.Log("The character you would like to control doesn't have animations. Moving her might look weird."); | |
controller = GetComponent<CharacterController>(); | |
} | |
public void UpdateSmoothedMovementDirection () | |
{ | |
Transform cameraTransform = Camera.main.transform; | |
bool grounded = IsGrounded(); | |
// Forward vector relative to the camera along the x-z plane | |
Vector3 forward = cameraTransform.TransformDirection(Vector3.forward); | |
forward.y = 0; | |
forward = forward.normalized; | |
// Right vector relative to the camera | |
// Always orthogonal to the forward vector | |
Vector3 right = new Vector3(forward.z, 0, -forward.x); | |
float v = Input.GetAxisRaw("Vertical"); | |
float h = Input.GetAxisRaw("Horizontal"); | |
// Are we moving backwards or looking backwards | |
if (v < -0.2) | |
movingBack = true; | |
else | |
movingBack = false; | |
bool wasMoving = isMoving; | |
isMoving = Mathf.Abs (h) > 0.1 || Mathf.Abs (v) > 0.1; | |
// Target direction relative to the camera | |
Vector3 targetDirection = h * right + v * forward; | |
// Grounded controls | |
if (grounded) | |
{ | |
// Lock camera for short period when transitioning moving & standing still | |
lockCameraTimer += Time.deltaTime; | |
if (isMoving != wasMoving) | |
lockCameraTimer = 0.0f; | |
// We store speed and direction seperately, | |
// so that when the character stands still we still have a valid forward direction | |
// moveDirection is always normalized, and we only update it if there is user input. | |
if (targetDirection != Vector3.zero) | |
{ | |
// If we are really slow, just snap to the target direction | |
if (moveSpeed < walkSpeed * 0.9 && grounded) | |
{ | |
moveDirection = targetDirection.normalized; | |
} | |
// Otherwise smoothly turn towards it | |
else | |
{ | |
moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000); | |
moveDirection = moveDirection.normalized; | |
} | |
} | |
// Smooth the speed based on the current target direction | |
float curSmooth = speedSmoothing * Time.deltaTime; | |
// Choose target speed | |
//* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways | |
float targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0f); | |
_characterState = CharacterState.Idle; | |
// Pick speed modifier | |
if (Input.GetKey (KeyCode.LeftShift) || Input.GetKey (KeyCode.RightShift)) | |
{ | |
targetSpeed *= runSpeed; | |
_characterState = CharacterState.Running; | |
} | |
else if (Time.time - trotAfterSeconds > walkTimeStart) | |
{ | |
targetSpeed *= trotSpeed; | |
_characterState = CharacterState.Trotting; | |
} | |
else | |
{ | |
targetSpeed *= walkSpeed; | |
_characterState = CharacterState.Walking; | |
} | |
moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth); | |
// Reset walk time start when we slow down | |
if (moveSpeed < walkSpeed * 0.3) | |
walkTimeStart = Time.time; | |
} | |
// In air controls | |
else | |
{ | |
// Lock camera while in air | |
if (jumping) | |
{ | |
lockCameraTimer = 0.0f; | |
} | |
if (isMoving) | |
inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration; | |
} | |
} | |
public void ApplyJumping () | |
{ | |
// Prevent jumping too fast after each other | |
if (lastJumpTime + jumpRepeatTime > Time.time) | |
return; | |
if (IsGrounded()) { | |
// Jump | |
// - Only when pressing the button down | |
// - With a timeout so you can press the button slightly before landing | |
if (canJump && Time.time < lastJumpButtonTime + jumpTimeout) { | |
verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight); | |
SendMessage("DidJump", SendMessageOptions.DontRequireReceiver); | |
_animator.SetTrigger("Jump"); | |
} | |
} | |
} | |
public void ApplyGravity () | |
{ | |
if (isControllable) // don't move player at all if not controllable. | |
{ | |
// Apply gravity | |
//float jumpButton = Input.GetButton("Jump"); | |
// When we reach the apex of the jump we send out a message | |
if (jumping && !jumpingReachedApex && verticalSpeed <= 0.0) | |
{ | |
jumpingReachedApex = true; | |
SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver); | |
} | |
if (IsGrounded ()) | |
verticalSpeed = 0.0f; | |
else | |
verticalSpeed -= gravity * Time.deltaTime; | |
} | |
} | |
public float CalculateJumpVerticalSpeed (float targetJumpHeight) | |
{ | |
// From the jump height and gravity we deduce the upwards speed | |
// for the character to reach at the apex. | |
return Mathf.Sqrt(2 * targetJumpHeight * gravity); | |
} | |
public void DidJump () | |
{ | |
jumping = true; | |
jumpingReachedApex = false; | |
lastJumpTime = Time.time; | |
lastJumpStartHeight = transform.position.y; | |
lastJumpButtonTime = -10; | |
_characterState = CharacterState.Jumping; | |
} | |
void Update() { | |
if (!isControllable) | |
{ | |
// kill all inputs if not controllable. | |
Input.ResetInputAxes(); | |
} | |
if (Input.GetButtonDown ("Jump")) | |
{ | |
lastJumpButtonTime = Time.time; | |
} | |
UpdateSmoothedMovementDirection(); | |
// Apply gravity | |
// - extra power jump modifies gravity | |
// - controlledDescent mode modifies gravity | |
ApplyGravity (); | |
// Apply jumping logic | |
ApplyJumping (); | |
// Calculate actual motion | |
Vector3 movement = moveDirection * moveSpeed + new Vector3 (0, verticalSpeed, 0) + inAirVelocity; | |
movement *= Time.deltaTime; | |
// Move the controller | |
collisionFlags = controller.Move(movement); | |
// ANIMATION sector | |
_animator.SetFloat("speed", controller.velocity.sqrMagnitude); | |
if(_characterState == CharacterState.Jumping) | |
{ | |
_animator.SetFloat("speed", movement.magnitude); | |
/*if (!jumpingReachedApex) { | |
_animator.speed = jumpAnimationSpeed; | |
} else { | |
_animator.speed = -landAnimationSpeed; | |
}*/ | |
} | |
else | |
{ | |
if(controller.velocity.sqrMagnitude < 0.1) | |
{ | |
_animator.SetFloat("speed",0.0f); | |
} | |
else | |
{ | |
if(_characterState == CharacterState.Running) | |
{ | |
_animator.speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, runMaxAnimationSpeed); | |
} | |
else if(_characterState == CharacterState.Trotting) { | |
_animator.speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, trotMaxAnimationSpeed); | |
} | |
else if(_characterState == CharacterState.Walking) { | |
_animator.speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, walkMaxAnimationSpeed); | |
} | |
} | |
} | |
// ANIMATION sector | |
// Set rotation to the move direction | |
if (IsGrounded()) | |
{ | |
transform.rotation = Quaternion.LookRotation(moveDirection); | |
} | |
else | |
{ | |
var xzMove = movement; | |
xzMove.y = 0; | |
if (xzMove.sqrMagnitude > 0.001) | |
{ | |
transform.rotation = Quaternion.LookRotation(xzMove); | |
} | |
} | |
// We are in jump mode but just became grounded | |
if (IsGrounded()) | |
{ | |
lastGroundedTime = Time.time; | |
inAirVelocity = Vector3.zero; | |
if (jumping) | |
{ | |
jumping = false; | |
SendMessage("DidLand", SendMessageOptions.DontRequireReceiver); | |
} | |
} | |
} | |
/*public void OnControllerColliderHit (ControllerColliderHit hit ) | |
{ | |
Debug.DrawRay(hit.point, hit.normal); | |
if (hit.moveDirection.y > 0.01) | |
return; | |
}*/ | |
public float GetSpeed () { | |
return moveSpeed; | |
} | |
public bool IsJumping () { | |
return jumping; | |
} | |
public bool IsGrounded () { | |
return (collisionFlags & CollisionFlags.CollidedBelow) != 0; | |
} | |
public Vector3 GetDirection () { | |
return moveDirection; | |
} | |
public bool IsMovingBackwards () { | |
return movingBack; | |
} | |
public float GetLockCameraTimer () | |
{ | |
return lockCameraTimer; | |
} | |
public bool IsMoving () | |
{ | |
return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5; | |
} | |
public bool HasJumpReachedApex () | |
{ | |
return jumpingReachedApex; | |
} | |
public bool IsGroundedWithTimeout () | |
{ | |
return lastGroundedTime + groundedTimeout > Time.time; | |
} | |
public void Reset () | |
{ | |
gameObject.tag = "Player"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment