Creating multiple spawnpoints in Unity Multiplayer - c#

I'm kinda new to unity, been spinning my head around it this couple of last days. I've encoutered a problem where when I'm spawning 2 or more players into my scene they just get launched out of the map. I've figured out that it's a problem with the spawn points. I did set up a range between some values where they will be spawned but seems that they still spawn into the same spot and get lauched across the map.
What I want to do is, create 4 spawnpoints(the maximum number of people that can play the game) in which they will get spawned based on the number of players.
I have a function called "SetPosition" with the code:
public void SetPosition()
{
transform.position = new Vector3(Random.Range(-1,11), 0.8f, Random.Range(-4,5));
}
and it is used here, if the scene is "Game"
private void Update()
{
if(SceneManager.GetActiveScene().name == "Game")
{
if(PlayerModel.activeSelf == false)
{
SetPosition();
PlayerModel.SetActive(true);
}
Any support is appreciated, been trying to find an answer but could find anything that would fit my need.

At early stage best solution will be create 4 empty game objects at the places where you want to keep spawn point.
Then take an array of Transform to store those in script and just pass the random index to get a point.
public Transform[] points;
public Vector3 GetRandomPoint()
{
return points[Random.Range(0,point.length)].position;
}

So I've done something. I've added 4 empty objects with a parent called "SpawnManager", also made a script for the spawn manager that is down below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public static SpawnManager Instance;
Spawnpoint[] spawnpoints;
void Awake()
{
Instance = this;
spawnpoints = GetComponentsInChildren<Spawnpoint>();
}
public Transform GetSpawnPoint()
{
return spawnpoints[Random.Range(0, spawnpoints.Length)].transform;
}
}
In the PlayerModel script where I set the spawn position for the player I've added this function but it doesn't work. No errors, but it doesn't respect the spawnpoints
public void SetPosition()
{
Transform spawnpoint = SpawnManager.Instance.GetSpawnPoint();
transform.position = spawnpoint.position;
}
The SetPosition function was like this before I edited it:
public void SetPosition()
{
transform.position = new Vector3(Random.Range(-1,11), 0.8f, Random.Range(-4,5));
}

Related

Random Obstacle Generator in unity 3D

I am looking to make my first actual game called Bottomless. It seems I am having a problem with the obstacle generator script in Unity2D. I want the obstacle to generate vertically, where a player is falling down the level.
Here my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Generator : MonoBehaviour
{
public GameObject spike;
public float maxspeed;
public float minspeed;
public float currentspeed;
void Awake()
{
currentspeed = Random.Range(maxspeed, minspeed);
spikegen();
}
void spikegen()
{
GameObject SpikeIns = Instantiate(spike, transform.position, transform.rotation);
}
void Update()
{
}
}
Seems like running this code crashes unity quickly. Does anyone have better alternative?
So, I have been considering your problem, and I think the best thing to do would be to create spawn points in your map pieces for the spikes to fit in. Then, when your player is falling, and the new map is loaded, grab the spawn points, add them to a list, choose one randomly, and then instantiate your spike there. Or get the map pieces themselves to spawn the spikes as they are loaded.
Method 1:
Create spawn points on your map piece. Create and add the following script. Place the spawn points in the list in inspector. And call function on Awake/Start.
using System.Collections;
using UnityEngine;
public class MapScript : MonoBehaviour
{
//Spike you want to instantiate
public GameObject spike;
//List for you to place spawn points in the inspector
public List<GameObject> Spawnpoints = new List<GameObject>();
public void Awake()
{
//Get a random number from 0 to our lists count
int random = Random.Range(0, Spawnpoints.Count);
//Instantiate at the random spawn point
GameObject SpikeIns = Instantiate(spike, Spawnpoints[random].transform.position, transform.rotation);
}
}
Method 2:
Similar to the example above, except the code to spawn happens in the Generator script (this is not the preferred method as we will have to access the map piece's script).
So use the code above but remove the Awake and spike GameObject, and then for your generator script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Generator : MonoBehaviour
{
public GameObject spike;
public float maxspeed;
public float minspeed;
public float currentspeed;
void Awake()
{
currentspeed = Random.Range(maxspeed, minspeed);
spikegen();
}
void Update()
{
}
void spikegen()
{
List<GameObject> Spawners = new List<GameObject>();
foreach(GameObject spawn in refernceToOtherScripts.Spawnpoints)
{
Spawners.Add(spawn);
}
int random = Random.Range(0, Spawners.Count);
GameObject SpikeIns = Instantiate(spike, Spawners[random].transform.position, transform.rotation);
}
}
Neither of these have been tested as I am on my work computer, but they should both work perfectly fine. As you have probably guessed, there are a lot of different way to go about this.

Finding a way for script to detect a child in a playerContainer game object

I'm currently making a 3D endless runner game with the choice to select different skins for the player. Everything's going smoothly until I come across a problem, in which I try to assign two collision game objects to each character prefab in the container that can detect the player's collision box when it collides with a powerup.
It only detects the first playerContainer's (Milo) 'Coin Detect' game object (eventhough it's been deactivated) and does not recognize the 'Coin Detect' collision game object in the Baddie playerContainer (which the player has chosen to play)
So my question is, how am I able to get the script to recognize the child that's active in the third playerContainer game object instead of it automatically detecting the first playerContainer game object's child?
From the Magnet powerup script I've made, the script detects the first playerContainer's 'Coin Detect' game object only. As shown in the attached picture below:
Here's my current script where the player is able to select their preferred characters.
using System.Collections.Generic;
using UnityEngine;
public class EnablePlayerSelector : MonoBehaviour
{
public GameObject[] players;
public int currPlayerCount;
void Start()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
foreach (GameObject player in players)
player.SetActive(false);
players[currPlayerCount].SetActive(true);
}
}
and here's the script where I deactivate the 'Coin Detect' collision game Object and activate it when the player collides with the powerup.
using System.Collections.Generic;
using UnityEngine;
public class Magnet : MonoBehaviour
{
public GameObject coinDetect;
void Start()
{
coinDetect = GameObject.FindGameObjectWithTag("CoinDetect");
coinDetect.SetActive(false);
Debug.Log("False");
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag == "Player")
{
StartCoroutine(coinActivate());
Destroy(transform.GetChild(0).gameObject);
}
}
IEnumerator coinActivate()
{
Debug.Log("Hi");
coinDetect.SetActive(true);
yield return new WaitForSeconds(5f);
coinDetect.SetActive(false);
}
}
Well your issue is rather that you are doing GameObject.FindGameObjectWithTag("CoinDetect") which will return whatever is the first encountered CoinDetect in your scene in this moment. So unless EnablePlayerSelector.Start is executed before the Magnet.Start it will always be the first one since they are still all active by then.
You could probably already solve your issue by following the general thumb-rule:
Do things where you do not depend on other components already in Awake
Do things where you o depend on other components in Start
This way you already cover most of the use cases and are sure that components are self-initialized (Awake) before anything is accessing them in Start.
So simply change the EnablePlayerSelector.Start into Awake and you should be fine.
In very specific cases you might still have to adjust the Script Execution Order or use events.
Instead of doing this at all though I would rather in the moment of the collision request the current one from the PlayerContainer directly:
// put this on each player root object
public class Player : MonoBehaviour
{
// reference these via the Inspector
[SerializeField] private GameObject coinDetector;
[SerializeField] private GameObject playerrange;
// ... and in general any information bout this Player that is relevant for other scripts
// Readonly accessor property
public GameObject CoinDetector => coinDetector;
}
and then rather have
public class EnablePlayerSelector : MonoBehaviour
{
[SerializeField] private Player[] players;
[SerializeField] private int currPlayerCount;
public Player CurrentPlayer => players[currPlayerCount];
// In general do things where you don't depend on other scripts already in Awake
// This way you could actually already solve your issue by keeping things where you do
// depend on others in Start
private void Awake()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
for(var i = 0; i < players.Length; i++)
{
players[i].gameObject.SetActive(i == currPlayerCount);
}
}
}
and then finally do e.g.
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
var playerSelector = other.GetComponentInParent<EnablePlayerSelector>();
if(!playerSelector) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = playerSelector.CurrentPlayer.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}
or alternatively if the other in this case refers to the object with the Player component anyway you could also directly do
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
if(!other.TryGetComponent<Player>(out var player)) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = player.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}

How to access the value of cinemachine's camera distance in Unity / C#?

I'm working on Unity/C# now and I'm stuck with accessing CinemachineVirtualCamera's camera distance value in the script. What I'm trying to do is change the value of camera distance in the body section.
First of all, how can I access the CinemachineVirtualCamera component in this game object? The MoveScript is what I attached to a player game object, and I want to zoom out the camera depending on the player's movement. Since the game I'm making is small, I won't make other .cs files.
I wrote
public class MoveScript: MonoBehaviour
{
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
camObj.GetComponent<CinemachineVirtualCamera>(); // <- but I get error saying, The type or namespace name 'CinemachineVirtualCamera' could not be found
}
}
I also read this document and I think the m_CameraDistance is what I'm looking for but how can I access that value?
For anyone else who wondering
GetComponent<CinemachineVirtualCamera>()
.GetCinemachineComponent<CinemachineFramingTransposer>()
.m_CameraDistance
As stated in the linked document, these classes are in Cinemachine namespace. To access the classes you have to either add
using Cinemachine;
to the beginning of your script or change your script to
public class MoveScript: MonoBehaviour
{
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
camObj.GetComponent<Cinemachine.CinemachineVirtualCamera>();
}
}
And as for accessing the m_CameraDistance variable, HuySora already answered that part
Try this and don't forgot to mention namespace
public class MoveScript: MonoBehaviour
{
private CinemachineVirtualCamera virtualCamera;
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
virtualCamera = camObj.GetComponent<CinemachineVirtualCamera>();
float f = virtualCamera.m_CameraDistance;
}
}

Can't figure out what i've done wrong

I'm new to programming and I'm currently trying to do a simple game in Unity.
The code makes some things dissapear when they touch the ground and it works well but the "Evaporated" variable does not update in the Unity Inspector. When something touch the ground, evaporated should be incremented, but it stays at 0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class EnemyEvaporate : MonoBehaviour
{
public GameObject Enemy;
public int Evaporated;
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.gameObject.name == "Map" || CollisionInfo.gameObject.name == "Player")
{
Thread.Sleep(15);
gameObject.SetActive(false);
Evaporated++;
}
}
}
I'm not really sure what's the behavior you're trying to get in this script. If Evaporated is some kind of 'Global counter' for score, it need to be written different and not store inside script for this gameObject.
It might be related to your gameObject.SetActive(false) which will deactivate your object, so in fact it will disables also the scripts connected with it (and especially this one. SetActive documentation) and removes them from Update() for a given gameObject (so given enemy. If you have 4 Enemy objects on the Scene, in fact you have 4 instances of this script and each has own Evaporated variable)
Why won't you move the void OnCollisionEnter(Collision CollisionInfo) to your Enemy scipt? I think Enemy should now if it collided with anything, not some external script.
You can also use tags instead of names when detecting Collisions.
Also, gameObject.SetActive(false) - if you set your gameObject to disabled, nothing more from its script will happen unless you set it to active again. It e.g. cancels all Coroutine. The gameObject is just "sleeping", I would say. It's not destroyed - you still can see it in your Hierarchy window - but it can't do anything. (By the way, in this case you're setting EnemyEvaporate gameObject as disabled, not Enemy gameObject.)
Also, you can use Coroutine instead of Thread.Sleep(15). It is more common to use Coroutines than Threads in Unity.
Now, Evaporation. If you want to count how many Enemies evaporated (I suppose, looking at Evaporated++;) you should have some external object to count it. The simplest way I can propose fow now is creating a GameObject with EvaporationCounter : MonoBehaviour script attached to it. You can also use Singleton pattern to be sure there's only one object of this type.
public class EvaporationCounter : MonoBehaviour
{
private int evaporates;
public int Evaporates {
get { return evaporates; }
private set
{
if (value >= 0) evaporates = value;
}
}
public void AddEvaporation()
{
Evaporates++;
}
}
Your Enemy class could look like:
public class Enemy : MonoBehaviour
{
private IEnumerator coroutine;
private EvaporationCounter evaporationCounter;
private void Start()
{
evaporationCounter = FindObjectOfType<EvaporationCounter>();
}
private IEnumerator WaitAndSetInactive(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision CollisionInfo)
{
if (!CollisionInfo.gameObject.CompareTag("Map") && !CollisionInfo.gameObject.CompareTag("Player")) return;
if (evaporationCounter != null)
evaporationCounter.AddEvaporation();
StartCoroutine(WaitAndSetInactive(15f));
}
}
Calling evaporationCounter.AddEvaporation() from the Enemy script is not the best solution, since it does not apply to the Dependency Inversion principle but I would say it's good for the beginning with Unity.

Upgrading the Character

I have a few scripts which are divided between the Player and the game objects Underneath player. The scripts are the accuracy, player speed, and Projectile Damage.
I have created a menu where the player (When interacted with an NPC) could buy certain upgrades to increase these values throughout the whole game.
Now I am wondering, is it better to transfer all of these scripts to 1 script and access that. Or is it better to leave it divided between the scripts and call them all independently?
Since this is my first time upgrading player stats any help is welcome!
Lets start off with the Player Script where the max Run speed is.
public class Player : Actor {
public int playerNumber = 1;
// Run Speed & Acceleration
public float MaxRun = 90f; // Maximun Horizontal Run Speed
public float RunAccel = 1000f; // Horizontal Acceleration Speed
public float RunReduce = 400f; // Horizontal Acceleration when you're already when your horizontal speed is higher or equal to the maximum
The second script I have is in the weapon itself where I have the accuracy of the weapon.
public class Weapon : MonoBehaviour {
protected float currentAngle = 0f;
public float randomAngle = 20;
The last Script is where I have put the damage. Since the damage is within the projectile's I put everything in the projectile script.
//[RequireComponent (typeof(Rigidbody2D))]
public class Projectile : MonoBehaviour {
[Header ("Speed")]
public float baseSpeed;
public float randomSpeed;
public Vector2 SpeedV2;
public Vector2 Direction;
[Header ("Damage")]
public int DamageOnHit;
To put everything within my Upgrademenu I made a script calling the different scripts.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UpgradeMenu : MonoBehaviour
{
[SerializeField]
private Text accuracyText;
[SerializeField]
private Text speedText;
[SerializeField]
private Text damageText;
[SerializeField]
private float accuracyMultiplier = 0.7f;
private Weapon weapon;
private Projectile projectile;
private Player player;
void OnEnable()
{
UpdateValues();
}
void UpdateValues ()
{
accuracyText.text = weapon.randomAngle.ToString();
damageText.text = projectile.DamageOnHit.ToString();
speedText.text = player.MaxRun.ToString();
}
public void UpgradeAccuracy ()
{
weapon.randomAngle = (int)weapon.randomAngle * accuracyMultiplier;
UpdateValues();
}
public void UpgradeDamage ()
{
projectile.DamageOnHit = (int)projectile.DamageOnHit + 1;
UpdateValues();
}
}
I'm pretty sure that the way I'm trying to call my other scripts within the UpgradeMenu script is wrong. So if anyone is able to help that would be great!
As you can see the machinegun is a child of the player. Because you are able to pick up this item. This already causes an issue with the gun. Since I can change the value from within Unity, but when I pick up another machine gun, this value goes back to the usual 4 value.
Aside from that, I have a button which has an onclick value. The idea is that when a player clicks on this button (Still need to change the UI) the value should be changed with the use of this UpgradeMenu script.
For now the buttons work, the only problem is that the upgrade functionalities "are not set to an instance of an object"
You should distinguish between configuration data, and logic data.
Configuration data does not change. e.g. Weapon magazine size.
Logic data does change. e.g. Bullets in the magazine.
Logic data is unique and only exists within some object instance, while configuration data exists in a database and is identifying by an id.
The following is just an example code that demonstrates the idea.
Data:
public class WeaponData
{
public string id;
public float spread;
public string projectileId;
}
public class ProjectileData
{
public string id;
public int damage;
public string prefabPath;
}
Library:
public static class Library
{
public static Dictionary<string, WeaponData> Weapons;
public static Dictionary<string, ProjectileData> Projectiles;
// add more ...
}
Configuration Setup:
WeaponData weapon = new WeaponData
{
id = "shotgun",
spread = 20f,
projectileId = "shotgun_shell"
};
ProjectileData projectile = new ProjectileData
{
id = "shotgun_shell",
damage = 100,
prefabPath = "Projectiles/ShotgunShell"
};
Library.Weapons.Add(weapon.id, weapon);
Library.Weapons.Add(projectile.id, projectile);
Logic:
public class Weapon
{
public WeaponData weapon = Library.Weapons["shotgun"];
public void Shoot()
{
GameObject.Instantiate(
Resources.Load<Projectile>(Library.Projectiles[weapon.projectileId].prefabPath)
);
}
}
For an upgrade system to work, you could setup multiple weapons:
"shotgun_v1" -> "shotgun_v2" -> "shotgun_v3"
Note: Another approach would be to use ScriptableObjects, that is if you like serializing data in the Unity editor. Cool video on the topic.
I believe your issue lies where you're trying to set randomAngle on a class type, which is not a static property:
public void UpgradeAccuracy ()
{
Weapon.randomAngle = (int)Weapon.randomAngle * accuracyMultiplier;
UpdateValues();
}
What is Weapon here? Just a class name, you can't set or get these things since it's not instantiated. What I believe you want to do is:
public void UpgradeAccuracy ()
{
angle.randomAngle = (int)angle.randomAngle * accuracyMultiplier;
UpdateValues();
}
Other than that, there are probably not much wrong with what you're doing. It's probably not the architecture I'd choose but I haven't seen your entire game and there might be a reason for your choices; a tip for the future would be that not everything has to be Components (derive from MonoBehaviour), you can work with "normal" classes as well when creating games in Unity if you don't need the component behaviour. I generally try to keep as few MonoBehaviour components scripts as possible.

Categories