How can I start the coroutine with time? - c#

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PlayerMouthSpeechController : MonoBehaviour
{
public TMP_Text[] texts;
public bool startTalking = false;
public float talkTime;
public float duration;
[Range(0, 100)]
public float valueRange;
private SkinnedMeshRenderer bodySkinnedMeshRenderer;
//private bool isTalking = true;
// Start is called before the first frame update
void Start()
{
bodySkinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
}
// Update is called once per frame
void Update()
{
/*if (startTalking && isTalking)
{
StartCoroutine(AnimateMouth());
StartCoroutine(TalkTime());
isTalking = false;
}
if(startTalking == false && isTalking == false)
{
isTalking = true;
}*/
}
//Lerp between startValue and endValue over 'duration' seconds
private IEnumerator LerpShape(float startValue, float endValue, float duration)
{
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float value = Mathf.Lerp(startValue, endValue, elapsed / duration);
bodySkinnedMeshRenderer.SetBlendShapeWeight(0, value);
yield return null;
}
}
//animate open and closed, then repeat
public IEnumerator AnimateMouth()
{
while (startTalking == true)
{
yield return StartCoroutine(LerpShape(0, valueRange, duration));
yield return StartCoroutine(LerpShape(valueRange, 0, duration));
}
}
public IEnumerator TalkTime()
{
yield return new WaitForSeconds(talkTime);
startTalking = false;
}
}
I messed it all before with too many flags and in the Update() messed it too.
I want to make something simple. To be able to call from any other script to the method AnimateMouth and that the method AnimateMouth will also get a float will be the time the mouth will be animated. something like :
StartCoroutine(AnimateMouth(4f));
but I messed it all. maybe to make the AnimateMout public static or when calling from another script first to make a reference to the PlayerMouthSpeechController and then call it something like :
playerMouthSpeechController.AnimateMouth();
and something will start the AnimatedMouth coroutine. but again I messed it too much.
Update :
This is working almost perfectly as I wanted still I need to use StartCoroutine each time I want the player to start talking but it's working.
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PlayerMouthSpeechController : MonoBehaviour
{
public TMP_Text[] texts;
public float duration;
[Range(0, 100)]
public float valueRange;
private bool startTalking = false;
private SkinnedMeshRenderer bodySkinnedMeshRenderer;
// Start is called before the first frame update
void Start()
{
bodySkinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
}
// Update is called once per frame
void Update()
{
}
//Lerp between startValue and endValue over 'duration' seconds
private IEnumerator LerpShape(float startValue, float endValue, float duration)
{
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float value = Mathf.Lerp(startValue, endValue, elapsed / duration);
bodySkinnedMeshRenderer.SetBlendShapeWeight(0, value);
yield return null;
}
}
//animate open and closed, then repeat
public IEnumerator AnimateMouth(float TimeToTalk)
{
startTalking = true;
StartCoroutine(TalkTime(TimeToTalk));
while (startTalking == true)
{
yield return StartCoroutine(LerpShape(0, valueRange, duration));
yield return StartCoroutine(LerpShape(valueRange, 0, duration));
}
}
private IEnumerator TalkTime(float TalkTime)
{
yield return new WaitForSeconds(TalkTime);
startTalking = false;
}
}
And using it for example in another script :
At the top :
public PlayerMouthSpeechController blendShapeController;
And
StartCoroutine(blendShapeController.AnimateMouth(10f));
I wish I could do somehow that I will not start a coroutine each time and that it will start the coroutine automatic and I will only do :
blendShapeController.AnimateMouth(10f);
And it will start the coroutine in the PlayerMouthSpeechController.

Working solution as I wanted :
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PlayerMouthSpeechController : MonoBehaviour
{
public TMP_Text[] texts;
public float duration;
[Range(0, 100)]
public float valueRange;
private bool startTalking = false;
private SkinnedMeshRenderer bodySkinnedMeshRenderer;
// Start is called before the first frame update
void Start()
{
bodySkinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
}
// Update is called once per frame
void Update()
{
}
//Lerp between startValue and endValue over 'duration' seconds
private IEnumerator LerpShape(float startValue, float endValue, float duration)
{
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float value = Mathf.Lerp(startValue, endValue, elapsed / duration);
bodySkinnedMeshRenderer.SetBlendShapeWeight(0, value);
yield return null;
}
}
//animate open and closed, then repeat
public void AnimateMouth(float TimeToTalk)
{
StartCoroutine(StartAnimating(TimeToTalk));
}
private IEnumerator StartAnimating(float TimeToTalk)
{
startTalking = true;
StartCoroutine(TalkingTime(TimeToTalk));
while (startTalking == true)
{
yield return StartCoroutine(LerpShape(0, valueRange, duration));
yield return StartCoroutine(LerpShape(valueRange, 0, duration));
}
}
private IEnumerator TalkingTime(float TalkTime)
{
yield return new WaitForSeconds(TalkTime);
startTalking = false;
}
}
In other script/s making a reference :
public PlayerMouthSpeechController blendShapeController;
And starting
blendShapeController.AnimateMouth(10f);

Related

Yield Return New breaking a line of code?

I'm trying to create an ability where you dash through a wall, so to do this I'm dashing and then disabling the colliders for a layer which is going to have everything you can dash through on it. When I add the 'yield return new WaitForSeconds' function, you no longer dash.
I don't have a clue what to do...
using Player;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Phase_Through_Walls : MonoBehaviour
{
public float cooldowntime = 1;
public float nextPhaseTime = 0;
public float currentDashTime = 0.0f;
Movement moveScript;
public float dashSpeed;
public float dashTime;
void Start()
{
moveScript = GetComponent< Movement>();
}
// Update is called once per frame
void Update()
{
if (Time.time > nextPhaseTime)
{
if (Input.GetKeyDown("e"))
{
StartCoroutine(Dash());
}
}
IEnumerator Dash()
{
float startTime = Time.time;
while(Time.time < startTime + dashTime)
{
moveScript.cc.Move(moveScript.moveDirection * dashSpeed * Time.deltaTime); //dashes
Physics.IgnoreLayerCollision(0, 8); //turns off colliders for a certain layer
yield return new WaitForSeconds(1); //waits a second
Physics.IgnoreLayerCollision(0, 8, false); //re enables the collider
yield return null; //returns a null value
}
}
}
}
You need to split up the responsibilities of your methods. Right now you have 1 method trying to accomplish 2 things and it is stepping on itself in the process. Once the yield return new WaitForSeconds(1); is called, control is not returned to the coroutine until that second is finished. So your "dashing" script has to wait for a second as well.
Instead make 2 separate coroutines. 1 of them to actually do the dash, and one for handling collisions. And call them at the same time as below. Also it looked like your missing a closing bracket for your Update method.
using Player;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Phase_Through_Walls : MonoBehaviour
{
public float cooldowntime = 1;
public float nextPhaseTime = 0;
public float currentDashTime = 0.0f;
Movement moveScript;
public float dashSpeed;
public float dashTime;
void Start()
{
moveScript = GetComponent< Movement>();
}
// Update is called once per frame
void Update()
{
if (Time.time > nextPhaseTime)
{
if (Input.GetKeyDown("e"))
{
DoDash();
}
}
}
void DoDash()
{
Physics.IgnoreLayerCollision(0, 8); //disable colliders BEFORE dashing initiates
StartCoroutine(Dash());
StartCoroutine(RestoreCollision());
}
IEnumerator Dash()
{
float startTime = Time.time;
while(Time.time < startTime + dashTime)
{
moveScript.cc.Move(moveScript.moveDirection * dashSpeed * Time.deltaTime); //dashes
yield return null; // waits for next frame
}
}
IEnumerator RestoreCollision()
{
yield return new WaitForSeconds(1); //waits a second
Physics.IgnoreLayerCollision(0, 8, false); //re enables the collider
}
}

What to do when VSCode is not giving me an error?

I'm new to C# (did some stuff in python earlier) and i cant get this code to work. Im making a mobile game and this script should run a timer, check if the timer is equal to "SaleTime" and if it is add money to the users balance and reset the timer to 0.
As VSCode is not giving me any errors i dont know what the problem is and after looking around i cant find a solution to it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Threading;
public class Sales : MonoBehaviour
{
public float Timer = 0.0f;
public float SaleTime = 5.0f;
public float ProductValue = 5.0f;
public float Money = 1000.0f;
void Start()
{
StartCoroutine(time());
}
public void GameTime()
{
Timer += 1;
}
IEnumerator time()
{
while (true)
{
GameTime();
yield return new WaitForSeconds(1);
}
}
public void SaleFunction()
{
if (Timer == SaleTime)
{
Timer = 0.0f;
Money = Money + ProductValue;
}
}
}
You are not calling SaleFunction(), so the program never checks the condition.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Threading;
public class Sales : MonoBehaviour
{
public float Timer = 0.0f;
public float SaleTime = 5.0f;
public float ProductValue = 5.0f;
public float Money = 1000.0f;
void Start()
{
StartCoroutine(time());
}
public void GameTime()
{
Timer += 1;
}
IEnumerator time()
{
while (true)
{
GameTime();
SaleFunction(); // Added line.
yield return new WaitForSeconds(1);
}
}
public void SaleFunction()
{
if (Timer == SaleTime)
{
Timer = 0.0f;
Money = Money + ProductValue;
}
}
}

Is there a way to teleport player after countdown timer hits zero? on unity

I'm very new to scripting and to unity as well, In my scene, I would like the player from its current location to teleport to a certain location after the countdown timer hits zero, is there a way to do this? I research online however I could not find many tips about it thus I am asking here.
I did a basic code timer I found online
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
public float timeRemaining = 150;
public bool timerIsRunning = false;
public Text timeText;
private void Start()
{
// Starts the timer automatically
timerIsRunning = true;
}
void Update()
{
if (timerIsRunning)
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
DisplayTime(timeRemaining);
}
else
{
Debug.Log("Time has run out!");
timeRemaining = 0;
timerIsRunning = false;
}
}
}
void DisplayTime(float timeToDisplay)
{
timeToDisplay += 1;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
float seconds = Mathf.FloorToInt(timeToDisplay % 60);
timeText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
You have many ways to do the "teleport", but it's basically change object transform position, so if you want that the object goes to the 3D space position (0,1,0), just assign it to it:
this.transform.position = new Vector3(0,1,0);
For the timer you can use Invoke or InvokeRepeating methods or a countdown like yours.
So in your code it will looke something like:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
///
public GameObject objectToTeleport = null; //assign it from inspector or code
public Vector3 destination = new Vector3(0,0,0); //assign it from inspector or code
///
public float timeRemaining = 150;
public bool timerIsRunning = false;
public Text timeText;
private void Start()
{
// Starts the timer automatically
timerIsRunning = true;
}
void Update()
{
if (timerIsRunning)
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
DisplayTime(timeRemaining);
}
else
{
Debug.Log("Time has run out!");
timeRemaining = 0;
timerIsRunning = false;
//Move object
objectToTeleport.transform.position = destination;
}
}
}
void DisplayTime(float timeToDisplay)
{
timeToDisplay += 1;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
float seconds = Mathf.FloorToInt(timeToDisplay % 60);
timeText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}

How can I spin an object each 5 seconds in other random values?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateCamera : MonoBehaviour
{
public GameObject objectToSpin;
public float spinX;
public float spinY;
public float spinZ;
public bool randomSpin = false;
private void Start()
{
var rb = GetComponent<Rigidbody>();
rb.angularVelocity = Random.insideUnitSphere;
}
private void Update()
{
if (randomSpin == true)
{
objectToSpin.transform.Rotate(Random.Range(spinX, 360), Random.Range(spinY, 360), Random.Range(spinZ, 360));
}
else
{
objectToSpin.transform.Rotate(spinX, spinY, spinZ);
}
}
}
Now when changing the randomSpin flag to true it will spin random nonstop changing random angle on x y z each frame.
But I want it to spin random for 5 seconds after 5 seconds to change to random values the x y z and continue from last point and then after 5 seconds random angles and so on.
If you want a constant speed and just a random direction, You can randomly select an Axis to rotate around using Random.onUnitSphere and then rotate around it at a speed.
Here's a solution that uses a Coroutine. If you want to stop the Coroutine, you can use StopCoroutine("Spin") to stop the coroutine and start it up later with StartCoroutine("Spin"):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateCamera : MonoBehaviour
{
public GameObject objectToSpin;
public Vector3 spinAxis;
public float timeToSpin = 5f;
public float spinSpeed = 20f;
public bool randomSpin = false;
private void Start()
{
var rb = GetComponent<Rigidbody>();
rb.angularVelocity = Random.insideUnitSphere;
StartCoroutine("Spin");
}
private void Update()
{
}
}
IEnumerator Spin()
{
float spinTimer;
while (true)
{
if (randomSpin == true)
{
spinAxis = Random.onUnitSphere;
}
spinTimer = timeToSpin;
while (spinTimer > 0f)
{
objectToSpin.transform.Rotate(spinAxis, Time.deltaTime * spinSpeed);
spinTimer -= Time.deltaTime;
yield return null;
}
}
}
For example have a simple timer using Time.deltaTime like e.g.
private float timer = 5;
private void Update()
{
if (randomSpin == true)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
objectToSpin.transform.Rotate(Random.Range(spinX, 360), Random.Range(spinY, 360), Random.Range(spinZ, 360));
timer = 5;
}
}
else
{
objectToSpin.transform.Rotate(spinX, spinY, spinZ);
}
}

How can I start conversation only if the object has finished scaling up?

The scaling script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Scaling : UnityEngine.MonoBehaviour
{
public GameObject objectToScale;
public GameObject lookAtTarget;
public float duration = 1f;
public Vector3 minSize;
public Vector3 maxSize;
public bool scaleUp = false;
public Coroutine scaleCoroutine;
[HideInInspector]
public bool scaledFinishedUp = false;
[HideInInspector]
public bool scaledFinishedDown = false;
public void Inits()
{
objectToScale.transform.localScale = minSize;
}
public IEnumerator scaleOverTime(GameObject targetObj, Vector3 toScale, float duration, Camera objectToScaleCamera)
{
float counter = 0;
Vector3 startScaleSize = targetObj.transform.localScale;
while (counter < duration)
{
counter += Time.deltaTime;
targetObj.transform.localScale = Vector3.Lerp(startScaleSize, toScale, counter / duration);
if (scaleUp)
{
var lookPos = lookAtTarget.transform.position - objectToScale.transform.position;
lookPos.y = 0;
var rotation = Quaternion.LookRotation(lookPos);
objectToScale.transform.rotation = Quaternion.Slerp(objectToScale.transform.rotation, rotation, counter / duration);
}
else
{
var lookPos = lookAtTarget.transform.position - objectToScale.transform.position;
lookPos.y = 0;
var rotation = Quaternion.LookRotation(objectToScaleCamera.transform.forward);//SwitchCameras.GetCurrentCamera().transform.forward);//Camera.main.transform.forward);
objectToScale.transform.rotation = Quaternion.Slerp(objectToScale.transform.rotation, rotation, counter / duration);
}
yield return null;
}
if(objectToScale.transform.localScale.x >= maxSize.x
&& objectToScale.transform.localScale.y >= maxSize.y
&& objectToScale.transform.localScale.z >= maxSize.z)
{
scaledFinishedUp = true;
}
if (objectToScale.transform.localScale.x <= maxSize.x
&& objectToScale.transform.localScale.y <= maxSize.y
&& objectToScale.transform.localScale.z <= maxSize.z)
{
scaledFinishedDown = true;
}
}
public IEnumerator scaleOverTime(GameObject targetObj, Vector3 toScale, float duration, float rotationSpeed)
{
float counter = 0;
Vector3 startScaleSize = targetObj.transform.localScale;
while (counter < duration)
{
counter += Time.deltaTime;
targetObj.transform.localScale = Vector3.Lerp(startScaleSize, toScale, counter / duration);
targetObj.transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime, Space.Self);
yield return null;
}
}
}
And the script with the conversations :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class NaviDialogue : MonoBehaviour
{
public ObjectsManipulation op;
public bool scale = true;
public Scaling scaling;
public ConversationTrigger conversationTrigger;
private void Start()
{
op.canScale = false;
}
private void Update()
{
if (scaling == true && DOFControl.hasFinished == true)
{
DOFControl.hasFinished = false;
PlayerController.disablePlayerController = true;
NaviConversations(0);
}
}
public void NaviConversations(int Index)
{
scaling.scaleUp = true;
op.Scaling(false);
StartCoroutine(conversationTrigger.PlayConversation(Index));
}
}
Just after the line :
op.Scaling(false);
I want to check if the object has finished scaling up then start the StartCoroutine. The problem is that in the Update I'm calling NaviConversations only once.
And then when the StartCoroutine has finished I want to do something else like this : This will scale down back the object :
scaling.scaleUp = false;
op.Scaling(false);
And last the class with the PlayConversatio method :
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class ConversationTrigger : MonoBehaviour
{
public List<Conversation> conversations = new List<Conversation>();
public GameObject canvas;
[HideInInspector]
public static int conversationIndex;
public bool conversationEnd = false;
private bool activateButton = false;
private DialogueManager dialoguemanager;
private bool startDialogue = false;
private void Start()
{
conversationIndex = 0;
dialoguemanager = FindObjectOfType<DialogueManager>();
}
public IEnumerator PlayConversation(int index)
{
if (conversations.Count > 0 &&
conversations[index].Dialogues.Count > 0)
{
for (int i = 0; i < conversations[index].Dialogues.Count; i++)
{
if (dialoguemanager != null)
{
dialoguemanager.StartDialogue(conversations[index].Dialogues[i]);
}
while (DialogueManager.dialogueEnded == false)
{
yield return null;
}
}
conversationIndex = index;
conversationEnd = true;
canvas.SetActive(false);
Debug.Log("Conversation Ended");
}
}
public void SaveConversations()
{
string jsonTransform = JsonHelper.ToJson(conversations.ToArray(), true);
File.WriteAllText(#"d:\json.txt", jsonTransform);
}
public void LoadConversations()
{
string jsonTransform = File.ReadAllText(#"d:\json.txt");
conversations.Clear();
conversations.AddRange(JsonHelper.FromJson<Conversation>(jsonTransform));
}
}
I don't see what happens in
conversationTrigger.PlayConversation(Index) but you could use WaitUntil to the top of it in order to make it wait until the scaling ends.
Something like
yield return new WaitUntil(scalingIsDoneCondition);
where scalingIsDoneCondition is the check whether the scaling has finished. As soon as it returns true the Coroutine continues.
Alternatively you can have an additional IEnumerator and wait there like e.g.
public void NaviConversations(int Index)
{
scaling.scaleUp = true;
op.Scaling(false);
StartCoroutine(StartConversationAfterScaling(Index));
}
private IEnumerator StartConversationAfterScaling(int index)
{
yield return new WaitUntil(scalingIsDoneCondition);
StartCoroutine(conversationTrigger.PlayConversation(Index));
}
If you needed to check the condition from within the same class.
Another form is making the whole scaling rather a public IEnumerator and yield it from the other one
public void NaviConversations(int Index)
{
scaling.scaleUp = true;
StartCoroutine(StartConversationAfterScaling(Index));
}
private IEnumerator StartConversationAfterScaling(int index)
{
yield return op.Scaling(false);
StartCoroutine(conversationTrigger.PlayConversation(Index));
}
this would require to make op.Scaling also an IEnumerator.
I didn't even read the whole of your scripts. Highly recommend you to use a tweening library for such stuff. You can easily scale, rotate, translate, blend colours and many more with these libraries.
Take a look at DoTween, with DoTween you can write scaling like this:
transform.DoScale(Vector3.one * 2, 1f).onCompelete( ()=>
{
print("Scaling finished!");
}
But tweening is not all about setting callbacks, with tween you can animate your object programmatically.
You can check a quick tutorial about capabilities:
YouTube Video

Categories