Unity C# check if player (Rigidbody2D) stopped moving for x seconds - c#

I'm working on a script, so I could detect when player is not moving for x seconds and load another scene accordingly.
If player started moving again after x seconds, then loading another scene should't be called.
I've tried using isSleeping function and delay it by including Coroutine with WaitForSeconds but it is still checking Rigidbody2D every frame. Is there any other way to check if Rigidbody2D hasn't moved for x seconds and only then load game over level, otherwise continue moving as before?
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
void Update() {
if (GetComponent<Rigidbody2D>().IsSleeping()) {
Application.LoadLevel(2);
}
}
}
In addition I have a script, which enables me to draw lines (with mouse) and stop player's movement, however lines disappear after x seconds. So for example if I'd set lines to disappear after 1 second, I would like to check if Rigidbody2D stopped moving for 2 seconds and only then load game over scene. Otherwise do nothing as Rigidbody2D will continue moving again after line disappears.

Try this
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
float delay = 3f;
float threshold = .01f;
void Update() {
if (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
StartCoRoutine("LoadTheLevel");
}
IEnumerator LoadTheLevel()
{
float elapsed = 0f;
while (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
{
elapsed += Time.deltaTime;
if(elapsed >= delay)
{
Application.LoadLevel(2);
yield break;
}
yield return null;
}
yield break;
}
}

You could try this...I'm not able to test this right now, so may need a little tweaking...
First some private variables:
private float _loadSceneTime;
private Vector3 _lastPlayerPosition;
private float _playerIdleDelay;
And in the Update method check if the player has moved:
private void Update()
{
// Is it time to load the next scene?
if(Time.time >= _loadSceneTime)
{
// Load Scene
}
else
{
// NOTE: GET PLAYERS POSITION...THIS ASSUMES THIS
// SCRIPT IS ON THE GAME OBJECT REPRESENTING THE PLAYER
Vector3 playerPosition = this.transform.position;
// Has the player moved?
if(playerPosition != _lastPlayerPosition)
{
// If the player has moved we will attempt to load
// the scene in x-seconds
_loadSceneTime = Time.time + _playerIdleDelay;
}
_lastPlayerPosition = playerPosition;
}
}

Related

Hit points bar wont fill completely, calculation only happens once

I have a hit points bar which is supposed to fill up by 0.1 each time the ball object collides with the goodOrb object. However the bar fills up by 0.1 only on the first collision. It does not move when the ball collides again.
I tried Debug.Log the value of the variable hitPoints which stores the current amount the bar is filled by. This variable is initialized to 0, I have another variable called increase which is set to 0.1, each time the two objects collide variable increase is supposed to be added to hitpoints. But this is happening only once. Debug.Log(hitpoints) shows 0.1 only once.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class goodOrb : MonoBehaviour
{
public int maxHealth = 100;
public int currentHealth;
public Image barImage;
private Rigidbody2D rb;
public float increase = 0.1f; //amount to increase bar each time a collision happens
public float hitPoints = 0.0f; // current amount the bar is filled by
// public AudioSource collisionSound;
int scoreValue= 5 ;
// Start is called before the first frame update
public void Start()
{
GameObject Filler = GameObject.Find("Filler");
Image barImage = Filler.GetComponent<Image>();
}
void OnTriggerEnter2D(Collider2D other)
{
ParticleSystem ps = GetComponent<ParticleSystem>();
if(other.tag=="Ball")
{
ps.Play();
HitPoints();
scoreManager.score += scoreValue;
// barImage.fillAmount = (float)currentHealth / (float)maxHealth;
// collisionSound.Play();
}
}
void Update()
{
}
// Update is called once per frame
void HitPoints()
{
GameObject Filler = GameObject.Find("Filler");
Image barImage = Filler.GetComponent<Image>();
hitPoints = hitPoints + increase;
barImage.fillAmount = hitPoints;
//print(hitPoints);
Debug.Log(hitPoints);
}
}
I expect increase to be added to hitpoints each time the collision happens and the hitpoints bar to be filled.
1- make sure the ball collides with same orb object every time.
2- make sure one collider isTrigger is true.
3- make sure trigger function is called every time ball collides.
by doing this
void OnTriggerEnter2D(Collider2D other){
Debug.Log (other.tag);
}

Still pausing/resuming and start a new game is not working good. Can'r figure what should I do?

First a screenshot of my Hierarchy :
Everything is inside a one scene name The :
The game start when Main Game is disabled. Inside Main Game sit all the game objects. The Main Menu and Game Manager are enabled.
When running the game first time, When the game start there is short animation of the player for 5 seconds. The player start from some rotating degrees on Z. Z = 50 when x and y both are 0. Then the player is rotating slowly over the Z to 0.
It's like the player is sleeping and awake up.
I'm using post processing stack by unity.
And here is the first problem. While the player is rotating on the Z and post processing effect is working if I press the Escape key it will bring me back to the Main Menu but then if I press the Escape key again it will start the game over again from the begin.
But if I'm waiting in my game for the player to finish rotating on the Z and the post processing effect is finished and then pressing on Escape it will bring the main menu and second time will resume the game from the same point.
I can't figure out why when the player is rotating and the post process is working the escape key make it start the game over again from the being ?
This is a screenshot of the game when start and after finish the rotating and the process stack :
Another problem I noticed now. After the game start using the post process and the player rotating finished if I press Escape it will go to main menu and escape again will be back to the game but for example in the second screenshot the conversation is not continue. It will return to the same point in the game but things not seems to continue like the conversation.
On the Back to main menu object I have a script attached to it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BackToMainMenu : MonoBehaviour
{
private bool _isInMainMenu = false;
public GameObject mainGame;
public GameObject mainMenu;
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (!_isInMainMenu)
{
// -- Code to freeze the game
mainGame.SetActive(false);
mainMenu.SetActive(true);
}
else
{
// -- Code to unfreeze the game
mainGame.SetActive(true);
mainMenu.SetActive(false);
}
_isInMainMenu = !_isInMainMenu;
}
}
}
On the Main Menu object under Main Menu I have attached this script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public GameObject mainGame;
public GameObject mainMenu;
public void PlayGame()
{
mainGame.SetActive(true);
mainMenu.SetActive(false);
}
public void QuitGame()
{
Application.Quit();
}
}
On the PLAY button I'm using this script method PlayGame from the MainMenu script.
In the Main Game object on the Player object attached to thew Player I have some scripts the controller :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 10.0f;
// Update is called once per frame
void Update()
{
float translatioin = Input.GetAxis("Vertical") * speed;
float straffe = Input.GetAxis("Horizontal") * speed;
translatioin *= Time.deltaTime;
straffe *= Time.deltaTime;
transform.Translate(straffe, 0, translatioin);
}
}
Player Lock Manager :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerLockManager : MonoBehaviour
{
public PlayerCameraMouseLook playerCameraMouseLook;
public PlayerController playerController;
// Start is called before the first frame update
public void PlayerLockState(bool LockPlayer, bool LockPlayerCamera)
{
if (LockPlayer == true)
{
playerController.enabled = false;
}
else
{
playerController.enabled = true;
}
if (LockPlayerCamera == true)
{
playerCameraMouseLook.enabled = false;
}
else
{
playerCameraMouseLook.enabled = true;
}
}
}
And Mouse Lock State :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseLockState : MonoBehaviour
{
public bool lockState = true;
private void Start()
{
LockState(lockState);
}
private void Update()
{
}
public void LockState(bool lockState)
{
if (lockState == false)
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
else
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
}
}
Under the Player as child I have the Player Camera object and attached to the Player Camera also some scripts :
Player Camera Mouse Look :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCameraMouseLook : MonoBehaviour
{
public float sensitivity = 5.0f;
public float smoothing = 2.0f;
private GameObject player;
private Vector2 mouseLook;
private Vector2 smoothV;
// Use this for initialization
void Start()
{
player = this.transform.parent.gameObject;
}
// Update is called once per frame
void Update()
{
var md = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));
md = Vector2.Scale(md, new Vector2(sensitivity * smoothing, sensitivity * smoothing));
smoothV.x = Mathf.Lerp(smoothV.x, md.x, 1f / smoothing);
smoothV.y = Mathf.Lerp(smoothV.y, md.y, 1f / smoothing);
mouseLook += smoothV;
mouseLook.y = Mathf.Clamp(mouseLook.y, -90f, 90f);
transform.localRotation = Quaternion.AngleAxis(-mouseLook.y, Vector3.right);
player.transform.localRotation = Quaternion.AngleAxis(mouseLook.x, Vector3.up);
}
}
And Depth Of Field script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PostProcessing;
public class DepthOfField : MonoBehaviour
{
public GameObject player;
public PostProcessingProfile postProcessingProfile;
public bool dephOfFieldFinished = false;
public PlayerLockManager playerLockManager;
private Animator playerAnimator;
private float clipLength;
// Start is called before the first frame update
void Start()
{
playerAnimator = player.GetComponent<Animator>();
AnimationClip[] clips = playerAnimator.runtimeAnimatorController.animationClips;
foreach (AnimationClip clip in clips)
{
clipLength = clip.length;
}
var depthOfField = postProcessingProfile.depthOfField.settings;
depthOfField.focalLength = 300;
StartCoroutine(changeValueOverTime(depthOfField.focalLength, 1, clipLength));
postProcessingProfile.depthOfField.settings = depthOfField;
}
IEnumerator changeValueOverTime(float fromVal, float toVal, float duration)
{
playerLockManager.PlayerLockState(true, true);
float counter = 0f;
while (counter < duration)
{
var dof = postProcessingProfile.depthOfField.settings;
if (Time.timeScale == 0)
counter += Time.unscaledDeltaTime;
else
counter += Time.deltaTime;
float val = Mathf.Lerp(fromVal, toVal, counter / duration);
dof.focalLength = val;
postProcessingProfile.depthOfField.settings = dof;
yield return null;
}
playerAnimator.enabled = false;
dephOfFieldFinished = true;
}
}
I have under Main Game also a object name Openning Scene and attached to it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OpeningCutscene : MonoBehaviour
{
public NaviConversations naviConversation;
public DepthOfField dephOfField;
// Update is called once per frame
void Update()
{
if (dephOfField.dephOfFieldFinished == true)
{
naviConversation.PlayNaviConversation(0);
dephOfField.dephOfFieldFinished = false;
}
}
}
It's a bit long but everything is connected.
The game start with the main menu.
When clicking the PLAY button a new game start.
When a new game start the script Depth Of Field is in action using the post processing. Also I'm locking the player so the player will not be able to move either the mouse or the player it self.
When the depth of field script finished his work a conversation between Navi and the Player start. When a conversation is in action the player can move the mouse 360 degrees but cant move the player to any direction.
When the conversation ended the player can also move to any direction.
Problems :
When start a new game while the depth of field script is in work pressing escape will bring the main menu but pressing escape again will not resume the depth of field script but will start it all over again.
When waiting the conversation to end if not pressing escape until the conversation ended then when moving around and pressing escape it will bring the main menu and again will resume the game from the same point.
The problems is when the game is doing something like the depth of field it will start the game over again instead resuming or when in conversation in the middle the conversation will never continue.
The ides with the escape key is once to get to main menu and second to resume the game.
The PLAY button is what should only start a new game and not the escape key.
It's a bit long but everything is connected.
I can't see which objects you hooked up to mainGame and mainMenu in BackToMainMenu from your hierarchy, so I have some conjecture in here.
Sounds like you want Escape key to pause, and unpause, as well as bring up a menu, you want Play button in the menu to restart your game.
However, in both MainMenu and BackToMainMenu you use the same code:
mainGame.SetActive(true);
mainMenu.SetActive(false);
This just turns the objects on and off in the hierarchy. This means the first time you do one of these things, all objects turned on under the gameobject referenced by mainGame will run their Awake and Start methods, but not the second time. Also depending which objects are active (like I said I can't fully see what component is on what object and which objects are referenced by which serialized field) you might be able to introduce a state error on BackToMainMenu._isInMainMenu because that field isn't changed when you hit play. Here is a fantastic image showing execution timeline in Unity:
Monobehavior Timeline
In summary:
The second time you run the game DepthOfField won't call Start a second time. Instead try OnEnable.
The conversation and game logic is not shown in your post, but likely it also is initializing in Start and won't run a second time.
A bit of trickiness around Coroutines is likely to blame here. When DepthOfField starts it also starts a coroutine. That coroutine keeps going even if you set the gameobject inactive. So you run the game once, that coroutine starts, you turn the object off when you hit escape, the coroutine finishes, you hit play again, but DepthOfField.dephOfFieldFinished == true and your playerAnimator is disabled and won't enable again.
Also 5. This kind of behavior can be tricky and is usually dependent on what else you have going on in your scene. Since you have one scene with everything at once, you need to watch out for putting stuff in Awake and Start since this will only run once. Instead you can try a number of things, my favorite is usually to set up a singleton or static class that works as a state machine for the whole scene. It will call custom Initialize functions in your behaviors instead of using Awake and Start.
Keep references to your coroutines by doing things like:
private Coroutine depthOfFieldRoutineRef;
private OnEnable()
{
if (depthOfFieldRoutineRef != null)
{
StopCoroutine(depthOfFieldRoutineRef);
}
depthOfFieldRoutineRef =
StartCoroutine(changeValueOverTime
(depthOfField.focalLength, 1, clipLength));
// Don't forget to set depthOfFieldRoutineRef to null again at the end of routine!
}
For simple pausing behavior you can also try setting Time.timescale = 0; and back to 1 when you want play to resume.
Less a question than a set of problems, hopefully this set of solutions helps!
Tutorial on Awake and Start
Docs on Coroutines
Docs on Time.timescale

How to spawn prefabs within a certain period of time in Unity?

I am trying to create a ball hitting game in the baseball format. I create a ball as a prefab. I want to push the ball to the main scene within a certain period of time.
For example; when the first ball is in the scene, the second ball will spawn after 5-6 seconds, then the third, fourth etc. I am the beginner level of Unity and I am not good at C#. I am not sure whether I am using the true functions such as Instantiate. Here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour {
public float RotateSpeed = 45; //The ball rotates around its own axis
public float BallSpeed = 0.2f;
public GameObject[] prefab;
public Rigidbody2D rb2D;
void Start() {
rb2D = GetComponent<Rigidbody2D>(); //Get component attached to gameobject
Spawn ();
}
void FixedUpdate() {
rb2D.MoveRotation(rb2D.rotation + RotateSpeed * Time.fixedDeltaTime); //The ball rotates around its own axis
rb2D.AddForce(Vector2.left * BallSpeed);
InvokeRepeating("Spawn", 2.0f, 2.0f);
}
public void Spawn ()
{
int prefab_num = Random.Range(0,3);
Instantiate(prefab[prefab_num]);
}
}
After I apply this script, the result is not what I want.
Add InvokeRepeating("Spawn", 2.0f, 2.0f); to the Start not the FixedUpdate.
InvokeRepeating invokes the method methodName in time seconds, then repeatedly every repeatRate seconds.
You can check the documentation here.
Use Coroutines
private IEnumerator SpawnBall() {
while(true) {
Instantiate(baseball);
yield return new WaitForSeconds(5);
}
}
Which can then be started with StartCoroutine() and terminated in one of three ways:
internally by breaking out of the while loop with break (the function would then have no more lines to execute and exit)
internally by yield break
externally by calling StopCoroutine() on a reference to the coroutine
Alternative to the other answers: Just use a countdown. This sometimes gives you more control
// Set your offset here (in seconds)
float timeoutDuration = 2;
float timeout = 2;
void Update()
{
if(timeout > 0)
{
// Reduces the timeout by the time passed since the last frame
timeout -= Time.deltaTime;
// return to not execute any code after that
return;
}
// this is reached when timeout gets <= 0
// Spawn object once
Spawn();
// Reset timer
timeout = timeoutDuration;
}
I updated my script by considering your feedbacks and it works like a charm. Thanks to all!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Threading;
public class Ball : MonoBehaviour {
public float RotateSpeed = 45; //The ball rotates around its own axis
public float BallSpeed = 0.2f;
public GameObject BaseBall;
public Transform BallLocation;
public Rigidbody2D Ball2D;
void Start() {
Ball2D = GetComponent<Rigidbody2D>(); //Get component attached to gameobject
InvokeRepeating("Spawn", 5.0f, 150f);
}
void FixedUpdate() {
Ball2D.MoveRotation(Ball2D.rotation + RotateSpeed * Time.fixedDeltaTime); //The ball rotates around its own axis
Ball2D.AddForce(Vector2.left * BallSpeed);
}
public void Spawn ()
{
Instantiate (BaseBall, BallLocation.position, BallLocation.rotation);
}
}

Make Animation Play After Boolean is Set to True?

I am pretty fluent in using Unity, but regarding Mechanim and Animations I am not the too great at currently, so please don't give me too much of a hard time lol. So I have this boolean that is in my GameManager script:
public bool countDownDone = false;
This boolean gets set to true once my "3,2,1, GO!" Countdown Timer ends at the beginning of my game. Everything in my game starts after that boolean is set to true. Example:
using UnityEngine;
using System.Collections;
public class PlaneMover : MonoBehaviour {
private GameManagerScript GMS; // Reference to my GameManager Script
public float scrollSpeed;
public float tileSizeZAxis;
public float acceleration; //This has to be a negative number in the inspector. Since our plane is coming down.
private Vector3 startPosition;
void Start () {
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> (); //Finds the Script at the first frame
// Transform position of the quad
startPosition = transform.position;
}
void Update () {
if (GMS.countDownDone == true) //Everything starts once the countdown ends.
{
/* Every frame - time in the game times the assigned scroll speed
and never exceed the length of the tile that we assign */
float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZAxis);
// New position equal to the start position plus vector3forward times new position
transform.position = startPosition + Vector3.forward * newPosition; // was vector3.forward
scrollSpeed += Time.deltaTime * acceleration; // This makes the plane increase in speed over time with
// whatever our acceleration is set to.
}
}
}
I have this Crawling animation that plays at the very beginning of the game (Even before the Timer ends) and loops forever. My question is , how do I make that crawling animation also start once that boolean is set to "true"? Or do I just apply it to a CoRoutine and make it play after 3 seconds? I have done extensive research on whether to reference Animation or Animator and I got confused on that too. Any advice? If you need more pictures or details on my question, just let me know. Thank you! :)
NEW CODE BELOW
using UnityEngine;
using System.Collections;
public class Crawling : MonoBehaviour {
Animator animator;
private GameManagerScript GMS;
void Start ()
{
animator = GetComponent<Animator> ();
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> ();
}
void Update ()
{
if (GMS.countDownDone == true)
{
animator.Play("Main Character Crawling", 1);
}
}
}
I have this Crawling animation that plays at the very beginning of the
game (Even before the Timer ends) and loops forever. My question is ,
how do I make that crawling animation also start once that boolean is
set to "true"?
The solution is to play the animation from script.
Remove the currentAnimation.
Select the GameObject your PlaneMover script is attached to, attach Animation and Animator components to it. Make sure that the Animation's Play Automatically is unchecked.
public class PlaneMover : MonoBehaviour {
private GameManagerScript GMS; // Reference to my GameManager Script
public float scrollSpeed;
public float tileSizeZAxis;
public float acceleration; //This has to be a negative number in the inspector. Since our plane is coming down.
private Vector3 startPosition;
Animation animation;
public AnimationClip animationClip; //Assign from Editor
void Start () {
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> (); //Finds the Script at the first frame
// Transform position of the quad
startPosition = transform.position;
animation = GetComponent<Animation>();
//Add crawing Animation
animation.AddClip(animationClip, "Crawling");
//Add other animation clips here too if there are otheres
}
void Update ()
{
if (GMS.countDownDone) //Everything starts once the countdown ends.
{
/* Every frame - time in the game times the assigned scroll speed
and never exceed the length of the tile that we assign */
float newPosition = Mathf.Repeat (Time.time * scrollSpeed, tileSizeZAxis);
// New position equal to the start position plus vector3forward times new position
transform.position = startPosition + Vector3.forward * newPosition; // was vector3.forward
scrollSpeed += Time.deltaTime * acceleration; // This makes the plane increase in speed over time with
// whatever our acceleration is set to.
//Play Animation
animation.PlayQueued("Crawling", QueueMode.CompleteOthers);
}
}
} }
I solved the problem!!
This is my script:
using UnityEngine;
using System.Collections;
public class Crawling : MonoBehaviour {
public Animator animator;
private GameManagerScript GMS;
void Start ()
{
animator = GetComponent<Animator> ();
GMS = GameObject.Find ("GameManager").GetComponent<GameManagerScript> ();
}
void Update ()
{
if (GMS.countDownDone == true) {
animator.enabled = true;
}
else
{
animator.enabled = false;
}
}
}
All I do is just enable the Animator that I attach in the Inspector whenever "countDownDone" becomes "true", and for added safety, I added the "else" for it to be disabled. If someone notices a way for me to improve this script, please let me know. Thank you :)
Shorter version:
if (GMS.countDownDone)
animator.enabled = true;
else
animator.enabled = false;

Start animation when player enters specific distance

I want to start an animation when my player enters a specific distance to an object, but my animation isn't starting. Any suggestion as to why it isn't starting?
Here is the code that I have so far:
using UnityEngine;
using System.Collections;
public class MoveTo : MonoBehaviour {
public Transform Player;
public Transform goal;
public Animator ani;
public Animator ani2;
// Use this for initialization
void Start ()
{
ani.Stop (); // this stop function is working accurate
ani2.Stop ();
}
void Update()
{
float dist = Vector3.Distance (Player.position, transform.position);
if (dist < 5)
{
ani.Play("Horse_Walk");// this is not working (Horse_Walk) is a state name
ani2.Play("Horse_Run");
pstart();
}
}
void pstart(){
NavMeshAgent agent=GetComponent<NavMeshAgent>();
agent.destination = goal.position;
}
}
There can be several reason including (1) wrong string (2) string name not matched and finally which seems more appropriate reason is that you playing animation again. you should need be call it with bool
bool isPlayAnim =true;
void Update()
{
float dist = Vector3.Distance (Player.position, transform.position);
if (dist < 5 && isPlayAnim)
{
isPlayAnim = false;//again true it on you specfic event
ani.Play("Horse_Walk");// this is not working (Horse_Walk) is a state name
ani2.Play("Horse_Run");
pstart();
}
}
isPlayAnim bool you can make and use to play animation only one time and again true it in specific event.
Or
Alternatively you need to use collider and its event to run this update code. I guess it is best way to do this job .

Categories

Resources