I have implemented the dialog system from this Brackeys video in my project. Everything works perfectly, but when I have done the build for android I have seen that printing long texts slows the game down.
The code divides the sentence to be displayed in the UI into an array of characters and then prints each character one by one, with a small delay. I have been doing several tests and first I thought that the problem was the coroutine from where the characters were printed. But then I have removed the code from the coroutine and I have seen that the more characters it prints, the more the game slows down.
void FixedUpdate()
{
if(typeSentence)
{
if(t <= 0)
{
TypeChar();
t = charDelay;
}
t -= Time.fixedDeltaTime;
}
}
private void TypeChar()
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
if (sentenceIndex >= charSentence.Length)
{
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
}
I don't know if there is a more efficient way to do it, or if someone can explain to me what is happening and why it slows down so much.
Instead of triggering TypeChar() method in fixed update, you can convert TypeChar() to a Coroutine that can handle time more performing way.
private IEnumerator TypeChar()
{
while(sentenceIndex >= charSentence.Length)
{
if(typeSentence)
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
yield return new WaitForSeconds(charDelay);
}
}
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
And you can delete typeSentence variable completely if you do not change it out of scope.
And you can call it at Start instead of FixedUpdate.
private void Start()
{
StartCoroutine(nameof(TypeChar));
}
Related
For the life of me I can't understand why it stops looping. I have an engine acceleration script that works great, except for the fact that it stops looping after a certain amount of time. I'm only calling the audiosource to be stopped if the user presses a button, it should work just fine. It has worked before implementing this too, I've no idea what breaks it.
private void PlayAccelerateSound()
{
m_audioSource2.loop = true;
m_audioSource2.clip = m_engineSound;
if (!alreadyPlayed)
{
m_audioSource2.PlayOneShot(m_audioSource2.clip);
alreadyPlayed = true;
}
if (rb.velocity.x < minPitch)
{
m_audioSource2.pitch = minPitch;
}
else if (rb.velocity.x > maxPitch)
{
m_audioSource2.pitch = maxPitch;
}
else
{
m_audioSource2.pitch = rb.velocity.x;
}
}
Fixed it by using m_audioSource2.Play() instead of m_audioSource2.PlayOneShot(m_audioSource2.clip).
I am making 2D game and want to cause delay of about 3 sec after player's all lives ran out. I tried to implement Coroutine method before the scene start all over again but it doesn't work.
I have already implemented Coroutine method for each time my player falls of a cliff and respawn back to its position. And it works like a charm.
public void Respawner()
{
StartCoroutine("RespawnCoroutine");
}
// Coroutine Delay of 2 sec for each time player Respawn
public IEnumerator RespawnCoroutine()
{
classobj.gameObject.SetActive(false);
yield return new WaitForSeconds(respawnDelaySec);
classobj.transform.position = classobj.respawnPoint;
classobj.gameObject.SetActive(true);
}
public void ReduceLives()
{
if (lives <= 3 && lives >= 2)
{
lives--;
live_text.text = "Remaining Live " + lives;
}
else
{
StartCoroutine("RestartScene1");
}
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
SceneManager.LoadScene("demo2");
}
here is no error on console window but SceneManager.LoadScene("demo2"); is never called and the player is respawning each time after i die and after 1 life remaining
The issue with your second coroutine is..
You have mistakenly used "IEnumerable" instead "IEnumerator", make it change to "IEnumerator" and it will work..
You should not call SceneManager.LoadScene("demo2"); after StartCoroutine("RestartScene1");.
StartCoroutine("RestartScene1"); this code you can say is an asynchronous code. It is called, and the execution of program keeps going forward (the execution does not await here). You should call the code you want to delay inside that Coroutine after yielding.
Small exampel:
public void SomeFunction()
{
StartCoroutine("RestartScene1");
// The code here will **not** be delayed
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
// The code here will be delayed
}
Good Morning Developers!
so here is what i'm trying to do: i created a block breaker game, and i wrote some code so that when all bricks in the scene are destroyed the next level is loaded.
It works fine, but there is a bug! when i lose before destroying all the bricks, and then i press "play again", the static variable who is responsible of counting bricks on scene does not reset to 0! it keeps the number of brick before i lost and add to it the number of bricks in the new scene!, so instead of returning 24 for ex (which is the correct number of bricks in scene) it returns 35 (11 + 24)
how can i fix that please?
here is the code i'm using: first the brick script :
public int maxHits;
public int timesHit;
public Sprite[] hitSprites;
public static int breakableCount = 0;
private bool isBreakable;
private LevelManager levelManager;
// Use this for initialization
void Start () {
isBreakable = (this.tag == "Breakable");
if(isBreakable){
breakableCount++;
}
print (breakableCount);
timesHit = 0;
levelManager = GameObject.FindObjectOfType<LevelManager> ();
}
void OnCollisionEnter2D(Collision2D collision) {
if (isBreakable) {
HandleHits ();
}
}
void HandleHits(){
//TODO remove the print!!
print ("collison");
timesHit++;
if (timesHit >= maxHits) {
breakableCount--;
print (breakableCount);
levelManager.BrickDestroyed ();
Destroy (gameObject);
} else {
LoadSprite ();
}
}
// Update is called once per frame
void Update () {
}
//TODO Remove this when player can WIN
void NextLevel(){
levelManager.LoadNextLevel ();
}
void LoadSprite(){
int spriteIndex = timesHit - 1;
this.GetComponent<SpriteRenderer> ().sprite = hitSprites [spriteIndex];
}
and here is the LevelManager script I'm using to manage levels :
public void LoadLevel (string name) {
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
}
public void ExitRequest() {
Debug.Log ("Exit game requested");
Application.Quit ();
}
public void LoadNextLevel () {
Application.LoadLevel (Application.loadedLevel + 1);
}
public void BrickDestroyed () {
if(Brick.breakableCount <= 0) {
LoadNextLevel ();
}
}
hope i explained correctly, and sorry if i made some English errors i'm not native speaker lol, Thank you have a nice day ^^
-Edited due to misunderstanding-
I didn't realize that was your BRICK script. The reset should be inside our LevelManager.
Your first line in your function to load a new level in LevelManager should be:
breakableCount = 0;
This will make it so that when the level is initialized that the counter is reset.
Also, you could reset the same way as soon as you've decided that a person has beat the current level.
Also, I recognize this from Ben Tristram's Unity Dev Course. You should try using the tools built into his class for questions, there is a lot of support there for these specific exercises!
Stack Overflow is great though, and it's a great source for when that stuff falls through. Another place to check is https://gamedev.stackexchange.com/
private static int breakableCount = 0;
public static int BreakableCount
{
get{ return breakableCount; }
set{
breakableCount = value;
if(breakableCount <= 0){ EndOfLevel() }
}
}
Turning your variable into property (or you can use a method if you prefer), you can now add some logic when it gets modified.
EndOfLevel is just a method you call to load the next level, save some data and reset some static values before leaving.
I Want to update this post because i find a solution and i have another question in the same subject!
First i'll tell you how i fixed it:
as #JorgeSantos suggested i created a ResetGame fonction in my loadlevel script :
void ResetGame(){
Brick.breakableCount = 0;
print ("breakableCount set to 0 ");
}
then i called that fontion in my LoadLevel fonction :
public void LoadLevel (string name) {
ResetGame ();
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
now the variable is resetting just fine
the only problem (it's not really a problem because the game runs fine, it's just that i want to know why it's happening) is that for ex let's say that i run the game, destroy 4 bricks, and then i lose, (keep in mind that there are 24 bricks in the scene) so i left 20 bricks non destroyed!
when i press play again, in the console, i notice that when i destroy a brick the breakableCount variable is not taking new values, then when i destroy 4 brick, (which means i'm in the same number of bricks left as i were before losing), then the breakableCount variable takes the value 20 (which is the right value) and continue decreasing when i destroy bricks normally!, You can see now the the game continue to work fine, but i dont understand why that variable is not reset to the right number of brick after i click on play again, and only takes effect when i reach the same number of destroyed brick as in my fist try?!
hope i made my point clear looking forward for your answers, and thank you all ^^
My goal is simple, like a lot of applications, I want to make my cursor invisible after a certain time of inactivity of my user.
My solution doesn't seem really optimized, here's the algorithm :
void Start {
Timer t = new Timer(5000);//Create a timer of 5 second
t.start();
}
void Update {
if(MouseMoved){
t.reset();
}
timeLeft = t.deltaTime;
if ( timeLeft == 5000 )
{
Cursor.visible = false;
}
}
I really don't like to check at every frame if the mouse is moved, I'd prefer that my mouse moved trigger something, but I'm lost here, is anyone have a better solution ?
This should be the good way to achieve the task, using coroutine, without any memory issue:
private Coroutine co_HideCursor;
void Update()
{
if (Input.GetAxis("Mouse X") == 0 && (Input.GetAxis("Mouse Y") == 0))
{
if (co_HideCursor == null)
{
co_HideCursor = StartCoroutine(HideCursor());
}
}
else
{
if (co_HideCursor != null)
{
StopCoroutine(co_HideCursor);
co_HideCursor = null;
Cursor.visible = true;
}
}
}
private IEnumerator HideCursor()
{
yield return new WaitForSeconds(3);
Cursor.visible = false;
}
There are two ways to check for things like this outside of the update loop, that I know of:
1 - Custom Events.
Writing your own custom event would allow you to call something like "OnMouseMove()" outside of the update function and it would only execute when the mouse cursor's position changes.
2 - Coroutines
Creating a separate coroutine would allow you to perform this check less frequently. To do this, you make an IEnumerator and put your mouse movement logic in there. Something like:
IEnumerator MouseMovement()
{
while(true)
{
if(MouseMoved)
{
//Do stuff
}
yield return new WaitForSeconds(1f);
}
}
That coroutine would perform the check once every second while it is running. You would start the coroutine by saying:
StartCoroutine(MouseMovement());
And to stop it, you call
StopCoroutine(MouseMovement());
If you Start it when the timer reaches 0 and stop it when the cursor is moved, you can also prevent the coroutine from running all the time, only using it while the cursor is inactive.
I have an animatormanager able to create run time animation from 1 animation (start frame, end frame and delay then the speed is calculated, will do morphing and more later). My unity version is 5.3.1f1
Everything work fine, some stuff are not done but are optional. The animator can queue requested animation to prevent interupting the current one.
The thing is, I can only start 1 time an animation with an object. The code to launch the next animation is exactly the same but there is just nothing to do.
Event if its 2 time the same animation with the same animation data
All formula are good and tested.
I did intensive debugging with breakpoint to make sure everything is fine at any point
Is there something to prevent me from starting an animation 2 time on an object one after an other?. I have no error or warning at all. The first animation work fine no matter the settings I put in my AnimData struct, but the second time, nothing happen.
Here is the essential:
public int? startAnim(int index)
{
if(index < animIndex.Count)
{
startAnim(animIndex[index]);
}
return null;
}
//private because the struct is internal, this make sure the animator keep control of the list.
private int? startAnim(AnimData animD)
{
if(locked)
{
#if UNITY_EDITOR
Debug.Log("anim locked");
#endif
return null;
}
//current anim (queue anim) not finished
if(endTime > Time.time)
{
if(canQueue)
{
addToQueue(animD);
return animDataQ.Count;
}
else
{
return null;
}
}
else
{
endTime = Time.time + animD.TotalTime;
StartCoroutine(animManager(animD));
return 0;
}
return null;
}
#endregion anim starters
private IEnumerator animManager(AnimData animData)
{
animator.speed = Mathf.Abs(animData.calculateSpeed(animLength, AnimType.main, currentKey).Value);
//animator.Play(0,0, animData.StartKey/animLength);
if(animData.AnimSpeed > 0)
{
animator.Play(0,0, animData.StartKey/animLength/2);
}
else
{
//animator.Play(0,0, (animLength * 2) - (animData.StartKey/animLength));
animator.Play(0,0, (((animLength*2) - animData.StartKey)/(animLength * 2)));
}
//animator.Play(0,0, (animData.AnimSpeed > 0) ? animData.StartKey/animLength : ((animLength * 2) - (animData.StartKey/animLength)));
yield return new WaitForSeconds(animData.Delay);
animator.Stop();
yield return null;
endAnim();
}
private void addToQueue(AnimData animD)
{
animDataQ.Enqueue(animD);
endTime += animD.TotalTime;
queueTime += animD.TotalTime;
}
private void endAnim()
{
if(canQueue && animDataQ.Count > 0)
{
StartCoroutine(animManager(animDataQ.Dequeue()));
}
}
Thanks for your time.
i found a work around.
I dont want to mark it as an accepted solution since its not "clean" in my opinion
there is certainly some kind of problem with the animator for some specific case like mine.
my solution was to use animator.speed = 0 instead of animator.stop();
everything worked instantly with that small change
i will ask my question to the unity forum because there is something strange for sure