Unity Coroutine for gameMenu - c#

I'm building a game like the roll a ball game on the tutorials in the Unity website, now I'm focused on the gameover menu, when the player lose all his lives I activate the gameover text, the yes and no text and the rawImage that is displayed behind the yes text by default, the thing is I want the image that is behind the yes text to go down to no and up to yes with the arrow keys. I think I implemented it right with the keys, but I feel like I need a coroutine to build this, because it just leaves the gameover function, without waiting the user input, so after this how can I build this in a right mode?
I did this at the moment:
public class Manager : MonoBehaviour {
private float checkPoint = 0;
public GameObject Ball;
private GameObject cam;
private Object newBall;
public int lifes;
public Text Lifetext;
private bool gameIsOver = false;
private Text gameOver;
private Text playAgain;
private Text yes;
private Text no;
private RawImage TopMenuBall;
void Awake(){
Lifetext = GameObject.Find("Lifes").GetComponent<Text>();
gameOver = GameObject.Find("GameOver").GetComponent<Text>();
playAgain = GameObject.Find("PlayAgain").GetComponent<Text>();
yes = GameObject.Find("Yes").GetComponent<Text>();
no = GameObject.Find("No").GetComponent<Text>();
TopMenuBall = GameObject.Find("TopMenuBall").GetComponent<RawImage>();
gameOver.enabled = false;
playAgain.enabled = false;
yes.enabled = false;
no.enabled = false;
TopMenuBall.enabled = false;
TopMenuBall.transform.localPosition = new Vector3(-68,-41,0);
}
void Start(){
lifes = 3;
Lifetext.text = " x" + lifes;
SpawnBall ();
cam = GameObject.Find ("Main Camera");
}
public void LifeUp(){
lifes++;
Lifetext.text = "X " + lifes;
}
public void LifeDown(){
if (lifes <= 0) {
GameOver ();
} else {
lifes--;
Lifetext.text = "X " + lifes;
}
}
public void GameOver(){
Debug.Log ("gameover");
gameOver.enabled = true;
playAgain.enabled = true;
yes.enabled = true;
no.enabled = true;
TopMenuBall.enabled = true;
if (Input.GetKeyDown (KeyCode.DownArrow)) {
TopMenuBall.transform.localPosition = new Vector3 (-68, -82, 0);
Debug.Log ("up");
}
else if (Input.GetKeyDown(KeyCode.UpArrow))
TopMenuBall.transform.localPosition = new Vector3(-68,-41,0);
}
void SpawnBall(){
Vector3 spawnPosition = new Vector3 (0.02f,1.4f,-39f);
Quaternion spawnRotation = Quaternion.identity;
newBall = Instantiate (Ball, spawnPosition, spawnRotation);
Camera.main.GetComponent<cameraMove>().player = (GameObject)newBall;
}
when the user lose all the lifes it enters the gameover function and there is the problem, how can I solve this?

You should use Update() method to get the user inputs. Because you need it to be constantly checked and the Update method exists exactly for this kind of situation, once it is called every frame.
So, your code should look like this:
void Update() {
if (TopMenuBall.enabled) {
if (Input.GetKeyDown (KeyCode.DownArrow)) {
TopMenuBall.transform.localPosition = new Vector3 (-68, -82, 0);
Debug.Log ("up");
}
else if (Input.GetKeyDown(KeyCode.UpArrow))
TopMenuBall.transform.localPosition = new Vector3(-68,-41,0);
}
}

Related

Need help in fixing a glitch with my Unity hiding script

I'm making a prototype for a horror game in Unity, and I'm trying to create a script that makes it so you can hide from the enemy in certain objects.
The intention is to make it that the camera lerps to the hiding spot in a smooth animation, and then will lerp the camera back to the player's position on exit.
I have it working so the player can enter and exit with a lerp, but the issue is that after exiting the hiding spot, interactions with any object cause the camera to sporadically rotate, rather than doing the intended interaction.
Here is my hide script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hidey : MonoBehaviour, IInteractable
{
[SerializeField] private Camera mainCamera;
[SerializeField] private Camera hideCamera;
[SerializeField] private float hideSpeed;
private float timer;
private Vector3 endPosition;
private Quaternion endRotation;
private Vector3 endPosition2;
private Quaternion endRotation2;
private bool canToggle;
private bool togglingLerp;
private bool hidden;
private void Awake()
{
hideCamera.enabled = false;
mainCamera.enabled = true;
canToggle = true;
togglingLerp = false;
hidden = false;
endPosition = hideCamera.transform.position;
endRotation = hideCamera.transform.rotation;
}
public void Interaction()
{
if (canToggle && !hidden)
{
hideCamera.enabled = true;
hideCamera.transform.position = mainCamera.transform.position;
hideCamera.transform.rotation = mainCamera.transform.rotation;
mainCamera.enabled = false;
endPosition2 = mainCamera.transform.position;
endRotation2 = mainCamera.transform.rotation;
togglingLerp = true;
}
else return;
}
private void Update()
{
if(togglingLerp && !hidden)
{
canToggle = false;
timer = Time.deltaTime * hideSpeed;
HideAnimation(hideCamera.transform.position, endPosition, hideCamera.transform.rotation, endRotation, timer, true, hideCamera);
}
if(hidden && Input.GetKeyDown(KeyCode.E))
{
canToggle = false;
mainCamera.enabled = true;
mainCamera.transform.position = hideCamera.transform.position;
mainCamera.transform.rotation = hideCamera.transform.rotation;
hideCamera.enabled = false;
timer = Time.deltaTime * hideSpeed;
HideAnimation(mainCamera.transform.position, endPosition2, mainCamera.transform.rotation, endRotation2, timer, false, mainCamera);
}
}
private void HideAnimation(Vector3 startPos, Vector3 endPos, Quaternion startRot, Quaternion endRot, float lerpTimer, bool isHidden, Camera currentCamera)
{
currentCamera.transform.position = Vector3.Lerp(startPos, endPos, lerpTimer);
currentCamera.transform.rotation = Quaternion.Lerp(startRot, endRot, lerpTimer);
if (Quaternion.Dot(currentCamera.transform.rotation, endRot) > .9999f && Vector3.Dot(currentCamera.transform.position, endPos) > .9999f)
{
hidden = isHidden;
togglingLerp = false;
canToggle = true;
}
}
}
I have no idea what is causing this issue at the moment.

How to switch between the OnTriggerExit/Enter logic depending on the situation?

The script is attached to two gameobjects.
One it's colliding area the collider is big enough so when the game start the player is already inside the collider area and then when he exit the area everything is working fine.
The problem is when I attached the object to another gameobject with collider but this time the collider s smaller and he is inside the bigger collider so now the player is entering the smaller collider and not first time exiting. but I want in both case to make the same effect.
If the player exit the collider he slow down wait then turn around and move inside back.
The same I want to make when the player getting closer to the fire flames slow down wait turn around and move back in it's just with the flames the player entering the collider area and then exit while in the bigger collider he first exit then enter.
Screenshot of the two colliders areas. The small one on the left is the one the player entering first and not exiting. The game start when the player is in the bigger collider area.
And the script :
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.Characters.ThirdPerson;
public class DistanceCheck : MonoBehaviour
{
public Transform targetToRotateTowards;
public Transform colliderArea;
public float lerpDuration;
public float rotationSpeed;
[TextArea(1, 2)]
public string textToShow;
public GameObject descriptionTextImage;
public TextMeshProUGUI text;
public ThirdPersonUserControl thirdPersonUserControl;
private Animator anim;
private float timeElapsed = 0;
private float startValue = 1;
private float endValue = 0;
private float valueToLerp = 0;
private bool startRotating = false;
private bool slowOnBack = true;
private bool exited = false;
private Vector3 exitPosition;
private float distance;
void Start()
{
anim = transform.GetComponent<Animator>();
}
private void FixedUpdate()
{
if (startRotating)
{
transform.rotation = Quaternion.RotateTowards(transform.rotation,
Quaternion.LookRotation(targetToRotateTowards.position - transform.position),
rotationSpeed * Time.deltaTime);
}
if (exitPosition != new Vector3(0, 0, 0) && slowOnBack)
{
distance = Vector3.Distance(transform.position, exitPosition);
}
if (distance > 5 && slowOnBack)
{
slowOnBack = false;
StartCoroutine(SlowDown());
}
}
private void OnTriggerExit(Collider other)
{
if (other.name == colliderArea.name)
{
exited = true;
slowOnBack = true;
exitPosition = transform.position;
thirdPersonUserControl.enabled = false;
descriptionTextImage.SetActive(true);
text.text = textToShow;
StartCoroutine(SlowDown());
}
}
private void OnTriggerEnter(Collider other)
{
if (other.name == colliderArea.name)
{
exited = false;
startRotating = false;
text.text = "";
descriptionTextImage.SetActive(false);
}
}
IEnumerator SlowDown()
{
timeElapsed = 0;
while (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration);
anim.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
yield return null;
}
if (exited)
{
yield return new WaitForSeconds(3f);
startRotating = true;
StartCoroutine(SpeedUp());
}
if (slowOnBack == false)
{
thirdPersonUserControl.enabled = true;
}
}
IEnumerator SpeedUp()
{
timeElapsed = 0;
while (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(endValue, startValue, timeElapsed / lerpDuration);
anim.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
yield return null;
}
}
}
Two problems I'm facing right now :
The smaller collider the player enter it then exit and the bigger collider the player exit it then enter so I need to change somehow the OnTriggerExit/Enter behavior logic in the script. but how to make the logic ?
Maybe it's better to make the script to be on the player object only and make it some how generic so I can drag to it many colliders and to make the effect in each one of the colliders the problem is how to make a text field for each collider area ? Now because I attach the script to each collider object I have one colliderArea variable but if I want to make the script only attached to the player I need to change the colliderArea variable to a List<Transform> collidersAreas and then how to create a text field/area to each collider area in the List ?
I think I would do this by creating two tags, NoExit and NoEntry. Once the tags are created you can set the tag on the GameObjects that hold your colliders. Then you can check for tags in the OnTriggerEnter and OnTriggerExit and act accordingly.
I can't tell for sure which functionality you've got written is for which case, or if it's both - everything looks muddled. Ultimately what you should be going for is a function like RepositionPlayer, that moves them back to before they're violating the no entry or no exit rules, and then some kind of OnPlayerRepositioned event that restores their control.
I'll leave it up to you to split out your functionality, but in general I'd look to do something like the following:
private void OnTriggerExit(Collider other)
{
if (other.tag == "NoExit")
{
RepositionPlayer();
}
else if(other.tag == "NoEntry")
{
OnPlayerRepositioned();
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "NoExit")
{
OnPlayerRepositioned();
}
else if(other.tag == "NoEntry")
{
RepositionPlayer();
}
}
And again here it's not clear to me what you're trying to do to reposition the player, but it looks like it's something like:
private void RepositionPlayer()
{
// Stuff that needs to happen to reposition the player
exited = true;
slowOnBack = true;
exitPosition = transform.position;
thirdPersonUserControl.enabled = false;
descriptionTextImage.SetActive(true);
text.text = textToShow;
StartCoroutine(SlowDown());
}
private void OnPlayerRepositioned()
{
// stuff you need to do to clear the "repositioning" status
exited = false;
startRotating = false;
text.text = "";
descriptionTextImage.SetActive(false);
}
Splitting up the logic like this makes it easier to both read and maintain.

Unity 3D Spring joint textures

Hello i'm doing a simple 3d game and wanted to make something like a grappling hook and it all works perfectly, but i can't find a solution how to make it to have a texture. Here's my code:
SpringJoint joint;
public GameObject playerCam;
Vector3 grapplepoint;
private void StartGrapplinghook()
{
print("grapple");
RaycastHit[] hits = Physics.RaycastAll(playerCam.transform.position, playerCam.transform.forward, 30f);
if (hits.Length < 1) return;
grapplepoint = hits[0].point;
joint = gameObject.AddComponent<SpringJoint>();
joint.autoConfigureConnectedAnchor = false;
joint.connectedAnchor = grapplepoint;
joint.spring = 2.5f;
joint.damper = 0.25f;
}
Hope you guys can help me.
I would use a LineRenderer for this, and update its positions every frame if it's enabled.
Make and configure a LineRenderer component on the same gameobject the script is on. Explanation for code in comments.
[RequireComponent(typeof(LineRenderer))]
public class MyClassName : MonoBehaviour
{
[SerializeField]
LineRenderer grappleRenderer;
SpringJoint joint;
public GameObject playerCam;
Vector3 grapplepoint;
void Awake()
{
// Whatever is already here...
// If you don't need a fresh grapple copy every time,
// create them once here or in the editor and re-use them
joint = gameObject.AddComponent<SpringJoint>();
joint.autoConfigureConnectedAnchor = false;
joint.spring = 2.5f;
joint.damper = 0.25f;
grappleRenderer = GetComponent<LineRenderer>();
grappleRenderer.positionCount = 2; // this could just be set in the inspector
// Don't use the grapple by default
joint.enabled = false;
grappleRenderer.enabled = false;
}
private void StartGrapplinghook()
{
print("grapple");
RaycastHit[] hits = Physics.RaycastAll(playerCam.transform.position,
playerCam.transform.forward, 30f);
if (hits.Length < 1) return;
grapplepoint = hits[0].point;
joint.enabled = true;
joint.connectedAnchor = grapplepoint;
grappleRenderer.enabled = true;
}
void StopGrapplingHook()
{
// turn off joint and renderer
joint.enabled = false;
grappleRenderer.enabled = false;
}
// grapple rendering can happen after all other updates finish
// determining if it should be rendered or not that frame
void LateUpdate()
{
if (grappleRenderer.enabled)
{
Vector3 grappleStart = transform.position; // or something that makes sense
grappleRenderer.SetPoints(new Vector3[]{grapplepoint, grappleStart});
}
}
}

The hazards in my unity game are not spawning

I have a game in which you are a player cube and you have to dodge other cubes as they come through your way. The enemy cubes should spawn because of a script I have called GameController. But the cubes are not spawning. Please Help. (Also whenever my cube gets destroyed the gameover and the restart functions are not working)
I tried Recreating the prefab and the code, but nothing happened. Also, the same code is working in my other games
Here is my code:
void Start()
{
gameOver = false;
restart = false;
restartText.text = "";
gameOverText.text = "";
score = 0;
UpdateScore();
StartCoroutine(SpawnWaves());
}
void Update()
{
if (restart)
{
if (Input.GetKeyDown(KeyCode.R))
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
}
IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(startWait);
while (true)
{
for (int i = 0; i < hazardCount; i++)
{
GameObject hazard = hazards[Random.Range(0, hazards.Length)];
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
GameObject newSpawn = Instantiate(hazard, spawnPosition, spawnRotation) as GameObject;
yield return new WaitForSeconds(spawnWait);
}
yield return new WaitForSeconds(waveWait);
if (gameOver)
{
restartText.text = "Press 'R' for Restart";
restart = true;
break;
}
}
}
public void AddScore(int newScoreValue)
{
score += newScoreValue;
UpdateScore();
}
void UpdateScore()
{
scoreText.text = "Score: " + score;
}
public void GameOver()
{
gameOverText.text = "Game Over!";
gameOver = true;
}
}
I expect the enemies to spawn but they are not
I have copy and pasted your example code into an example project. In this example your script works as expected, as you already mentioned that it works in other projects.
So in my opinion it has something to do with the inspector values.
Check the following:
Did you checked that your hazardCount is greater than zero?
Is your hazardArray completely filled with prefabs?
(If not that should cause the not-spawning but should also cause a null-reference-exception.)
Another option is that you make a screenshot or provide your values in the inspector somehow, so its possible to reproduce the error.
Offtopic: Your Restart after the gameOver is delayed, because the if(gameOver)-Clause comes after the WaitForSeconds(waveWait). My suggestion is to change it to the following:
public GameObject[] hazards;
public Vector3 spawnValues;
public int hazardCount;
public float spawnWait;
public float startWait;
public float waveWait;
public Text scoreText;
public Text restartText;
public Text gameOverText;
private bool gameOver;
private bool restart;
private int score;
private Coroutine gameRoutine;
void Start()
{
gameOver = false;
restart = false;
restartText.text = "";
gameOverText.text = "";
score = 0;
UpdateScore();
gameRoutine = StartCoroutine(SpawnWaves());
}
void Update()
{
if (restart)
{
if (Input.GetKeyDown(KeyCode.R))
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
}
IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(startWait);
while (true)
{
for (int i = 0; i < hazardCount; i++)
{
GameObject hazard = hazards[Random.Range(0, hazards.Length)];
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
GameObject newSpawn = Instantiate(hazard, spawnPosition, spawnRotation) as GameObject;
yield return new WaitForSeconds(spawnWait);
}
yield return new WaitForSeconds(waveWait);
}
}
public void AddScore(int newScoreValue)
{
score += newScoreValue;
UpdateScore();
}
void UpdateScore()
{
scoreText.text = "Score: " + score;
}
public void GameOver()
{
gameOverText.text = "Game Over!";
restartText.text = "Press 'R' for Restart";
restart = true;
gameOver = true;
StopCoroutine(gameRoutine);
}
What i have changed here is, that the if-condition is removed and the Coroutine is now stored at Start() and is directly stop as the gameOver() method gets executed. Another option instead of the StopCoroutine() would be to set the condition of your while-loop to while(!gameOver).

Unity Input triggered twice

I created a small 2D sidescroller movement.
private Rigidbody2D rigid;
private BoxCollider2D playerCollider;
private bool isMovingRight = false;
private Vector2 movement;
private bool jumpPressed;
private const int MOVEMENT_SPEED = 5;
private const int JUMP_POWER = 5;
private const float GROUNDCHECK_TOLERANCE_SIDE = 0.05f;
private const float GROUNDCHECK_TOLERANCE_BOTTOM = 0.05f;
private void Start()
{
rigid = GetComponent<Rigidbody2D>();
playerCollider = GetComponent<BoxCollider2D>();
}
private void Update()
{
SetMovement();
}
private void FixedUpdate()
{
Move();
}
private void SetMovement()
{
float horizontalMovement = Input.GetAxis("horizontal") * MOVEMENT_SPEED;
if (Input.GetButtonDown("Jump"))
{
jumpPressed = true;
}
movement = new Vector2(horizontalMovement, rigid.velocity.y);
}
private void Move()
{
if (GroundCheck(true) || GroundCheck(false))
{
if (jumpPressed)
{
movement.y = JUMP_POWER;
jumpPressed = false;
}
}
rigid.velocity = movement;
}
private bool GroundCheck(bool checkLeftSide)
{
Bounds colliderBounds = playerCollider.bounds;
Vector2 rayPosition = colliderBounds.center;
float horizontalRayPosition = colliderBounds.extents.x + GROUNDCHECK_TOLERANCE_SIDE;
if (checkLeftSide)
{
rayPosition.x -= horizontalRayPosition;
}
else
{
rayPosition.x += horizontalRayPosition;
}
return Physics2D.Raycast(rayPosition, Vector2.down, (playerCollider.size.y / 2) + GROUNDCHECK_TOLERANCE_BOTTOM);
}
I register Inputs in Update and handle the Physics in FixedUpdate. When pressing the Jump Button the players jump works fine.
But when pressing jump multiple times, the player jumps up in the air, comes down and jumps one time again.
So if pressing the button more than 1 time the player will jump a second time after finishing the first jump.
How can I avoid this behaviour?
Using your code, I added 2 comments and an else statement to clarify what I mean in my comments. This should fix your issue.
private void SetMovement()
{
float horizontalMovement = Input.GetAxis("horizontal") * MOVEMENT_SPEED;
if (Input.GetButtonDown("Jump"))
{
// Only ever gets set to false if you are grounded and it makes you jump.
// Should only be set to true if you can jump. So you could add your groundChecks here...
// or use the method I am showing
jumpPressed = true;
}
movement = new Vector2(horizontalMovement, rigid.velocity.y);
}
private void Move()
{
if (GroundCheck(true) || GroundCheck(false))
{
if (jumpPressed)
{
movement.y = JUMP_POWER;
// This was the only place this every was set to false, and
// specific conditions were required to make this happen.
jumpPressed = false;
}
}
// fastest way to test
else
{
// yes they pressed jump but they can't jump so reset it, now
// they wont jump as soon as they land. just because they got
// trigger happy.
jumpPressed = false;
}
rigid.velocity = movement;
}

Categories