I'm trying to make an audio player in Unity.
I first created a ResourceManager, that has a
AudioClip[] array.
public class ResourceManager : MonoBehaviour
{
public AudioSource audioSrc;
public AudioClip[] clips;
private int trackNum = 0;
public void Awake()
{
audioSrc = GetComponent<AudioSource>();
audioSrc.Stop();
...omitted...
public void LoadClip()
{
audioSrc.clip = clips[trackNum];
}
}
So that they appear in the Clips drag-and-drop box.
However, I want to make the AudioClip array static in ResourceManager static, so other scripts (e.g. PlayPause.cs can access clips statically (via function calls).
But the problem is once I make clips static, the drag-and-drop for disappears.
Is there a way to fix that? Or are there some better design patterns? Thanks.
You can e.g. simply do
public class ResourceManager : MonoBehaviour
{
...
// This is the one in the Inspector
// Usually I keep the Inspector private and only provide public access
// where needed via properties
[SerializeField] private AudioClip[] clips;
// This will be set in Awake to the ones from the Inspector
// readonly for others, only this class can assign it
public static AudioClip[] Clips { get; private set; }
public void Awake()
{
Clips = clips;
...
}
}
The alternative suggested is make the ResourceManager a so called singleton like e.g.
public class ResourceManager : MonoBehaviour
{
// Here you store your own instance reference
// and provide public readonly access
public static ResourceManager Instance { get; private set; }
[SerializeField] private AudioClip[] clips;
public AudioClip[] Clips => clips;
...
public void Awake()
{
// Make sure only one instance exists at a time
if(Instance && Instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
// optional: Don't destroy this instance when the scene is switched
// Careful: Make sure this applies also to the AudioSource instance!
// For now I assume it is either attached to this object or a child
// if not simply do the same DontDestroyOnLoad on it as well
DontDestroyOnLoad (this.gameObject);
...
}
public void LoadClip()
{
audioSrc.clip = clips[trackNum];
}
}
And then access everywhere ReaourcesManager.Instance.Clips
Or a built-in alternative to the singleton is using FindObjectOfType so any other script in the Scene can actually simply access
FindObjectOfType<ResourcesManager>().clips
without you having to change anything at all ;)
Except as said I would make it
[SerializeField] private AudioClip[] clips;
public IReadonlyList<AudioClip> Clips => clips;
depending a bit of course what exactly you want to do with the clips but this way the array can not be altered by any other script
Related
I need to call a protected variable from a public class into an if statement in a private method of another public class
I am programing a video game in unity and I need to use a bool variable (that shows if the character is out of stamina) in an if statement to determine whether or not the character can run
This is what my code looks like excluding everything unrelated to the problem
Public class CharacterStats : MonoBehaviour
{
[SerialzeField] protected bool Tired;
}
Public class PlayerMovement : MonoBehaviour
{
Private void HandleRunning()
{
If (Input.GetKeyDown(KeyCode.LeftShift) && X != True)
{
Speed = RunSpeed;
}
}
}
X is where I want the Tired variable to be.
Use a public readonly property like e.g.
public class CharacterStats : MonoBehaviour
{
// Keep you serialized field protected
[SerialzeField] protected bool tired;
// Have a public read-only accessor property
public bool Tired => tired;
}
and then e.g.
public class PlayerMovement : MonoBehaviour
{
// Somehow you will need to get a reference to the CharacterStats instance
// e.g. via the Inspector
[SerializeField] private CharacterStats stats;
[SerializeField] private float RunSpeed;
private float Speed;
private void HandleRunning()
{
if (Input.GetKeyDown(KeyCode.LeftShift) && !stats.IsTired)
{
Speed = RunSpeed;
}
}
}
Alternatively (and my apologies to #aybe who had answered this) you can actually directly serialize a property using explicitely
[field: SerializeField] public bool Tired { get; protected set; }
this is a property which can be accessed by everyone but only this and inherited classes (and due to the serialization now the Inspector) have the permission to set the value.
In general: Fix your casing! In c# all keywords are lower key!
Can i create custom region with grouped methods for list onClick like dynamic and statics?
like this
Yes and no! ^^
Yes, you can create your own event type taking a parameter and assign dynamic callbacks to it. What you are looking for is UnityEvent.
For the dynamic parameterized ones see UnityEvent<T0> to UnityEvent<T0, T1, T2, T3> depending on how many parameters you need.
For the example with a single int it would be (exactly as in the API example)
// Since Unity doesn't support direct serialization of generics you have to implement this [Serializable] wrapper
[Serializable]
public class MyIntEvent : UnityEvent<int>
{
}
public class ExampleClass : MonoBehaviour
{
public MyIntEvent m_MyEvent;
}
No, you can not simply change the existing implementation of UI.Button.onClick which is parameterless.
What you could do, however, is build a new component and attach it on a button like
[RequireComponent(typeof(Button))]
public class ExampleClass : MonoBehaviour
{
[SerializeField] private Button _button;
public MyIntEvent onClickWithIntParameter;
private void Awake()
{
if(!_button) _button = GetComponent<Button>();
_button.onClick.AddListener(HandleOnClick);
}
private void HandleOnClick()
{
// Wherever you get your int from
var value = 123;
onClickWithIntParameter.Invoke(value);
}
}
In the case that [Serializable] isn't working for you try [System.Serializable] or using System; at the top.
For Those who want to create a custom event Script:
I made a collision detection script. The events set in the inspector are called when a collision is detected. Just like a button.
https://i.stack.imgur.com/r4epP.png
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;
public class CollisionCallFunc : MonoBehaviour {
[System.Serializable]
public class CollisionEvent : UnityEvent<object> {
public object value;
}
[SerializeField]
private CollisionEvent collisionEvents = new();
private void OnCollisionEnter(Collision collision) {
try {
collisionEvents.Invoke(collisionEvents.value);
} catch(System.Exception exception) {
Debug.LogWarning("Couldn't invoke action. Error:");
Debug.LogWarning(exception.Message);
}
}
private void OnCollisionEnter2D(Collision2D collision) {
try {
collisionEvents.Invoke(collisionEvents.value);
} catch(System.Exception exception) {
Debug.LogWarning("Couldn't invoke action. Error:");
Debug.LogWarning(exception.Message);
}
}
}
public class GameControl : NetworkBehaviour {
public GameObject localPlayer;
[TargetRpc]
public void TargetGetLocalPlayer(NetworkConnection conn)
{
localPlayer = GameObject.Find("Local");
}
public override void OnStartServer()
{
base.OnStartServer();
TargetGetLocalPlayer(connectionToClient);
}
}
i have a script attached to a server object which should be able to grab the local player GameObject (which i denoted by changing it's name to 'Local' once it's spawned in another script) from the client but when i try to call TargetGetLocalPlayer , i get the following error :
Exception in OnStartServer:Object reference not set to an instance of an object at
UnityEngine.Networking.NetworkBehaviour.SendTargetRPCInternal
(UnityEngine.Networking.NetworkConnection conn, UnityEngine.Networking.NetworkWriter writer,
System.Int32 channelId, System.String rpcName) [0x0002e]
i am totally new to networking in unity and i feel like i should have gone with photon instead of unet , it seems like no one is interested in unet anymore and the docs suck at explaining anything and i will be very grateful if anyone could answer me , thanks in advance
I think a better solution would be to attach a script to each player. and make it so that when a "new" player joins it runs a method in your GameControl to add the player to a player list. like this:
//this is on your gameControl.
public List<Player> players;
public void AddPlayer(Player player)
{
players.Add(player);
}
this is working if you have your GameControl as a singleton. if you do not know how to do that check the last piece of code.
//this is on your Player script called player(if you have another name change all
//Player scripts in here to that name
public void Start()
{
GameControl.AddPlayer(this);
}
Or
Instead of making the players list a List you can make it a dictionary and make a key for each player who joins to make it more accesible.
How to make a script a singleton and why.
why:
if you have a manager class/script you always want there to only be ONE instance of it so you can call its methods easily and without problems.
How:
Basically you make it so that Only THIS script can change values and variables in the manager, and other scripts can get/call methods and functions. this makes it easily accesible and you will have less problems.
private static GameControl _GameControl;
private Player player;
public static GameControl gameControl
{
get
{
if(_GameControl == null)
{
Debug.LogError("Game Control is null");
}
return _GameControl;
}
}
void Awake()
{
_GameControl = this;
player = GameObject.Find("Player").GetComponent<Player>();
}
Well i see what you mean, well create a method that can be run by any script like a singleton. then you pass in the gameobject that you want to add like this:
public class GameManager
{
public GameObject _player;
//this is a singleton.
private static GameManager _gm;
public static GameManager gameManager
{
get
{
if(_gm == null)
{
Debug.LogError("Game manager is null");
}
return _gm;
}
}
void awake()
{
_gm = this;
}
void GetPlayer(GameObject player)
{
_player = player;
}
void AddPlayer(GameObject player)
{
//add it to whatever you want to.
}
}
call the method this way:
public class Player : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameManager.gameManager.GetPlayer(this.gameObject);
}
}
I have seen a similar error to this but for Video, however I can not find anywhere that explains my current error.
I will attach my code below for reference, does anyone know what the error means here?
using UnityEngine;
using System.Collections;
public class PlaySounds : MonoBehaviour {
public AudioClip SoundToPlay;
public float Volume;
AudioSource audio;
public bool alreadyPlayed = false;
void Start()
{
audio = GetComponent<AudioSource>();
}
void OnTriggerEnter2D(Collider2D other)
{
if (!alreadyPlayed)
{
audio.PlayOneShot(SoundToPlay, Volume);
alreadyPlayed = true;
}
}
}
The MonoBehaviour class is already presenting a member called "audio" so your PlaySounds class has that member already. If you want to ignore the one from the base class you should declare it like so:
private new AudioSource audio;
But if you want the inherited member to be available as well as the one in your class simply give it a name other than "audio". To be honest unless you have a particular reason you want to override the inherited member I'd do the latter.
I have an array of prefabs and I want to be able to Instantiate randomly picked prefabs thru Zenject Factory and perform their bindings in their sub-containers.
What I want to do is the same as in this code sample from Zenject documentation, but for randomly selected prefabs.
https://github.com/modesttree/Zenject/blob/master/Documentation/SubContainers.md#using-game-object-contexts-no-monobehaviours
using UnityEngine;
using Zenject;
public class GameInstaller : MonoInstaller
{
[SerializeField]
GameObject ShipPrefab;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameRunner>().AsSingle();
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<ShipInstaller>(ShipPrefab);
}
}
I was able to partially make it work with
[SerializeField] private GameObject[] ships;
...
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewGameObjectMethod(SpawnShip);
...
private void SpawnShip(DiContainer container, float speed)
{
container.Bind<ShipFacade>().AsSingle();
container.Bind<Transform>().FromComponentOnRoot();
var shipPrefab = ships[Random.Range(0, ships.Length)];
var ship = container.InstantiatePrefab(shipPrefab);
container.Bind<ShipHealthHandler>().FromNewComponentOn(ship).WhenInjectedInto<ShipFacade>();
container.BindInstance(speed).WhenInjectedInto<ShipInputHandler>();
}
But it's awkward and in this case I guess I'm not using an advantage of sub-container. And also prefabs spawns in an empty GameObject.
What I want to achieve is to be able to use ShipInstaller for sub-container binding.
You're right, there wasn't really a very elegant way to choose the sub-container prefab dynamically.
I took some time to make this better today with this commit. If you use the latest version of Extenject then you can now do things like this:
public class QuxInstaller : Installer {
float _speed;
public QuxInstaller(float speed) {
_speed = speed;
}
public override void InstallBindings() {
Container.BindInstance(_speed);
Container.Bind<QuxFacade>().AsSingle();
}
}
public class CubeInstaller : MonoInstaller
{
public List<GameObject> QuxPrefabs;
public override void InstallBindings()
{
Container.BindFactory<float, QuxFacade, QuxFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<QuxInstaller>(ChooseQuxPrefab);
}
UnityEngine.Object ChooseQuxPrefab(InjectContext _) {
return QuxPrefabs[Random.Range(0, QuxPrefabs.Count)];
}
}