How to make Unity Tilemap.SetTile() to work in multiplayer? - c#

I'm trying to make 2D-multiplayer game with randomly generated map.
Here's some simplified code what I have tried
public class Map : NetworkBehaviour
{
private TilePainter _tilePainter;
[Command]
public void CmdStartGenerating()
{
_tilePainter.PaintSection(section)//section is randomly generated map information
}
}
public class TilePainter : NetworkBehaviour
{
public Tile tile;
private Tilemap _tilemap;
public void PaintSection(Section section)
{
section.Foreach(x=>{
_tilemap.SetTile(x.Vector3(), tile);
})
}
}
Of course I made them have NetworkIdentity and CmdStartGenerating is called by button click.
The problem is that generated tilemap is visible only in host player.
The other client cannot see generated tile map.
In real code, TilePainter also instantiate some prefabs and they are visible to both player.
I have tried making them network spawnable prefabs but didn't work. (with same problem)
Is there any method like NetworkServer.Spawn for tilemap??
I'm new to Unity multiplayer feature so maybe I'm making some stupid mistake...
Thanks for reading my question!
####Edit
Uploading additional code I've tested in test scene.
public class NetTest : NetworkBehaviour
{
public TileRiverMap tileRiverMap;
private SectionGenerator _sectionGenerator =
new SectionGenerator(10, 20, 1f);
public void Test()
{
Debug.Log("hihi");
tileRiverMap.PaintSection(_sectionGenerator.GenerateSection((0, 0)), 5);
}
}
Class below is Player object
public class TestPlayer : NetworkBehaviour
{
private void FixedUpdate()
{
if (isLocalPlayer)
{
if (Input.GetButtonDown("Jump"))
{
CmdTest();
}
}
}
[Command]
private void CmdTest()
{
FindObjectOfType<NetTest>().Test();
}
}
public class TileRiverMap : NetworkBehaviour, IRiverMap
{
public Tile tile;
public GameObject stone;
private Tilemap _tilemap;
......
}
I don't think whole code of TileRiverMap is needed

Related

{Unity} effect other objects after collision

I am creating a game with unity and I have a question.
I have 5 game objects + player. And they always rotate when the game has started. I want that if the player collides with the object that is tagged snowflake, the other 4 objects pause the rotation.
Have a static event on your player script, when player collide invoke that event. On other scripts subscribe to that event.
For example in your PlayerScript should be something like
public class PlayerScript : MonoBehaviour {
public static UnityAction OnPlayerCollidedWithSnowFlakes;
private void OnCollisionEnter(Collision other) {
if (other.gameObject.tag.Equals("snowflake")) {
OnPlayerCollidedWithSnowFlakes?.Invoke();
}
}
}
And your RotatingObjectScript should be something like
public class RotatingObjectScript : MonoBehaviour {
private void Awake() {
PlayerScript.OnPlayerCollidedWithSnowFlakes += CollidedWithSnowflakeEventHandler;
}
private void OnDestroy() {
PlayerScript.OnPlayerCollidedWithSnowFlakes -= CollidedWithSnowflakeEventHandler;
}
private void CollidedWithSnowflakeEventHandler() {
.
.
// Stop rotating
.
.
}
}

How do I pass a variable between scripts in Unity 2d C#

For example, I have a variable "Wisps" that I want to change when the player picks up an object. But I don't know how to do it. I tried to add a WispDisplay object to call the classes, like in Java, but it doesn't seem to work.
public class WispCode : MonoBehaviour
{
WispDisplay wd = new WispDisplay();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
wd.setWisp(wd.getWisp()+1);
Destroy(gameObject);
}
}
}
public class WispDisplay : MonoBehaviour
{
public int Wisp = 5;
public Text WispText;
void Start()
{
}
void Update()
{
WispText.text = "Wisp: " + Wisp.ToString();
}
public int getWisp()
{
return Wisp;
}
public void setWisp(int newWisp)
{
Wisp = newWisp;
}
}
Easiest (a tiny bit dirty) way is to use a static variable. Downside: you can only have exactly ONE.
Example:
public class MyClass: MonoBehaviour {
public static int wisps;
}
Then, in ANY class, just use this to access it:
MyClass.wisps = 1234;
The more elegant way, working with multiple class instances, is using references.
Example:
public class PlayerClass: MonoBehaviour {
public int wisps = 0;
}
public class MyClass: MonoBehaviour {
public PlayerClass player;
void Update(){
player.wisps += 1;
}
}
Then, you need to drag-drop (aka "assign") the "PlayerClass" Component (attached to the player) to the the Gameobject that should increase the Wisps count. You can duplicate these objects after assigning the reference.
Now, if you actually want to have some sort of collectible, I'd suggest this approach:
You Have a Player "PlayerClass" and some Objects that are collectible, which have Trigger Colliders.
The objects have this code:
public class Example : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// probably a good idea to check for player tag:
// other.compareTag("Player");
// but you need to create the "Player" Tag and assign it to Player Collider Object.
if(TryGetComponent(out PlayerClass player))
{
player.wisps += 1;
}
}
}

How could I create a static array or list in a class that holds that class' objects c#

I couldn't find anything online so I'm sorry if this question is too basic.
I want to create a class that has a static list which contains all the objects of that class.
Something along these lines:
public class PlanetSpawner : MonoBehaviour
{
public static List<PlanetSpawner> planetList = new List<PlanetSpawner>();
public PlanetSpawner(Vector3 aStartingCoord, GameObject planet)
{
GameObject.Instantiate(planet,aStartingCoord,new Quaternion(0,0,0,0));
planet.transform.position = aStartingCoord;
planetList.Add(object that called the constructor);
}
}
This code is for a unity script and the object is created in another method (and class):
public GameObject planetPrefab;
// Start is called before the first frame update
void Start()
{
PlanetSpawner testPlanet = new PlanetSpawner(new Vector3(1, 1, 1), planetPrefab);
}
I know I could do this instead:
public GameObject planet;
// Start is called before the first frame update
void Start()
{
PlanetSpawner testPlanet = new PlanetSpawner(new Vector3(1, 1, 1), planet);
PlanetSpawner.planetList.Add(testPlanet);
}
and modify the constructor:
public class PlanetSpawner : MonoBehaviour
{
public static List<PlanetSpawner> planetList = new List<PlanetSpawner>();
public PlanetSpawner(Vector3 aStartingCoord, GameObject planet)
{
GameObject.Instantiate(planet,aStartingCoord,new Quaternion(0,0,0,0));
planet.transform.position = aStartingCoord;
}
}
But I believe the way I want to do it is cleaner and a little bit easier to use
You can just use this to refer to the current object:
public class PlanetSpawner : MonoBehaviour
{
public static List<PlanetSpawner> planetList = new List<PlanetSpawner>();
public PlanetSpawner(Vector3 aStartingCoord, GameObject planet)
{
GameObject.Instantiate(planet,aStartingCoord,new Quaternion(0,0,0,0));
planet.transform.position = aStartingCoord;
planetList.Add(this);
}
}
welcome to the community!
I see this is Unity3D related question, consider joining game dev exchange for more game development related questions and answers.
Problems
Firstly, don't use constructors with MonoBehaviours.
Secondly, class itself should not track its instances count. Make a second class, e.g. PlanetsContainer, which would track all those instances.
Thirdly, to separate logic, have planet data, creation and instances tracking on seperate classes, e.g. PlanetInstance, PlanetsSpawner, PlanetsContainer. PlanetsSpawner should be responsible only for instantiating planets (and maybe adding spawned planets to PlanetsContainer).
Solution
I'm providing complete source just to clarify the idea.
PlanetInstance.cs
public class PlanetInstance : MonoBehaviour
{
// Your planet data here
}
PlanetsContainer.cs
public class PlanetsContainer
{
public readonly List<PlanetInstance> planets;
public PlanetsContainer() : this(0)
{ }
public PlanetsContainer(int initialSize)
{
planets = new List<PlanetInstance>(initialSize);
}
}
PlanetsSpawner.cs
public class PlanetsSpawner : MonoBehaviour
{
[SerializeField] private PlanetInstance _defaultPlanetPrefab;
private PlanetsContainer _planetsContainer;
private void Awake()
{
_planetsContainer = new PlanetsContainer();
}
public void SpawnPlanet(Vector3 startingCoord, PlanetInstance prefab = null)
{
var prefabToSpawn = prefab ?? _defaultPlanetPrefab;
var spawnedPlanet = Instantiate(prefabToSpawn, startingCoord, Quaternion.identity);
_planetsContainer.planets.Add(spawnedPlanet);
}
}

GameObject won't change after updating it with a Method (object reference not set)

Unity newb here :)
I have a button which calls this function which basicly sets a prefab to instantiate.
public void setTurret()
{
towerNode.setTurretType(turret)
Debug.Log("selected shop turret:" + turret.name);
}
My Tower_Node class handles the actual instantiate
public class Tower_Node : MonoBehaviour
{
private GameObject turret;
public void setTurretType(TowerType _turret)
{
turret= _turret.prefab;
}
private void OnMouseDown()
{
Instantiate(turret,GetBuildPosition(),Quaternion.identity);
}
...}
EDIT: What I also tried
public class Tower_Node : MonoBehaviour
{
private TowerType turret;
public void setTurretType(TowerType _turret)
{
turret = _turret;
}
private void OnMouseDown()
{
Instantiate(turret.prefab,GetBuildPosition(),Quaternion.identity);
}
...}
EDIT:
This is how the references in the inspector look. The shop script is the script with the setTurret() method
--
Setting the turret with the setTurretType method works. If i check it with Debug.Log() i get the right TowerType but outside of the function the gameobject is still =null and when i try to instantiate it gives me an NullReferenceException (because Gameobject is null obviously)
What am i missing here?
Thank you for your answers.
switch OnMouseDown() for
if (Input.GetMouseButtonDown(0))
in your update loop
just make sure that turret.prefab is a prefab
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
I would say that if your TowerType class has a GameObject var named prefab, you should either assign the prefab to the prefab, or assign the turret to the _turret and the access the prefab.
Either this:
public void setTurretType(TowerType _turret) {
turret = _turret;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}
or
public void setTurretType(TowerType _turret) {
turret.prefab = _turret.prefab;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}
should work.
Edit:
You could set the turret while instiantiating:
public void setTurretType(TowerType _turret) {
turret.prefab = _turret.prefab;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
turret = Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}

Client doesnt get spawned gameObjects from server, and the other way around

So, I'm struggling with the whole Unet system. right now, all I want to do is to spawn object from one end to the other. but even that doesn't work. It spawns the object on each the server and the client. but doesn't sync. I've searched so many places and watched so many videos - none helped. here are the code and the inspector details
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class NetworkController : NetworkBehaviour
{
public GameObject[] Spawns;
GameObject Canvas;
GameObject spawn;
[SyncVar]
public NetworkInstanceId ParentId;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
[Command]
public void CmdspawnPrefab()
{
Transform trns = GameObject.Find("Canvas").transform;
trns.position = trns.position + new Vector3(100, 200, 0);
GameObject go = Instantiate(Spawns[0], trns);
NetworkServer.Spawn(go);
}
}
What am I missing?
As it seems, my whole workflow was not correct. the solution is firstly to attach a listener to the button, instead of using the onclick function on the inspector -
Button b = BoardController.Buttons[0].GetComponent<Button>();
b.onClick.AddListener(delegate () { Cmd_ButtonPressed(1); });
the second is to create gameobjects, containing a script "GameManager" who controls your board, and one "RpcManager" which contains the Rpc functions. then in the playerobject script, use the commands.
Here are the classes i have used, to update an image instead of spawning an object, the idea is basically the same - to undersand the fundamentals of Unet and passing commands.
public class PlayerObject : NetworkBehaviour {
public BoardManager BoardController;
public ClientRPCmanager ClientRPCManager;
// Use this for initialization
void Start () {
ClientRPCManager = GameObject.FindGameObjectWithTag("ClientRPCmanager").GetComponent<ClientRPCmanager>();
BoardController = ClientRPCManager.BoardController;
Button b = BoardController.Buttons[0].GetComponent<Button>();
b.onClick.AddListener(delegate () { Cmd_ButtonPressed(1); });
}
// Update is called once per frame
void Update () {
}
public void SetButton(int i)
{
BoardController.SetButton(i);
}
[Command]
public void Cmd_ButtonPressed(int i)
{
ClientRPCManager.Rpc_ButtonPressed(i);
}
boardmanager
public class BoardManager : NetworkBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public GameObject[] Buttons;
public Sprite[] Sprites;
public void SetButton(int index)
{
GameObject img = GameObject.Find("X");
img.GetComponent<Image>().sprite = Sprites[0];
}
RpcManager
public class ClientRPCmanager : NetworkBehaviour {
public BoardManager BoardController;
public NetworkManager Manager;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
[ClientRpc]
public void Rpc_ButtonPressed(int index)
{
BoardController.SetButton(index);
}
hopefully, this will somewhat help people with understanding Unet.
I learned this by carefully looking at this project https://www.youtube.com/watch?v=8Kd2RAfgzW0

Categories