WaitForSeconds firing immediately - c#

I'm trying to set up a dash ability that updates the player velocity, waits a set amount of time (dashTime) and then sets the canDash to false for another set amount of time (dashCooldown).
However the waitForSeconds don't seem to be working as the canDash bool only remains false for a split second before switching back to true.
PlayerAbilities.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAbilities : MonoBehaviour
{
// Scripts & Data
[SerializeField] PlayerController s_PC;
internal PlayerData pd = new PlayerData();
private void Awake() {
pd.canDash = true;
}
// Dash
internal IEnumerator Dash()
{
pd.CanDash = false;
Debug.Log("dash, dash baby");
// Perform dash and update canDash
s_PC.rb2d.velocity = s_PC.s_PlayerMovement.moveInput * pd.dashSpeed;
yield return new WaitForSeconds(pd.dashTime); // Wait set time for dash to happen
s_PC.UpdateState(PlayerController.State.Normal); // Change state back to normal
// Cooldown dash
yield return new WaitForSeconds(pd.dashCooldown); // Cooldown dash
pd.CanDash = true;
}
}
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
// Scripts & Data
[SerializeField] internal PlayerAbilities s_PlayerAbilities;
[SerializeField] internal PlayerInput s_PlayerInput;
internal PlayerData pd = new PlayerData();
// Update is called once per frame
private void FixedUpdate()
{
// Check if user has control of player
if (pd.canControlPlayer)
{
// Update player state
switch (playerState)
{
case State.Normal:
s_PlayerMovement.Move();
break;
case State.Dash:
StartCoroutine(s_PlayerAbilities.Dash());
break;
case State.DodgeRoll:
StartCoroutine(s_PlayerAbilities.DodgeRoll());
break;
}
}
}
}
PlayerData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class PlayerData
{
// Dash
internal float dashSpeed = 50f;
internal float dashTime = 0.2f;
internal float dashCooldown = 5f;
internal bool canDash = true;
internal bool CanDash {
get { return canDash; }
set { canDash = value; }
}
}
PlayerInput.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerInput : MonoBehaviour
{
// Scripts & Data
[SerializeField] PlayerController s_PC;
internal PlayerData pd = new PlayerData();
// Input Actions
internal PlayerInputActions playerInputActions;
private void Awake() {
// Define new player input
playerInputActions = new PlayerInputActions();
}
private void Update()
{
// Check if player state is normal
if (s_PC.playerState == PlayerController.State.Normal)
{
// Check for button presses
if (playerInputActions.Movement.DodgeRoll.triggered)
s_PC.UpdateState(PlayerController.State.DodgeRoll);
}
}
}

The code is so unnecessarily complicated, you're just making one of a zillion simple mistakes somewhere
Follow normal debugging.
To begin with change this
yield return new WaitForSeconds(pd.dashCooldown);
to this
Debug.Log("test with number");
yield return new WaitForSeconds(20f);
Debug.Log("tested with number");
and go from there.
BTW there's something completely wrong at FixedUpdate(). For some reason you're call Dash (and others) every frame. That seems totally wrong. You'd call that sort of thing once on key down.
ALSO:
You've probably suffered this Gotchya:
https://stackoverflow.com/a/35166004/294884
due to all the bizarre variables.

Related

How to start animation state only once per object distance check?

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityStandardAssets.Characters.ThirdPerson;
public class SpreadedFireDistanceCheck : MonoBehaviour
{
public Transform player;
public List<GameObject> spreadedFires = new List<GameObject> ();
private Animator anim;
private bool doOnce = true;
void Start()
{
spreadedFires = GameObject.FindGameObjectsWithTag("Spreaded Fire").ToList();
anim = player.GetComponent<Animator>();
}
private void Update()
{
foreach(GameObject spreadedFire in spreadedFires)
{
if(Vector3.Distance(player.transform.position, spreadedFire.transform.position) < 5f && doOnce)
{
anim.Play("Walk Backward");
doOnce = false;
}
else
{
doOnce = true;
}
}
}
}
There are 8 spreaded fires objects and I want to make that if the player is getting close to one of the spreaded fires play animation once.
The problem is that I need to make a loop so it will play the animation 8 times instead only once depending on how close the player is to one of the spreaded fires. And if two or more spreaded fires objects are in range also play the animation once.
If what you want is if any of your spreadedFire is close enough, then play the fire animation (only) once in each frame, then you can use LinQ Any Operator:
using System.Linq;
......
private void Update()
{
if (spreadedFires.Any(x => IsCloseEnough(x)))
{
anim.Play("Walk Backward");
}
}
private bool IsCloseEnough(GameObject spreadedFire)
{
return Vector3.Distance(player.transform.position, spreadedFire.transform.position) < 5f;
}

How to write about Arrived Function with C# on Unity

When Character is arrived destination, I want to get callback.
However I don't want to write into Update Function.
If I've get to write into Update, I want to write smartly and elegant code.
when we make game, if there is design pattern.
Let me teach about it.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Unit : MonoBehaviour
{
[SerializeField] Vector3 targetPosition;
private NavMeshAgent agent;
private bool arrived;
private Transform myPosition;
void Start()
{
myPosition = GetComponent<Transform>();
agent = GetComponent<NavMeshAgent>();
agent.updateRotation = false;
agent.updateUpAxis = false;
}
void Update()
{
agent.SetDestination(targetPosition);
}
public void MoveTo(Vector3 position, float stopDistance, Action onArrivedAtPosition)
{
targetPosition = position;
// here!
if (arrived)
{
onArrivedAtPosition();
}
}
private void IsArrived()
{
if (Vector3.Distance(myPosition.position, agent.destination) < 1.0f)
{
arrived = true;
}
arrived = false;
}
}
I would use a Coroutine. Coroutines are like small temporary Update routines but easier to control and maintain.
private Coroutine _currentRoutine;
private bool IsArrived()
{
// Instead of setting a field directly return the value
return Vector3.Distance(myPosition.position, agent.destination) < 1.0f;
}
public void MoveTo(Vector3 position, float stopDistance, Action onArrivedAtPosition)
{
// Here can/have to decide
// Either Option A
// do not allow a new move call if there is already one running
if(_currentRoutine != null) return;
// OR Option B
// interrupt the current routine and start a new one
if(_currentRoutine != null) StopCoroutine (_currentRoutine);
// Set the destination directly
agent.SetDestination(position);
// and start a new routine
_currentRoutine = StartCoroutine (WaitUntilArivedPosition(position, onArrivedAtPosition));
}
private IEnumerator WaitUntilArivedPosition (Vector3 position)
{
// yield return tells Unity "pause the routine here,
// render this frame and continue from here in the next frame"
// WaitWhile does what the name suggests
// waits until the given condition is true
yield return new WaitUntil (IsArrived);
_currentRoutine = null;
onArrivedAtPosition?.Invoke();
}
You could create a script like the one bellow and attach it to an empty gameobject, then place that empty gameobject at target position. Make sure your IsArrived method (in Unit script) is public and also assign the unit on the empty gameobjec's TargetPoint script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetPoint : MonoBehaviour
{
public float radius = 1f;
public Unit unit = null;
private bool called = false;
private void Start()
{
SphereCollider c = gameObject.AddComponent<SphereCollider>();
c.radius = radius;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawSphere(transform.position, radius);
}
private void OnTriggerEnter(Collider other)
{
if (called)
{
return;
}
if(other.transform == unit.transform)
{
unit.IsArrived();
called = true;
}
}
}

How can I create a instantiate my border around a "bronze" when it is the closest and destroy the border when no longer the closest?

I've been stuck at this for a while. What I want is for my outline object to be instantiated at the location of my bronze Base game object and for it to destroy when the bronze base is no longer the closest to the player.
I'm willing to completely restart my bronze script if it means I can make this easier.
Thanks in advance!
Find Closest Bronze Script
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class FindBronze : MonoBehaviour
{
void Update()
{
FindClosestBronze();
}
void FindClosestBronze()
{
float distanceToClosestBronze = Mathf.Infinity;
Bronze closestBronze = null;
Bronze[] allBronze = GameObject.FindObjectsOfType<Bronze>();
foreach (Bronze currentBronze in allBronze)
{
float distanceToBronze = (currentBronze.transform.position - this.transform.position).sqrMagnitude;
if (distanceToBronze < distanceToClosestBronze)
{
distanceToClosestBronze = distanceToBronze;
closestBronze = currentBronze;
}
if (distanceToBronze > distanceToClosestBronze)
{
closestBronze.GetComponent<Bronze>().notSelected();
}
closestBronze.GetComponent<Bronze>().Selected();
}
}
}
Bronze (includes outline) script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bronze : MonoBehaviour
{
public bool isSelected = false;
public Animator anim;
[SerializeField]
public GameObject selectedBox;
public GameObject bronzeBase;
private GameObject clone;
// Update is called once per frame
void Awake()
{
clone = (GameObject)Instantiate(selectedBox, bronzeBase.transform);
}
public void Selected()
{
if (!isSelected)
{
clone = (GameObject)Instantiate(selectedBox, bronzeBase.transform);
isSelected = true;
}
else
{
Destroy(clone);
isSelected = false;
}
}
public void notSelected()
{
Destroy(selectedBox);
}
}
In the Bronze in notSelected you are destroying the prefab selectBox!
You probably rather wanted to destroy the clone instance.
Anyway I would suggest a few things that I would do different
Instead of Instantiate and Destroy all the time rather only use SetActive
Instead of using FindObjectOfType in Update store them in a HashSet event driven: Each Bronze instance registers and unregisters itself
Depends on personal taste but I would use Linq to find the closest instance
This could look somewhat like
public class Bronze : MonoBehaviour
{
// Every instance of this component registers and unregisters itself here
public static readonly HashSet<Bronze> Instances = new HashSet<Bronze>();
[Header("References")]
public Animator anim;
[SerializeField] private GameObject selectedBox;
[SerializeField] private GameObject bronzeBase;
[Header("Debugging")]
[SerializeField] bool _isSelected = false;
private GameObject clone;
// Have a property for the selection
public bool IsSelected
{
// when something reads this property return _isSelected
get => _isSelected;
// This is executed everytime someone changes the value of IsSelected
set
{
if(_isSelected == value) return;
_isSelected = value;
clone.SetActive(_isSelected);
}
}
// Update is called once per frame
void Awake()
{
// Instantiate already returns the type of the given prefab
clone = Instantiate(selectedBox, bronzeBase.transform);
// Register yourself to the alive instances
Instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the Instances
Instances.Remove(this);
}
}
And then use it
using System.Linq;
public class FindBronze : MonoBehaviour
{
private Bronze currentSelected;
private void Update()
{
UpdateClosestBronze();
}
void UpdateClosestBronze()
{
if(Bronze.Instances.Count ==0) return;
// This takes the instances
// orders them by distance ascending using the sqrMagnitude
// of the vector between the Instance and you
// sqrMagnitude is more efficient than Vector3.Distance when you only need to compare instead of the actual distance
// then finally it takes the first item
var newClosest = Bronze.Instances.OrderBy(b => (b.transform.position - transform.position).sqrMagnitude).First();
// skip if the result is the same as last time
if(newClosest == currentSelected) return;
// otherwise first deselect the current selection if there is one
if(currentSelected)
{
currentSelected.IsSelected = false;
}
// Then set the new selection
currentSelected = newSelected;
currentSelected.IsSelected = true;
}
}

C# Unity Queue NullReferenceException

I am trying to create a simple dialogue system for my game in Unity. I've set up a special trigger for a Dialogue to start and the code is passing right variable but somehow it gets stuck at clearing the queue and throws a NullReferenceException.
I've seen through debugger that all variables and triggers work perfectly fine till cs:27 inside DialogueManager.cs. I've also checked the inspector to make sure everything is correctly assigned.
Dialogue class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Dialogue
{
public string name;
[TextArea(3,10)]
public string[] sentences;
}
DialogueTrigger class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPC_DialogTrigger : MonoBehaviour
{
// Player
public Transform player;
// GameMaager to close
public GameObject Gameplay;
// Camera and Canvas to turn on
public GameObject DialogueManager;
// NPC Details
public GameObject InteractionNPCNameTextField;
public Transform interactionTransform;
public float radius = 3f;
private bool isBusy = false;
// DialogueStart
public GameObject DialogueStart;
void Start()
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
void Update()
{
float distance = Vector3.Distance(player.position, interactionTransform.position);
if (distance <= radius)
{
if (isBusy == false)
{
InteractionNPCNameTextField.gameObject.SetActive(true);
if (Input.GetKeyDown(KeyCode.E))
{
Dialogue();
Debug.Log("Started Dialogue procedure.");
}
}
}
else if (distance > radius)
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
}
public void Dialogue()
{
Gameplay.SetActive(false);
DialogueManager.SetActive(true);
DialogueStart.GetComponent<DialogueStart>().TriggerDialogue();
Debug.Log("Triggered Dialogue.");
}
void OnDrawGizmosSelected()
{
if (interactionTransform == null)
{
interactionTransform = transform;
}
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(interactionTransform.position, radius);
}
}
DialogueStart class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueStart : MonoBehaviour
{
public Dialogue dialogue;
public void TriggerDialogue()
{
FindObjectOfType<DialogueManager>().StartDialogue(dialogue);
Debug.Log("Dialogue sent to dialogue manager.");
}
}
DialogueManager class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class DialogueManager : MonoBehaviour
{
public Text nameText;
public Text DialogueText;
private Queue<string> sentences;
public GameObject DialogueManagerUI;
void Start()
{
if (sentences == null)
{
sentences = new Queue<string>();
}
}
public void StartDialogue (Dialogue dialogue)
{
Debug.Log("Received dialogues: " + dialogue);
nameText.text = dialogue.name;
Debug.Log("Start Dialogue: " + sentences.Count);
sentences.Clear();
Debug.Log("Sentences Cleared: " + sentences);
foreach (string sentence in dialogue.sentences)
{
sentences.Enqueue(sentence);
}
DisplayNextSentence();
}
public void DisplayNextSentence()
{
if(sentences.Count == 0)
{
EndDialogue();
return;
}
string sentence = sentences.Dequeue();
DialogueText.text = sentence;
}
void EndDialogue()
{
Debug.Log("End of conversation.");
}
private void Update()
{
if (DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else if (!DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
}
}
Visual Studio doesn't give me any errors.
Unity error code -> Screenshot
Debugger right before issue -> Screenshot2
Seems to me like the Queue is never assigned to sentences and it remains empty.
If it is so - why?
It sounds like DialogueManager.StartDialogue probably via DialogueStart.TriggerDialogue is called from somewhere in either Awake or at least before your Start was executed.
Especially
DialogueManager.SetActive(true);
lets assume the DialogueManager object is inactive at first anyway. So maybe your stuff is called before it is set to active.
This might also be due to the Start where you set
InteractionNPCNameTextField.gameObject.SetActive(false);
so any component on this GameObject might not have its Start method getting called. Maybe you referenced the wrong GameObject here?
Debugging would help to figure out in which order your methods get called.
In general my thumb rule for initializing is
Do everything that depends only on yourself in Awake
Do everything that depends on other components being setup already in Start
This way you can (almost) always be sure that stuff is already initialized when you need it.
However, these are only guesses but
The simple solution here:
You could already solve this by simply initializing the sentences right away using
private readonly Queue<string> sentences = new Queue<string>();
now it is definitely initialized even before Start or Awake get called!

Unity Highscore reset at respawn

I'm trying to be able to respawn as well as reset the score counting as soon as i walk into my "Gold" Object.
As for now i'm not even able to respawn which was possible earlier before trying to implement the "Score-Stuff" (at first the "FoundGold" Script was only used to be able to respawn). Also i'm trying to make the lowest Score the High-Score.
Note that im new to C# and i kinda put everything together from the tutorials i needed so a answer with some actual code/stating where something went wrong would be much appreciated.
//GoldFound Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GoldFound : MonoBehaviour
{
private ScoreManager theScoreManager;
public Transform target;
[SerializeField] private Transform player;
[SerializeField] private Transform respawnpoint;
private void Start()
{
theScoreManager = FindObjectOfType<ScoreManager>();
}
private void OnTriggerEnter(Collider other)
{
theScoreManager.scoreIncreasing = false;
player.transform.position = respawnpoint.transform.position;
theScoreManager.scoreCount = 0;
theScoreManager.scoreIncreasing = true;
}
}
other code
//ScoreManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour
{
public Text scoreText;
public Text hiScoreText;
public float scoreCount;
public float hiScoreCount;
public float pointPerSecond;
public bool scoreIncreasing;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (scoreIncreasing)
{
scoreCount += pointPerSecond * Time.deltaTime;
}
if(scoreCount > hiScoreCount)
{
hiScoreCount = scoreCount;
}
scoreText.text = "Score: " + Mathf.Round (scoreCount);
hiScoreText.text = "High Score: " + Mathf.Round (hiScoreCount);
}
}
If you want to save your highscore in-between play sessions, then the easiest way to do so is to save the value to PlayerPrefs. If you want to start saving more / more complex stuff you really should save it in a file you generate yourself. But in your case, PlayerPrefs is fine.
Here's a Unity tutorial on the subject:
https://unity3d.com/learn/tutorials/topics/scripting/high-score-playerprefs
Otherwise, you can just do it like this:
public void SetHighscore (float currentScore)
{
if (PlayerPrefs.HasKey("highscore"))
{
float highscore = PlayerPrefs.GetFloat("highscore");
if (highscore > currentScore)
{
PlayerPrefs.SetFloat("highscore", currentScore);
PlayerPrefs.Save();
}
}
else
{
PlayerPrefs.SetFloat("highscore", currentScore);
PlayerPrefs.Save();
}
}
Then just write PlayerPrefs.GetKey("highscore") whenever you need it.
(Though I'd also recommend you check if it exists by using the PlayerPrefs.HasKey("highscore"))
https://docs.unity3d.com/ScriptReference/PlayerPrefs.html

Categories

Resources