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.
I'm going to enable bloody screen when got attack then disble it for two seconds.
The first way I tried is using SetActive (true/flase) .
It could work once but never worked back anymore.
I found the question by google, it seems that function will disable everything of that component.
So I found the second way which is using Image Class to do this.
but I not sure whether the way I used correctly or not.
The error msg : Object reference not set to an instance of an object
First Way (working once)
public class GameControllerScript : MonoBehaviour {
public GameObject bloodyScreen;
void Start () {
bloodyScreen.gameObject.SetActive (false); // disable at first
}
void Update () {
}
public void zombieAttack(bool zombieIsThere ){
bloodyScreen.gameObject.SetActive (true);
StartCoroutine(WaitTwoSeconds());
}
IEnumerator WaitTwoSeconds(){
yield return new WaitForSeconds (2f);
bloodyScreen.gameObject.SetActive (false);
} }
Second Way (not working)
public class GameControllerScript : MonoBehaviour {
public Image image;
void Start () {
GameObject go = GameObject.Find("Canvas");
if (!go)
return;
image = go.GetComponent<Image>();
image.enable = false;
}
void Update () {
}
public void zombieAttack(bool zombieIsThere ){
image.enabled = true;
StartCoroutine(WaitTwoSeconds());
}
IEnumerator WaitTwoSeconds(){
yield return new WaitForSeconds (2f);
image.enabled = false;
} }
Sorry for my english if soemthing is not clear.
What my object likes https://i.stack.imgur.com/DChuS.png
there are so many ways of doing the BLOODY SCREEN.
1.)
STEP 1
Make an array of texture then put all of your texture there
STEP 2
On your OnTriggerEnter or OnCollisionEnter or what ever function you want and try implementing this code
Texture[] arrayOfTexture = new Texture
foreach(Texture textures in arrayOfTexture){
//set the alpha here from 1-0/0-1
}
2.)
You can do it also in an Update() function and put it on a timer you can do it something like
float timer = 10f;
void Update(){
timer -= time.DeltaTime;
if(timer < 1){
//reset timer here
}else{
//do the alpha thing here
}
}
void ResetTimer(){
timer = 10f;
}
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 4 years ago.
So I'm getting an error which says
Object reference not set to an instance of an object Explode.LateUpdate () (at Assets/Scripts/Explode.cs:40)
I'm not sure why I get this error as everything works fine and I have the exact same script in a different scene without the error message. I'd love any help please.
I'm exploding my player with different coloured boxes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explode : MonoBehaviour {
[System.Serializable]
public class ExplodeColours
{
public Color32[] Colours;
}
public static bool explode, explodeOnce;
public ParticleSystem Explodes;
private ParticleSystem explosionSystem;
public Transform player;
public List<ExplodeColours> ColoursList;
// Use this for initialization
void Start () {
PlayerPrefs.SetInt("SelectedChar", 0);
PlayerPrefs.Save();
explodeOnce = false;
explode = false;
}
// Update is called once per frame
void Update () {
if (explode == true)
{
explodeOnce = true;
PlayerExplode();
}
}
void LateUpdate()
{
if (explode)
{
ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[explosionSystem.main.maxParticles];
//ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[Explodes.main.maxParticles];
int NumParticlesAlive = explosionSystem.GetParticles(Particles);
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i].startColor = (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
explosionSystem.SetParticles(Particles, NumParticlesAlive);
explode = false;
}
}
void PlayerExplode()
{
explosionSystem = Instantiate(Explodes, player.position, player.rotation);
explosionSystem.Play();
}
}
If this is line 40 (I had to count):
ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[explosionSystem.main.maxParticles];
Then explosionSystem or main or maxParticles is null. I'm going to close this as a duplicate, this error is so common and the way to resolve it is easy.
But a break point on the line of code, when the code control halts, check which object is null and make sure its set.
EDIT:
Use the GetComponent to initialize the explosionSystem variable in the Start function.First, find the GameObject the ParticleSystem is attached to the use GetComponent to get the ParticleSystem.
private ParticleSystem explosionSystem;
void Start()
{
GameObject obj = GameObject.Find("NameOfObjectParticleSystemIsAttachedTo");
explosionSystem = obj.GetComponent<ParticleSystem>();
}
Also, inside the for loop, you tried to use Particles's startColor property without initializing each one. You can do that with Particles[i] = new ParticleSystem.Particle();.
Change:
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i].startColor
= (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
to
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i] = new ParticleSystem.Particle();
Particles[i].startColor
= (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
To avoid the null reference error, make sure all variables (which are using) has values other wise at least assign default values in constructor level
I have a script to position the player where fell off from the platform.
Platform Tracker.cs
public GameObject platformTrackerPoint;
public Vector3 platformTransform;
public PlayerMovement thePlayerMovement;
void Start()
{
platformTrackerPoint = GameObject.Find("PlatformTrackerPoint");
thePlayerMovement = FindObjectOfType<PlayerMovement>();
}
void Update()
{
if ((transform.position.x < platformTrackerPoint.transform.position.x) && thePlayerMovement.grounded)
{
platformTransform = gameObject.transform.position;
Debug.Log ("Plaform Transform =" + platformTransform);
}
}
As I debugged, platformTransform shows the value I need. But its not reflecting in the code below.
GameController.cs
public PlatformTracker thePlatformTracker;
public Vector3 tempPosition;
void Start () {
platformStartPoint = platformGenerator.position;
playerStartPoint = thePlayer.transform.position;
thePlatformTracker = FindObjectOfType<PlatformTracker>();
thePlayer = FindObjectOfType<PlayerMovement>();
}
public void RespawnPlayer()
{
thePlayer.transform.position = thePlatformTracker.platformTransform;
}
Your help is much appreciated. Please let me know if anything is not clear.
To access thePlatformTracker correctly, you need to use GetComponent. In GameController.cs Start() simply change:
thePlatformTracker = FindObjectOfType<PlatformTracker>();
To:
thePlatformTracker = GameObject.Find("").GetComponent<Platform Tracker>();
Be sure and add the game controller object name between the GameObject.Find("") quotation marks.
Then you should be able to access the platformTransform no problem since thePlatformTracker is now correctly referencing the script.
I can successfully load a material saved in my Unity project using the code below:
RenderSettings.mat1 = (Material)Resources.Load ("images/img1.jpg", typeof(Material));
However, I am now trying to load an external image by downloading it.
Texture2D imgDownloaded;
string url = "http://www.intrawallpaper.com/static/images/1968081.jpg";
void Start()
{
StartCoroutine(getImg());
fucntionx ();
}
public void functionx()
{
RenderSettings.mat1 = (Material)imgDownloaded;
}
IEnumerator getImg()
{
yield return 0;
WWW dl = new WWW(url);
yield return dl;
imgDownloaded = dl.texture;
}
However, I get the message that I cannot convert from Texture2D to Material .
Is there any way to fix this?
Try:
yourMaterial.mainTexture = yourTexture;
A material consists of many textures, so naturally you can't convert between them.