I'm making/want to make a bullet-hell game where there are essentially two players, each own their own identical copy of a map competition for the high score. And the game ends whenever a player dies.
I have all of this working in multiplayer, but my approach seems a bit hacky, so I'm wondering if there's a better way for me to accomplish this.
At first I was using just unity's network manager, and in my player Start() function I checked if(!isLocal), and if so I set the gameObject enabled to false. This worked great, but like I said, felt a little hacky.
Example of the code:
if (!isLocalPlayer) {
gameObject.SetActive(false);
return;
}
Next I moved to unity's LobbyManager. This is where things got really sticky. Now on the Host, the game loads fine, but on the client, only one game object is created, and it's set as disabled which leads me to believe that it is the 'enemy' or not local player object.
I slowly figured out that the cause of this was setting the enemy game object active to false. If I left it true, both players would spawn on both screens. My solution now is to not disable the enemy player object, but every component on it so it doesn't get in the way.
Again this feels very hacky, and like it could lead to problems down the road. Is this really the best option, or am I missing something obvious?
Example of the Code:
if (!isLocalPlayer) {
gameObject.GetComponent<MeshRenderer>().enabled = false;
gameObject.GetComponent<BoxCollider>().enabled = false;
gameObject.GetComponent<CharacterController>().enabled = false;
gameObject.GetComponent<PlayerController>().enabled = false;
gameObject.GetComponent<Rigidbody>().detectCollisions = false;
guns = gameObject.GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer gun in guns) {
gun.enabled = false;
}
}
Thanks in advance! Sorry this is so long, hopefully it isn't a chore to read.
Make an empty scene with nothing but a score manager and what else you need to transfer.
Have another scene as an asset in your project folder, this is where the map is.
When a player joins, have them join the scene with the score manager.
If they also are the local player, they should load the map scene.
That way, both players will be in the same scene while also having their own instance of the map.
You can load scenes in asynchronously and additively, which would be ideal for your situation.
Related
I am currently working on translating a VR game to the Oculus Quest 2 from a PC standalone version. In this game, the game menu cannot be accessed by the player wearing the headset, as it is not visible to the player; it is instead accessed by another party at the computer itself. When the person at the computer clicks, start game, a number of processes begin, including a coroutine to spawn multiple instances of a game object in a non-player enemy's hands.
Part of the translation process includes allowing the player to start the game from the Oculus Touch controllers, I am attempting to implement a feature where either of the four face buttons will start the game.
if (OVRInput.GetDown(OVRInput.Button.One) || OVRInput.GetDown(OVRInput.Button.Two) || OVRInput.GetDown(OVRInput.Button.Three) || OVRInput.GetDown(OVRInput.Button.Four))
{
startGameClick();
}
However, it seems like calling startGameClick(); more than once, whether in the same script or otherwise, causes the game to not run certain processes, chief among them the ball spawn coroutine. This causes the NPC enemies to activate their throwing animations without having a ball to throw, and they do not return to their idle animations afterwards. I am unsure why this is, however it has been a major roadblock in attempting the platform translation.
Additionally, this is the startGameClick(); function:
// If the start game button is clicked
public void startGameClick() {
StandaloneServer.startgame = true;
if (Master.usingMM && ServerController.IsServerReady())
Master.ready = true;
else if (!Master.usingMM)
Master.ready = true;
roundController.startInput();
beginGameButton.GetComponentInChildren<Text>().text = "In Progress";
beginGameButton.interactable = false;
}
My assumption is that one of the references in this function is the source of the issue, but I cannot pinpoint which one.
Base Information
Unfortunately, to answer your question, we would need to see all of the related scripts for this function which just isn't feasible as that would also require us to be kind enough to sift through your code to find your error.
A Cheap Solution
However, there is a cheap solution to your issue. You can simply reload the scene (or reset the script if it's DNDOL) and it should work again.
It seems I've found a solution at long last.
Instead of calling the startGame() function again, I decided to invoke onClick() to simulate the effect of clicking the button with a mouse, although the player is actually touching the button in the VR space. This worked, and the game is running as it should.
I’m trying to find a good way to play background music in Unity 3D. I want the music to keep playing consistently through scene loads. Don’t Destroy on load is fine and works, but every time I load the same scene, it makes another music game object because the scene itself has the game object in it. How can I solve my problem? I am a “beginner” (kind of), so I would like code I can understand.
I'd hands down recommend starting with an Asset like 'EazySoundManagerDemo'. It needs a little refactoring and refinement (ie it uses 3 arrays of audios with 3 sets of accessibility functions instead of one set with an AudioPurpose enum to increase code-reuse).
It does however solve the basic problem you have and is a good intro to using an audio manager / layer instead of simply playing audio directly from your GameObjects. Give that a shot, learn from it and then adapt it or create your own audio management layer.
Good Luck!
I recommend creating an audioSource object, then creating an script for this object and on the awake function do this:
void Awake() {
DontDestroyOnLoad(this.gameObject);
}
This will make the background music to keep playing between scenes. For more information you could use Unity's documentation about this function.
With help from a question on the unity forum, I think I have solved my problem. The link to the question is here...
https://answers.unity.com/questions/982403/how-to-not-duplicate-game-objects-on-dontdestroyon.html
The Best Answer is the one I’m using.
The code is this...
private static Player playerInstance;
void Awake(){
DontDestroyOnLoad(this);
if (playerInstance == null) {
playerInstance = this;
} else {
Destroy(gameObject); // Used Destroy instead of DestroyObject
}
}
I have the following hierarchy on my Player prefab for what will be a very simple multiplayer shooter.
It works this way, the Controller object has the scripts that deal with player input, the PlayerShip object has all of the turning, moving, shooting scripts etc and the Camera is just as it sounds, a camera with a few scripts on it for moving it about.
When a new Player is instantiated the Control script needs to locate its relevant PlayerShip, simple enough.
This can be achieved using the following code:
_playerShip = gameObject.transform.parent.gameObject.transform.FindChild("PlayerShip").GetComponent<PlayerShip>();
Which works fine, the only thing is, to me that looks very clunky, inelegant and brittle. Consequently, I'm wondering if there's a better, more efficient, less ugly way of achieving the same thing?
First of all,
_playerShip = gameObject.transform.parent.gameObject.transform.FindChild("playerShip").GetComponent<PlayerShip>();
is redundant. That can be reduced to
_playerShip = gameObject.transform.parent.FindChild("playerShip").GetComponent<PlayerShip>();
or even use '/' just like you do with folder names.
_playerShip = GameObject.Find("Player/playerShip").GetComponent<PlayerShip>();
Now, instead of instantiating the Object and searching for it later on, you can actually instantiate it and retrieve the reference at the-same time.
GameObject obj = Instantiate(prefab,Vector3.zero,Quaternion.identity) as GameObject;
_playerShip = obj.GetComponent<PlayerShip>();
If it is a network game that you instantiate with Network.Instantiate:
GameObject obj = Network.Instantiate(prefab,Vector3.zero,Quaternion.identity,0) as GameObject;
_playerShip = obj.GetComponent<PlayerShip>();
Now, send the PlayerShip reference to the Control script.
If this isn't being called every frame consider this:
_playerShip = GameObject.Find("PlayerShip").GetComponent>PlayerShip>();
Read more about the above here: https://docs.unity3d.com/ScriptReference/GameObject.Find.html
If it is being called every frame look in to this:
https://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html
Either one should be easier to understand.
EDIT: I may have misunderstood. Would rearranging the hierarchy so that controller is a parent of playerShip? Then you should be able to just look at the child, rather than going through a parent to a sibling.
I'm making simple game manager. I have a script, which will be accessible from all scenes in the game. And I need to check values of its variables after loading new scene. But my code runs only once after starting the simulation while an object with this script exists in all scenes. What is wrong? Why doesn't it work after loading a new scene?
In every Unity project you must have A PRELOAD SCENE.
It is quite confusing that Unity does not have a preload scene "built-in".
They will add this concept in the future.
Fort now you have to click to add a preload scene yourself.
This is the SINGLE GREATEST MISUNDERSTANDING for new programmers trying Unity!
Fortunately, it is extremely easy to have a preload scene.
Step 1.
Make a scene named "preload". It must be scene 0 in Build Manager.
Step 2.
In the "preload" scene make an empty GameObject called, say, "__app".
Simply, put DontDestroyOnLoad on '__app'.
Note:
This is the only place in the whole project you use DontDestroyOnLoad.
It's that simple.
In the example: the developers have made a one-line DDOL script.
Put that script on the "__app" object.
You never have to think about DDOL again.
Step 3
Your app will have (many) "general behaviors". So, things like database connectivity, sound effects, scoring, and so on.
You must, and can only, put your general behaviors on "_app".
It's really that simple.
The general behaviors are then - of course - available everywhere in the project, at all times, and in all scenes.
How else could you do it?
In the image example above, notice "Iap" ("in-app purchase") and the others.
All of your "generally-needed behaviors" - sound effects, scoring, and so on - are right there on that object.
Important...
This means that - of course, naturally -
...your general behaviors will have ordinary Inspectors, just like everything else in Unity.
You can use all the usual features of Unity, which you use on every other game object. Inspector variables, drag to connect, settings, and so on.
(Indeed: say you've been hired to work on an existing project. The first thing you will do, is glance at the preload scene. You will see all the "general behaviors" in the preload scene - sound effects, scoring, AI, etc etc. You will instantly see all the settings for those things as Inspector variables ... speech volume, playstore ID, etc etc.)
Here's an example "Sound effects" general behavior:
Looks like there's also a "voice over" general behavior, and a "music" general behavior".
To repeat. Regarding your "general behaviors". (Sound effects, scoring, social, etc etc.) These CAN ONLY GO on a game object in the preload scene.
This is not optional: there's no alternative!
It's that easy.
Sometimes engineers coming from other environments get caught up on this, because it seems like "it can't be that easy".
To repeat, Unity just plain forgot to "build-in" a preload scene. So, you simply click to add your preload scene. Don't forget to add the DDOL.
So, during development:
Always start your game from Preload scene.
It's that simple.
Important: Your app will certainly have "early" scenes. Examples:
"splash screen"
"menu"
Note. Tou CAN NOT use splash or menu as the preload scene. You have to literally have a separate preload scene.
The preload scene will then load your splash or menu or other early scene.
The central issue: "finding" those from other scripts:
So you have a preload scene.
All of your "general behaviors" are simply on the preload scene.
You next have the problem of, quite simply, finding say "SoundEffects".
You have to be able to find them easily, from, any script, on any game object, in any of your scenes.
Fortunately it is dead easy, it is one line of code.
Sound sound = Object.FindObjectOfType<Sound>();
Game game = Object.FindObjectOfType<Game>();
Do that in Awake, for any script that needs it.
It's honestly that simple. That's all there is to it.
Sound sound = Object.FindObjectOfType<Sound>();
Tremendous confusion arises because of the 100s of absolutely wrong code examples seen online.
It really is that easy - honest!
It's bizarre that Unity forgot to add a built-in "preload scene" - somewhere to attach your systems like SoundEffects, GameManager, etc. It's just one of those weird thing about Unity. So, the first thing you do in any Unity project is just click once to make a preload scene.
That's it!
A Detail...
Note that, if you really want to type even less (!) lines of code, it's remarkably easy - you can just use a global for each of these things!
This is explained in detail here , many folks now use something like this, a Grid.cs script ...
using Assets.scripts.network;
using UnityEngine;
static class Grid
{
public static Comms comms;
public static State state;
public static Launch launch;
public static INetworkCommunicator iNetworkCommunicator;
public static Sfx sfx;
static Grid()
{
GameObject g = GameObject.Find("_app");
comms = g.GetComponent<Comms>();
state = g.GetComponent<State>();
launch = g.GetComponent<Launch>();
iNetworkCommunicator = g.GetComponent<INetworkCommunicator>();
sfx = g.GetComponent<Sfx>();
}
}
Then, anywhere in the project you can say
Grid.sfx.Explosions();
It's just that easy, that's the whole thing.
Don't forget that each of those "general systems" is on, and can only be on, the DDOL game object in the preload scene.
DylanB asks: "During development it's quite annoying that you have to click to the preload scene every time before you click "Play". Can this be automated?"
Sure, every team has a different way to do this. Here's a trivial example:
// this should run absolutely first; use script-execution-order to do so.
// (of course, normally never use the script-execution-order feature,
// this is an unusual case, just for development.)
...
public class DevPreload:MonoBehaviour
{
void Awake()
{
GameObject check = GameObject.Find("__app");
if (check==null)
{ UnityEngine.SceneManagement.SceneManager.LoadScene("_preload"); }
}
}
But don't forget: what else can you do? Games have to start from a preload scene. What else can you do, other than click to go to the preload scene, to start the game? One may as well ask "it's annoying launching Unity to run Unity - how to avoid launching Unity?!" Games simply, of course, absolutely have to start from a preload scene - how else could it be? So sure, you have to "click to the preload scene before you click Play" when working in Unity - how else could it be?
#Fattie: Thanks for elaborating all this, it's great! There is a point though that people are trying to get through to you, and I'll just give it a go as well:
We do not want every instantiation of everything in our mobile games to do a "FindObjectOfType" for each and every every "global class"!
Instead you can just have it use an Instantiation of a static / a Singleton right away, without looking for it!
And it's as simple as this:
Write this in what class you want to access from anywhere, where XXXXX is the name of the class, for example "Sound"
public static XXXXX Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; } else { Debug.Log("Warning: multiple " + this + " in scene!"); }
}
Now instead of your example
Sound sound = Object.FindObjectOfType<Sound>();
Just simply use it, without looking, and no extra variables, simply like this, right off from anywhere:
Sound.Instance.someWickedFunction();
Alternately (technically identical), just use one global class, usually called Grid, to "hold" each of those. Howto. So,
Grid.sound.someWickedFunction();
Grid.networking.blah();
Grid.ai.blah();
Here is how you can start whatever scene you like and be sure to reintegrate your _preload scene every time you hit play button in unity editor. There is new attribute available since Unity 2017 RuntimeInitializeOnLoadMethod, more about it here.
Basically you have a simple plane c# class and a static method with RuntimeInitializeOnLoadMethod on it. Now every time you start the game, this method will load the preload scene for you.
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadingSceneIntegration {
#if UNITY_EDITOR
public static int otherScene = -2;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InitLoadingScene()
{
Debug.Log("InitLoadingScene()");
int sceneIndex = SceneManager.GetActiveScene().buildIndex;
if (sceneIndex == 0) return;
Debug.Log("Loading _preload scene");
otherScene = sceneIndex;
//make sure your _preload scene is the first in scene build list
SceneManager.LoadScene(0);
}
#endif
}
Then in your _preload scene you have another script who will load back desired scene (from where you have started):
...
#if UNITY_EDITOR
private void Awake()
{
if (LoadingSceneIntegration.otherScene > 0)
{
Debug.Log("Returning again to the scene: " + LoadingSceneIntegration.otherScene);
SceneManager.LoadScene(LoadingSceneIntegration.otherScene);
}
}
#endif
...
An alternate solution from May 2019 without _preload:
https://low-scope.com/unity-tips-1-dont-use-your-first-scene-for-global-script-initialization/
I've paraphrased from the above blog to a how-to for it below:
Loading a Static Resource Prefab for all Scenes
In Project > Assets create a folder called Resources.
Create a Main Prefab from an empty GameObject and place in the Resources folder.
Create a Main.cs C# script in your Assets > Scripts or wherever.
using UnityEngine;
public class Main : MonoBehaviour
{
// Runs before a scene gets loaded
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void LoadMain()
{
GameObject main = GameObject.Instantiate(Resources.Load("Main")) as GameObject;
GameObject.DontDestroyOnLoad(main);
}
// You can choose to add any "Service" component to the Main prefab.
// Examples are: Input, Saving, Sound, Config, Asset Bundles, Advertisements
}
Add Main.cs to the Main Prefab in your Resources folder.
Note how it uses RuntimeInitializeOnLoadMethod along with Resources.Load("Main") and DontDestroyOnLoad.
Attach any other scripts that need to be global across scenes to this prefab.
Note that if you link to other scene game objects to those scripts you probably want to use something like this in the Start function for those scripts:
if(score == null)
score = FindObjectOfType<Score>();
if(playerDamage == null)
playerDamage = GameObject.Find("Player").GetComponent<HitDamage>();
Or better yet, use an Asset management system like Addressable Assets or the Asset Bundles.
actually as a programmer who comes to unity world I see none of these approaches
standard
the most simplest and standard way: create a prefab, according to unity docs:
Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects
as a reusable Asset. The Prefab Asset acts as a template from which you can create new Prefab instances in the Scene.
Details:
Create a prefab within your Resources folder:
if you don't know how to create a prefab study this unity document
if you don't have resources directory create a folder and name it exactly Resources because it is a unity Special folder name
create a script with contents like below:
using UnityEngine;
public class Globals : MonoBehaviour // change Globals (it should be the same name of your script)
{
// loads before any other scene:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void LoadMain()
{
Debug.Log("i am before everything else");
}
}
assign it to your prefab
and you can make it even better:
use prefab and namespaces together:
in your prefab script:
using UnityEngine;
namespace Globals {
public class UserSettings
{
static string language = "per";
public static string GetLanguage()
{
return language;
}
public static void SetLanguage (string inputLang)
{
language = inputLang;
}
}
}
in your other scripts:
using Globals;
public class ManageInGameScene : MonoBehaviour
{
void Start()
{
string language = UserSettings.GetLanguage();
}
void Update()
{
}
}
In my game I am creating in unity I am trying to play an audio clip every time that a collision happens on one of my game objects. For some reason the audio will play the first time there is a collisiom, but from then on it no longer plaus the sound.
The game object that is collided with has a component for the AudioSource and I have the audio clip selected in that component.
Here is the code where I start the audio:
if (PlayerPrefs.GetInt ("Sound Playing", 1) == 1)
{
audio = GetComponent <AudioSource>();
audio.Play ();
}
I have also tried using the PlayOneShot () method but it does the same thing.
EDIT
Here is a small representation what of my game is doing here:
public class Ship : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D collider)
{
Laser laser = collider.gameObject.GetComponent<Laser> ();
if (laser)
{
if (!immune)
{
//update healthbar
healthbar.hit();
//reset health boost
forcefield.resetHealthBoost();
health -= laser.getDamage(); //remove health from ship
//create explosion
GameObject explosion = Instantiate(explode, gameObject.transform.position, Quaternion.identity) as GameObject;
explosion.GetComponent<ParticleSystem>().startColor = this.GetComponent<SpriteRenderer>().color;
if (SoundEffects.soundOn && PlayerPrefs.GetInt("Sound Playing", 1) == 1)
{
audio = GetComponent<AudioSource>();
audio.Play();
}
}
laser.hit();//destroy the laser that hit the ship
}
}
}
Here is to show that I have the AudioSource and the AudioClip setup in the inspector (the AudioSource is attached to the ship):
The issue with my game was that I didn't realize that using gameobject.SetActive(false) later in my code was stopping the second audio clip from being able to play. I set it to false when the ship was destroyed and this is what the problem was all along. It works fine now.
It's looks with audio it's all right. May be problem is in collisions or something else. Add code Debug.Log("Play sound."); before code line audio.Play();, and test.
Make sure your audio listener is near the explosion too. It could be far away and may not be heard. Also do a check to see if audio.isPlaying, then play audio. Audio may be still playing and you have to stop it properly.
if (audio.isPlaying) {
audio.Stop();
audio.Play();
}
If all else fails you can always do PlayOneShot(), which will essentially do as it says. audio.PlayOneShot(); This could be problematic though since if you are putting it OnTriggerEnter() it may run more than once in a short duration. You may hear it stutter because it's queued up however many explosions.
Finally, don't use PlayerPrefs for those specific things. You should reserve it honestly for Game Specific data storage. Why can't you simply use isPlaying? PlayerPrefs will only make the task very convoluted. I learned it the hard way when I used to use PlayerPrefs to transfer data from C# to UnityScript. In the end it's almost best to use Static Variables, or create a Singleton class and commit values to various instances. Plus I learned that I can use methods and variables cross languages when I adjust the compilation order(s). A little bit off topic, but still substantially important.