Prefabs don't sync for different players - c#

I have Bullet prefab with Network Transform and Network Identity.
Player script for spawn bullet:
using UnityEngine;
public class Weapon : MonoBehaviour {
bool ready = true;
public Transform firePoint;
public bool shot;
public GameObject bulletPrefab;
void FixedUpdate () {
if ((Input.GetKey(KeyCode.Space)) || shot)
{
if (ready)
{
Shoot();
}
else
{
// click sound
}
}
}
void Shoot()
{
ready = false;
var bulletGo = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
LeanTween.delayedCall(6f, () => { ready = true; });
}
}
But when I shoot, my enemy don't see this bullet.
Why it doesn't sync? My player also has Network Transform and Network Identity, and all is ok.

To spawn a networked object Unity provides a specific function NetworkServer.Spawn (https://docs.unity3d.com/ScriptReference/Networking.NetworkServer.Spawn.html)
This function needs to be called after you instantiate an object so that all the clients spawn it in their scene.
In your case, after you instantiate your bulletGo object you should call:
NetworkServer.Spawn(bulletGo)

Related

Unity 2D: Making the Player Gameobject Reactivate after being turned off

I can't get the Player Gameobject to reappear. The player deactivates the moment the Timeline starts when they touch the box collider, but the player never reactivates. I have tried using Player.SetActive(true) in the coroutine and even using an Invoke method with no luck. Any ideas on how to fix this? Please help.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
public class TimelineTrigger : MonoBehaviour
{
// calling items for Unity
public PlayableDirector timeline;
public GameObject Player;
public GameObject CutsceneCollider;
public GameObject CutsceneMC;
// Start is called before the first frame update
void Start()
{
// calls the playable director and turns off the MC for the scene
timeline = timeline.GetComponent<PlayableDirector>();
CutsceneMC.SetActive(false);
}
void Update()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
private void EnableAfterTimeline()
{
Player = GameObject.FindGameObjectWithTag("Player");
Player.SetActive(true);
}
public void OnTriggerEnter2D (Collider2D col)
{
if (col.CompareTag("Player"))
{
// plays the cutscene and starts the timer
timeline.Play();
Player.SetActive(false);
Invoke("EnableAfterTimeline", 18);
CutsceneMC.SetActive(true);
StartCoroutine(FinishCut());
}
IEnumerator FinishCut()
{
// once the cutscene is over using the duration, turns off the collider and the MC.
yield return new WaitForSeconds(17);
CutsceneMC.SetActive(false);
CutsceneCollider.SetActive(false);
}
}
}
The issue here is that coroutines in Unity can't be run on inactive GameObjects, so FinishCut never gets executed.
This can be worked around by having a separate MonoBehaviour in the scene to which the responsibility of running a coroutine can be off-loaded. This even makes it possible to start static coroutines from static methods.
using System.Collections;
using UnityEngine;
[AddComponentMenu("")] // Hide in the Add Component menu to avoid cluttering it
public class CoroutineHandler : MonoBehaviour
{
private static MonoBehaviour monoBehaviour;
private static MonoBehaviour MonoBehaviour
{
get
{
var gameObject = new GameObject("CoroutineHandler");
gameObject.hideFlags = HideFlags.HideAndDontSave; // hide in the hierarchy
DontDestroyOnLoad(gameObject); // have the object persist from one scene to the next
monoBehaviour = gameObject.AddComponent<CoroutineHandler>();
return monoBehaviour;
}
}
public static new Coroutine StartCoroutine(IEnumerator coroutine)
{
return MonoBehaviour.StartCoroutine(coroutine);
}
}
Then you just need to tweak your code a little bit to use this CoroutineHandler to run the coroutine instead of your inactive GameObject.
public void OnTriggerEnter2D (Collider2D col)
{
if (col.CompareTag("Player"))
{
// plays the cutscene and starts the timer
timeline.Play();
Player.SetActive(false);
Invoke("EnableAfterTimeline", 18);
CutsceneMC.SetActive(true);
CoroutineHandler.StartCoroutine(FinishCut()); // <- Changed
}
IEnumerator FinishCut()
{
// once the cutscene is over using the duration, turns off the collider and the MC.
yield return new WaitForSeconds(17);
CutsceneMC.SetActive(false);
CutsceneCollider.SetActive(false);
}
}
If you have only one Player instance and it's accessible from the start, you'd better set it once in the Start method.
void Start()
{
// calls the playable director and turns off the MC for the scene
timeline = timeline.GetComponent<PlayableDirector>();
CutsceneMC.SetActive(false);
Player = GameObject.FindGameObjectWithTag("Player");
}
void Update()
{
}
private void EnableAfterTimeline()
{
Player.SetActive(true);
}

Destroying a game object that's part of a prefab once an action key is pressed inside a trigger

so I'm making a 2d platform. My level is made up of a multiple platforms that are all part of a prefab. I want to make it so when my player presses a key (in this case 'E') inside of a collider2d the platform above the player is destroyed and the box resting on the platform falls down.
I've got the detection working for when 'E' is pressed inside of the trigger but can't figure out how to destroy just the single platform in the prefab.
Any help would be appreciated!
public class SwitchController : MonoBehaviour
{
public Collider2D switchCollider;
public Rigidbody2D player;
void Start()
{
switchCollider = GetComponent<Collider2D>();
}
private void OnTriggerStay2D(Collider2D col)
{
// var player = col.GetComponent<PlayerController>();
var actionBtn = PlayerController.action;
if (player)
{
Debug.Log("Collided");
if (Input.GetKeyDown(KeyCode.E))
{
actionBtn = true;
Debug.Log("Action Pressed");
}
}
}
}
If possible, the simplest solution would be to store the platform via the inspector (of the prefab).
Then you would destroy the game-object when needed, like so:
public class SwitchController : MonoBehaviour {
// ...
public GameObject targetPlatform;
private void OnTriggerStay2D(Collider2D col) {
// ...
Destroy(targetPlatform); // Destroy the platform.
}
}
Or, you can raycast upwards from the player
public class SwitchController : MonoBehaviour {
public Collider2D switchCollider;
public Rigidbody2D player;
[SerializeField, Tooltip("The layer mask of the platforms.")]
public LayerMask platformLayerMask;
void Start() {
switchCollider = GetComponent<Collider2D>();
}
private void OnTriggerStay2D(Collider2D col) {
var actionBtn = PlayerController.action;
if (player) {
if (Input.GetKeyDown(KeyCode.E)) {
actionBtn = true;
// Raycast upwards from the player's location.
// (Raycast will ignore everything but those with the same layermask as 'platformPlayerMask')
RaycastHit2D hit = Physics2D.Raycast(player.transform.position, Vector2.up, Mathf.Infinity, platformLayerMask);
if (hit.collider != null) {
// Destroy what it hits.
Destroy(hit.transform.gameObject);
}
}
}
}
}
Compared to the first solution, this solution is more dynamic.
You just have to set the Layers of the platforms in the inspector.

Destroy particle system when initalised programmatically

I have a game with a player and an enemy.
I also have a particle system for when the player dies, like an explosion.
I have made this particle system a prefab so I can use it multiple times per level as someone might die a lot.
So in my enemy.cs script, attached to my enemy, I have:
public GameObject deathParticle;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "Player" && !player.dead){
player.dead = true;
Instantiate(deathParticle, player.transform.position, player.transform.rotation);
player.animator.SetTrigger("Death");
}
}
So this plays my particle system when the player gets killed by an enemy. Now on my player script, I have this. This specific function gets played after the death animation:
public void RespawnPlayer()
{
Rigidbody2D playerBody = GetComponent<Rigidbody2D>();
playerBody.transform.position = spawnLocation.transform.position;
dead = false;
animator.Play("Idle");
Enemy enemy = FindObjectOfType<Enemy>();
Destroy(enemy.deathParticle);
}
This respawns the player like normal but in my project each time I die I have a death (clone) object which I don't want. The last 2 lines are meant to delete this but it doesn't.
I have also tried this which didn't work:
Enemy enemy = FindObjectOfType<Enemy>();
ParticleSystem deathParticles = enemy.GetComponent<ParticleSystem>();
Destroy(deathParticles);
There is no need to Instantiate and Destroy the death particle, that will create a lot of overhead, you can simply replay it when you want it to start and stop it, when you dont need it
ParticleSystem deathParticleSystem;
private void OnTriggerEnter2D(Collider2D collision)
{
#rest of the code
deathParticleSystem.time = 0;
deathParticleSystem.Play();
}
public void RespawnPlayer()
{
//rest of the code
deathParticleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
}
or you can enable and disable the gameObject associated with your particle prefab
public GameObject deathParticlePrefab;
private void OnTriggerEnter2D(Collider2D collision)
{
#rest of the code
deathParticlePrefab.SetActive(true);
}
public void RespawnPlayer()
{
//rest of the code
deathParticlePrefab.SetActive(false);
}
You could always create a new script and assign it to the prefab that destroys it after a certain amount of time:
using UnityEngine;
using System.Collections;
public class destroyOverTime : MonoBehaviour {
public float lifeTime;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
lifeTime -= Time.deltaTime;
if(lifeTime <= 0f)
{
Destroy(gameObject);
}
}
}
So in this case you would assign this to your deathParticle. This script is useful in a multitude of scenarios if you're instantiating objects so that you don't have a load of unnecessary objects.

Why isn't my program recognizing my target object

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AIController : MonoBehaviour {
[Header("Player interaction")]
[SerializeField] private GameObject target;
[SerializeField] private int damage;
[SerializeField] private float attackDelay;
[SerializeField] private float stunTime;
[SerializeField] private int health;
public int Health{
get{return health; }
set{ health = value;
agent.speed = runningSpeed;
agent.SetDestination(target.transform.position);
onPatrol = false;
if (health <= 0){
Destroy(gameObject);
}
}
}
// Update is called once per frame
void Update () {
CanAttack(target);
}
here i want to make sure that the AI will attack the player only when he is in front of the AI and within .5 meters. the reason i use this instead of OnTriggerEnter is that the AI will only end up attacking once and then just stand on top of the player without really doing anything further
void CanAttack (GameObject other)
{
Vector3 vectorToTarget = other.transform.position - transform.position;
float angleToTarget = Vector3.Angle(transform.forward, vectorToTarget);
if (angleToTarget <= FOV)
{
RaycastHit raycastData;
if (Physics.Raycast(transform.position, vectorToTarget, out raycastData, .5f))
{
GameObject newTarget = raycastData.collider.gameObject;
Attack(target);
}
}
}
I have also used Attack(other) and this still does not work
void Attack(GameObject other)
{
if (!hasAttacked)//if the AI has not recently attacked
{
if (!Player.isShieldOn)//if the player shield is not up
{
if (!Player.hasShieldBraclet)//if the player has the shield braclet
{
other.gameObject.GetComponent<Player>().Health -= damage - ShieldBraclet.dmgReduction;//reduce the amount of damage taken
}
else
{
other.gameObject.GetComponent<Player>().Health -= damage;//otherwise take normal damage
}
StartCoroutine("DelayAttack");//delay time until AI can attack again
}
else
{
StartCoroutine(Stun(stunTime));//if the shield was up stun this enemy
}
}
}
this is used to make sure that the AI waits a moment before attacking again that way the player doesn't die instantly upon touching the AI.
IEnumerator DelayAttack()
{
hasAttacked = true;//the AI has attacked
yield return new WaitForSeconds(attackDelay);//wait to attack again
hasAttacked = false;//the AI can now attack again
}
}
For the most part everything runs fine but when it reaches the code to deal damage to the player it tells me that the object reference is not set to an instance of an object. How can I fix this?
The Specific error code is: NullReferenceException: Object reference not set to an instance of an object Enemy.Attack(UnityEngine.GameObject other).
This is referring to the line
other.gameObject.GetComponent<Player>().Health -= damage - ShieldBraclet.dmgReduction;//reduce the amount of damage taken

Unity C# 4.5.2 2D destroy instantiated particle system prefab

I acquired this snippit of code and it instantiates a particle system prefab. The problem I'm having is the clones do not get destroyed after the 5 second delay. Any advice is appreciated.
private ParticleSystem instantiate(ParticleSystem prefab, Vector3 position)
{
ParticleSystem newParticleSystem = Instantiate(
prefab,
position,
Quaternion.identity
) as ParticleSystem;
if(newParticleSystem.gameObject != null)
{
Destroy(
newParticleSystem.gameObject,
newParticleSystem.startLifetime
);
}
return newParticleSystem;
}
Your code relies on whateveris called ParticleSystem to keep track of when to Destroy the system. What I would do is this:
private ParticleSystem instantiate(ParticleSystem prefab, Vector3 position)
{
ParticleSystem newParticleSystem = Instantiate(
prefab,
position,
Quaternion.identity
) as ParticleSystem;
newParticalSystem.AddComponent<TimedDestroy>().delay = newParticleSystem.startLifetime;
return newParticleSystem;
}
and then add this script to your project:
using UnityEngine;
public class TimedDestroy : MonoBehaviour
{
public float delay;
void Start()
{
Invoke("destruct",delay);
}
public void destruct()
{
Destroy(gameObject);
}
}

Categories