Spawn objects don't sync - c#

I am trying to do a multiplayer game using photon and when I try to spawn objects near my player, they don't sync the same for both players. What I should add in the inspector (Already both objects that need to be spawn have PhotonView and PhotonView Transform). What I should add to my script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
public class SpawnFix : MonoBehaviour
{
public GameObject[] powersPrefab;
public Transform[] points;
public float beat = (60 / 130) * 2;
private float timer;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
[PunRPC]
void Update()
{
if (timer > beat)
{
GameObject powers = Instantiate(powersPrefab[Random.Range(0, 2)]);
powers.transform.localPosition = points[Random.Range(0, points.Length)].position;
timer -= beat;
}
timer += Time.deltaTime;
}
}

Given the similarity of your code:
Was exactly this question not already asked and answered here? (Only that there you accepted the stupid answer ^^)
Anyway
well ... that's not how [PunRPC] works. It would need to be invoked via the network somewhere e.g. via
var photonView = PhotonView.Get(this);
photonView.RPC("Update", RpcTarget.All);
HOWEVER, you definitely wouldn't want to synchronize the Update method itself every frame but rather only the spawning.
You probably should rather use PhotonNetwork.Instantiate
public class SpawnFix : MonoBehaviour
{
public GameObject[] powersPrefab;
public Transform[] points;
public float beat = (60 / 130) * 2;
private float timer;
// Update is called once per frame
void Update()
{
// only run on the master client
if(!PhotonNetwork.isMasterClient) return;
if (timer > beat)
{
PhotonNetwork.Instantiate(
powersPrefab[Random.Range(0, 2)].name,
points[Random.Range(0, points.Length)].position,
Quaternion.idendity,
0);
timer -= beat;
}
timer += Time.deltaTime;
}
}
For this the prefabs need to be registered in the Photon as spawnable objects.

Related

Why can I not collect coins and I get an error message

I'm new to unity and C# as well but I do have a small understanding of it because I've learnt python. At the moment I've run into an error, I'm trying to make a coin collection system and also the players movement in one script simply because it seems neater that way and easier to play audio.
The error comes up on this line, [SerializeField] private Transform groundcheck = null; (it's used so the player can jump over and over). I have a theory that maybe I just need to change the collision value, but other then no clue what I need to do to change this error.
Full Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField] private Transform groundcheck = null;
private bool jumpkeywaspressed;
private float horizontalInput;
private Rigidbody rigibodycomponent;
public AudioSource coincollect;
// Start is called before the first frame update
void Start()
{
rigibodycomponent = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
jumpkeywaspressed = true;
horizontalInput = Input.GetAxis("Horizontal");
}
private void FixedUpdate()
{
rigibodycomponent.velocity = new Vector3(horizontalInput * 3, rigibodycomponent.velocity.y,0);
if (Physics.OverlapSphere(groundcheck.position, 0.1f).Length == 1)
{
return;
}
if (jumpkeywaspressed)
{
rigibodycomponent.AddForce(Vector3.up * 5, ForceMode.VelocityChange);
jumpkeywaspressed = false;
}
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.layer == 6)
{
coincollect.Play();
ScoreManager.sManger.IncreaseScore(1);
Destroy(other.gameObject);
}
}
}
I also have another script which is used to increase the score but I don't think it effects my error.

Why does my enemy one hit me when I have 3 lives

I am in the process of making a game in Unity and I have run into a problem. I created a heart system UI + script and and enemy + script. In my game, I have 3 lives but when I made the enemy attack me, he one hits me. Is there a way that I can have a delay between each attack.
Here are the scripts.
Enemy Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform target;
IEnumerator Cooldown()
{
yield return new WaitForSeconds(3);
}
private void Update()
{
if (target != null)
{
float step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, target.position, step);
Collider2D[] hitplayers = Physics2D.OverlapCircleAll(attackPoint.position, attackrange, playerlayers);
foreach (Collider2D player in hitplayers)
{
player.GetComponent<HeartSystem>().TakeDamage(1);
StartCoroutine(Cooldown());
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = other.transform;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = null;
}
}
void OnDrawGizmosSelected()
{
if (attackPoint == null)
return;
Gizmos.DrawWireSphere(attackPoint.position, attackrange);
}
}
And here is my health system script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class HeartSystem : MonoBehaviour
{
public GameObject[] hearts;
public int life;
void Update()
{
if (life < 1)
{
Destroy(hearts[0].gameObject);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
else if (life < 2)
{
Destroy(hearts[1].gameObject);
}
else if (life < 3)
{
Destroy(hearts[2].gameObject);
}
}
public void TakeDamage(int d)
{
life -= d;
}
}
I hope we can find a solution.
So from what i understood from your code: the enemy targets the player on collision and once the player is targeted it receives damage on update.
The problem may be caused by the fact that the update method runs more than once until the collision ends. That would mean that your cooldown is not working.
I don't understand the way your timer works but I can tell you how i usually do.
Let's say the cooldown is 1sec. After attacking the player the first time you get the current time, add 1 second to it and store it like "nextAttackTime". Next time the enemy tries to attack it will check if the current time is equal or higher to the "nextAttackTime".
Your way of doing the cooldown looks very elegant but if it really isn't working you can consider trying it the way i described. it may not be as elegant but it is reliable.
-----EDIT-----
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform target;
//First we will need a variable to store the time when the next attack will be possible, it starts with 0. We'll also create a public variable to define the cooldown duration
private float nextAttackTime = 0f;
public float attackCoolDown = 3f;
IEnumerator Cooldown()
{
yield return new WaitForSeconds(3);
}
private void Update()
{
if (target != null)
{
float step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, target.position, step);
Collider2D[] hitplayers = Physics2D.OverlapCircleAll(attackPoint.position, attackrange, playerlayers);
foreach (Collider2D player in hitplayers)
{
//then before dealing the damage we check if it is already time to attack again
if(Time.time >= nextAttackTime){
player.GetComponent<HeartSystem>().TakeDamage(1);
//After dealing damage we reset the time for the next attack. The Time.time should return the time in seconds that the game has been running and we'll add 3 seconds to that to define the time for the next attack.
nextAttackTime = (Time.time + attackCoolDown);
//StartCoroutine(Cooldown());
}
}
}
}
So this is the editted code with the changes I suggested. I edited it right here in StackOverflow with no help of a code editor so there could be some typos.

Getting print to only print once when countdown Deltatime reaches certain number (Unity)

I have a 10-second countdown script that runs on DeltaTime.
In my Update function, I'm trying to make it print, only once, "Hello" whenever it reaches second 8.
The problem is that deltaTime is repeatedly printing "Hello" while it hangs on the 8th second, rather than only printing once, and I don't know how to stop that behavior.
I kept trying to introduce triggers in the if-block that set to 0 as soon as the block is entered but it still keeps continuously printing "Hello" so long as the timer is on second 8.
Countdown Timer
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public int n = 0;
public int switcher = 0;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
}
// Update is called once per frame
void Update()
{
currentTime -= 1 * Time.deltaTime; //does it each frame
n = Convert.ToInt32(currentTime);
if (n == 8)
{
switcher = 1;
}
}
}
Update Method in different class
if (CountdownTimer.switcher == 1)
{
CountdownTimer.switcher = 0;
print("hey");
}
Any ideas on how to make print("hey") only happen once? It's important because later I would replace the print code with an important method and I need to make sure the method happens only once.
This is where you want to implement a system of subscriber with event/listener.
Add an event to the countdown, if countdown is meant to be unique, you can even make it static. Also, if the update is no longer needed after the setting of switcher to 1 then you can convert that to coroutine
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public static event Action RaiseReady;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
StartCoroutine(UpdateCoroutine());
}
// Update is called once per frame
IEnumerator UpdateCoroutine()
{
while(true)
{
currentTime -= 1 * Time.deltaTime; //does it each frame
int n = Convert.ToInt32(currentTime);
if (n == 8)
{
RaiseReady?.Invoke();
RaiseReady = null; // clean the event
yield break; // Kills the coroutine
}
yield return null;
}
}
}
Any component that needs to know:
public class MyClass : MonoBehaviour
{
void Start()
{
CountdownTimer.RaiseReady += CountdownTimer_RaiseReady;
}
private void CountdownTimer_RaiseReady()
{
Debug.Log("Done");
// Remove listener though the other class is already clearing it
CountdownTimer.RaiseReady -= CountdownTimer_RaiseReady;
}
}
The solution #Everts provides is pretty good, but as you are using unity I would recommend a couple of tweaks to play nicer with the editor. Instead of a generic event I recommend using a UnityEvent from the UnityEngine.Events namespace. I would also advise against statics due to how unity goes about serializing them across scenes. There are some weird edge cases you can get into if you aren't familiar with how unity handles their serialization. If you just need to send a message to another object in the same scene I would actually recommend a game manager. You can safely do a GameObject.Find() in onvalidate() and link your variables to avoid a performance hit at runtime doing the find. If that data needs to carry across to a different scene for this message then use a ScriptableObject instead. It would look something like below.
Put this component on the scene's "Game Manager" GameObject
public class CountingPassthrough : MonoBehaviour
{
public CountdownTimer countdownTimer;
}
put this component on the scene's "Timer" GameObject
public class CountdownTimer : MonoBehaviour
{
public float startingTime = 10f;
public UnityEvent timedOut = new UnityEvent();
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
FindObjectOfType<CountingPassthrough>().countdownTimer = this;
}
private void Start()
{
StartCoroutine(TimerCoroutine());
}
// Coroutine is called once per frame
private IEnumerator TimerCoroutine()
{
float currentTime = 0f;
while (currentTime != 0)
{
currentTime = Mathf.Max(0, currentTime - Time.deltaTime);
yield return null;//wait for next frame
}
timedOut.Invoke();
}
}
Put this component on the GameObject you want to use the timer
public class user : MonoBehaviour
{
[SerializeField, HideInInspector]
private CountingPassthrough timerObject;
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
timerObject = FindObjectOfType<CountingPassthrough>();
}
private void OnEnable()
{
timerObject.countdownTimer.timedOut.AddListener(DoSomething);
}
private void OnDisable()
{
timerObject.countdownTimer.timedOut.RemoveListener(DoSomething);
}
private void DoSomething()
{
//do stuff here...
}
}
This workflow is friendly to prefabs too, because you can wrap the find() in onvalidate() with if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene) to prevent grabbing the wrong asset from other loaded scenes. And again, if you need this to carry data across scenes then have CountingPassthrough inherit from ScriptableObject instead of MonoBehaviour, create the new ScriptableObject to your project folder somewhere, and ignore that extra if check to constrain scene matching. Then just make sure you use a function to find it that includes assets if you use the cross-scene ScriptableObject approach.
EDIT:Forgot nested prefabs edgecase in unity 2018+ versions. You need to add this to account for it: && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene() I've updated the code snippet above. Sorry about that.
Since Updtade() is called once per frame, then switcher is set to 1 once per frame during the 8th second (and there is a lot of frames in 1 sec).
An answer could be something like this to prevent it from printing again :
if (CountdownTimer.switcher == 1)
{
if (!AlreadyDisplayed)
{
print("hey");
AlreadyDisplayed = true;
}
}
Where AlreadyDisplayed is a Boolean set to false when declared.
This should do what you want to achieve. :)

How to destroy and randomly generate pipes of different size?

So this is the code which I use to generate the pipe thingies, but after it generates it I want to destroy it after its out of screen or any other possible way, maybe after 3 seconds or any way possible and I want this pipe thingies to generate randomly in different sizes you get me ryt just like flappy bird.
using UnityEngine;
using System.Collections;
public class Generate : MonoBehaviour {
public GameObject lightnest;
public float initialDelay;
public float finalDelay;
public float rampDuration;
protected float _delay;
protected float _runTime;
protected float _timeSinceSpawn;
// Use this for initialization
void Start () {
_delay = initialDelay;
_runTime = _timeSinceSpawn = 0.0f;
}
// Update is called once per frame
void Update() {
_runTime += Time.deltaTime;
_timeSinceSpawn += Time.deltaTime;
_delay = Mathf.Lerp(initialDelay, finalDelay, _timeSinceSpawn / rampDuration);
if (_timeSinceSpawn > _delay) Spawn();
}
protected void Spawn()
{
_timeSinceSpawn = 0.0f;
Instantiate(lightnest);
}
}
Use gameobject.transform.localScale to change the size on Start. And use gameobject.transform.position to check if the object is out of the screen. And use Destroy(gameobject) to destroy the object.
https://docs.unity3d.com/ScriptReference/Transform.html
Hope this helps!
I recommend you to watch this tutorial.
Object pooling is the most efficient way handle this kind of situtation.

Give every prefab his own speed

Is was building a star background simulation for a spaceshooter game in UNITY3D.
In c# i built The Instantitiation of prefabs which look like dots. While the generation of multiple dots on random spots on the x-axis go perfectly i wanted to add one thing.
Random speed. The problem is that i don't know how to give every prefab his own speed en keep it instead of getting overwritten by the next randomnization.
backgroundloop.cs
using UnityEngine;
using System.Collections;
public class backgroundloop : MonoBehaviour {
public GameObject star;
public float spawnTime;
private GameObject starPrefab;
private float timestamp;
//public float hoogte = Random.Range(-1.01f,1.1f);
//public float rotationPrefab = transform.localEulerAngles.z;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Time.time > timestamp) {
starPrefab = Instantiate
(star, new Vector3(7,Random.Range(-5.0f,5.0f),0),
Quaternion.Euler(0, 0, 0)) as GameObject;
timestamp = Time.time + spawnTime;
}
}
}
And the problem is in this script:
prefabMovement.cs
using UnityEngine;
using System.Collections;
public class prefabMovement : MonoBehaviour {
float speed = Random.Range (-3f, -0.1f);
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.Translate (new Vector3(-1f,0,0) * Time.deltaTime);
if (transform.position.x < -6.8) {
Destroy (this.gameObject);
}
}
}
Huh? So the speed is overwritten and eventually ends up being the same on all instantiated prefabs?
Maybe it helps if initialize the speed variable inside the Start method.

Categories