Audio not playing when triggered from script in Unity - c#

I made a script that is supposed to manage audio files, so multiple can play at once.
Here is the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MultipleSoundManager : MonoBehaviour
{
#region AudioSource Variables
public AudioSource AS1;
public AudioSource AS2;
public AudioSource AS3;
public AudioSource AS4;
public AudioSource AS5;
#endregion
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void AudioSourceManager(AudioClip clippy)
{
for (int i = 1; i > 1; i++)
{
AudioSource[] audioManagerArray = {AS1, AS2, AS3, AS4, AS5};
audioManagerArray[i].clip = clippy;
audioManagerArray[i].Play();
if(i == 5)
{
i = 1;
}
}
}
}
And the AudioSourceManager function is called from other scripts like so:
public MultipleSoundManager soundManagerScript;
soundManagerScript.AudioSourceManager(AKMShot);
When this function is called, nothing happens whatsoever. I am a bit of a noob to Unity, and I really don't know what the issue is.

I think your problem is
for (int i = 1; i > 1; i++)
How many times do you think this cycle will run?

If you provide more information, it will help a lot.
Here is some suggestion:
1.Try to find out if the [AKMShot] is null, may be is this cause the problume.
2.Try to find out if the [AS1/2/3/4/5] is null, may be is this cause the problume.
2.you can use "foreach" to replace the "for" in your [AudioSourceManager], try to google "C# foreach" and learn, but it cant solve this question, it just can make your code look pretty.
3.The "start" and update part is useless. You can just delete them.(also just to make your code pretty)

The code will never run into the for-loop:
for (int i = 1; i > 1; i++)
{
//...
}

Related

How to get the index of an element (an array of buttons)

I have an array of 9 strings.
I also created 9 UI buttons.
Task:
when pressing the button [0] the line [0] appears.
when button [1] is pressed, line [1] appears
and so on.
using Assembly_CSharp;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
public class WorldMapScr : MonoBehaviour
{
public GameObject RoomMap;
public TMP_Text txtHeader;
public TMP_Text txtDescription;
public TMP_Text txtNameRoom_1;
public TMP_Text txtNameRoom_2;
public TMP_Text txtNameRoom_3;
public TMP_Text txtNameRoom_4;
public Button[] buttons;
allTxtRoomMap txtRoom = new();
private void Update()
{
for (int i = 0; i < buttons.Length; i++)
{
buttons[i].onClick.AddListener(OpenWindow);
txtHeader.text = txtRoom.headerAndDestcriptionlvl[i];
txtDescription.text = txtRoom.headerAndDestcriptionlvl[i];
txtNameRoom_1.text = txtRoom.roomLvlName[i];
txtNameRoom_2.text = txtRoom.roomLvlName[i];
txtNameRoom_3.text = txtRoom.roomLvlName[i];
txtNameRoom_4.text = txtRoom.roomLvlName[i];
break;
}
}
void OpenWindow()
{
RoomMap.SetActive(true);
}
}
I understand that the operations in the for loop don't matter because there is a "break". I sent this code only for an example, so that you understand what I want to achieve. I also want to clarify. The easiest way would be to just create a few separate methods for each button, but that's completely unprofessional in my opinion. Please tell me how this can be done with an array of buttons. Thanks for any replies.
Added:
Thank you very much for the explanation and code example. Of course, with your help, I managed to run the code, but as you rightly pointed out, because of the for loop, listening and reacting occurs many times. This significantly affected the speed. In the end I have this:
private void Update()
{
for (int i = 0; i < buttons.Length; i++)
{
int index = i;
buttons[i].onClick.AddListener(() => OpenWindow(index));
}
}
void OpenWindow(int i)
{
RoomMap.SetActive(true);
Debug.Log(i);
txtHeader.text = txtRoom.headerAndDestcriptionlvl[0, i];
txtDescription.text = txtRoom.headerAndDestcriptionlvl[1, i];
txtNameRoom_1.text = txtRoom.roomLvlName[i, 0];
txtNameRoom_2.text = txtRoom.roomLvlName[i, 1];
txtNameRoom_3.text = txtRoom.roomLvlName[i, 2];
txtNameRoom_4.text = txtRoom.roomLvlName[i, 3];
}
To be honest, I don't have any idea how I can implement the same without using "for". If you have any ideas let me know. Thank you again. I just put the listener in the Start method and it worked. But I'm still confused: did I do the right thing?
P.S:Delegation is a topic I haven't gotten to yet, but will soon!
You don't have to create separated methods for each button. Create one method with an integer parameter, pass this method to every Button and pass it's corresponding number as parameter. Then the appear the line with the m_textList[number] value where m_textList contains your text.
You can add a function with parameters to the button's listener by using a delegation. Here's a small example that shows how it works with an int, but this can be applied to any type.
void Start()
{
for (int i = 0; i < buttons.Length; i++)
{
int tempvalue = i;
buttons[i].onClick.AddListener(() => Examplefunction(tempvalue));
}
}
void Examplefunction(int i)
{
Debug.Log(i);
}
Note that i is saved in tempvalue, which is passed to the function. This is done so that it doesn't print the Length of the button array, but rather the correct index that was set at the time.

Trying to create an interruptible dialogue system, with subtitles, using coroutines

I am currently working on a game where to characters are having a conversation. There are a few dialogue options, but the conversation trees are quite simple. A key aspect is that the player is able to interrupt the other party mid-sentence and change the course of the conversation.
To preface, I have learned all I know from youtube and I've been getting by on increasingly complex if-statements, so I'm trying something new here.
So I did my first attempt using what I know: Invoke and if-statements.
public class SubtitleSystem : MonoBehaviour
{
public float subtitleTimeBuffer;
public string[] dialogue;
public string[] specialDialogue;
public float[] subTiming;
public float[] specialSubTiming;
public AudioClip[] diaClips;
public Text sub;
AudioSource player;
int subNum;
int specialSubNum;
// Start is called before the first frame update
void Start()
{
player = gameObject.GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (sub.enabled == false) {
SubSystem();
}
}
}
void SubSystem()
{
if(subNum < dialogue.Length)
{
if (subNum != 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = dialogue[subNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
subNum++;
}
else if (subNum == 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = specialDialogue[specialSubNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
Invoke("SpecialSub", specialSubTiming[specialSubNum]);
}
} else
{
Debug.Log("No more dialogue");
}
}
void DisableText()
{
sub.enabled = false;
}
void SpecialSub()
{
sub.text = dialogue[subNum];
subNum++;
}
This was functional, though clunky and very work intensive for the person that has to find the individual timing for each line of dialogue and manually break up a subtitle line if it was too long.
Another big problem was that it was impossible to fully interrupt dialogue, because Invoke was called and would run regardless of what I pressed. Maybe I could add some kind of bool condition to prevent that, but the project I'm working on will have a few hundred lines of dialogue, so I need to come up with something where I don't have to type in every line in the inspector and find the manual timing.
This is where it becomes murky for me as I am unfamiliar with a lot of these methods.
An obvious solution to the Invoke problem would be to use Coroutines. These can be interrupted and I could even have a check for input inside a loop to let a player interrupt.
IEnumerator SubtitleRoutine()
{
while (DialogueIsPlaying)
{
//Display Subtitles
if(Input.GetButton("Interrupt button"){
//interrupt
}
yield return null;
}
//Wait for next piece of dialogue
}
Something like that is what I'm imagining.
The next problem is tying dialogue to some kind of system so I can pull the correct piece of audio and display the correct subtitles. In my first attempt this was simple enough because the four pieces of test audio I created were short sentences, but if a character speaks for longer it would be tedious to break up the dialogue manually.
Alternatively I thought about breaking the audio files up into "subtitle-length" so every audio file had a subtitle string directly associated with it, but this seems inefficient and troublesome if dialogue needs to change down the line.
So I thought if I could somehow create a class, that contained all the information needed, then my coroutine could pull in the correct dialogue using it's id (perhaps an integer) and plug in all the information from the object into my coroutine.
So something like this:
public class dialogue
{
//Int ID number
//Audiofile
//Who is speaking
//Length
//Subtitle String 1
//Subtitle String 2
//Subtitle String 3
// etc
}
IEnumerator SubtitleRoutine(dialogue)
{
while (DialogueIsPlaying)
{
//Display Subtitles - divide number of subtitle string by Length and display each for result.
if (Input.GetButton("Interrupt button"){
//interrupt audio and subtitles - stop the coroutine
//set correct dialogue Int ID for next correct piece of dialogue and start coroutine with new dialogue playing.
}
yield return null;
}
//Wait for next piece of dialogue
}
Though this is all outside of what I know, from what I've been able to read and understand, this seems like it might work.
So my question is:
Is this approach going to work?
If so, where should I look for ressources and help to teach me how?
If not, what methods should I look at instead?
Thank you so much for your help!
So what I understand is that you want to make an interruptable dialog system. You chose the coroutine approach which is a great choice, but you're not using it to its full potential. When you use StartCoroutine(IEnumerator _enumerator); you'll get a coroutine class back. If you store it, you can later use StopCoroutine(Coroutine _routine); to stop it. So you won't have to use a while loop or if statements to check interrupting.
Hope this will help you. If it doesn't I'll send some code.
After receiving some help from a coding mentor I found a system that works using a custom class as a datatype and coroutines to display subtitles with correct timing.
The names of the variables are in Danish but the code works in Unity without issues.
using System.Collections.Generic;
using UnityEngine;
public class Undertekster
{
public int id;
public AudioClip audioFile;
public float length;
public string[] subtitles;
public bool isMonk;
public SubSystem subsys;
public Undertekster(int id, int dialogClipNummer, float length, string[] subtitles, bool isMonk, SubSystem subsys)
{
this.subsys = subsys;
this.id = id;
this.audioFile = subsys.dialogClip[dialogClipNummer];
this.length = length;
this.subtitles = subtitles;
this.isMonk = isMonk;
}
}
Notice that use another script when constructing the class to make use of Monobehavior. That way I can assign the correct audiofile to each line of dialogue using an array created in the inspector. The proper way would probably be to look for the file somehow, but that's beyond me.
Next is the subtitle system. For demonstration you hit space in-game to start dialogue and hit F to interrupt. The "subtitles" are Debug.log in the console, but you can easily tie them to a Text object in the UI.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SubSystem : MonoBehaviour
{
[Header("DialogClip")]
public AudioClip[] dialogClip;
[Header("Indstillinger")]
public float subtitleBuffer;
public AudioSource munk;
public AudioSource salamander;
List<Undertekster> alleUndertekster = new List<Undertekster>();
int currentDialogueNumber;
Undertekster currentDia;
float timeDivided;
void Start()
{
currentDialogueNumber = 1;
LoadDialog();
}
public void LoadDialog()
{
alleUndertekster.Add(new Undertekster(1, 0, dialogClip[0].length, new string[] { "Kan du huske mig?" }, true, this));
alleUndertekster.Add(new Undertekster(2, 1, dialogClip[1].length, new string[] { "Øh...", "Lidt..." }, false, this));
alleUndertekster.Add(new Undertekster(3, 2, dialogClip[2].length, new string[] { "Jeg er din nabo din idiot!" }, true, this));
alleUndertekster.Add(new Undertekster(4, 3, dialogClip[3].length, new string[] { "Shit!" }, false, this));
}
IEnumerator PlayNextDialogue()
{
int count = 0;
while (munk.isPlaying || salamander.isPlaying)
{
ShowSubtitle(count);
yield return new WaitForSeconds(timeDivided + subtitleBuffer);
count++;
yield return null;
}
//yield return new WaitForSeconds(subtitleBuffer);
currentDialogueNumber++;
Debug.Log("Coroutine is stopped and the current dialogue num is " + currentDialogueNumber);
StopCoroutine(PlayNextDialogue());
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
InterruptDialogue();
}
if (Input.GetKeyDown(KeyCode.Space))
{
StartDialogue();
}
}
public void StartDialogue()
{
currentDia = alleUndertekster.Find(x => x.id == currentDialogueNumber);
timeDivided = currentDia.length / currentDia.subtitles.Length;
if (currentDia.isMonk)
{
munk.clip = currentDia.audioFile;
munk.Play();
} else if (!currentDia.isMonk)
{
salamander.clip = currentDia.audioFile;
salamander.Play();
}
StartCoroutine(PlayNextDialogue());
}
public void InterruptDialogue() {
StopCoroutine(PlayNextDialogue());
munk.Stop();
salamander.Stop();
currentDialogueNumber++;
StartDialogue();
}
public void ShowSubtitle(int i)
{
if(i <= currentDia.subtitles.Length - 1)
{
Debug.Log(currentDia.subtitles[i]);
} else
{
return;
}
}
}
I chose to put all the dialogue classes into a list so it was easily searchable for the id-numbers. It might have been better to use a Dictionary, but this worked for me and that was good it enough for this project.
With this system, my manuscript writer can put in every line of dialogue with its associated audioclip in the LoadDialog() function and determine who is speaking and in how many pieces the subtitles should be broken into to fit on the screen. They are then displayed one after the other while the audio is playing.
This probably isn't the best solution in the world, but I hope it works for whoever might need it - plus I learned a ton.

Unity PlayerPrefs is not updating my 'high score'

I'm making a 2D game where you're in the middle of the screen and you move round an endless green (screen) world and white cubes spawn randomly around you, and I have finished the game mechanics and a main menu and game over screens. The one thing I'm trying to add now is a high score. I did a bit of research and found PlayerPrefs is probably the way to do it. I have a seperate scene for my main menu and my gameplay level (which includes the game over screen). I have no error messages. I have created a HSSetter (High Score Setter) script on the high score text in the main menu screen:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Start is called before the first frame update
void Start()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
}
and in my score script which is in my actual game level, here's the bit where I try to create the high score:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class score : MonoBehaviour
{
public int scoreCount = 0;
public int highScoreIFA;
void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
void Update()
{
if (scoreCount >= highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
}
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
}
}
}
In AddToScore(), I increment scoreCount.
Through some debugging, I have found that everything in the HSSetter script works - when I change the highScoreText.text, the text on screen changes, which led me to believe the issue might be with the change of scenes? Thanks!
Multiple things you should do here
The first you already updated in your question afterwards: You had the condition wrong and always updated only
if(highScoreIFA > scoreCount)
which would almost always be the case.
Now you have changed it to
if(scoreCount >= highScoreIFA)
which still is not good since if the score is equal there is no reason to update it, yet.
I would rather use
if(scoreCount > highScoreIFA)
so only really update it when needed.
Secondly in both scripts do not use Update at all! That is extremely inefficient.
I would rather use event driven approach and only change and set stuff in the one single moment it actually happens.
You should only one single class (e.g. the score) be responsible and allowed to read and write the PlayerPrefs for this. I know lot of people tent to use the PlayerPrefs for quick and dirty cross access to variables. But it is exactly this: Quick but very dirty and error prone.
If you change the keyname in the future you'll have to do it in multiple scripts.
Instead rather let only the score do it but then let other scripts reference it and retrieve the values directly from that script instead
And finally you should use
PlayerPrefs.Save();
to create checkpoints. It is automatically done in OnApplicationQuit, bit in case your app is force closed or crashes the User would lose progress ;)
Might look like
public class score : MonoBehaviour
{
public int scoreCount = 0;
// Use an event so every other script that is interested
// can just register callbacks to this
public event Action<int> onHighScoreChanged;
// Use a property that simply always invoked the event whenever
// the value of the backing field is changed
public int highScoreIFA
{
get => _highScoreIFA;
set
{
_highScoreIFA = value;
onHighScoreChanged?.Invoke(value);
}
}
// backing field for the public property
private int _highScoreIFA;
private void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
// Only update the Highscore if it is greater
// not greater or equal
if (scoreCount > highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
// Save is called automatically in OnApplicationQuit
// On certain checkpoints you should call it anyway to avoid data loss
// in case the app is force closed or crashes for some reason
PlayerPrefs.Save();
}
}
}
}
Then your other script only listens to the event and updates its display accordingly. It is even questionable if both scripts should not rather simply be one ;)
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Reference your Score script here
[SerializeField] private score _score;
private void Awake ()
{
// Find it on runtime as fallback
if(!_score) _score = FindObjectOfType<score>();
// Register a callback to be invoked everytime there is a new Highscore
// Including the loaded one from Start
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnDestroy()
{
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnHighScoreChanged(int newHighScore)
{
highScoreText.text = $"High Score: {newHighScore}";
}
}

Variables in other scripts in Unity

I am trying to create a game in Unity 2d. I have finished most of what I want to do and have moved on to the enemies. The enemies (dragons) come in from different points of screen. To do this I have placed sprite game objects where I want the dragon to spawn. I have made all of these objects a child of another object called DragonAncores. I attached a script to DragonAncores which says this...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DragonTracker : MonoBehaviour {
// is gold dragon in play?
public bool GoldDragonInit = false;
// curently active dragons
public int DragonCount = 0;
// defalts to 5
public int Difficulty = 5;
}
I am then attaching a script to each sprite which will eventually summon in a dragon Prefab (containing 2 colliders and an animator) biased of If statment logic derived from the other variables.
Below is the code I am using.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dragons : MonoBehaviour {
// same as GoldDragonInit
bool GoldDragonSpawn = false;
// same number as DragonCount in DragonTrackeer
int LiveDragons;
// same as Difficulty
int DifLev;
//get cariables from other script
DragonAncors.cs.GetComponent.<DragonTracker>() GoldDragonInit = GoldDragonSpawn;
System.Random RNG= new System.Random();
void update()
{
RSpawn=RNG.Next(0,2)
DragonType=RNG.Next(0,101)
if (RSpawn = 1) ;
{
if (LiveDragons > DifLev) ;
{
if (DragonType > 99) ;
{
// summon regular dragon
}
if (DragonType = 100) ;
{
if (GoldDragonSpawn = true) ;
{
// summon gold dragon
}
}
}
}
}
}
This is throwing up this error list.
This shows my hierarchy in unity and the anchor points (the Squair crosshair looking things)
I have looked for other threads that adress this topic and they all try different methods, none work.
I am using Unity 2018.2.18f1
There are a few errors in your code here. The following is incorrect.
//get cariables from other script
DragonAncors.cs.GetComponent.<DragonTracker>() GoldDragonInit = GoldDragonSpawn;
The correct way to access this, seeing as you said DragonAncors is the parent would be:
GetComponentInParent<DragonTracker>().GoldDragonInit = GoldDragonSpawn;
This sets the GoldDragonInit Boolean to the value of GoldDragonSpawn. This has to be inside a function, as you have it outside of a function I presume you needed this set on start. Therefore I have placed it in the void Start() function. This is called at the start of the game(loaded scene).
You also do not need semi-colons ; after an if statement, however it does need to appear after every line of difinitive code. The code you have provided should instead look like this.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dragons : MonoBehaviour {
// same as GoldDragonInit
bool GoldDragonSpawn = false;
// same number as DragonCount in DragonTrackeer
int LiveDragons;
// same as Difficulty
int DifLev;
void Start()
{
// variables from other script
GetComponentInParent<DragonTracker>().GoldDragonInit = GoldDragonSpawn;
}
System.Random RNG= new System.Random();
void update()
{
RSpawn=RNG.Next(0,2);
DragonType=RNG.Next(0,101);
if (RSpawn = 1)
{
if (LiveDragons > DifLev)
{
if (DragonType > 99)
{
// summon regular dragon
}
if (DragonType = 100)
{
if (GoldDragonSpawn = true)
{
// summon gold dragon
}
}
}
}
}
}
This works because DragonTracker is a script in the objects parent. If this was not the case then GetComponentInParent().GoldDragonInit = GoldDragonSpawn; would be replaced like so:
[SerializeField]
private GameObject DragonAncors;
void Start()
{
DragonAncors.GetComponent<DragonTracker>().GoldDragonInit = GoldDragonSpawn;
}
This is not valid c# code:
//get cariables from other script
DragonAncors.cs.GetComponent.<DragonTracker>() GoldDragonInit = GoldDragonSpawn;
Why? Because it isn't inside a method.
Also, the comment is wrong. It isn't getting a variable (typo, too), its setting a variable in another script!
The reason for the first...16 problems Visual Studio is complaining about are because of this line.
At this location you are only allowed to declare methods, fields, and properties and you're currently trying to access another class and change one of its members (something you can only do inside a method).
Additionally, you have .cs which I assume is because "DragonAnchors.cs is the file name!" which you don't need to do. I'm not sure how to go about rewriting this line (inside Start()) as I'm not sure what you're trying to actually do. That is, I don't know where an instance of DragonAnchors actually resides. You're calling GetComponent(), which is typically reserved for accessing components attached to game objects, but as you've attempted to do so on a static class reference, I'm not sure if you meant to call it on this or on something else.
This is how you can get to DragonTracker:
DragonTracker dt = GameObject.Find("DragonAncores").GetComponent<DragonTracker>()
Debug.Log(dt.DragonCount);
There are a lot of errors there and they may take some steps to go through, but first things first you should clear up the issue with the code you're trying to use being unsupported. Go into the project settings and change the compiler language version as it notes on the 5th error down. This should allow you to use the newer functionality.

Get level index by using scene name in Unity

I know that there is a way to get level name by index:
string levelName = Application.GetLevelNameByIndex(2);
But is there a way to identify the level index of a scene using its name?
Looks like that's not very well supported right now, see this answer.
Only in editor - as per this question.
Vote for it here, it's a bizarrely absent feature.
Note that if you want to show text to the user, you probably want a long name, a description, and a screenshot anyway.
EDIT: Found an answer that has an editor script that may be of assistance.
Here is a method you could use, assuming you know how many scenes are in your game.
using UnityEngine;
using System.Collections;
public class SceneDetector : MonoBehaviour {
public int numberOfScenes = 5;
public String[] sceneNames;
void Start() {
sceneNames = new String[numberOfScenes];
for(int i = 0; i < numberOfScenes; i++)
{
sceneNames[i] = Application.GetLevelNameByIndex(i);
}
}
public int GetSceneIndex(String sceneName)
{
for(int i = 0; i < sceneNames.length; i++)
{
if(sceneName == sceneNames[i])
{
return i;
}
}
return -1;
}
}
And of course as you can see if you run GetSceneIndex and it returns -1, then the string you passed in is not a name of a scene.

Categories

Resources