Not cycling through an array as expected - c#

I'm trying to get the player to continue on after he jumps (see code), but what he does is return back to the jumpPosition transform without continuing on to the next point.
Here is what it is doing in Unity:
...
Here is my code for playerMovment:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class MovementController : MonoBehaviour
{
// public GameObject playerToMove; // not sure why I need this
public float moveSpeed; // move speed of the player going from player postion to current point, possible to use somewhere else
private Transform currentPoint; // used to determine where the next point the player has to move by cycling through 'points' array
public Transform jumpPoint; // used as a location trigger to tell the player when to jump -- will be attempting to make into an array
public Transform crouchPoint; // used as a location trigger to tell the player when to crounch -- will be attempting to make into an array
public Transform[] points; // an array of location for the 'currentPoint' to cycle through
public float maxPause = 100; // used to determine the length of time between when the player arrives at the 'currentPoint' and when to leave said point; default = 100
public float reducedPause = 2; // used to set 'maxPause' to a smaller number so that player won't keep jumping/crouching
public TestCharacterController2D controller; // acceses the TestCharacterController2D script (I didnt write this script but plan to modiify) used for basic move, jump, and crouch funtions
public Animator animator; // my attempt to find the player's animator
public bool isRight; // used to to determine which way the character is facing -- I think this can be accesed through the 'controller' variable (TestCharacterController2D script)
private bool jump; // to tell the 'controller' when to jump
private bool crouch; // to tell the 'controller' when to crouch
private bool pause = false; // used to determine when the player arrives at the 'currentPoint' and the 'maxPause' countdown begins
public int pointsSelection; // used to cycle the 'points' array when maxPause cycle is over and player is at current point
// public float jumpHeight = 100f; // not sure why used
void Start() // looking into 'onAwake' maybe? (or others)
{
currentPoint = points[pointsSelection]; // sets currentPoint to default location ('pointSelection' is 'publc' so can be modified in Unity
isRight = true; // player starts facing right -- as per character animations
}
void Update() // not sure if should have more in 'FixedUpdate' or others (maybe?)
{
jump = false;
if (Vector2.Distance(transform.position, currentPoint.position) < 0.05f)
// checks to see if player is at 'currentPoint'
{
pause = true; // starts the pause sequenece
Debug.Log("Pause = " + pause);
if (pause) // when the movement is pause do the the following
{
moveSpeed = 0;
animator.SetFloat("Speed", 0); // player stops moving -- works!
if (maxPause <= 100) // checks to see if still paused
{
Debug.Log("this is maxPause: " + maxPause);
if (maxPause < 0) // found 'maxPause' was going to far below zero
maxPause = 0;
maxPause--; // reduce pause amount (working way out of loop)
}
if (maxPause == 0) // when 'maxPause' timer has finished
{
pointsSelection++; // move to next point
maxPause = 100; // reset 'maxPause' timer
pause = false; // resume 'transform.position == currentPoint.position' process
}
}
if (pointsSelection == points.Length) // makes sure 'pointsSelection' doesn't go out of bounds
{
Debug.Log("at end of array");
pointsSelection = 0; // start the player's movement process over again
}
}
else // not sure if requried
{
Debug.Log("pause = false");
Debug.Log("this is the moveSpeed " + moveSpeed);
Debug.Log("pointsSelection: " + pointsSelection);
}
if (Vector2.Distance(transform.position, jumpPoint.position) < 0.05f && jumpPoint == currentPoint) // conditions for the jump action (automatic) -- I fell the whole thing needs to be more elaborate ** WORK IN PROGRESS **
{
jump = true;
}
else
jump = false;
currentPoint = points[pointsSelection]; // moved to line 130 -- not sure if better here
}
void FixedUpdate()
{
if (isRight && transform.position.x > currentPoint.position.x) // flipping the character -- I'm pretty sure I can use TestCharacterController2D to do this for me, this is comparing the player's 'transform'
{
moveSpeed = -0.25f; // tells controller to head in the left direction
isRight = false; // no longer facing right
}
if (!isRight && transform.position.x < currentPoint.position.x) // reverse of above
{
moveSpeed = 0.25f; // tells controller to head in the right direction
isRight = true; // no longer facing left
}
if (moveSpeed > 0 || moveSpeed < 0)
animator.SetFloat("Speed", 1); // player starts PlayerRun animation -- works!
// Move our character
controller.Move(moveSpeed, crouch, jump); // draws from the TestCharacterController2D script
}
public void OnLanding()
{
animator.SetBool("Jumped", false);
}
}

Related

Objects not colliding in Unity 3D

I am following along with creating the Obstacle Game from Game Programming with Unity and C# by Casey Hardman. I have reached the point in the first few pages of Chapter 16 where you create a Hazard to kill the player. In the beginning stages, you write code that kills the player object if the player object collides with the hazard. I've followed the instructions (as far as I can tell) to the letter and yet when I created a sphere as a test hazard, assigned the script to it, and ran the player object into it, nothing happened when they collided. I thought maybe there was an error with the Hazard code, so I commented out the "when collide with object on player layer, kill player" code, wrote code to have it just write to the console when they collide, and tested it. Unfortunately, there doesn't seem to be any collision detection when these two objects touch each other. I've Googled "objects not colliding unity" and every combination of "collision not detected in unity" I can think of and none of the answers have helped so I'm posting here in hopes I get an answer. I'm including screenshots of the two objects and their settings, my physics settings in Unity, and the code I've written for both objects in hopes that someone can catch what I'm doing wrong.
The Player Object
The Test Hazard Object
Layer Collision Matrix
The Player Object Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
//References
[Header("References")]
public Transform trans;
public Transform modelTrans;
public CharacterController characterController;
//Movement
[Header("Movement")]
[Tooltip("Units moved per second at maximum speed.")]
public float movespeed = 24;
[Tooltip("Time, in seconds, to reach maximum speed.")]
public float timeToMaxSpeed = .26f;
private float VelocityGainPerSecond{ get { return movespeed / timeToMaxSpeed; }}
[Tooltip("Time, in seconds, to go from maximum speed to stationary.")]
public float timeToLoseMaxSpeed = .2f;
private float VelocityLossPerSecond { get { return movespeed / timeToLoseMaxSpeed; }}
[Tooltip("Multiplier for momentum when attempting to move in a direction opposite the current traveling direction (e.g. trying to move right when already moving left.")]
public float reverseMomentumMultiplier = 2.2f;
private Vector3 movementVelocity = Vector3.zero;
private void Movement()
{
// IF W or the up arrow key is held:
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
if (movementVelocity.z >= 0) // If we're already moving forward
//Increase Z velocity by VelocityGainPerSecond, but don't go higher than 'moveSpeed':
movementVelocity.z = Mathf.Min(movespeed,movementVelocity.z + VelocityGainPerSecond * Time.deltaTime);
else // Else if we're moving back
//Increase Z velocity by VelocityGainPerSecond, using the reverseMomentumMultiplier, but don't raise higher than 0:
movementVelocity.z = Mathf.Min(0,movementVelocity.z + reverseMomentumMultiplier * Time.deltaTime);
}
//If S or the down arrow key is held:
else if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
if (movementVelocity.z > 0) // If we're already moving forward
movementVelocity.z = Mathf.Max(0,movementVelocity.z - VelocityGainPerSecond * reverseMomentumMultiplier * Time.deltaTime);
else // If we're moving back or not moving at all
movementVelocity.z = Mathf.Max(-movespeed,movementVelocity.z - VelocityGainPerSecond * Time.deltaTime);
}
else //If neither forward nor back are being held
{
//We must bring the Z velocity back to 0 over time.
if (movementVelocity.z > 0) // If we're moving up,
//Decrease Z velocity by VelocityLossPerSecond, but don't go any lower than 0:
movementVelocity.z = Mathf.Max(0,movementVelocity.z - VelocityLossPerSecond * Time.deltaTime);
else //If we're moving down,
//Increase Z velocity (back towards 0) by VelocityLossPerSecond, but don't go any higher than 0:
movementVelocity.z = Mathf.Min(0,movementVelocity.z + VelocityLossPerSecond * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
if (movementVelocity.x >= 0) //If we're already moving right
//Increase X velocty by VelocityGainPerSecond, but don't go higher than 'movespeed':
movementVelocity.x = Mathf.Min(movespeed,movementVelocity.x + VelocityGainPerSecond * Time.deltaTime);
else //If we're moving left
//Increase X velocity by VelocityGainPerSecond, using the reverseMomentumMultiplier, but don't raise higher than 0:
movementVelocity.x = Mathf.Min(0,movementVelocity.x + VelocityGainPerSecond * reverseMomentumMultiplier * Time.deltaTime);
}
else if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
if (movementVelocity.x > 0) //If we're already moving right
movementVelocity.x = Mathf.Max(0,movementVelocity.x - VelocityGainPerSecond * reverseMomentumMultiplier * Time.deltaTime);
else // If we're moving left or not at all
movementVelocity.x = Mathf.Max(-movespeed,movementVelocity.x - VelocityGainPerSecond * Time.deltaTime);
}
else //If neither left nor right are being held
{
//We must bring the X Velocity back to 0 over time.
if (movementVelocity.x > 0) //If we're moving right,
//Decrease X velocity by VelocityLossPerSecond, but don't go any lower than 0:
movementVelocity.x = Mathf.Max(0,movementVelocity.x - VelocityLossPerSecond * Time.deltaTime);
else //If we're moving left
//Increase X velocity (back towards 0) by VelocityLossPerSecond, but don't go any higher than 0:
movementVelocity.x = Mathf.Min(0,movementVelocity.x + VelocityLossPerSecond * Time.deltaTime);
}
//If the player is moving in either direction (left/right or up/down):
if (movementVelocity.x != 0 || movementVelocity.z != 0)
{
//Applying the movement velocity:
characterController.Move(movementVelocity * Time.deltaTime);
//Keeping the model holder rotated towards the last movement direction:
modelTrans.rotation = Quaternion.Slerp(modelTrans.rotation, Quaternion.LookRotation(movementVelocity), .18F);
}
}
//Death and Respawning
[Header("Death and Respawning")]
[Tooltip("How long after the player's death, in seconds, before they are respawned?")]
public float respawnWaitTime = 2f;
private bool dead = false;
private Vector3 spawnPoint;
private Quaternion spawnRotation;
private void Update()
{
Movement();
}
void Start()
{
spawnPoint = trans.position;
spawnRotation = modelTrans.rotation;
}
public void Die()
{
if (!dead)
{
dead = true;
Invoke("Respawn", respawnWaitTime);
movementVelocity = Vector3.zero;
enabled = false;
characterController.enabled = false;
modelTrans.gameObject.SetActive(false);
}
}
public void Respawn()
{
modelTrans.rotation = spawnRotation;
dead = false;
trans.position = spawnPoint;
enabled = true;
characterController.enabled = true;
modelTrans.gameObject.SetActive(true);
}
}
The Hazard Object Script:
`using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hazard : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.layer == 8)
{
//Player player = other.GetComponent<Player>();
//if (player != null)
//player.Die();
Debug.Log("Yay!");
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}`
I have tried:
Adding a separate box collider to the Player Object
Adding a Rigidbody to one, both, and the other objects
Pulling my hair out
Restarting Unity
Restarting my computer
Tagging the Player Object as Player (I saw a question that looked similar to mine so I thought maybe this would help; I'm not very familiar with Unity or C# yet so I'm unsure what or why things would help.)
None of the above has made it seem like Unity is detecting collision between these two objects.
Here's a line from the documentation for OnTriggerEnter:
"Both GameObjects must contain a Collider component. One must have Collider.isTrigger enabled, and contain a Rigidbody."
Have you tried adding the Collider to your player at the same time as the Rigidbody component to your hazard?
For OnTriggerEnter() you need colliders with Is Trigger ticked and a ridig body. But this check
if (other.gameObject.layer == 8)
does not what it looks like. Layer values are used as bit masks, read more about it here.
To check against the correct layer, you need bitwise operations:
int playerMask = LayerMask.GetMask("Player"); // something like 00110101001
int otherLayer = 1 << other.gameObject.layer; // something like 00010000000
if ((playerMask | otherLayer ) != 0) { // we hit the mask ...1.......
Debug.Log("yay");
}
Be careful when using bitwise operators to not get tricked by operator precedences. Short version from above would be
if ((playerMask | (1 << other.gameObject.layer)) != 0) { ... // extra parentheses!
This will succeed for all objects within the player layer. An alternative would be to set the tag of the player object and compare against that tag:
if (other.gameObject.CompareTag("Player")) { ... // "Player" tag is not the same as "Player" layer

Input.GetKeyDown calls a coroutine to put in 1st person but it blocks my character's movements

In this script, I call a coroutine using Input; this coroutine allows that if we press F5, change the view of our character that we see in 3rd person mode to 1st person mode and if we press F5 again, it will replace the view. Only, by using input.GetKey and not input.GetKeyDown the coroutine will read several times which gives an ugly result. But when I use Input.GetKeyDown, and I'm in 1st person mode (the basic view is in 3rd person) my character's movements are blocked: I can't move forward, backward, jump etc... error in the script ?
If you want to test on unity, just download the file on this link https://github.com/TUTOUNITYFR/unitypackages-jeu-survie-2022-tufr/blob/main/Episode01/personnage-et-environnement.unitypackage then in the AimBehaviourBasic script, replace all the code with:
`
using UnityEngine;
using System.Collections;
// AimBehaviour inherits from GenericBehaviour. This class corresponds to aim and strafe behaviour.
public class AimBehaviourBasic : GenericBehaviour
{
public Texture2D crosshair; // Crosshair texture.
public float aimTurnSmoothing = 0.15f; // Speed of turn response when aiming to match camera facing.
public Vector3 aimPivotOffset = new Vector3(0.5f, 1.2f, 0f); // Offset to repoint the camera when aiming.
public Vector3 aimCamOffset = new Vector3(0f, 0.4f, -0.7f); // Offset to relocate the camera when aiming.
private int aimBool; // Animator variable related to aiming.
public bool aim; // Boolean to determine whether or not the player is aiming.
// Start is always called after any Awake functions.
void Start ()
{
// Set up the references.
aimBool = Animator.StringToHash("Aim");
}
// Update is used to set features regardless the active behaviour.
void Update ()
{
// Activate/deactivate aim by input.
if (Input.GetKeyDown (KeyCode.F5) && !aim)
{
StartCoroutine(ToggleAimOn());
}
else
if (Input.GetKeyDown (KeyCode.F5) && aim)
{
StartCoroutine(ToggleAimOff());
}
}
// Co-rountine to start aiming mode with delay.
private IEnumerator ToggleAimOn()
{
yield return new WaitForSeconds(0.05f);
// Aiming is not possible.
if (behaviourManager.GetTempLockStatus(this.behaviourCode) || behaviourManager.IsOverriding(this))
yield return false;
// Start aiming.
else
{
aim = true;
int signal = 1;
yield return new WaitForSeconds(0.1f);
aimCamOffset.x = Mathf.Abs(aimCamOffset.x) * signal;
aimPivotOffset.x = Mathf.Abs(aimPivotOffset.x) * signal;
yield return new WaitForSeconds(0.1f);
behaviourManager.GetAnim.SetFloat(speedFloat, 0);
// This state overrides the active one.
behaviourManager.OverrideWithBehaviour(this);
}
}
// Co-rountine to end aiming mode with delay.
private IEnumerator ToggleAimOff()
{
aim = false;
yield return new WaitForSeconds(0.3f);
behaviourManager.GetCamScript.ResetTargetOffsets();
behaviourManager.GetCamScript.ResetMaxVerticalAngle();
yield return new WaitForSeconds(0.05f);
behaviourManager.RevokeOverridingBehaviour(this);
}
// LocalFixedUpdate overrides the virtual function of the base class.
public override void LocalFixedUpdate()
{
// Set camera position and orientation to the aim mode parameters.
if(aim)
{
behaviourManager.GetCamScript.SetTargetOffsets (aimPivotOffset, aimCamOffset);
}
}
// LocalLateUpdate: manager is called here to set player rotation after camera rotates, avoiding flickering.
public override void LocalLateUpdate()
{
AimManagement();
}
// Handle aim parameters when aiming is active.
void AimManagement()
{
// Deal with the player orientation when aiming.
Rotating();
}
// Rotate the player to match correct orientation, according to camera.
void Rotating()
{
// Always rotates the player according to the camera horizontal rotation in aim mode.
Quaternion targetRotation = Quaternion.Euler(0, behaviourManager.GetCamScript.GetH, 0);
float minSpeed = Quaternion.Angle(transform.rotation, targetRotation) * aimTurnSmoothing;
// Rotate entire player to face camera.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, minSpeed * Time.deltaTime);
}
// Draw the crosshair when aiming.
void OnGUI ()
{
if (crosshair)
{
float mag = behaviourManager.GetCamScript.GetCurrentPivotMagnitude(aimPivotOffset);
GUI.DrawTexture(new Rect(Screen.width / 2 - (crosshair.width * 0.5f),
Screen.height / 2 - (crosshair.height * 0.5f),
crosshair.width, crosshair.height), crosshair);
}
}
}
`
Now you can test, press play and then press F5: the camera will change position but if you try to move (les touches : Z,Q,S,D, escape or arrowUp, arrowDown etc..) absolutely nothing will happen: here is my problem... Sorry for my bad English I am French ; )
Using coroutines like this will mess up your code because both coroutines can run almost at the same time, for example:
If you press F5 the first time ToggleAimOn() will be executed but before it get's finished you can press F5 again and call ToggleAimOff(), and since coroutines are async, that means they can run at the same time so it will create a real messed up behaviour
What you can try is to have another flag beside aim that will check if a coroutine is running like this:
private bool IsCoroutineActive = false;
void Update ()
{
// Activate/deactivate aim by input.
if (Input.GetKeyDown (KeyCode.F5) && !IsCoroutineActive && !aim)
{
StartCoroutine(ToggleAimOn());
}
else
if (Input.GetKeyDown (KeyCode.F5) && !IsCoroutineActive && aim)
{
StartCoroutine(ToggleAimOff());
}
}
// Co-rountine to start aiming mode with delay.
private IEnumerator ToggleAimOn()
{
IsCoroutineActive = true;
yield return new WaitForSeconds(0.05f);
// Aiming is not possible.
if (behaviourManager.GetTempLockStatus(this.behaviourCode) || behaviourManager.IsOverriding(this))
{
IsCoroutineActive = false;
yield return false;
}
// Start aiming.
else
{
aim = true;
int signal = 1;
yield return new WaitForSeconds(0.1f);
aimCamOffset.x = Mathf.Abs(aimCamOffset.x) * signal;
aimPivotOffset.x = Mathf.Abs(aimPivotOffset.x) * signal;
yield return new WaitForSeconds(0.1f);
behaviourManager.GetAnim.SetFloat(speedFloat, 0);
// This state overrides the active one.
behaviourManager.OverrideWithBehaviour(this);
}
IsCoroutineActive = false;
}
// Co-rountine to end aiming mode with delay.
private IEnumerator ToggleAimOff()
{
IsCoroutineActive = true;
aim = false;
yield return new WaitForSeconds(0.3f);
behaviourManager.GetCamScript.ResetTargetOffsets();
behaviourManager.GetCamScript.ResetMaxVerticalAngle();
yield return new WaitForSeconds(0.05f);
behaviourManager.RevokeOverridingBehaviour(this);
IsCoroutineActive = false;
}

How can I make a smooth transition between two animations?

First animation when starting the game the character is examine or typing and then after 10 seconds I want the character to start walking and just before reaching the destination to change to idle.
The order should be : Typing , Walking , Idle
With this script the character is typing waiting 10 seconds rotating looking the target and then move to the target without the two animations Walking and Idle.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentControl : MonoBehaviour
{
public List<Transform> points;
public bool waitTimeToMove = false;
//notice WaitTime is a float now
public float WaitTime = 10f;
public bool randomWaitTime;
float waitMinTime = 1f;
float waitMaxTime = 10f;
public bool loop = false;
private int destPoint = 0;
private NavMeshAgent agent;
private Transform originalPos;
//two vars for handling timer
private float timer = 0;
private float originSpeed;
void Start()
{
agent = GetComponent<NavMeshAgent>();
// Disabling auto-braking allows for continuous movement
// between points (ie, the agent doesn't slow down as it
// approaches a destination point).
agent.autoBraking = false;
originSpeed = agent.speed;
if (randomWaitTime == true)
{
WaitTime = Random.Range(waitMinTime, waitMaxTime);
}
//transforms dont exist without A GameObject and a GameObject doesn't exist without a transform
//create a new GameObject to hold our position
GameObject originalPositionObject = new GameObject();
originalPositionObject.name = "WP";
originalPositionObject.tag = "Waypoint";
originalPositionObject.transform.parent = GameObject.Find("Waypoints").transform;
//set the new gameobjects position equal to where the transform is right now
originalPositionObject.transform.position = transform.position;
//add this to the points list instead
points.Add(originalPositionObject.transform);
}
void GotoNextPoint()
{
// Returns if no points have been set up
if (points.Count == 0)
return;
// Set the agent to go to the currently selected destination.
agent.destination = points[destPoint].position;
// Choose the next point in the array as the destination,
// cycling to the start if necessary.
destPoint = (destPoint + 1) % points.Count;
}
void Update()
{
// Choose the next destination point when the agent gets
// close to the current one.
if (!agent.pathPending && agent.remainingDistance < 1f)
{
//if wait to move is true
if (waitTimeToMove)
{
//if timer is less than 10
if (timer < WaitTime)
{
//add Time.deltaTime each time we hit this point
timer += Time.deltaTime;
}
//no longer waiting because timer is greater than 10
else
{
waitTimeToMove = false;
}
}
//if we hit here waitToMove is false, so go ahead as usual
else
{
if (loop == false && destPoint == points.Count - 1)
{
agent.speed = 0;
}
if (loop == true || destPoint != points.Count - 1)
{
agent.speed = originSpeed;
// Not working if setting back to loop = true in the inspector
// After it was not loop and agent in last waypoint
// When setting to loop = true it's not continue to check why
// Should continue the loop if loop true !
// Loop = true is not working when game is running only on Start
GotoNextPoint();
}
}
}
}
}
In the editor I have an animator controller for the character with 3 states : The touc/examine , walk , idle
The first animation is working fine but it's not changing to walk after 10 seconds and not changing to walk at all. It keep looping the first animation all the time. and the first animation length is more then 0 seconds.
between the first animation state and the second walk state there is a transition and exit time is disabled with one condition Walk True and a parameter I added of type bool name Walk.
between the second state walk and the last state idle also there is a transition and exit time disabled too with condition Walk True.
Screenshot of the first transition :
The main idea is to change transitions smooth between the 3 states and then stop when the last state played without looping. Each state should play once.
What I did so far :
In both transitions disabled Has Exit Time (enabled false)
Then changed the script added a line to start the Walk animation state :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentControl : MonoBehaviour
{
public List<Transform> points;
public bool waitTimeToMove = false;
//notice WaitTime is a float now
public float WaitTime = 10f;
public bool randomWaitTime;
float waitMinTime = 1f;
float waitMaxTime = 10f;
public bool loop = false;
public Animator anim;
private int destPoint = 0;
private NavMeshAgent agent;
private Transform originalPos;
//two vars for handling timer
private float timer = 0;
private float originSpeed;
void Start()
{
agent = GetComponent<NavMeshAgent>();
// Disabling auto-braking allows for continuous movement
// between points (ie, the agent doesn't slow down as it
// approaches a destination point).
agent.autoBraking = false;
originSpeed = agent.speed;
if (randomWaitTime == true)
{
WaitTime = Random.Range(waitMinTime, waitMaxTime);
}
//transforms dont exist without A GameObject and a GameObject doesn't exist without a transform
//create a new GameObject to hold our position
GameObject originalPositionObject = new GameObject();
originalPositionObject.name = "WP";
originalPositionObject.tag = "Waypoint";
originalPositionObject.transform.parent = GameObject.Find("Waypoints").transform;
//set the new gameobjects position equal to where the transform is right now
originalPositionObject.transform.position = transform.position;
//add this to the points list instead
points.Add(originalPositionObject.transform);
anim = GetComponent<Animator>();
}
void GotoNextPoint()
{
// Returns if no points have been set up
if (points.Count == 0)
return;
// Set the agent to go to the currently selected destination.
agent.destination = points[destPoint].position;
// Choose the next point in the array as the destination,
// cycling to the start if necessary.
destPoint = (destPoint + 1) % points.Count;
}
void Update()
{
// Choose the next destination point when the agent gets
// close to the current one.
if (!agent.pathPending && agent.remainingDistance < 1f)
{
//if wait to move is true
if (waitTimeToMove)
{
//if timer is less than 10
if (timer < WaitTime)
{
//add Time.deltaTime each time we hit this point
timer += Time.deltaTime;
}
//no longer waiting because timer is greater than 10
else
{
waitTimeToMove = false;
anim.SetBool("Walk", true);
}
}
//if we hit here waitToMove is false, so go ahead as usual
else
{
if (loop == false && destPoint == points.Count - 1)
{
agent.speed = 0;
}
if (loop == true || destPoint != points.Count - 1)
{
agent.speed = originSpeed;
// Not working if setting back to loop = true in the inspector
// After it was not loop and agent in last waypoint
// When setting to loop = true it's not continue to check why
// Should continue the loop if loop true !
// Loop = true is not working when game is running only on Start
GotoNextPoint();
}
}
}
}
}
After 10 seconds :
anim.SetBool("Walk", true);
but now I want that a bit before or when the agent get to the target destination to change it to the Idle state animation :
anim.SetBool("Idle", true);
but I'm not sure where to put it in the script. I tried in this place :
if (loop == false && destPoint == points.Count - 1)
{
agent.speed = 0;
anim.SetBool("Idle", true);
}
but it didn't work fine the agent kept walking after the target destination then moved back to the target destination then changed to idle a mess.

Unity: Animation looping while waiting for state transition

I previously posted this question on the gameDev SE but with no luck, therefore I am trying to see if I could find some help here.
I am having some troubles with the transitions in my animator. Specifically, I am trying to set up a some code to handle combo sequences, and to do so I am using coroutines that exploit the state machine given by the animations in the animator. Here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.SceneManagement;
/*enum PlayerState is a list of states that can be taken by the player character. They will be used to implement a finite state machine-like
behavior with the actions it can take*/
public enum PlayerState {
walk,
attack,
interact,
dash,
stagger
}
public class Player_Base : MonoBehaviour {
//Basic parameters
Rigidbody2D myRigidBody; //These are to call the components of the Player GameObject
public static Transform playerPos;
public PlayerState currentState;
Animator myAnimator;
public HealthManager myStatus;
private bool isAlive = true;
//Sorting layers parameters
public CapsuleCollider2D myFeet;
public static float playerVertPos; // Need this to sort other objects layers in another script.
//Movement parameters
[SerializeField] float moveSpeed = 3f; // use [serfiel] or public in order to have something that you can modify from Unity UI
public Vector2 moveInput;
private bool isMoving;//Implementing the state machine and the *blend trees*, you need only to define one bool for all animations of a kind (eg walking anims)
//Combat parameters
private int comboCounter = 0;
private float comboTimer = 0;
//dash parameters
[SerializeField] float dashTimeMax = 1f;
[SerializeField] float dashTime = 0;
[SerializeField] float dashPush = 0.001f;
[SerializeField] float dashSpeed = 10f;
// Use this for initialization
void Start()
{
currentState = PlayerState.walk;//Initial default state of the player
myRigidBody = GetComponent<Rigidbody2D>(); /*the getcomp looks for the related component in the <> and uses it in the code*/
myFeet = GetComponent<CapsuleCollider2D>();
myAnimator = GetComponent<Animator>();
myAnimator.SetFloat("MoveX", 0);//If i do not set a default values for these, if player attacks without moving first, all colliders will activate and will hit all around him
myAnimator.SetFloat("MoveY", -1);
myStatus = GameObject.FindObjectOfType<HealthManager>();
}
// Update is called once per frame
void Update()
{
playerVertPos = myFeet.bounds.center.y;
moveInput = Vector2.zero;/*getaxis and getaxisraw register the input of the axes and outputs +1 or -1 according to the axis direction*/
moveInput.x = Input.GetAxisRaw("Horizontal");
moveInput.y = Input.GetAxisRaw("Vertical");
if (!isAlive)
{
return;
}
else {
if (currentState == PlayerState.walk)//It will consider walking only when in that state, this means that if it is attacking for instance,
//it needs to change its state. Good for compartimentalization of the actions (otherwise I could have changed the direction of the attacks)
{
if (moveInput != Vector2.zero)//This if statement is such that if there is no new input to update the movement with, the last (idle) animation
//will remain, so if you go right and stop, the player keeps facing right
{
Move();
myAnimator.SetFloat("MoveX", moveInput.x);
myAnimator.SetFloat("MoveY", moveInput.y);
myAnimator.SetBool("isMoving", true);
}
else {
myAnimator.SetBool("isMoving", false);
}
}
//Attack inputs
if (Input.GetKeyDown(KeyCode.Mouse0) && currentState != PlayerState.attack)//second clause because i do not want to indefinitely attack every frame
{
StartCoroutine(FirstAttack());
}
if (Input.GetKeyDown(KeyCode.Space) && currentState != PlayerState.dash)
{
StartCoroutine(Dashing());
}
DeathCheck();//check if player is still alive
}
}
public void Move()
{
moveInput.Normalize();
myRigidBody.MovePosition(myRigidBody.position + moveInput * moveSpeed * Time.deltaTime);
//If i want to work with the velocity vector: i have to use rb.velocity, not just taking the xplatinput times movespeed
}
public void MoveOnAnimation(int xMove, int yMove, float displacement)
{
moveInput.x = xMove;
moveInput.y = yMove;
moveInput.Normalize();
myRigidBody.MovePosition(myRigidBody.position + moveInput * displacement * Time.deltaTime);
}
private IEnumerator FirstAttack() {
//Start Attack
comboCounter = 1;
myAnimator.SetInteger("comboSequence", comboCounter);
currentState = PlayerState.attack;
yield return new WaitForSeconds(AttackTemplate.SetDuration(0.6f) - comboTimer);//Problem: if i reduce the time below the animation time of the second animation, the second animation won't go untile the end
comboTimer = AttackTemplate.SetComboTimer(0.4f);
//if combo not triggered:
while (comboTimer >= 0)
{
Debug.Log(comboTimer);
comboTimer -= Time.deltaTime;
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Debug.Log("Chained");
StopCoroutine(FirstAttack());
StartCoroutine(SecondAttack());
}
yield return null;
}
comboCounter = 0;
myAnimator.SetInteger("comboSequence", comboCounter);
currentState = PlayerState.walk;
}
private IEnumerator SecondAttack()
{
comboCounter = 2;
myAnimator.SetInteger("comboSequence", comboCounter);
currentState = PlayerState.attack;
yield return null;
//if combo not triggered:
yield return new WaitForSeconds(AttackTemplate.SetDuration(0.9f));
comboCounter = 0;
myAnimator.SetInteger("comboSequence", comboCounter);
currentState = PlayerState.walk;
}
private void Dash()
{
if (dashTime >= dashTimeMax)
{
dashTime = 0;
myRigidBody.velocity = Vector2.zero;
currentState = PlayerState.walk;
}
else
{
currentState = PlayerState.dash;
dashTime += Time.deltaTime;
moveInput.Normalize();
Vector2 lastDirection = moveInput;
myRigidBody.velocity = lastDirection * dashSpeed;
}
}
private IEnumerator Dashing()
{
currentState = PlayerState.dash;
for (float timeLapse = 0; timeLapse < dashTime; timeLapse = timeLapse + Time.fixedDeltaTime)
{
moveInput.Normalize();
Vector2 lastDirection = moveInput;
myRigidBody.velocity = lastDirection * dashSpeed;
}
yield return null;
myRigidBody.velocity = Vector2.zero;
currentState = PlayerState.walk;
}
private void DeathCheck() //if the player health reaches 0 it will run
{
if (HealthManager.health == 0) {
isAlive = false; // this is checked in the update, when false disables player inputs
myRigidBody.constraints = RigidbodyConstraints2D.FreezePosition; // if i don't lock its position, the last bounce with the enemy pushes the player towards inifinity
myAnimator.SetTrigger("death");//triggers the death animation
StartCoroutine(LoadNextScene());
}
}
[SerializeField] float LevelLoadDelay = 5f;
[SerializeField] float LevelSlowMo = 1f;
IEnumerator LoadNextScene()
{
Time.timeScale = LevelSlowMo;
yield return new WaitForSecondsRealtime(LevelLoadDelay);
Time.timeScale = 1f;
var CurrentSceneIndex = SceneManager.GetActiveScene().buildIndex;
SceneManager.LoadScene(CurrentSceneIndex + 1);
}
}
What I am doing basically is to use enums to define the player states and on input, if the player is not already in the attacking state, perform an attack. Once the FirstAttack() is called, it will first of all update an integer, comboCounter, which handles the transitions between consecutive attacks, input said integer in the animator and then change my state to attack. After this, I created a while loop that goes on until the end of an established time interval during which the player would be able to press the same attack button to chain the combo. If this does not happen, the state and integer parameter are reset.
The problem I am facing is that while the player can actually perform the combo with the second attack, during all the interval in which the first animation is active it keeps looping. Furthermore, I noticed that the second animation does not reach the end, it seems like it stops once the interval that I previously set will end.
Update: This is the screenshot of my animator window:
The transitions any state -> 1stAttack and 1stAttack -> 2ndAttack is handled by the same integer parameter, comboSequence, which is set to 0 normally, to 1 for 1stAttack and to 2 for the second one. I observed that the transition any state -> 1stAttack is triggered multiple times whenever I press the hit button, in line with the looping problem I am facing.
I have tried a couple of things, for instance using normal functions instead of a coroutine, but in this way, I do not understand why, there are problems with the enums states, also I think that in the long term this approach would be more modular and customisable. I feel like I am missing something trivial but I do not understand what and it has been some time now, so any help would be much appreciated!
Disable Can Transition To Self = false

Player don't move slowly ( smoothly)

I have a question please in my game when i write "LEFT" in a InputField and click on a UI Button the cube move "LEFT" and eat coins(the same for up, down , right) my problem is when i wrote this code below the player moved but not slowly more like disappear than appear in the position that declare it
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public InputField mainInputField;
//public float speed;
public GameObject Player;
public Button Click_me;
public float smoothing = 1f;
public Transform TargetRight1;
public Transform TargetRight2;
public Transform TargetUP;
// Start is called before the first frame update
void Start()
{
}
public void SubmitName()
{
string[] lines = mainInputField.text.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
if (lines[i] == "UP")
{
// moveUP();
StartCoroutine(MyCoroutineUP(TargetUP));
}
else if (lines[i] == "DOWN")
{
//MoveDown();
}
else if (lines[i] == "LEFT")
{
//MoveLeft();
}
else if (lines[i] == "RIGHT")
{
StartCoroutine(MyCoroutineUP(TargetRight1));
}
}
// Click_me.interactable = false;
}
IEnumerator MyCoroutineUP(Transform target)
{
while (Vector3.Distance(Player.transform.position, target.position) > 0.05f)
{
Player.transform.position = Vector3.Lerp(Player.transform.position, target.position, smoothing * Time.deltaTime);
}
yield return null;
}
}
know if i put the yield return null; inside the while loop like this
while (Vector3.Distance(Player.transform.position, target.position) > 0.05f)
{
Player.transform.position = Vector3.Lerp(Player.transform.position, target.position, smoothing * Time.deltaTime);
yield return null;
}
the player move slowly and get the coins but if i have more than 2 ligne for example i wrote LEFT , UP the while loop won't work properly when i call the function in the first line. sorry for my English
You will get concurrent Coroutines.
It sounds like what you actually are asking is how to stack multiple commands and work them one by one. This gets a bit more complex but sounds like the perfect usecase for a Queue
private readonly Queue<Transform> _commands = new Queue<Transform>();
public void SubmitName()
{
var lines = mainInputField.text.Split('\n');
mainInputField.text = "";
foreach (var line in lines)
{
switch (line)
{
case "UP":
// adds a new item to the end of the Queue
_commands.Enqueue(TargetUp);
break;
case "DOWN":
_commands.Enqueue(TargetDown);
break;
case "LEFT":
_commands.Enqueue(TargetLeft);
break;
case "RIGHT":
_commands.Enqueue(TargetRight);
break;
}
}
StartCoroutine(WorkCommands());
}
private IEnumerator WorkCommands()
{
// block input
Click_me.interactable = false;
// run this routine until all commands are handled
while (_commands.Count > 0)
{
// returns the first element and at the same time removes it from the queue
var target = _commands.Dequeue();
// you can simply yield another IEnumerator
// this makes it execute and at the same time waits until it finishes
yield return MovementCoroutine(target);
}
// when done allow input again
Click_me.interactable = true;
}
To the lerping itself:
I wouldn't lerp like that. That starts the movement very quick and gets slower in the end but never really reaches the target position. If thats what you want leave it but I would rather recommend doing something like
private IEnumerator MovementCoroutine(Transform target)
{
var startPos = transform.position;
var targetPos = target.position;
var timePassed = 0f;
do
{
var lerpFactor = Mathf.SmoothStep(0, 1, timePassed / smoothing);
transform.position = Vector3.Lerp(startPos, targetPos, lerpFactor);
timePassed += Time.deltaTime;
yield return null;
}
while(timePassed < smoothing);
// just to be sure there is no over or undershooting
// in the end set the correct target position
transform.position = targetPos;
}
In smoothing you would then instead set the time in seconds the lerping should take in total. In my opinion this gives you more control. The SmoothStep makes the movement still being eased in and out.
If you want you could additionally also take the current distance into account for always making the object move with more or less the same speed regardless how close or far the target position is by adding/changing
var distance = Vector3.Distance(startPos, targetPos);
var duration = smoothing * distance;
do
{
var lerpFactor = Mathf.SmoothStep(0, 1, timePassed / duration);
...
}
while (timePassed < duration);
now in smoothing you would rather set the time in seconds the object should need to move 1 Unity unit.
I don't know your exact setup for the targets ofcourse but this is how it would look like with targets attached to the player (so they move along with it)

Categories