I'm currently learning how this whole networking thing works in unity. In my code I'm creating a spaceship made from multiple prefabs.
It all starts with a single Hardpoint. A Hardpoint can hold a single object, which will be instantiated later on in the loop.
In the PlayerController (the starting point) i have this code to spawn the first object, the cockpit:
[Command]
void CmdOnConnect() {
string json = GameObject.Find("TestPlayer").GetComponent<ComponentObject>().ToJSON();
CompressedComponent compressedComponent = JsonUtility.FromJson<CompressedComponent>(json);
gameObject.GetComponent<Hardpoint>().Hold(GameObject.Find("Component Repository").GetComponent<ComponentRepository>().cockpit[compressedComponent.componentNumber]);
gameObject.GetComponent<Hardpoint>().SpawnComponent();
gameObject.GetComponent<Hardpoint>().RollThroughDecompression(compressedComponent);
Camera.main.GetComponent<PlayerCamera>().player = gameObject;
}
Next up is the SpawnComponent() code, located in the Hardpoint script:
public void SpawnComponent() {
Clear();
CmdSpawn();
}
CmdSpawn, also located in Hardpoint:
[Command]
public void CmdSpawn()
{
Debug.Log("[COMMAND] Spawning " + holds.name);
heldInstance = Instantiate(holds, transform.position, transform.rotation) as GameObject;
heldInstance.transform.SetParent(transform);
NetworkServer.SpawnWithClientAuthority(heldInstance, transform.root.gameObject);
}
And finally RollThroughDecompression, which just calls the Decompress() function:
public void RollThroughDecompression(CompressedComponent c) {
heldInstance.GetComponent<ComponentObject>().Decompress(c);
}
And just to leave no information out, Decompress():
public void Decompress(CompressedComponent c) {
componentType = (Type)Enum.Parse(typeof(Type), c.componentType);
componentNumber = c.componentNumber;
UpdateHardPoints();
GameObject[] typeRepository = GetRepository(componentType);
//update children
int point = 0;
foreach (Transform child in transform)
{
Hardpoint hardpoint = child.GetComponent<Hardpoint>();
if (hardpoint != null) {
if (c.hardpoints[point] != null) {
//get the hardpoint's repository
GameObject[] hardpointRepo = GetRepository((Type)Enum.Parse(typeof(Type), c.hardpoints[point].componentType));
//set the hardpoint to hold this object
hardpoint.Hold(hardpointRepo[c.hardpoints[point].componentNumber]);
hardpoint.SpawnComponent();
hardpoint.RollThroughDecompression(c.hardpoints[point]);
point++;
}
}
}
}
Sorry the code's a little messy/confusing but I've been driven up the walls trying to figure out why newly spawned objects don't have client authority with the exception of the first object spawned (likely because it's called from the PlayerController). I've been stuck on this problem for days now. Newly spawned objects are being set as children of the local player object and are even spawned with NetworkServer.SpawnWithClientAuthority yet when testing:
Trying to send command for object without authority. when calling CmdSpawn().
NetworkManager:
The result i'm getting:
As you can see, the cockpit (very first part) gets spawned as expected. but parts mounted on those Hardpoints don't. To clarify, the EmptyHardpoint is just that. A hardpoint with no children, just an empty game object with the hardpoint script and playercontroller attached to it. The cockpit prefab also includes the img and hardpoints
I guess you have forget to add your child Spawn in the Spawn list of NetworkManager.
Related
I use Playfab and Photon for my Unity project. I added some weapons, shields and body types to my player prefab and disabled all of them (except the head). I store which weapon, shield and body type the user have, and pull them from playfab when the player logs in. But the problem is, I can see my own weapon, shield and body but cannot see other player's. I can only see their heads! I assume that it is because when other user joins the scene, their prefab instantiates with PhotonNetwork but SetActive method is working for individuals, not for the server. Here is my code:
public override void OnJoinedRoom()
{
s = "PlayerArmature(Clone)/";
if(PFLogin.prefabName=="Female"){
gameObject = PhotonNetwork.Instantiate(playerPrefabFemale.name, new Vector3(73, 22, 34), Quaternion.identity, 0,null);
s = "PlayerArmatureF(Clone)/";
s += "FemaleCharacterPolyart/";
view = playerPrefabFemale.GetComponent<PhotonView>();
}else{
gameObject = PhotonNetwork.Instantiate(playerPrefab.name, new Vector3(73, 22, 34), Quaternion.identity, 0,null);
s += "MaleCharacterPolyart/";
view = playerPrefab.GetComponent<PhotonView>();
}
GameObject body = GameObject.Find(s+PFLogin.body);
GameObject cloak = GameObject.Find(s+PFLogin.cloak);
GameObject shield = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_l/upperarm_l/lowerarm_l/hand_l/weapon_l/"+PFLogin.shield);
GameObject weapon = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_r/upperarm_r/lowerarm_r/hand_r/weapon_r/"+PFLogin.weapon);
body.SetActive(true);
cloak.SetActive(true);
shield.SetActive(true);
weapon.SetActive(true);
if (view.IsMine)
{
view.RPC("ShowMesh", RpcTarget.AllBuffered, s);
}
}
void ShowMesh(string s)
{
GameObject body = GameObject.Find(s+PFLogin.body);
GameObject cloak = GameObject.Find(s+PFLogin.cloak);
GameObject shield = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_l/upperarm_l/lowerarm_l/hand_l/weapon_l/"+PFLogin.shield);
GameObject weapon = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_r/upperarm_r/lowerarm_r/hand_r/weapon_r/"+PFLogin.weapon);
body.SetActive(true);
cloak.SetActive(true);
shield.SetActive(true);
weapon.SetActive(true);
}
PFLogin is a static class which stores static variables(body&weapon etc) that are assigned with playfab responses.
So my question is how can I call SetActive(true) such that everyone see others bodys(or other things).
At first I've tried without RPC and all I see was heads without bodies in the screen, now I've tried with RPC but nothing changed. It could be my lack because I am very new to all of this.
I would try to use the isMine to make every prefab active:
//at the beginning public PhotonView PV;
public void Awake()
{
PV = Gameobject.GetComponent<PhotonView>();
}
if (!PV.isMine)
{ Prefab.gameobject.SetActive(true) }
I hope it helps, I have never done it like you did, so I am
When I retrieve an object from a list of created objects and reactivate it, it destroys itself, but only if I have force applied to start it moving. If I never apply force, everything works as intended and I can keep activating the object over and over.
I've tried putting a debug.log in the OnCollision and it isn't colliding with anything.
As said above, if I never initiate speed, the rest works fine.
When speed is applied, the first projectile works, the second destroys itself.
I've tried manually activating them instead of by code. Same result.
Exception: If I manually activate/deactivate objects myself through the inspector, they work if I only have 1 shot in the pool.
The one shot rule doesn't work if I do it through code. It breaks even if I only have one shot.
All the code worked when I wasn't using pooling.
Spawn code:
void CreateBullet(int GunID)
{
GameObject ShotFired = Guns[GunID].GetComponent<Weapon>().FireWeapon();
if (ShotFired == null)
{
ShotFired = Instantiate(Guns[GunID].GetComponent<Weapon>().Projectile,Guns[GunID].gameObject.transform);
Physics.IgnoreCollision(ShotFired.GetComponent<SphereCollider>(), Guns[GunID].GetComponentInParent<CapsuleCollider>());
Guns[GunID].GetComponent<Weapon>().AmmoPool.Add(ShotFired);
}
ShotFired.transform.position = Guns[GunID].transform.position;
ShotFired.transform.rotation = Guns[GunID].transform.rotation;
ShotFired.SetActive(true);
}
Projectile Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class Projectile : NetworkBehaviour
{
public float Speed;
public float LifeTime;
public float DamagePower;
public GameObject ExplosionFX;
// Start is called before the first frame update
void Start()
{
GetComponent<Rigidbody>().AddRelativeForce(Vector3.up * Speed, ForceMode.VelocityChange);
StartCoroutine(DeactivateSelf(5.0f));
}
private void OnDestroy()
{
Debug.Log("WHY!");
}
IEnumerator DeactivateSelf(float Sec)
{
yield return new WaitForSeconds(Sec);
Explode();
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.GetComponentInChildren<Vehicle>())
{
collision.gameObject.GetComponentInChildren<Vehicle>().CmdTakeDamage(DamagePower);
}
Explode();
}
void Explode()
{
GameObject ExplosionEvent = Instantiate(ExplosionFX, this.transform.position, Quaternion.identity);
NetworkServer.Spawn(ExplosionEvent);
GetComponent<Rigidbody>().AddRelativeForce(Vector3.zero);
gameObject.SetActive(false);
}
}
Any thoughts welcome. Thanks in advance!
In case anyone else stumbles across this, here's what happened and why assigning a movement speed to the object was what caused the issue.
My projectiles had a 'trailrenderer' on them.
By default 'Autodestruct' is true.
Autodestruct does not destruct the trail, but the game object when the trail disappears. So if you disable anything that had a trail, and activated it through movement, when the object stops moving (say to reposition in an object pool) it will destroy the parent object.
If the object never moves, it never generates a trail, and never needs to be destroyed.
To fix, just uncheck autodestruct.
https://docs.unity3d.com/ScriptReference/TrailRenderer-autodestruct.html
I'm using the following script in a separate scene to keep my object "player"(it's a car) and load another scene.
DontDestroyOnLoad(transform.gameObject);
SceneManager.LoadSceneAsync(1);
but in my game, the state of car will be changed like its speed, indicator, the level of getting hit.
i wanna reset the status of this car to the original status when I click the button Restart Game.
Is there any way I can do to reset the car apart from destroying the car and switching back to the original scene to execute DontDestroyOnLoad again?
If I understood you right, you have public variables set to user defined values and you would like these to be the initial ones which was assigned by the user when you reload your initial scene. There are two solutions for this:
(a) Destroy the DontDestroyOnLoad gameObject so that when you reload the initial scene, a new instance of this will be created, hence your user defined values will be retained. So when you want to reload to initial scene from the end scene, create a script and add these:
Destroy (GameObject.Find("NameOfTheGameObject"));
SceneManager.LoadSceneAsync(1);
(b) Retain the car and its script, instead create another script and copy the initial values to the second script. For example, attach the below code to a new script and attach that script to your car gameObject:
Script 1 - DontDestroyOnLoad
public int Acceleration_Of_Car = 20;
public int Car_Force = 100;
public static ClassNameHere instance;
void Awake()
{
if(instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}else if(instance != this)
{
Destroy(gameObject);
}
}
Now create second script in the End Scene or even in the Initial Scene. Create variables that are the same as first script so that you do not get confused. Copy values from first script to second script, later apply values from second script to first script.
Script 2 - Copy and Apply
public int Acceleration_Of_Car;
public int Car_Force;
private Script1 script1; //Reference your first script here
public void Start()
{
script1 = (Script1) GameObject.FindObjectOfType(typeof(Script1)); //Call the first script
}
public void CopyValues()
{
Acceleration_Of_Car = script1.Acceleration_Of_Car;
Car_Force = script1.Car_Force;
}
public void ApplyValues()
{
script1.Acceleration_Of_Car = Acceleration_Of_Car;
script1.Car_Force = Car_Force;
}
I would say its better to use DontDestroyOnLoad() even for second script so that no other instance will be running :)
Destroy(GameObject.Find("GameObjectName"));
SceneManager.LoadSceneAsync(SceneIndex);
I recently tried asking this question but I realized it was not a sufficient question. In my game the player is a fire fighter learner and i want to broke out fire randomly in my game (like not predictable by player), but i did not know how to implement this.
So far i have done this but nothing goes good.(I have a empty object called t in unity which have 3 to 5 particles systems, and all are set to dont awake at start)
code is here :
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
public ParticleSystem[] particles;
public int numOn = 3;
public int j;
void Start() {
for (int i = 0; i < particles.Length - 1; i++) {
j = Random.Range(i + 1, particles.Length - 1);
ParticleSystem t = particles[j];
particles[j] = particles[i];
particles[i] = t;
}
for (j = 0; j < numOn; j++ )
{
particles[j].Play();
}
}
}
help will be appreciated :-)
You could try using prefabs. Create a game object in the editor that has any particle systems and scripts your fire objects need. Once it's good, drag the object from the hierarchy into your project. This will create a prefab (you can now remove it from the scene). Now, on your spawning script, add a field of type GameObject and drag the prefab you made before into it. Now, when you need to create one, just call Instantiate(prefabVar) to create a copy of your prefab.
Edit:
For your specific case, since you only want one fire to be instantiated in a random location, you could have your spawning script look something like this:
public Transform[] SpawnPoints;
public GameObject FirePrefab;
void Start() {
Transform selectedSpawnPoint = SpawnPoints[(int)Random.Range(0, SpawnPoints.Count - 1)];
Instantiate(FirePrefab, selectedSpawnPoint.position, selectedSpawnPoint.rotation);
}
This solution would allow for you to potentially spawn more than one fire object if you needed. An alternative would be if you will only ever have exactly one fire object in the scene at all. Instead of instantiating from a prefab, the object is already in the scene and you just move it to one of your spawn points at the start of the scene. An example script on the fire object itself:
public Transform[] SpawnPoints;
void Start() {
Transform selectedSpawnPoint = SpawnPoints[(int)Random.Range(0, SpawnPoints.Count - 1)];
transform.position = selectedSpawnPoint.position;
transform.rotation = selectedSpawnPoint.rotation;
}
I have a player object who can equip multiple weapons. When a weapon is equipped, its transform's parent is set to its hand. I have messed around with this for some time and cannot get this to work for both the host and the client. Right now I am trying to equip the weapon on the server, and tell all the clients to set their parents transforms.
public NetworkInstanceId weaponNetId;
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.transform.parent = hand;
NetworkServer.Spawn (weaponObject);
//set equipped weapon
var weapon = weaponObject.GetComponent<Weapon> () as Weapon;
weaponNetId = weaponObject.GetComponent<NetworkIdentity> ().netId;
Rpc_SetParentGameobject (weaponNetId);
}
[ClientRpc]
public void Rpc_SetParentGameobject(NetworkInstanceId netID)
{
weaponNetId = netId;
}
And in the update I am updating the weapons transform
void Update () {
// set child weapon tranform on clients
if (!isServer) {
if (weaponNetId.Value != 0 && !armed) {
GameObject child = NetworkServer.FindLocalObject (weaponNetId);
if (child != null) {
child.transform.parent = hand;
}
}
}
I know this isn't the most optimized way to do this..but right now I am just trying to get this to work any way possible and then work on tweaking it. Seems like it should be a simple task.
We do a similar thing in our multiplayer game. There are a few things you need to do to get this working. Firstly, the concept:
Setting the weapon's parent on the server is trivial, as you have found. Simply set the transform's parent as you would normally in Unity. However, after spawning this object on the server with NetworkServer.Spawn, it will later be spawned on clients in the root of the scene (hierarchy outside of the spawned prefab is not synchronised).
So in order to adjust the hierarchy on the client I would suggest that you:
Use a SyncVar to synchronise the netID of the parent object between the server and client.
When the object is spawned on the client, find the parent using the synchronised netID and set it as your transform's parent.
Therefore, I would adjust your code to look something like this. Firstly, set the weapon's parent netId before you spawn it. This will ensure that when it is spawned on clients, the netId will be set.
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.parentNetId = hand.netId; // Set the parent network ID
weaponObject.transform.parent = hand; // Set the parent transform on the server
NetworkServer.Spawn (weaponObject); // Spawn the object
}
And then in your weapon class:
Add a parentNetId property.
Mark it as [SyncVar] so that it synchronises between server and client copies.
When spawned on a client, find the parent object using the netId and set it to our transform's parent.
Perhaps something like:
[SyncVar]
public NetworkInstanceId parentNetId;
public override void OnStartClient()
{
// When we are spawned on the client,
// find the parent object using its ID,
// and set it to be our transform's parent.
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
transform.SetParent(parentObject.transform);
}
I found this post really useful but I have a small addendum.
The netId will be on the root of the hierarchy so its useful to know that you can traverse down the hierarchy using transform.Find.
Something like this ..
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
string pathToWeaponHolder = "Obj/targetObj";
transform.SetParent(parentObject.transform.Find(pathToWeaponHolder));
After reading Andy Barnard's solution, I came up with this slightly modified solution. Instead of a SyncVar, there is a Client RPC for the server to call any time a NetworkIdentity needs to change parents. This does require the parent to also have a NetworkIdentity (though it need not be a registered prefab).
public void Server_SetParent (NetworkIdentity parentNetworkIdentity) {
if (parentNetworkIdentity != null) {
// Set locally on server
transform.SetParent (parentNetworkIdentity.transform);
// Set remotely on clients
RpcClient_SetParent (parentNetworkIdentity.netId, resetTransform);
}
else {
// Set locally on server
transform.SetParent (null);
// Set remotely on clients
RpcClient_SetParent (NetworkInstanceId.Invalid, resetTransform);
}
}
[ClientRpc]
void RpcClient_SetParent (NetworkInstanceId newParentNetId) {
Transform parentTransform = null;
if (newParentNetId != NetworkInstanceId.Invalid) {
// Find the parent by netid and set self as child
var parentGobj = ClientScene.FindLocalObject (newParentNetId);
if (parentGobj != null) {
parentTransform = parentGobj.transform;
}
else {
Debug.LogWarningFormat ("{0} Could not find NetworkIdentity '{1}'.", gameObject.name, newParentNetId.Value);
}
}
transform.SetParent (parentTransform);
}
These two are part of a NetworkBehavior, which obviously RequiresComponent(typeof(NetworkIdentity)). If you really need this behavior on a client to server, I'd suggest creating a command that passed the NetworkIdentity to the server, which would just call the server's public method. It's one set of network messages more than optimal, but meh.