0
\$\begingroup\$

Context

Hello, currently creating a clone of "Crossy Road" and what I'm trying to do is to spawn my moving object called "Vehicle" at a random speed and rate of spawn. This is also applicable to "Plank", but I will start first with the vehicle. So far, everything is working fine as intended for the game mechanics, but I would like to finalize with this issue so it is fully functional in terms of playability.

Problem

enter image description here

My issue now is I 3 different spawns objects: grass, river, and road. Each object holds other objects (let's call it spawners) depending of what field is being spawn. For example, if grass field object is spawned, it will spawn trees depending in a random varied selection. Another example is with road field. When the road is spawned, a vehicle will be spawned from either left or right in its current initial position. This vehicle will moves as intended with a random speed, but not with the original spawn position and rate (as shown in the GIF. The vehicle spawns in the middle of the road and not in the beginning of the left/right road).

As far I'm aware, my rate is currently unused because it is not the main issue I want to solve. However, the issue now is with the transform position not working as I have pictured in my head. So what is happening is that when the road is spawned again, the vehicle is spawned in the middle of the trajectory instead of resetting to the beginning.

Also, I have noticed that when I print the vehicle object, the Z-axis has a weird number compared to the original position.

Attempts done

I have been thinking that maybe it is the way I have set everything up. I have 4 vehicle objects with a child object called "Tank". However, in each vehicle object, I'm using SetActive(...) only and not really reusing the object itself to the beginning. Later on, I want to organize this spaghetti code and optimize it (e.g ObjectPool to spawn my roads and other GameObjects after hitting a certain range, adding a player range detection to spawn a field to name a few).

To be honest, my whole code feels bloated for something simple. This will be fixed once everything is working accordingly.

Code

SpawnManager.cs (some links provided too from learning to make this)

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

/*
** Weighted randomness: https://forum.unity.com/threads/random-numbers-with-a-weighted-chance.442190/
** Scriptable Object Weight spawn example: https://www.youtube.com/watch?v=FCksj9ofUgI&ab_channel=LlamAcademy
** From scratch loot tables with Scriptable Objects to make a loot table: https://www.youtube.com/watch?v=tX3RWsVLnzM&ab_channel=GregDevStuff
** Creating a random with an animation curve: https://www.youtube.com/watch?v=zw1OERK5xvU&ab_channel=HamzaHerbou
** Random Vehicle position spawn (maybe this can help me): https://stackoverflow.com/questions/51312481/move-spawn-object-to-random-position
*/

public class SpawnManager : MonoBehaviour
{
    public GameObject Player;
    public Spawn[] Field;
    public GameObject[] SpawnObjectTrees;
    public GameObject[] SpawnObjectVehicles; //different vehicles
    public GameObject[] SpawnObjectPlanks; //3 sizes (small, medium, large)
    private PlayerControl2 playerControlScript;
    private int distancePlayer;
    private int toggle;
    private bool keepSpawning;
    bool vehicleFlag = false;
    bool plankFlag = false;
    public float randomNumSpawn;

    void Awake()
    {
        keepSpawning = true;
        playerControlScript = GameObject.Find("PlayerObject").GetComponent<PlayerControl2>();
        InvokeRepeating("Spawner", 3f, randomNumSpawn);
    }

    void Update()
    {
        if (Input.GetButtonDown("up") && !playerControlScript.gameOver)
            SpawnField();
    }

    void Spawner()
    {
        bool activeLeft = false;
        bool activeRight = false;

        if (vehicleFlag)
        {
            print(initialObjectSpawn);
            for (int i = 0; i < SpawnObjectVehicles.Length; i++)
            {
                print($"{SpawnObjectVehicles[i]}: {SpawnObjectVehicles[i].transform.position}"); //Here I get the weird position.z values pretty wonky
                toggle = Random.Range(0, 2);
                if (toggle == 1 && !activeLeft)
                {
                    activeLeft = true;
                    SpawnObjectVehicles[i].SetActive(true);
                }
                if (toggle == 0 && !activeRight)
                {
                    activeRight = true;
                    SpawnObjectVehicles[i].SetActive(true);
                }
                else
                    SpawnObjectVehicles[i].SetActive(false);
            }
        }
    }

    void SpawnField()
    {
        //I want to spawn the vehicles, planks, and trees in sets accordingly to the field (grass, river, road)
        //For vehicles and planks, they can move horizontally from either -z or z boundaries
        //NOTE: keepSpawning may be useless if i have a playerControlScript.gameOver already in here
        if (keepSpawning)
        {
            distancePlayer += 3;
            Vector3 intPos = new Vector3(0, 0, 0);
            int i = Random.Range(0, 1000);
            for (int j = 0; j < Field.Length; j++)
            {
                if (i >= Field[j].minProbabilityRange && i <= Field[j].maxProbabilityRange)
                {
                    intPos = new Vector3(distancePlayer, -1f, 0);
                    GameObject Surface = Instantiate(Field[j].spawnField);
                    if (Surface.CompareTag("Grass"))
                        TreeToggle();
                    if (Surface.CompareTag("Road"))
                    {
                        vehicleFlag = true;
                        VehicleToggle();
                    }
                    // if (Surface.CompareTag("River")) this will be the same as vehicle
                    // {
                    //     plankFlag = true;
                    //     PlankToggle();
                    // }
                    //Add spawn for vehicles and planks with given spawnrate/spawn intervals
                    Surface.transform.position = intPos;
                    vehicleFlag = false;
                    plankFlag = false;
                }
            }
        }
    }
    void TreeToggle()
    {
        int counter = 0;
        for (int i = 0; i < SpawnObjectTrees.Length; i++)
        {
            int toggle = Random.Range(0, 2); //[0, 2)
            if (toggle == 1 && counter < 5) //True and when there are already 5-4 trees to toggle
            {
                counter++;
                SpawnObjectTrees[i].SetActive(true);
            }
            else //fills the rest to inactive Trees
                SpawnObjectTrees[i].SetActive(false);
        }
    }
    void VehicleToggle()
    {
        // I have Left and Right with 2 vehicles in each. My goal is to setActive one of them each side at a time with a different interval spawnrate and speed
        Spawner();
    }
    
    void PlankToggle()
    {
        Spawner();
    }
}

[System.Serializable]
public class Spawn
{
    public GameObject spawnField;
    public float minProbabilityRange = 0.0f;
    public float maxProbabilityRange = 0.0f;
}

Hierarchy/Inspector

enter image description here

If there is any information you want to know, feel free to ask and I will make a quick edit to fulfill these goals. Again, thank you for your time and appreciate it :D I hope you are having a good day!

\$\endgroup\$
4
  • \$\begingroup\$ Instead of including a disclaimer "there may be unused variables" why not just remove the unused variables? This is part of creating a Minimal Complete Verifiable Example. \$\endgroup\$
    – DMGregory
    Nov 24, 2022 at 18:47
  • \$\begingroup\$ It looks like you're spawning copies of the current living fields in your game scene, not prefabs from your assets folder. So as the car drives across the road in your scene, you spawn a copy of it already partway along the road. Have you tried using prefabs instead? \$\endgroup\$
    – DMGregory
    Nov 24, 2022 at 18:51
  • \$\begingroup\$ True about the disclaimer, maybe it was just redundant and wanted to be friendly with it. I think for the prefab part, they are already prefabs (highlighted as blue-sky color), no? \$\endgroup\$ Nov 24, 2022 at 22:23
  • \$\begingroup\$ For starters, Grass, Road, and River are not highlighted in blue in the screenshot you sent. We can't see if those are what's referenced in your Field array because you kept that collapsed in your screenshot. But more importantly, all those blue things you're showing in this screenshot are instances of prefabs living in your scene (Hierarchy tab), not the originals living in your Assets folder (Project tab). \$\endgroup\$
    – DMGregory
    Nov 24, 2022 at 22:31

1 Answer 1

0
\$\begingroup\$

It looks like your Field array is storing references to the "Grass", "Road", and "River" child objects of your SpawnManager in this scene.

That means these objects and their children like vehicles are part of the active game scene, ticking and updating every frame. So the original car(s) start at their respective waypoints and begin driving along the road. Then sometime later you spawn a new road, which copies the whole Road object and its children in their current state - partway along the road.

This code needs some major refactoring to fix this, because it's relying on these objects being in the scene to vary their setups.

Right now the only things you do to the copy are instantiate it and position it:

GameObject Surface = Instantiate(Field[j].spawnField);
// ...
Surface.transform.position = intPos;
// These two could be one line, by the way: 
// var Surface = Instantiate(Field[j].spawnField, intPos, Quaternion.identity);

Everything else - VehicleToggle / TreeToggle / PlankToggle - never gets a reference to this Surface variable, so they can't alter the newly-spawned slice of the world in any way.

But how are you seeing differences?

Because they keep modifying the originals - watch them carefully in the Scene view while your game is running and you'll see the original world slices have their trees, vehicles, and planks blinking in and out of existence. Whatever state was left after the last world slice spawned is the one that's cloned to make the next world slice.

To fix this, I'd recommend moving most of this logic from the manager spawning the world slices, and into the world slice prefabs themselves:

public class SpawnPoint : MonoBehaviour {
    [SerializeField] Transform _prefab;

    public void Spawn() {
        Instantiate(_prefab, transform.position, _prefab.rotation);
    }
}

public class MapChunk : MonoBehaviour {
    [System.Serializable]
    public struct SpawnCollection {
        [Range(0, 1)]
        [SerializeField] float _chancePerSpawner;
        [SerializeField] int _maxSpawns;
        [SerializeField] SpawnPoint[] _spawnPoints;

        public void Spawn() {
            int spawned = 0;
            foreach (var spawner in _spawnPoints) {
                if (spawned == _maxSpawns) break;
                if (Random.value < _chancePerSpawner) {
                    spawner.Spawn();
                    spawned++;
                }
            }
        }
    }

    [SerializeField] int _width = 1;

    [SerializeField] SpawnCollection[] _collections;

    public Vector3 SpawnAt(Vector3 position) {
        var instance = Instantiate(this, position, transform.rotation);

        foreach (var collection in instance._collections) {
            collection.Spawn();
        }

        position.x += _width;
        return position;
    }
}

Then your SpawnManager does not need to know about grass/road/river - it can just have an array of MapChunk prefabs from the Assets folder, not the scene.

It can randomly select one, call its Spawn method with the current spawn position, and get back the position for the next spawn, taking into account the width of the selected chunk. This lets you mix and match wider and narrower prefabs for more complex setups.

All the logic of how to set up the contents of a given slice is delegated to that slice - simplifying your manager, and ensuring you don't accidentally repeat the mistake of modifying the global state / original prefab instead of the spawned instance.

Each map slice exposes a collection of related spawners you can use to group the left vs right lanes of traffic, and enforce limits on the number of cars per direction or number of trees. Just note that - matching the code you provided - spawn points earlier in the array have a higher probability of being selected, while spawn points later in the list are only used if the earlier ones fail their random roll.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .