What is Modifying My Array
Asked Answered
A

9

0

I have declared an array in a script like so:
public GameObject[] players = new GameObject[1];
I do not fill the array with anything in the inspector, because I want this array to store all game objects in the scene that are controlled by player(s). As such, I need to modify this array at various points in runtime, like when the scene loads, players die or respawn, etc. As such, after initializing the array, there is exactly one line in all of my scripts that modifies the array. It is:
players = GameObject.FindGameObjectsWithTag("Player");
This successfully populates my array with the player’s game object instead of the previous value of null. However, at an unknown point after this line of code, something sets my array back to being filled with a null value. I have spent hours in visual studio / unity’s debuggers trying to pinpoint where the array gets modified again, but haven’t found anything…

To the very best of my knowledge, the FindGameObjectsWithTag() is the only line of code that modifies the array after it is initialized. And that line only gets called once. I don’t think there is an issue with scene changes deleting the game object that holds this array, since it is a child of another object with DontDestroyOnLoad() enabled. This leads me to feel confident the array is only initialized once, and only mutated once, within the context of my own code, throughout the entire application lifecycle. What I am wondering is if there could be any Unity engine code that is manipulating my array to reset it to null values without my knowledge? Are events, asynchronous code, or other concepts causing my code to run in a non-straightforward way? I don’t really know how to search through all the engine code or even if I have access to see it, and I recognize this is an unlikely cause of the bug, but I have spent hours looking through my own code and can’t even see another place that modifies the array…

One other question is how do I verify that there is only one active scene running in my project? If multiple scenes exist, will both appear simultaneously in the object hierarchy window? (Currently only one scene is shown in the window, which leads me to believe it is not an issue, but I also don’t know for sure if I explicitly unload the previous scene correctly) What I’m getting at is that if there are two scenes, maybe there are two instances of my script that creates the players array, and that might cause the bug?

Finally, if more context is needed, I am trying to position my main game camera’s y position at the average y position(s) of all players in the game each frame (Hence my need to have a players array that gets set by GameObject.FindGameObjectsWithTag(), dynamically at runtime.) This is my first unity project, so I might be overlooking a much simpler design. If so, I’d love to know! Thanks for the help.

Antiknock answered 9/8, 2023 at 9:33 Comment(2)

Can't really tell without more info.. but keep in mind that you are not modifying / populating an array, you are replacing the array in the variable with another array returned by 'FindGameObjectsWithTag'. If you are keeping a reference to that array somewhere else, then that may still be referring to the old array. Also, making it public means it's probably serialized, and therefore can be modified by serialization. If you are only modifying the array through code of that class, make it private. All active scenes would show in the hierarchy.

Norwood

In my personal opinion, it is nearly impossible unity engine modifies to null. How about check if there are any code that reinitializing players, if not tracking with debug.log () to trace your code -like FindGameObjectsWithTag- by the logic.

Marla
A
0

Solved it! It turns out there was a second camera instance in my scene that did not appear in the object hierarchy, because I set one of my GameObject fields to a prefab in the inspector, and when I ran the game, I thought this field was referencing the correct camera. As many of you said, I did not provide enough setup detail. Thank you all for your help, though, I greatly appreciate it!

Antiknock answered 27/8, 2023 at 14:32 Comment(0)
A
0

The game is real early in development, so fortunately there aren’t a ton of additional moving parts. There are two scenes. One is named Startup, and that’s where I create my persistent game state. Almost immediately afterwards, it switches to a scene called Match, which is where the core gameplay takes place. I instantiate my player by dropping its prefab into the Match scene, so to the best of my knowledge, there is only one object with the “Player” tag, and it is only initialized once, in response to the SceneManager.sceneLoaded event.

Using cmd+shift+f in visual studio, I searched through my entire assets folder for the following phrases, in a case sensitive manner: “players”, and “FindGameObjectsWithTag(." The “players” search yielded 16 finds. Two of them were followed directly by the assignment operator, and they were the same two instances where I initialize the array described in the original question. I also kept an eye out for things like “players[0] =”, but there were no instances where I mutated an individual element in the array either. There were also no statements in my code to the effect of “anotherVariable = players;”. (I think this rules out Pangamini’s idea that there might be another variable referencing an outdated version of the players array? Or did you mean something else?) Finally, the “FindGameObjectsWithTag(“ search yielded only the one statement mentioned in the original question. This one successfully re-initializes the array to contain the desired GameObject instance, but sometime afterward the array mysteriously returns to possessing one null element… Are there other phrases worth looking up that might modify my players array? Should I expand my visual studio search outside the Assets folder?

I did change the fields at the top of my class to declare the players array as private. Thanks again for the help!

Antiknock answered 3/8, 2023 at 21:52 Comment(0)
A
0

Also, if more context helps, here is my CameraController class. Though incomplete, it compiles, and the intention is for it to persist between scene changes, and handle how the camera should position itself throughout the game - such as if it is in a menu, animation, following players etc.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    private GameObject[ ] players = new GameObject[1];  //an array of all the players in the climb, for averaging their position to decide where the camera should be positioned.
    public string following = "static";  //should the camera position itself to follow the players, or something else?

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    private void Update()
    {
        if (following == "players")
        {
            PositionByPlayers();
        }
        else
        {
            //keep the camera's default position
        }
    }

    public void FindAllPlayers()
    {  //Called whenever the number of players in the scene changes. This way, the average position of each player can be accounted for when changing the camera’s position.
        Debug.Log("FindAllPlayers()");
        players = GameObject.FindGameObjectsWithTag("Player");
        following = "players";
    }

    public void PositionByPlayers()
    {  //Actually position the camera on a frame-by-frame position, based on the data in players array
        float sum = 0;
        foreach (GameObject player in players)
        {  //iteratively build up the sum
            if (player == null)
            {
                Debug.Log("Player null");
            }
            if (player != null)
            {
                Debug.Log("ReadingPlayerPosition");
                sum += player.transform.position.y;
            }
        }
        if (players.Length == 0)
        {  //handle divide by zero exception
            Debug.Log("SsnA: No players detected");
            transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
        }
        else
        {  //use sum and players.Length to find an average y position that the camera will update to. *THIS is the block I want to be able to enter, but can’t because of null array
            Debug.Log("SsnA: Positioning relative to players");  //temp
            transform.position = new Vector3(transform.position.x, sum / players.Length, transform.position.z);
        }
    }
}

So, I have a single main camera that I want to persist between scene changes. I facilitate this by making a main camera prefab, and instantiating it in the Startup scene as a child of another instantiated prefab called PersistentGameState, which is just an empty object with a script that utilizes DontDestroyOnLoad() to prevent (I think) anything from changing on this object or its children as scenes change. Correct me if I am wrong.

So, after the Startup scene has initialized my persistent game state, I immediately load up the Match scene and unload the Startup scene. It is essentially in response to the UnityEngine.SceneManagement.MatchManager.sceneLoaded event that I first call FindAllPlayers(). At this point, my player object is already in the Match scene, so it gets found. I also change the following field to the string players to specify that I want the camera to start positioning itself relative to players, not just some default static location. Using breakpoints in VS, I verified that by the end of FindAllPlayers(), my CameraController’s players and following fields are properly set to an array with a game object instance in it (or maybe it is a prefab? I don’t think so, but I actually haven’t verified this) and the string players respectively. Then, some additional code runs, and by the time CameraController.Update() is supposed to call PositionByPlayers() if(following == “players”), both of my fields have reset to what they originally were at the top of the class definition…

So I guess it’s not just arrays that are getting reset, but strings too?

Antiknock answered 9/8, 2023 at 9:34 Comment(0)
A
0

Update: The issue is not specific to arrays, but to all my fields in the CameraController class. I verified this by initializing a BRAND NEW string field at the top of the class (So there won’t be any other references to it anywhere in the codebase), and then changing it within FindAllPlayers(). What happens is that every tick, my fields reset themselves to their original values in time for Update() to be called. It’s almost like my class is constantly restarting itself… Ironically, the inspector in unity shows the fields with the desired values if I make them public, but when I set breakpoints in VS within the Update() method to inspect the values, they tell a different story.

I tried declaring the fields at the top of the class and initializing them in the Start() method, but this changed nothing. I also tried loading the scene asynchronously instead of synchronously, but that changed nothing either (unless I’m doing something wrong there too, I don’t know much about using synchronous events versus asynchronous events). Finally, I also tried applying DontDestroyOnLoad() directly to the camera prefab, in case this property wasn’t being inherited by the parent object PersistentGameState. Again, nothing changed. Please help me. This bug has bottlenecked my progress for quite awhile…

Antiknock answered 4/8, 2023 at 15:33 Comment(0)
A
0

Still need help. The players array is private, and the only way to change it is through the public method FindAllPlayers(). I have set a breakpoint in this method to verify that my game only calls it once, as intended, and properly creates an array with one game object instance (not prefab) in it. Should be good to go…

Then, at some other point in the game, players[0] magically becomes null again. This field is supposed to be private. There are exactly two points in this class where players gets mutated, and both work as intended. Nothing else is supposed to have access to this private field. There isn’t even a getter/setter for it. What else can possibly be violating my private field?

Antiknock answered 8/8, 2023 at 23:43 Comment(0)
A
0

Was able to step through the code a ways and get a better idea of when my array gets unexpectedly mutated. It holds the proper value until the very last line in my method body for the callback I subscribed to UnityEngine.SceneManagement.SceneManager.sceneLoaded. Then, when I step to the next line of code (inside CameraController.PositionByPlayers()), players[0] becomes null again. I only used the “step over” button in visual studio’s debugger, since “step into” often bugs out if it has to delve into a unity-defined method. So it’s possible I may be missing some intermediate steps. But correct me if I’m wrong - this would indicate the array gets mutated by Unity engine code that runs in-between the end of my event callback and the beginning of my PositionByPlayers() method? If so, what should I do? It would be a real bummer if Unity is modifying my private fields without my consent… I doubt that’s what’s happening, but if it is, I don’t think I can continue using Unity.

Antiknock answered 9/8, 2023 at 5:14 Comment(0)
G
0

This sounds a bit like it could be the root of all evil. What exact callback do you assign here? Maybe you use a closure that has captured old data? Are you sure you call your methods on the right objects? You can use Debug.Log to check this. It has an optional second “context” argument which can be any UnityEngine.Object reference. When you click on the log message in the console, Unity will highlight that context object whereever it may be. This may help to identify on which object / instance the methods actually run. Common issues when data looks one way and suddenly different in another place, it’s due to using two or more instances. Note that prefabs are also instances. They don’t live in the scene but are in memory and you can call methods on them.

So my guess is that you reference the wrong object at some point. Since you do a scene change, is it possible that the new scene is referencing a prefab of the object you have in the scene? How exactly do you manage the communication between your scenes? The second scene can not reference something from the first scene and have it serialized. The second scene has to dynamically reach out to find the instance that was brought over from the previous scene.

Also note that when you do players = XXX you do not “mutate” the array, you replace it with whatever XXX is. “players” is just a variable that can point to an array instance. That players variable isn’t an array itself. Arrays are objects. FindGameObjectsWithTag returns a new array and when you assign it to your players variable, you replace the old one.

As @Burrstone said, we need more details about your setup. We can’t tell what’s going on from your vague descriptions.

Also note that DontDestroyOnLoad only works on root objects in a scene. Calling it on a nested object won’t work. Also keep in mind that DDOL does only hold the gameobject tree alive, not data that this object might be referencing that is not part of that gameobject tree. So for example when your “players” array is referencing player objects in your first scene and only that object with the DDOL persists, when you load the new scene the player objects would get destroyed and all the references you had would become (fake) null since the objects don’t exist anymore.

When you use Debug.Log statements, make sure to include details in the log messages. Things like the gameobject’s name of the script that this is executing on or the length of the players array. Though the context parameter is usually the most valuable in such cases.

Gathers answered 9/8, 2023 at 11:24 Comment(2)

It appears I misunderstood how prefabs work. I didn't realize they were instances themselves. Is there a good place for me to read up more about prefabs versus objects that live in the scene, etc? And while we're at it, I'm pretty spotty on my knowledge of how scenes and events work in unity/c# too. Thank you for the problem solving ideas!

Antiknock

My goal was to store any data that needs to persist between scenes or between play sessions in my GameState prefab, which has a GameState script that just holds a bunch of data I want to have saved. Then, my MatchManager class would handle the logic of "communicating between scenes" if I understand what you mean by that. (Or are you referring to a more well defined process/design for switching scenes that I don't know of?)

Antiknock
A
0

Ok I’ll try to describe the entire problem better within this one post. Let me know if more detail is needed:

SCENES: I have 2 scenes in my game - Startup and Match.

Startup is my initial scene, and it is where I include all of my persistent game state, as well as some other common game objects I didn’t want to have to remember to include each scene. PersistentGameObjects owns the GameManager and DontDestroyOnLoad scripts. MatchManager owns the MatchManager script, and MainCamera owns the CameraController script.
StartupScene

Match is my secondary scene. It only holds my main player object, which is what I am trying to reference in the players array. This is the scene where the core gameplay takes place

SCRIPTS:

GameManager script:

using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{  //This class essentially handles how to switch between scenes from a high-level perspective. It relies on data from my GameState class, and functionality from Unity's UnityEngine.SceneManagement class.
    // Start is called before the first frame update
    public GameState gameState;
    public MatchManager matchManager;
    void Start()
    {
        SceneManager.LoadScene("Match");  //ultimately will start with your company icon or something, but for now just jumps right into a match.
        SceneManager.sceneLoaded += OnSceneLoaded;
        SceneManager.sceneUnloaded += OnSceneUnloaded;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        Debug.Log(scene.name);
        if(scene.name == "Match")
        {
            matchManager.Activate();  //Called when you want to start a new match.
        }
    }

    void OnSceneUnloaded(Scene scene)
    {
        if(scene.name == "Match")
        {
            matchManager.Deactivate();  //Called when you want to end a match.
        }
    }
}```

MatchManager Script: Sorry, a lot of this code is probably superfluous. Note that the `MyCamera` field is set in the inspector to my prefab called `MainCamera`.
```using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MatchManager : MonoBehaviour
{  //Match Manager is basically a system. It should be a singleton, that handles all overarching rules and data for a match. Some of these things include transitioning between gameplay, animation, and character select, as well as how to start and finish a match. It will also build "climbs" by connecitng random "chunk" instances together.
    //as of 7/25/23, each chunk is 1 screen, 15 tiles, or 30 meters tall. so each climb is about 150 meters.
    public GameObject myCamera;
    public List<GameObject> chunks = new List<GameObject>();  //an array of "chunk" game objects to use as the building blocks for constructing a level. Each chunk prefab will have a ChunkData cpnt describing various attributes it has.
    public List<GameObject> bossChunks = new List<GameObject>();  //similar to chunks array, except these are specifically for boss battles. The order and size of this array matters, due to the way it is used in MapBossToChunk(). That's why I made it readonly.
    public string boss;  //String telling which tiki boss you're facing on a given climb.
    public string phase = "inactive";  //string telling what phase of the climb you are in. Legal values: "inactive", "blackScreen", "startAnimation", "characterSelect", "playing", "endAnimation"
    public int height = 0;  //represents how far up the mountain(s) you have climbed. Each tile is 2 meters, or 16px, or 16 game units. (This field represents meters)
    private int mountainNumber = 0;  //keeps track of how many mountains you have climbed.
    private string mountainName;  //describes the predominant environment of the mountain, and which tiki boss will be on it. (Though some things will still be randomized for variety's sake.) legal values: "tide", "beach", "jungle", "snow", "town", "lava".
    private readonly int chunkSizeInMeters = 30;  //possible this may need to be updated, and it would be nice if it was only here.
    private readonly int chunkSizeInGameUnits = 240;  //possible this may need to be updated, and it would be nice if it was only here.

    // Start is called before the first frame update
    void Start()
    {
        //TEMP: this might be where you instanciate all the game objects in your game object lists, but I think more likely it will be in PositionChunks()
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void Activate()
    {  //Since the game object carrying this script will persist between scenes, it is important to ensure it only does it's job in the appropriate scenes. (during a match).
        phase = "blackScreen";
        BuildClimb();
    }
    public void Deactivate()
    {  //see "Activate()"
        phase = "inactive";
    }
    private List<GameObject> FindSuitableChunks(int level)
    {  //at the start of each climb, you must BuildClimb() from chunks. This method creates a sub-array from the chunks[] field consisting only of desirable chunks for the given climb. (Easier chunks for the earlier mountains (level param), more tide pool chunks if you visit a tide pool mountain, etc (environment param).
        List<string> legalEnvironments = new List<string> {"tide", "beach", "jungle", "town"};  //other environments that are allowed to be featured in a climb, even though they are less commonly seen.
        List<int> dontRepeats = new List<int>();  //as you build a climb, you will select chunks from the "chunks" field. In order to not select the same one twice, this array of ints will keep track of the indexes of chunks that have already been selected, so you can remember not to pick them again.
        List<GameObject> climbChunkRoster = new List<GameObject>();  //This is what
        int heightInChunks = 5;  //how many chunks should exist in the climb
        string rememberLastMountainName;
        int desiredChunkDifficulty;  //int [1,3] to compare to ChunkData to see if you want to include it in the mountain.
        string desiredChunkEnvironment;  //string of the legal environment types that you can compare to ChunkData to see if it is a chunk you want to include in the mountain.

        //handle when it is POSSIBLE to start seeing certain terrains.
        if(level > 1)
        {  //you might start seeing snow after the first mountain
            legalEnvironments.Add("snow");
        }
        if(level > 2)
        {  //you might see lava after the second mountain.
            legalEnvironments.Add("lava");
        }

        //decide the predominant environment that will be featured on this climb.
        rememberLastMountainName = mountainName;
        legalEnvironments.Remove(mountainName);  //don't go to the same mountain as last time.
        mountainName = legalEnvironments[Utilities.RandomInt('[', 0, legalEnvironments.Count - 1, ']')];  //after you have decided on all the legally available environments, randomly pick one.
        legalEnvironments.Add(rememberLastMountainName);

        //Each match follows a pattern of two shorter climbs followed by a longer final boss battle climb. Use modulus to keep track of where you are in this loop.
        if(level % 3 == 0)
        {  //every "third" level guarantees you visit the lava mountain for final boss fight. As such, the match loop goes in intervals of 3. The lava mountain is also a little bit taller than the first and second.
            heightInChunks++;  //make the mountain a little higher than the first two.
        }
        else
        {  //every "first" OR "second" level
            if (Utilities.RandomInt('[', 0, 1, ']') == 0)
            {  //50/50 chance for the climb to be either 4 or 5 chunks high.
                heightInChunks--;
            }
            if (level % 3 == 1/3)
            {  //every "first" level
                
            }
            else if(level % 3 == 2/3)
            {  //every "second" level
                
            }
        }

        //search through chunks field, starting at a random location, to find a suitable chunk to add to the suitableChunks list. Repeat as many times as the value of heightInChunks.
        void DecideChunkAttributes()
        {  //use probability to decide the difficulty and environment type of the chunk that you want to look for in the chunks array. Once you know these two data points, you can compare them to each chunk's ChunkData until you find a match. (By using SearchChunks)
            List<string> bag = new List<string>();
            foreach(string str in legalEnvironments)
            {
                if(str != mountainName)
                {  //create a list of all the oddball environments
                    bag.Add(str);
                }
            }
            if(Utilities.RandomInt('[', 1, 4, ']') == 1)
            {  //25% chance to pick a legal oddball environment
                desiredChunkEnvironment = bag[Utilities.RandomInt('[', 0, bag.Count - 1, ']')];
            }
            else
            {  //75% chance to pick the primary environment
                desiredChunkEnvironment = mountainName;
            }
            desiredChunkDifficulty = (int)Mathf.Ceil((float)height / chunkSizeInMeters * 5 + Random.value - .5f);  //As you climb higher, the chunks will become more difficult, with a bit of random variation. Chunks' levels are from 1-3. chunkSizeInMeters * 5 = 150 at time of writing. The design intention is to have you "level up" at a rate of about 1 level per climb, max 3.
        }
        void SearchChunks()  //TEMP: Note that early in the development process, before a bunch of different chunks have been implemented, this function will return the default, chunks[0] pretty frequently. There's nothing wrong with the logic of the function, but rather, you have an incomplete set of input data.
        {  //will search sequentially through chunks collection and add 1 chunk to the climbChunkRoster collection.
            int startingLocation = Utilities.RandomInt('[', 0, chunks.Count - 1, ']');  //pick a random place to start searching through the list to find chunks
            int currentLocation = startingLocation;
            bool meetsRequirements = false;
            bool isFirstIteration = true;

            bool IsRepeat()
            {
                for(var i = 0; i < dontRepeats.Count; i++)
                {
                    if (currentLocation == dontRepeats[i])
                    {  //you are iterating over a chunk that has already been used. Don't repeat it.
                        return true;
                    }
                }
                return false;
            }
            do
            {  //iterate through chunks[] until you find a chunk that meets the difficulty and environment requirements.
                //De/bug.Log(chunks[currentLocation].GetComponent<ChunkData>().difficulty == desiredChunkDifficulty && chunks[startingLocation].GetComponent<ChunkData>().environment == desiredChunkEnvironment && !IsRepeat());
                //De/bug.Log(currentLocation);
                //De/bug.Log("startExp");
                //De/bug.Log(chunks[currentLocation].GetComponent<ChunkData>().difficulty);
                //De/bug.Log(desiredChunkDifficulty);
                //De/bug.Log(chunks[currentLocation].GetComponent<ChunkData>().environment);
                //De/bug.Log(desiredChunkEnvironment);
                //De/bug.Log(IsRepeat());
                //De/bug.Log("endExp");
                if(currentLocation < 0 || currentLocation > chunks.Count - 1)
                {  //keep from going out of bounds.
                    currentLocation = 0;
                }
                if (chunks[currentLocation].GetComponent<ChunkData>().difficulty == desiredChunkDifficulty && chunks[currentLocation].GetComponent<ChunkData>().environment == desiredChunkEnvironment && !IsRepeat())
                {  //if the randomly selected chunk meets the difficulty and environment requirements, and has not already been selected in this climb, then add it to the final climbRoster.
                    climbChunkRoster.Add(chunks[currentLocation]);
                    Debug.Log("EUREKA");
                    dontRepeats.Add(currentLocation);  //also, keep track of its index, so you don't select it again this climb.
                    meetsRequirements = true;
                }
                if(currentLocation == startingLocation && !isFirstIteration)
                {  //you've iterated through the entire list and not found a match. this should never happen, but if it does, catch it here to avoid infinite lp.
                    Debug.Log("iterated through entire chunks list & no match. Inf lp");
                    Debug.Log("FOOLSGOLD");
                    climbChunkRoster.Add(chunks[0]);  //better to just append the first chunk you can find than have an inf lp. (Might want to ensure this is isn't a boss battle though...)
                    meetsRequirements = true;
                }
                currentLocation++;
                isFirstIteration = false;
            }
            while (!meetsRequirements);
        }
        for (int i = 0; i < heightInChunks; i++)
        {  //search for a new chunk as many times as needed
            if (i == heightInChunks - 1)
            {  //always make the last chunk a boss battle
                climbChunkRoster.Add(MapBossToChunk());
            }
            else
            {
                DecideChunkAttributes();
                SearchChunks();
            }
        }
        return climbChunkRoster;
    }
    private void PositionChunks(List<GameObject> climbChunkRoster)
    {  //from an array of 4 to 6 chunks, position each of the chunks in the right locations to build a climb. "climbChunkRoster is the list of chunks you will need to position.
        for(int i = 0; i < climbChunkRoster.Count; i++)
        {
            Debug.Log("---");
            Debug.Log(climbChunkRoster[0].name);
            Debug.Log(i * chunkSizeInGameUnits);
            Debug.Log("----");
            Instantiate(climbChunkRoster[i]);
            climbChunkRoster[i].transform.position = new Vector2(climbChunkRoster[i].transform.position.x, i * chunkSizeInGameUnits);
        }
    }
    private GameObject MapBossToChunk()
    {
        int index = 0;
        switch(boss)
        {
            case "Coco":  //Tide Pool Tiki that rolls coconuts
                index = 0;
                break;
            case "Bumpa":  //Beach Tiki that rolls bouncing beach balls
                index = 1;
                break;
            case "Jungo":  //Jungle Tiki that rolls giant banana balls
                index = 2;
                break;
            case "Frifri":  //Snow Tiki that rolls expanding snow balls
                index = 3;
                break;
            case "Tona":  //Town Tiki that ?plays music to speed up enemies? ?Rolls soccer ball that changes direction any time it hits an enemy?
                index = 5;
                break;
            case "Easta":  //Lava Tiki that rolls exploding lava boulders
                index = 6;
                break;
        }
        return bossChunks[index];
    }
    private void BuildClimb()
    {  //called whenever you want to connect several random "chunks" of level data together to form a single "climb"
        PositionChunks(FindSuitableChunks(mountainNumber));
        //I think this line of code is unnecessary?: camera.GetComponent<CameraController>().following = "players";
        int x = 2;
        Instantiate(myCamera);
        myCamera.GetComponent<CameraController>().FindAllPlayers();  //TEMP: This might be called someplace else once I have a menu for selecting characters, but the general concept remains; Once you have players loaded into the scene, alert the camera to follow the players.
    }
    private void BuildCharacterSelectMenu()
    {  //instanciate any menu / ui items necessary to allow the player to select their character.

    }
}```

CameraController Script: In the inspector, I assign the following serialized fields: `following = “players”`, and `testingString = “original”`
```using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    private GameObject[] players = new GameObject[1];  //an array of all the players in the climb, for averaging their position to decide where the camera should be positioned.
    public string following = "static";  //should the camera position itself to follow the players, or something else?
    public string testingString = "original";

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    private void Update()
    {
        if (following == "players")
        {
            PositionByPlayers();
        }
        else
        {
            //keep the camera's default position
        }
    }

    public void FindAllPlayers()
    {  //Called whenever the number of players in the scene changes. This way, the average position of each player can be accounted for when changing the camera’s position.
        Debug.Log("FindAllPlayers()");
        this.players = GameObject.FindGameObjectsWithTag("Player");
        string objectType = players[0] ? "Instance" : "Prefab";
        Debug.Log($"Array element is {objectType}: {players[0].name}");
        following = "players";
        testingString = "desired";
        int x = 2;  //Place where I can set a breakpoint. Not sure if additional things happen if I try to set one at the end bracket of a method defn.
    }

    public void PositionByPlayers()
    {  //Actually position the camera on a frame-by-frame position, based on the data in players array
        float sum = 0;  //Another place for a breakpoint.
        foreach (GameObject player in players)
        {  //iteratively build up the sum
            if (player == null)
            {
                Debug.Log("Player null");
            }
            if (player != null)
            {
                Debug.Log("ReadingPlayerPosition");
                sum += player.transform.position.y;
            }
        }
        if (players.Length == 0)
        {  //handle divide by zero exception
            Debug.Log("SsnA: No players detected");
            transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
        }
        else
        {  //use sum and players.Length to find an average y position that the camera will update to. *THIS is the block I want to be able to enter, but can’t because of null array
            Debug.Log("SsnA: Positioning relative to players");  //temp
            transform.position = new Vector3(transform.position.x, sum / players.Length , transform.position.z);
        }
    }
}```

DontDestroyOnLoad Script:
```using UnityEngine;

public class DontDestroyOnLoad : MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}```
Antiknock answered 10/8, 2023 at 18:34 Comment(0)
A
0

One other thing I remembered - when I create a new scene, instead of using the “File” > “New Scene” dropdown in unity, I have just been copy/pasting the .unity files already in my project folder from my Mac’s filesystem. (My thinking was this way I wouldn’t accidentally forget certain settings between scenes when I make a new one). Is it possible that this process confuses the system? Maybe incorrectly generates or syncs with the corresponding .unity.meta file?

Antiknock answered 15/8, 2023 at 0:26 Comment(0)
A
0

Solved it! It turns out there was a second camera instance in my scene that did not appear in the object hierarchy, because I set one of my GameObject fields to a prefab in the inspector, and when I ran the game, I thought this field was referencing the correct camera. As many of you said, I did not provide enough setup detail. Thank you all for your help, though, I greatly appreciate it!

Antiknock answered 27/8, 2023 at 14:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.