Issues with my rocket after export :(

cu2_s03_sharing_with_teaser_video

#1

When I test my game in Unity, it behaves as it should. However, after game export, the rocket behaves strangely and I can’t even begin to imagine why. What happens is, after death, the rocket that is regenerated spins as though I am holding down the A or D key, even though I’m not. There seems to be no way to control this. :frowning: Please help!! I’m so close and was so psyched to export this and show it off to my friends.


#2

I’d have to see your source-code, I think. There are a number of issues I’ve dealt with when doing a build that go away if I re-load the project and re-build… is this repeatable?


#3

I tried doing that export a couple of times, and still having the same issues.


#4
public class Rocket1 : MonoBehaviour
{

    [SerializeField] float rcsThrust = 100f; //SerializeField allows us to change thrust in the Inspector
    //rcs - reaction control system
    //100 will be the default value, will appear in the rocket script component
    [SerializeField] float mainThrust = 100f; //makes main thrust adjustable
    [SerializeField] float levelLoadDelay = 2f;

    [SerializeField] AudioClip mainEngine; //adds a spot in Inspector to add a specific sound instead of just default
    [SerializeField] AudioClip success;
    [SerializeField] AudioClip death;

    [SerializeField] ParticleSystem mainEngineParticles; 
    [SerializeField] ParticleSystem successParticles;
    [SerializeField] ParticleSystem deathParticles;

    Rigidbody rigidBody;     // add reference to rigid body
    AudioSource audioSource;

    enum State { Alive, Dying, Transcending }  //used to slow the loading of screens
    State state = State.Alive; //default mode of rocket

    bool collisionsDisabled = false; //by default, collisions are enabled. Debug settings, use C to toggle collsions on/off 

    // Use this for initialization
    void Start ()
    {
        rigidBody = GetComponent<Rigidbody>(); //gives access to rigid body
        audioSource = GetComponent<AudioSource>();
	}
	
	// Update is called once per frame
	void Update ()
    {
        if (state == State.Alive) //makes it so that once you die, you can't control rocket during scene load
        {
            RespondToThrustInput();
            RespondToRotateInput();
        }
        if (Debug.isDebugBuild) // allows debug when in Development build, turns off debug keys when not in Dev build
        {
            RespondToDebugKeys();
        }
    }
    
    //Debug keys below
    private void RespondToDebugKeys()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            LoadNextLevel();
        }
        else if (Input.GetKeyDown(KeyCode.C))
        {
            collisionsDisabled = !collisionsDisabled; //toggle collisions - collisions off = no death 'God Mode'
        }
    }

    void OnCollisionEnter(Collision collision) //whenever rocket hits object not tagged 'friendly'
    {
        if (state!= State.Alive || collisionsDisabled) { return; } //if not alive OR if collisions are disabled
        //!= means 'does not equal'
        //ignores multiple collisions - this will start the reload process immediately rather than going to next stages below
        switch (collision.gameObject.tag)
        {
            case "friendly": 
                // friendly = do nothing to rocket //tags are case sensitive
                break;
            case "Finish": //Success Sequence
                state = State.Transcending;
                audioSource.Stop();
                audioSource.PlayOneShot(success);
                successParticles.Play();
                Invoke("LoadNextLevel", levelLoadDelay); // adds a screen loading delay of 1 sec
                break;
            default:
                state = State.Dying; //Death Sequence
                audioSource.Stop(); //stops thrust sound
                audioSource.PlayOneShot(death);
                deathParticles.Play();
                Invoke("LoadFirstLevel", levelLoadDelay); //brings player back to beginning 
                break;
                //objects tagged 'default' kill rocket
        }
    }

    private void LoadNextLevel() //remove the word 'static' from private static void
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        int nextSceneIndex = currentSceneIndex + 1;  
        if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
        {
            nextSceneIndex = 0; //will loop back to start after final level
        }
        SceneManager.LoadScene(nextSceneIndex);
    }

    private void LoadFirstLevel()
    {
        SceneManager.LoadScene(0);
    }

    private void RespondToThrustInput()
    {
        if (Input.GetKey(KeyCode.Space)) //can thrust while rotating
        {
            ApplyThrust();
        }
        else
        {
            audioSource.Stop();
            mainEngineParticles.Stop();
        }
    }

    private void ApplyThrust()
    {
        rigidBody.AddRelativeForce(Vector3.up * mainThrust * Time.deltaTime); //thrust
        if (!audioSource.isPlaying) //exclamation point negates, so this means 'if audio source is NOT playing'
        {
            audioSource.PlayOneShot(mainEngine); //rocket sound begins when thrust begins
        }
        mainEngineParticles.Play();
    }

    private void RespondToRotateInput()
    {
        rigidBody.freezeRotation = true; // take manual control of rotation

        float rcsThrust = 100f; //100f is floating point number
        float rotationThisFrame = rcsThrust * Time.deltaTime; //when this line placed here, becomes available to entire function below

        if (Input.GetKey(KeyCode.A))// can rotate left OR right, not both at same time
        {
            transform.Rotate(Vector3.forward * rotationThisFrame); //rotate left/anticlockwise around Z axis, aka vector3.forward
        }
        else if (Input.GetKey(KeyCode.D))
        {
            transform.Rotate(-Vector3.forward * rotationThisFrame); //rotate right/clockwise around Z axis
        }

        rigidBody.freezeRotation = false; //resumes physics control
    }
}

#5

So, when you did the re-build… had you re-loaded the project first?

I don’t see anything jumping out at me from the source-code as being “the problem”.

You should check this, perhaps: https://docs.unity3d.com/Manual/webgl-debugging.html

Debugging and troubleshooting WebGL builds

Visual Studio does not support debugging Unity WebGL
content. To help you to find out exactly what’s going on with your content, here are some tips on how to get information out of your build.

The browser’s JavaScript console

Unity WebGL does not have access to your file system, so it does not write a log file like other platforms. However, it does write all logging information (such as Debug.Log , Console.WriteLine or Unity’s internal logging) to the browser’s JavaScript console.

To open the JavaScript console:

  • In Firefox, press Ctrl-Shift-K on Windows or Command-Option-K on a Mac.
  • In Chrome, press Ctrl-Shift-J on Windows or Command-Option-J on a Mac.
  • In Safari, go to Preferences > Advanced > Develop, and press Command-Option-C.
  • In Microsoft Edge or Internet Explorer, press F12.

Development builds

For debugging purposes, you might want to make a development build in Unity (open the Build Settings window and click the Development Build
checkbox). Development builds allow you to connect the profiler
, and Unity does not minify them, so the emitted JavaScript code still contains human-readable (though C+±mangled) function names. The browser can use these to display stack traces when you run into a browser error, when using Debug.LogError, or when you throw an exception and exception support is disabled . Unlike the managed stack traces that can occur when you have full exception support (see below), these stack traces have mangled names, and contain not only managed code, but also the internal UnityEngine code.

Exception support

WebGL has different levels of exception support (see documentation on Building for WebGL). By default, Unity WebGL only supports explicitly thrown exceptions. You can enable Full exception support, which emits additional checks in the IL2CPP-generated code, to catch access to null references and out-of-bounds array elements in your managed code. These additional checks significantly impact performance and increase code size and load times, so you should only use it for debugging.

Full exception support also emits function names to generate stack traces for your managed code. For this reason, stack traces appear in the console for uncaught exceptions and for Debug.Log statements. Use System.Environment.Stacktrace to get a stack trace string.


#6

I do note that while you have a variable called rcsThrust configured to be adjusted in the inspector, you’ve also gone and declared a local variable of the same name inside the method: RespondToRotateInput

You should probably try to avoid re-using variable names this way. Decide if you want it to be local in scope to the one method, or global, available to the entire class (and adjustable in the inspector). As it, it’s ambiguous at best.


#7

As I am totally new to coding, I followed along with the tutorials as closely as I possibly could, so as far as I know, I have been reproducing exactly what has been written in the code for the tutorials. Is there a way to include a SerializeField so that there’s no default number in the code?

I don’t really understand how to change any of that stuff to global, local, etc and how to make it unambiguous, as you suggested… I’m feeling pretty lost right now :frowning:


#8

Also, I’m not exactly sure what you mean by reloading the game before rebuilding.


#9

We were all new once :slight_smile: So, one thing at a time:

  1. When I mention “reloading” the project, it’s basically, exiting Unity and loading the project fresh then building the export. I have a handful of game-bugs that generally resolve this way (sometimes I might have to repeat 3-4 times or more to get a bug-free build, other times I can build all day long without any glitch… One of Unity’s charms).

  2. When you are looking at the course content (https://www.udemy.com/unitycourse2/learn/v4/content) there are links to the “Lecture Project Changes”, i.e. https://github.com/CompleteUnityDeveloper2/3_Project_Boost/commit/701bbf22db2c2e0e02876a8beccdbc049f997016#diff-50800c04d8b23a105564a3fbf1952411

The output can be daunting, when trying to look at the diff (with all the red/green highlights) but you can also drill into the raw file at any point in it’s history to see what the instructors are using (the View button, upper rightish). https://github.com/CompleteUnityDeveloper2/3_Project_Boost/blob/701bbf22db2c2e0e02876a8beccdbc049f997016/Assets/Rocket.cs

  1. Local/Global: these are conveying the “visibility” of a variable. Let me show an example…
using UnityEngine;

public class MyClass : MonoBehaviour
{
   // any variables declared here are 'global', ie.e. visible to the entire class.
   // if they are private, they are only visible within the class (default).
   // if they are public, they are visible outside of the class.
   string ambiguousString = "Default global ambiguous string"; // Visible to entire class... or is it?
   string privateGlobalString = "Default private global string value"; // Visible to entire class, i.e. 'global'.
   public string publicGlobalString = "Default public global string value"; // *Visible to entire program.

   void MyFunction()
   {
      string hiddenString = "Default string variable only 'visible' within the MyFunction() method, i.e. 'local'";

      Debug.Log(ambiguousString); // This outputs "Default global ambiguous string" to the console.
      Debug.Log(hiddenString); // Output as expected.
      Debug.Log(privateGlobalString); // Output as expected.
      Debug.Log(publicGlobalString); // Output as expected.
   }

   void Start()
   {
      string ambiguousString = "Replacement local ambiguous string"; // local to the Start() method.
      MyFunction(); // This will output the 4 'default' strings to the console.

      Debug.Log(ambiguousString); // This outputs the local ambiguousString value, "Replacement local ambiguous string".
      Debug.Log(hiddenString); // This generates an error, "The name hiddenString does not exist in this context".
   }
}

So, where you re-declare the variable rcsThrust inside a function, even though you already have a global variable of the same name, the compiler (I presume) is using the local variant. This creates unneeded confusion, because you can change “Rcs Thrust” in the inspector, and it will make zero difference, because that’s not the variable you’re using in the calculations.

You’ll note, if you actually run the above code (Note, I’ve updated it since first posting it) that the value of ambiguousString changes depending on context. Maybe this is something you want, but most programmers would eschew doing that. For example, in the Start() method, we explicitly declare our ambiguousString, then we call a function (MyFunction) that uses the variable ambiguousString… so why doesn’t it use the value we just assigned to it? ! ? ! ? this is ambiguous.

While it’s possible to have variable as a [Serialize Field] that has no default value, it’s probably a better practice to assign what you feel should be the “default” value there, and then find a way to understand how and why the inspector value overrides the value in the script. The main reason Ben is using this serialize-field feature is to give an easy way to tweak the number while play-testing, to find a value that makes you happy. It’s not strictly necessary… A person could use a regular private variable, give it a value, do a play-test, then stop and tweak it and repeat the test. With the variable serialized, you can make changes and see them immediately without stopping and restarting the game.


#10

I’m going to give these suggestions a go and see what happens, thank you!


#11

Okay, I tried a couple of minor things. I reloaded the project a few times as you suggested, and I “commented out” [SerializeField] float rcsThrust = 100f; in my code. The issue with the rocket behavior is still occurring. I have retried the game a few times now, and what seems to be happening is, as soon as I press the ‘A’ key to turn the rocket left, it never stops. This happens even after the rocket crashes and is respawned. The ‘D’ key does not do the same thing, only A. :confounded:


#12

Sounds like a good time to add some debug output for some clues, i.e.

    private void RespondToRotateInput()
    {
        Debug.Log("Respond To Rotate Input @ " + Time.time);
        rigidBody.freezeRotation = true; // take manual control of rotation

        float rcsThrust = 100f; //100f is floating point number
        float rotationThisFrame = rcsThrust * Time.deltaTime; //when this line placed here, becomes available to entire function below

        if (Input.GetKey(KeyCode.A))// can rotate left OR right, not both at same time
        {
            Debug.Log("KeyCode.A @ " + Time.time);
            transform.Rotate(Vector3.forward * rotationThisFrame); //rotate left/anticlockwise around Z axis, aka vector3.forward
        }
        else if (Input.GetKey(KeyCode.D))
        {
            Debug.Log("KeyCode.D @ " + Time.time);
            transform.Rotate(-Vector3.forward * rotationThisFrame); //rotate right/clockwise around Z axis
        }
        else Debug.Log("Negative KeyCode.A & D @ " + Time.time); // <--new line

        rigidBody.freezeRotation = false; //resumes physics control
    }

Hopefully, this extra output to the console will help you diagnose what’s happening. I encourage you to modify the content and location of the debug output so that it makes sense to you what you are logging and why. The intention is to peek inside the “thought process” of the executing code to understand why it does what it does.

You can comment-out or delete such debug output when it has achieved it’s purpose, of course.


#13

Hello again,
I added these lines as you suggested. I began with them commented-out then activated them one at a time to see what the output was. The first one gives output to the console regardless of what is happening in scene. The second, KeyCode.A, outputs to console while A is pressed, as expected, and stops when A is not pressed. Same for KeyCode.D. The last one outputs to console unless A or D is pressed, then it stops. Those are the observations I made, but other than that I don’t know really how to interpret that to see if it has anything to do with the errors I am seeing.


#14

…and when you release ‘A’, it stops generating the debug message, but it continues to rotate?

If so, I’m at a loss… the only place I see your code doing rotation are within that set of if/if-else/else statements:

if (Input.GetKey(KeyCode.A))// can rotate left OR right, not both at same time
        {
            Debug.Log("KeyCode.A @ " + Time.time);
            transform.Rotate(Vector3.forward * rotationThisFrame); //rotate left/anticlockwise around Z axis, aka vector3.forward
        }
        else if (Input.GetKey(KeyCode.D))
        {
            Debug.Log("KeyCode.D @ " + Time.time);
            transform.Rotate(-Vector3.forward * rotationThisFrame); //rotate right/clockwise around Z axis
        }
        else Debug.Log("Negative KeyCode.A & D @ " + Time.time); // <--new line

In other words, if you are doing a transform.Rotate you are also debug.logging so… How can it possibly continue rotating without also logging that it is doing so? They happen in the same exact part of the code…

If you want to .zip up your project (minus the Library folder) I can take a closer look, but otherwise, I’m at a loss to explain this oddity.


#15

Sure, I will send it over as I still haven’t figured it out. How can I send it to you?


#16

Most cloud fileservers allow sharing, I think, i.e. dropbox, google drive, spideroak… I’m pretty sure I’ve seen people on here using dropbox before. I know spideroak can do sharing, as well as google drive. If those flavors don’t work for you I’m sure you can find something else if you shop around.

Once you have a fileserver lined-up you need to prepare the files.

Option 1) “ZIP” the source-tree. If you do this, please do NOT include ./Library/* (also, if you’ve downloaded asset-packs that you’re not using, you can keep those out as well… they can really make the file huge…)

Option 2) Export a Unity Package of your broken scene and anything it needs to export completely, scripts, assets, etc…

PS - if you decide to share it on Google Drive, I can PM you my google username.


#17

I do have Google Drive so I will be using that.


#18

Okay, so this is really weird! I tried building the game again today, and I uploaded it to ShareMyGame. And it works! No issues. I don’t know what actually fixed it, but it’s working.


#19

Congratulations! As for me, I have had so many ‘transient’ issues with Unity over the years that I’ve been tinkering with it, I can’t even recall them all.

My latest one makes absolutely no sense to me, but I suspect someone in the Unity dev team would have some ideas. Here’s the ‘transient bug’ I have to deal with from time to time, my thoughts on it, and my Jerry-rig solution:

Description Of A Transient Bug

Background:

I have several objects in my scene that have a colour-lerp applied to their texture (i.e. I give a function two colours and some numbers, and it hands back a colour that is colour A, colour B, or a mix of the two with a ratio anywhere from one extreme to the other.) Specifically, I have four GUI (HUD) ‘progress bars’, plus two texture zones on the player object that all get their colour this way (from the lerping function).

“Bug” As Observed:

I can run my game inside Unity all day long, and all six of these colour-changing areas work exactly as expected. I can build my game, and once again, all six of these colour-changing areas work exactly as expected. ALSO, I can generate a build, and find that there is a bug in the built version: one, only one, and one specific texture from the six referenced will revert to it’s prefab base colour [the “bell” on the thruster for my Boost player object, fwiw].
Once this happens, which (to be clear) only happens intermittently, when I subsequently play the game inside the Unity Editor, the “bug” consistently persists. The same applies to any build for any platform. Once “triggered” this one specific texture refuses to play along with the other five. The ONLY way to resolve this issue that I have found, is to re-load the entire project (basically, shut down Unity, and restart it).

Analysis:

Obviously, while Unity runs it caches this, that and the other thing. Whatever triggers this ‘bug’ seems to corrupt one of these caches. It would seem to be a ‘deep’ cache, as I restate: once it starts there’s no stopping it: it is pervasive on each platform and consistent on each play.

Summary / Solution:

Again, the only thing I’ve found to resolve this issue is to reload the project (i.e. File… Open Project, or simply close/re-open Unity). I can not force the bug to manifest, it is transient and might plague me for an hour of building, reloading, building, reloading, but… it is transient, and might not bother me all day.