#Dialog Disaster XD
1 messages · Page 1 of 1 (latest)
Alright.
These are the first script called Dialog Manager
if hes at a restaurant.. might be nice to use a paste-bin website..
2nd script, Object Dialog
📃 Large Code Blocks
Use links to services like:
https://paste.mod.gg/, https://hastebin.skyra.pw/, https://paste.ofcode.org/, https://paste.myst.rs/, https://scriptbin.xyz/
📃 Inline Code
Surround code with three backquotes. Not quotation marks.
To format as C#, add cs to the first line:
```cs
// Your code here
```
Add a comment with a line number if there is an error message.
Havn't used one unless you mean like DropBox. Is there a free one that's preffered?
Pastebin is about as easy as it gets.
@calm raven Thanks!
copy- paste ur code in there.. hit the save button.. and give us the link it generates in the URL bar
got u mate 👍
Ok will do, thanks!
np.. if u paste the entire scripts, its easier for us..
and we can just look at the Class names to figure out which scripts are what
Saves spinning a phone like a steering wheel, too.
Choose C# as the syntax highlight option, if you can, too pls.
my dialog systems consist of a few sentences, an icon, and a toast notification type window
yup, thats the main class correct?
and u can do the same for any other scripts u have that might matter
and then.. just so we're all on teh same page.. can u state the issue?
- whats happening..
- what you expect to happen..
- etc
Yeah, so what I'd like to happen is that a dark box I made appears for the background and the text types out green in front (rightish). To the left is a character animation of 4 frames.
Currently I have nothing happening after trying to fix something. Just before that, I did have the box appear, and the text run, but no character profile. The text had an issue where it would keep typing out each time you hit the mouse until you closed out of the dialog box (it has a "Close" button.
so the TypeText coroutine working as it should?
and the text formatting
Yeah so in video, the cursor circles around where the character's head animation should be
im sorta confused on what the AnimateTextures is supposed to do
Okay, so just to be clear, are you wanting to add this functionality? Is a partially-working version of this already in the code? I’m just settling to look a it now.
I'm trying to keep scripts universal of course so clicking other objects will have different text and character image.
yea, thats fine.. thats how u should do it..
each time u type dialog u should have like an icon, a color, and a string..
a string "minimum" lol
I had AI try to troubleshoot the issue and the current script that I sent you has removed the textbox entirely, though the component fields in screenshots remain.
ohh jeez.. i wouldnt tell too many ppl that 😄
guys around these part are anti-AI.. even if ur using it responsibly lol
Oh ok. I am definately anti-AI when it comes to image generation, but wasn't sure about the ethics aspect for coding. I get it isn't a 'fix-all' solution to everything so trying to be careful with it.
Yeah, as the video shows, the text and font is easily adjustable per object.
Okay, so first of all, I would avoid AddComponent and unless you think you’ll really need to be adding components at runtime. You are better off just placing an Image component through UGUI and referencing it in source. I’m guessing AI wrote this?
Is Photo_Sprite the thing that you want to show the animation?
I undid the script to the previous one. This is a video of what it does now. It's the same as previous video, but shows how clicking multiple times generates the text again and again until closed.
Photo_Sprite is the object in video that says "Friends". It has a separate script to blow it up and that script is separate completly from the dialog.
u should add a debug at the beginning of ur coroutine.. and at the end of ur coroutine
to try to compare that to ur mouse-clicks..
see when the coroutine starts and ends..
This is to fix the text issue?
well its a start yes. we need to figure out whats going wrong.. debugging helps do that
so we can follow along with the code..
maybe catch something happening that shouldn't. or vice versa
ur coroutine looks like it should go until the text has all been typed.
not only when u click.. (you should probably add a delay in ur script so you cant click again.. until the text is finished) or.. since people would probably hate that.. when u click once the text starts -->
and then it should continue until finished.. or if u click again (or press esc or something).... then that should just end the dialog box right?
Here's the updated script that is working in the videos (no Debug yet)
A tool for sharing your source code with the world!
i have a dialog script too.. im trying to open the project so i can compare to mine and see if i notice anything out of place..
but its been loading for 10 minutes so far 😄
Actually yeah, should I expect the loading to to increase more-and-more as game is built? This is my first scene and was wondering if it is ever normal to separate parts of the game into separate projects?
ya, im gonna make a decision now..
-# my bet is:
- u causing multiple coroutines to start and run at the same time..
whenShowDialogis called again before the previous typing effect is finished, a new coroutine starts while the old one is still running..
thats probably whats causing the text to appear in chunks when u click multiple times
projects no..
seperating ur code-base into different assemblies +/or namespaces yes
it usually only takes a while the first time.. once its open and all the cache exists in ur c:/boot drive then it'll load up way quicker..
and one the quirks of unity is it just gets slower the longer its open...
best advice i can give you is restart as often as u need to keep it running fast
So in this part, should I have after it an "If" statement to say no trigger while typing?
finally opened..
so im guessing this is what ur going for..
you need to probably either
- cache the coroutine when u start it (that way u can stop it if u need to)
- orr... stop all the coroutines that are running
StopCoroutines()
when u call it.. (that'll stop the old one.. and start a brand new one..)
no overlaps
Yep, that is pretty on-point with character profile using 4 frames for animation. Some dialog boxes use other animation with more frames so trying to use a texture array like I have with grabby cursor
Okay, so I’ve admittedly been a bit distracted.
Do you understand how coroutines work?
Not really TBH
i'll show u my script..
but i was gonna do that last resort
u dont really learn much by copying tbh
That's true
Okay, so AnimateTextures is a coroutune. It’s a bit like Update() in that it runs once per frame.
The animate textures? That was supposed to be for the texture array for the character profile,
ohh!
Let me show you my cursor one which works
Separate script of course, but you see at the bottom I can assign frames to animate
soo like this?
Can you post the source to your file directly in Discord with following format?
‘ ‘ ‘ cs
source
‘ ‘ ‘
those should be backticks without spaces.
` <- these.. u can copy and paste..
but its the key typically above the tab key
lol, thats for someone else.. but it works here too
It turns out I can’t select text in pastebin on my phone. >_>
Sick work dude.
Ok. You need the whole script like that or were you needing just a part of it?
If you post the whole thing, I can just scroll up to it.
And copy/paste parts in here with edits.
public void ShowDialog(string message, Texture[] animationFrames)
{
dialogText.text = "";
dialogPanel.SetActive(true);
if (typingCoroutine != null) // if theres already a coroutine like this running, then stop it
StopCoroutine(typingCoroutine);
// here we'll cache the coroutine so we know specifically which one is running
typingCoroutine = StartCoroutine(TypeText(message));
CreateImage(animationFrames);
}
private IEnumerator TypeText(string message)
{
dialogText.text = "";
foreach (char letter in message.ToCharArray())
{
dialogText.text += letter;
yield return new WaitForSeconds(0.05f);
}
typingCoroutine = null; // when we finish we clear out the cached variable of the coroutine
}```
---------
---------
His last posted script 👇
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class DialogManager : MonoBehaviour
{
public GameObject dialogPanel; // Reference to the dialog panel
public Text dialogText; // Reference to the text component
public Button closeButton; // Reference to the close button
public Canvas canvas; // Reference to the Canvas
void Start()
{
dialogPanel.SetActive(false);
closeButton.onClick.AddListener(CloseDialog);
}
public void ShowDialog(string message, Texture[] animationFrames)
{
dialogText.text = ""; // Clear previous text
dialogPanel.SetActive(true); // Show the dialog
StartCoroutine(TypeText(message)); // Start typing the message
CreateImage(animationFrames); // Create the image directly
}
private void CreateImage(Texture[] textures)
{
// Create a new GameObject for the Image
GameObject imageObject = new GameObject("DialogImage");
// Set it as a child of the dialog panel
imageObject.transform.SetParent(dialogPanel.transform);
// Add the Image component
Image imageComponent = imageObject.AddComponent<Image>();
// Set the size and position (adjust as needed)
RectTransform rectTransform = imageComponent.GetComponent<RectTransform>();
rectTransform.sizeDelta = new Vector2(100, 100); // Set size
rectTransform.anchoredPosition = new Vector2(0, 0); // Set position
// Set the initial sprite as the first texture
imageComponent.sprite = TextureToSprite(textures[0]);
// Start the animation coroutine
StartCoroutine(AnimateTextures(imageComponent, textures));
}
private IEnumerator TypeText(string message)
{
foreach (char letter in message.ToCharArray())
{
dialogText.text += letter; // Add one letter at a time
yield return new WaitForSeconds(0.05f); // Typing speed
}
}
private IEnumerator AnimateTextures(Image imageComponent, Texture[] textures)
{
int index = 0;
while (true) // Loop through textures indefinitely
{
imageComponent.sprite = TextureToSprite(textures[index]);
index = (index + 1) % textures.Length; // Cycle through textures
yield return new WaitForSeconds(0.1f); // Adjust the speed of the animation
}
}
private Sprite TextureToSprite(Texture texture)
{
return Sprite.Create((Texture2D)texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
}
private void CloseDialog()
{
dialogPanel.SetActive(false); // Hide the dialog
}
}```
Thanks! If you put “cs” on the first line with the backticks and then start “public void” on the second line, you get syntax highlighting.
there ya go @ornate hare
anyway.. @frigid trench this was what i was talkin about #1345565687589896253 message
public void Yo(int x) {} ```
about caching the coroutine u currently have running so u can stop it
Thanks. Sorry I'm dumb 😵💫
me too i forgot to link the message ;D
#1345565687589896253 message
This stops coroutine?
No sorry, that was for me.
Yes, so… coroutines.
When you use StarCoroutine(AnimateTextures), that sets a new coroutine on this MonoBehaviour’s coroutine stack, which runs the method AnimateTextures on the next frame when Update() would run - up until where ‘yield return null’ appears in that method.
AnimateTextures handles the flipping of the texture on the Image component for that frame.
Notice while(true). This will run forever technically, but because of how coroutines work, it exits at yield return and picks up at that same point in. The next frame.
Hold on, my wife doesn’t want to drive. Give me 15 minutes.
when i call Speak("BlablaBla"); <- my dialog
it calls Reveal()
that StopsAllCoroutines() before running the other two
private Coroutine typingCoroutine;
typingCoroutine = StartCoroutine(TypeText(message));
StopCoroutine(typingCoroutine);
you'll need to cache the coroutine when u start it.. and then pass that into the StopCoroutine() to stop it.. or StopAllCoroutines
but that only fixes the issue of it not being chunky...
if u click repeatedly.. it'll start over instead of being chunky, and inconsistent..
after that.. then you'll have the rest of the problems still to solve.. but atleast ur coroutine would be not overlapping anymore
https://scriptbin.xyz/rajihebufo.cs you can take a look at mine if u want..
I was going to recommend storing the coroutine like: Coroutine r = StartCoroutine(AnimateTextures); Then, does the animation need to run forever or just once?
thats good too
Loop once or loop forever?
but it'd need to be a global variable right?
When you say TypeTextmessage though for the Start, does that mean it'd have to be the actual phrase (like in video "...idiot...")?
it couldn't be local.. unless he plans on stopping it within that same method
Okay, starting to drive. I’ll talk soon.
you should have a method that just takes a string..
public method that u can call from other scripts..
Ok yeah, that's what I have currently
WriteDialog("Your string or Anything Else You Want");
or
var newString = "Just Another String";
WriteDialog(newString );
Well, ObjectsDialog script has it
when u do that tho.. u need to stop all current coroutines..
and then start ur new ones
thats the whole idea of what we're talkin about rn
thats fine.. (but it should only be called like once per mouse click)
i assume
ya, that all looks fine..
now.. start simple.. (add a debug.log) when u call that method..
I wonder if this is what Omi was reffering to
In the TypeText (replacing StopCoroutin)?
I could do a voice call.
should only get (1) log per mouse click
if u get multiple somethings wrong..
id definitely take Omi up on his offer
it'd be much easier to debug this realtime.
Okay, got the car audio set up.
Yep.
what happens when u click just once
does it still happen in a sequence like that?
first the image.. and then the text
should they both happen at the same time?
Clicking again anywhere (except close button) does the overlapping
They should, but the big size is separate script so wasn't sure how to deal with yet. Was working out other parts
thats yet another issue.. u should start both coroutines at the same time.. so it happens all at one..
not sure why it happens in steps like that..
oh okay.
I had the big size before ever starting dialog stuff
The photo is supposed to grow and be seen, but most other stuff does not, hence the separation so that dialog stuff could be universal
you might need a boolean to use as a flag too..
something like
isBusyTyping
so u could check if thats true before trying to start the coroutine again
Okay, stopped at a train.
private bool isTyping = false;
private IEnumerator TypeText(string message)
{
if (isTyping) yield break; // Prevent multiple coroutines from running
isTyping = true;
foreach (char letter in message.ToCharArray())
{
dialogText.text += letter;
yield return new WaitForSeconds(0.05f);
}
isTyping = false;
}```
simple fix ^ imo
a boolean/flag that gets set to true.. when u begin the coroutine..
doesn't become false until its finished
Okay, so let’s start with the TypeText logic.
First of all, that Boolean is barking up the wrong tree.
once thats in place.. i think u can debug a bit better.. (overlappin coroutines is hard to debug with hehe)
i just brought it up as a "beginner" friendly solution
if he doesn't know how coroutines work in the first place..
thought it might be a good call.. but carry on
Start with this:
IEnumerator TypeText(string textToType) {
}
and pass the text in as a parameter in your ShowDialog method.
ok.. i really gotta get to work this time.. chao!
no worries
Then the logic is….
while(there is still text to type) {
Add a letter to the display
yield return null;
}
That will print one letter, then wait until the next frame.
ah yea, since ur using coroutines a while would work too.. bool still sufficient tho 😈
The clicking issue as far as Having Photo grow and dialog box and text run with one click is still off.
Click photo to grow
Click to make dialog box
Click to make dialog (Should happen with dialog box automatically)
Click to remake dialog (shouldn't happen)
The thing is, when using coroutines, the exit condition should always be getting to the end of the method or exiting by return. Using a bool to control state overrides the fact that the coroutine is either running or not - that should be state enough.
thats fair..
i actually use while in my own system
just thought bool would be quicker / easier.. but yea.. i see what u mean
Sorry, had to move so just reading this now
Would mine be able to use this?
No problem, I just got to my inlaws’ house.
Can I get a Copy/Paste?
if u know how long ur string is.. and how many chars have been typed..
u can do something like while(typedChars < totalChars)
no no no..
most of us often type in psuedo code..
or example code.. (the idea is there.. but we use made up variables and methods and stuff)
Gotcha. I may need to review more coding in general
Why don’t we rebuild ShowDialog from scratch and go over what all it needs to do?
Then we can cover coroutines and their execution step by step to understand what this all does. Coroutines might take some explaining…
#1253440741322133545 message heres a post i made a bit back... has alot of good resources and some beginner (crash-course) playlists..
and theres always !learn later on.. always helps to go and learn/refresh the basics
:teacher: Unity Learn ↗
Over 750 hours of free live and on-demand learning content for all levels of experience!
@frigid trench i'd 💯 take him up on this offer
Mine is DialogManager, but I'm down with that
don't delete urs..
just build with him.. you'll learn some new stuff and some better ways
As I understand, it does two things:
- Write text one letter at a time.
- Animate an image.
and u always got urs to fallback on if something happens
Gotcha, I'll do a separate one
Yeah just comment out that method with /* */
best of luck.. guys 🍀 🧡
Thanks Spawn for helping earlier.
all good.. thats what we're here for
- Writes text
- Displays dialog box background (black)
- SUPPOSED to have animation of character face to left (hasn't worked yet)
Thanks a bunch @calm raven !
||or procrastinating|| 🫠
Okay. Text worked so far, but I think we can make this a bit simpler and generic enough to just copy to the animation.
It’s honestly the same problem animating the image and typing the text, but I think your AI bloated it a bit.
Probably so 😶
Let me know when you’re ready to go with a clean ShowDialog() method, complete with the same parameters that you have now.
Ok, I made a new script that is blank. You want me to Copy?Paste my old one or have us create a new one entirely?
Note, current method uses two scripts
Starting blank is probably good. Yeah, we’ll rebuild those IEnumerators as we go. Make sure that ShowDialog matches the signature that your other script expects.
After we build this new one, I can change the refering parts in the shorter script to call this new larger one
Sure! Either way, start your ShowDialog method (or whatever you want to call it) and we’ll lay out our goals, then build each step from scratch.
Understanding coroutines is key for this effect.
Ok, so goals are to Click an object to summon the black dialog box with text typing out and the close button fading in after, say, 3 seconds (this hasn't been attempted). At the same time, a character profile animation is on the left.
In this specific case, the animation is four frames, but some others go up to 8, so I wanted it to be adjustable for frames and images for universal use. The main character uses a black background box while all other character use a white version. Little difference otherwise.
Text color for main character is neon green. 3 other characters will each have a different color and the rest of the world has black text. I plan to use a different font than ariel, but plan to add that later when I find one.
// Show the box
// Start typing text
// Start animating the portrait
}```
This is how I see the task. Agree?
Simultaneously occuring, yes
We can add colour later, etc.
Okay, copy that, and let’s start replacing those comments one at a time.
Roger
To make this simplest, can you arrange your project so that your gameobject with the background, text and image are on a child object of ShowDialog?
So, like this:
ShowDialog
‘- Canvas
‘— Image, Text, Background
This way, we can disable the canvas object in the inspector and keep the ShowDialog monobehaviour enabled.
Ok, my set up is like this atm. The "Canvas_Locker" has the actual background image. This hold the non-draggable objects like the photo we are working with.
The "Canvas_priority_drag" is invisible and holds all the draggable objects like the trash, books, etc. This is separate so that when items are dragged, they remain on-top of all other images instead of hiding under them during the drag.
Almost everything (dragable and non-) are supposed to have dialog and images as we've discussed in goals.
Okay. Is ShowDialog going on “UI”, then?
Havn't made it yet, but both canvas are UI. The DialogPanel and such that you see is for the previous set-up
Okay, you’re making a UI as we go?
Should I make "ShowDialog"->UI->Image under the "Canvas_Locker"?
In that chain, the black background would be on UI. ShowDialog should have your monobehaviour and nothing else. The child should have the canvas object.
Also, if you didn’t know, it is most performant to use a new canvas for every part of your UI that updates frequently.
You can nest a lot of canvases.
Ok, so I could actually make ShowDialog and have both canvases under it?
Yeah probably.
And selecting UI->Image is ok for this? I believe that was what the DialogManager and such was
You want to be able to disable the game object to hide the UI, but ShowDialog should always be active in the hierarchy.
Oh I see.
ShowDialog is one gameobject with your script on it.
Put a gameobject under that as a child with a canvas.
Add an image using the UI->Image component menu to a new child object under the UI (canvas)
So that’s three objects chained as children.
Canvas_Locker has the canvas component?
Yes, with the image
Okay.
So now, make sure that the text and portrait image are on child objects under that.
Okay, first off, do you have TextMeshPro installed?
So I believe that's the DialogPanel
Okay. We need to change something here.
These fields?
Copy your color and settings that you like and delete the Text component.
You shouldn’t use the Text component as it has been completely replaced with TMP since like 2014. Unity keeps it around for backwards compatibility.
See “Text (Script)”?
That component is highly inefficient.
UI?
Yes, add that one with (UI) in the name.
Then, install the basic package if prompted by a dialog with two buttons. You don’t need the “extras”.
No prompt
Okay, your project probably has the dependencies then.
Okay, now on a separate object at the same hierarchy level, make sure you have a gameobject with an image for your portrait.
The text and image should be equal children of the canvas.
If it's 4 frames?
We need a box to draw in.
Or is this the box/background?
It’s a square place to put a graphic.
Your portrait. Your background is on the same object as your canvas, right?
Background of the dialog box (the black one seen here)
Okay. So canvas_locker has the canvas, dialog panel is an image and portrait is a different image, just to be sure. (There are many ways you could organize this)
Dark area is background dialog box. Profile of character (not seen in video) should be on left side
Yeah, part of the problem that started this thread was that the character profile wasn't showing up when it should.
Perfect. Let’s create a brand new script for your Dialog Panel. Make a new C# script called DialogPanel and have it inherit MonoBehaviour, then put it on you DialogPanel object.
This is an object to act as the thing to hold properties for the separate UI objects that make up the parts. This will make sense in a moment.
Ok, should I delete old one or edit it?
Nope, new file. Well use them all.
What we are doing is separating our concerns. ShowDialog is a controller script which tells the DialogPanel what to do. The DialogPanel is a view script that displays the information.
We are separating our concerns to keep the code clean and shorter in each file.
Great! Delete Update. We won’t need that.
Add the following to the top of the script above Start():
TMPro.TextMeshProUGUI textObject;
Image portraitObject;
sorry, I’m fighting autocorrect
TMPro.TextMeshProUGUI*
Should be in the UnityEngine.UI namespace. If you press Ctrl*. you might get a quick action to add that.
portraitObject should be one word. I got autocorrected on my phone.
Put your text cursor on Image and press ctrl+.
See if it suggests to add UnityEngine.UI as a reference.
Perfect! I wanted to see if you could use VS to add it automatically like the rest of us do.
Does TMPro. look ok? Is there supposed to be a space?
NVM, it doesn't have a space
Let me show you something. Remove TMPro. from the start of that line. This will make the rest of that line underline red, but try it anyway for a second.
ok, red is there
Now, add using TMPro; to the references at the top of the file.
Error is gone, because TextMeshProUGUI is being found in the TMPro namespace.
using basically makes everything in that namespace accessible to the current file.
I see. So does that work with anything typed as "THING.blahblah". Like you can just have "using THING" at top and then have "blahblah" down below?
Exactly, and sometimes you need to because class names only have to be unique relative to their namespace.
So, NamespaceA.MyClass and NamespaceB.MyClass are not the same class.
Different packages sometimes use the same generic names for their parts.
Ok, so you don't throw Namespace A & B up top because you need to separate the 2 different "MyClass"?
You’ll get an ambiguity error if that happens.
Then, you know you only need one, you can get specific like:
using MyClass = NamespaceB.MyClass;
This would not add access to all of NamespaceB, but any reference to MyClass will point to that specific combination of namespace and class name.
using OtherClass = NamespaceA.MyClass;
This would bring in the other class under a totally new name for this file only to break ambiguity.
This is beyond the scope of the lesson, but you should know how to reference other scripts.
ok, makes sense. Altering names for easier reference
Exactly, and also to solve naming issues.
So now, go into your inspector and find the DialogPanel component. Drag the gameobjects with your text and image into the relevant boxes on it.
In the Unity editor, I mean.
Should just be drag and drop.
Which object am I attaching text and image to though? The DialogPanel uses previous script
Okay, with DialogPanel selected, click on your DialogPanel.cs file and drag it over to the inspector. This should add a component called Dialog Panel to your DialogPanel gameobject.
That’s the file that you have open in Visual Studio right now.
Okay, what is the error showing up in your console?
Assets\Scripts\ShowDialog.cs(7,17): error CS0542: 'ShowDialog': member names cannot be the same as their enclosing type
Change yellow to DialogPanel?
So the class name can’t be the same as the method name.
Right, think there was confusion in early set-up
This is because typically, in C#, the constructor is a method with the same name as the class, but we don’t use constructors for technical reasons in Unity that I shouldn’t get into.
Change one of those words. If you change the class name, rename the file to match.
This ok?
Monobehaviours must use a class name that matches the file. Personally, I don’t understand why Unity forces that. I think I get it, but it’s due to an old limitation.
I would recommend naming the class DialogController and the method ShowDialog. Always put the verb in the method name since the method is the thing that does something. The class name should be a noun.
I was going to let you use whatever names you want, but you got it completely backwards. >_>
That makes sense
Great. Did you set up the DialogPanel object with the component?
Since we're doing a new thing, should I remove the DialogManager component?
Okay. So far so good. There are two ways to get your object references to show up in the inspector. Do you know what they are?
I know I can drag/drop from hierarchy and can drag images and such from window
Into appropriate fields that will allow them
The answers are, you mark the data as public or flag it to be serialized.
Oh like making the fields? I know of [Serialized] ...
Try this: add public in front of one of your TextMeshProUGUI or your Image in your file.
Just one.
Perfect. Now put [SerializeField] in front of the other one.
[Serializable] does something else and isn’t relevant here.
So, I wanted to show you that both these methods work, but make both [SerializeField] for this class.
The difference is that public automatically gets serialized, but we don’t need or want these fields marked public.
Now, add the following before Start():
string _text;
public string Text {
get => _text;
set {
_text = value;
}
}
Sorry, I’m getting a bit distracted. Delete the string _text; line and replace both remaining instances of _text with textObject.text;
I starting coding in the wrong direction because I’m thinking on an iPhone lol.
public string Text {
get => textObject.text;
set {
textObject.text = value;
}
}
Great. Now, let’s go over to your other class DialogController.
ok
Like you did in DialogPanel, let’s add a reference to your object here.
Add [SerializeField] DialogPanel dialogPanel; above your method.
I am inconsistent about capitalization because of autocorrect btw.
Next, go to the Unity editor and drag DialogPanel into that box on your DialogController component.
Great. Let’s make it do something finally, then we’ll explain what we did.
In ShowDialog(), add this:
dialogPanel.text = “Hello World”;
Next, can you share the source again that calls ShowDialog() that you showed me at the start of this thread?
Oh yeah, capital T on text.
Also make sure you use this character for the quotes: “
If those aren’t the same thing.
Ugh, let’s ignore the null for now. That’s a bit weird to see since the inspector should be overloading that.
I have a theory, but let’s ignore it for now.
Show me the code where you called ShowDialog before.
(scrolling up in this conversation is hard on my phone)
I didn't have a ShowDialog before our construction. I had scripts of DialogManager and ObjectsDialog
A tool for sharing your source code with the world!
Okay, it was the other class (the smaller one)
Whatever called ShowDialog in its source.
Right. Replace every mention of DialogManager with DialogController.
Then, cut out animationFrames from the method call so that it reads DialogController.ShowDialog(message);
This is in DialogPanel script, yes?
Right, I thought I was transferring it.
I’m trying to tie this in to your existing system.
Make sure that DialogController is referenced in ObjectsDialog, since it’ll probably be null now.
In the inspector I mean.
Then, run your game.
You should see Hello World show up in your dialog box.
Yep. Make sure that DialogController is assigned in the inspector and run the game.
NullReferenceException: Object reference not set to an instance of an object
ObjectsDialog.OnMouseDown () (at Assets/Scripts/ObjectsDialog.cs:11)
UnityEngine.SendMouseEvents:DoSendMouseEvents(Int32)
I should be getting home soon, so we can move faster if you have time to continue.
I don’t know where you put ObjectsDialog. I know it was running before. Frankly, it shouldn’t matter.
You aren’t emotionally attached to that class at all? I think that might have been a button or something?
Sorry, it goes on the individual objects and it's there.
The problem now is that the Dialog COntroller has to be an object in the Hierarchy. Is that ok if I make one? Previously, this was the DialogManager I believe
Just trying to make sure I'm addressing the previous set-up
Yeah, I don’t know how you had it. If you don’t necessarily care about the click functionality now, you could just put your function call into Start() on the DialogController class. It’ll perform the click operation when the game starts.
I should be heading home soon, where we can probably move a bit quicker, if you have time today/tonight.
It's midnight by me. USA EST
Ok, do you want to set a time for tomorrow or something?
I can be around whenever tomorrow, I’ll probably be up for another 5 hours tonight.
Ok, wanna do 1400EST/1300CST?
Sure!
Do you understand what we did so far?
I didn’t explain the Text property- I was going to explain that once we ran the game one time.
But about the object hierarchy: can you guess at least why we’re structuring the objects this way?
Awesome! I greatly appreciate it!
I understand producing the fields and assigning parts of the hierarchy. I think there are some issues between set-ups as I know the code we made is supposed to say "Hello World", but I still have the message field in the ObjectsDialog
I get the grouping of the DialogPanel being under ShowDialog, But surprised Canvas is within ShowDialog
Although actually I suppose it makes sense since I have two canvases and objects in both need dialog
Well, we didn’t write enough to really see it, probably, but I want to completely encapsulate the portrait and text inside of one object, so you only really have to interact with dialogPanel instead of a bunch of separate components.
Compiling certainly makes it easier
Then, DialogController commands the dialog system.
DialogPanel is the thing that you see.
Setting this up now will make things clearer as those files grow.
Leaving,will be back in 15 minutes.
Yeah, I'm wondering if I should remove Manager
Ok, hope you get a safe trip home. I'll be back on at 1400EST. Thanks again!
No problem, are you available tonight?
Hey, yeah! Anytime today/tonight. Ping me when you want.
Maine
Ahh, I'm in Winnipeg.
Just a moment. I'll grab some coffee. We could do this over voice or keep texting - either is cool with me,.
Texting is better since someone else is on the phone.
I wish Maine would get annexed by Canada though, lol
Alright! I'm at my computer today, so this should be quicker from my part.
Awesome, thanks for your time!
My pleasure!
So first of all, let's talk about textObject.text for a moment. When you assign to that, it updates the text on the object during play mode. This is notable, because the act of assigning data launches some logic inside the object to cause the text to be drawn.
So one thing I wanted to ask about is when we made the ShowDialog object and placed the canvas under it, it shrunk the scene. Moving the canvas above the ShowDialog fixes it back, but I assume we don't want that placed there
Ugh, this probably has something to do with a Canvas Scaler component taking over if your canvas was a child or parent of ShowDialog.
How many separate Canvas components do you have?
The Canvas_Locker which was the original I made and the Canvas_Priority_Drag which was made for the movable object to be in front. I notice now that as a child of ShowDialog, the Rect Transform of the canvas is locked at Bottom Left when before it was full stretch.
Okay, it could be a reset transform.
So, in DialogPanel, what we did was create a property called Text. A property combines a field with up to two methods under one name: one to "get" and one to "set".
When you assign a value to DialogPanel.Text, the property acts like a method with a string passed into it as an argument. We pick that value up as value inside the set {} method of the property. This is the same as writing:
public string SetText(string value) {
textObject.text = value;
}```
The get => part is a method for retrieval. In this case, we just read whatever is at textObject.text and return that;
The important thing to note here is that we are not just assigning and reading to/from textObject.text, we could add new logic here if we wanted to.
Also, textObject.text is a property as well, which is how it handles actually drawing stuff when an assignment is made.
So is the 'get' part similar to 'return' for other functions?
get is essential a method.
private string _internalText;
public string Text {
get {
// what to do when reading a value from Text... eg var x = thisObject.Text;
return _internalText;
},
set {
// what to do when writing a value to this property... eg thisObject.Text = var;
_internalText = value; // value is a special keyword that is automatically set for us
}
}```
This is shorthand for:
```cs
private string _internalText;
public string SetText(string value) {
_internalText = value;
}
public string GetText() {
return _internalText;
}```
However, outside the class, all you need to interact with is `DialogPanel.Text`, like this:
```cs
DialogPanel myPanel;
myPanel.Text = "Hello";
Debug.Log(myPanel.Text); // Prints 'Hello'
"Text" doesn't hold data... it's a word that, when assigned to or read from, kicks off some logic because its actually made up of two methods.
I created _internalText as a place to store data inside the object.
Any questions?
So in my case with the dialog boxes, the get section would be the actual text and the set piece would be how we stop it from having the repeating error?
When you set a value to DialogPanel.Text from outside the class, it sets the given value to textObject.text inside the class. textObject is not marked public, so it can't be altered from outside.
Isn't it inside the public string though?
What is "the public string"?
If you're talking about public string Text {}, that is a property. It is two methods under one name.
Public means the rest of the script can access it outside brackets and I think can also make fields in components within unity.
string refers to a line of text "fuhikfhk"
Yeah so I meant isn't the textObject.text public because it is within public string Text?
Oh I see, technically not but you're on the right track. The get and set methods are inside DialogPanel and execute there.
What this does is guard against direct access to textObject because that is not marked public. Text IS public and is an access point that we use to modify textObject only however we allow it to be used.
Here's an example of what this does not allow:
DialogPanel dp;
dp.textObject = null; // Not allowed because textObject is private
You can also make Text read-only by omitting a set method altogether.
public class DialogPanel : MonoBehaviour {
public string Text {
get => textObject.text;
}
}
dp.Text = "newValue"; // Illegal because there is no set method defined
You could technically also make write-only properties where checking the value of DialogPanel.Text would throw an error, but you could write to it.
public class DialogPanel : MonoBehaviour {
public string Text {
set => textObject.text = value;
}
}
//-----------------------
dp.Text = "Data to write";
Debug.Log(dp.Text); // Illegal because this is not allowed to be read.
Does this make sense so far?
I believe so. Values are necessary or else you'll get the Errors describing null values.
What null values?
It defaults to null values I mean
What is "it"?
Not the code itself, but when trying to run the program, essentially not performing the desired function
The methods missing a value(s)
What, like regarding set if I only define a get method for some property?
Right
In that circumstance, the is no get. It's not defined.
A thing is not null that does not exist.
Gotcha
So anyway, if that's clear... lets just talk about my use of => and {}
These are equivalent:
get {
return textObject.text;
}```
```cs
get => textObject.text;
Basically, => just assumes that whatever is to the right of it is a value, so omits the need to write return.
For set,
set => textObject.text = value;
Is the same as:
set {
return (textObject.text = value);
}
But set doesn't really need to return anything but void, since there is no return type to an assignment. We would never use a return type here, but technically it can return one.
So, we can do this:
public string Text {
set => textObject.text = value;
get => textObject.text;
}```
It means the same as what you already had, just wrapped in a smaller package.
Ok. And as we did in the DialogPanel, you can mix the formats?
Now, a step further, we can do this:
When all the property does is simply assign to or read from some field, there is a shorthand that skips defining set and get entirely:
public string Text => textObject.text; // Contains both a set and get method in a short definition
I'm not sure what you mean by "mix formats", can you elaborate?
Like this is a mix, but as you said it could be that set is "Set => textObject.text = value;" or vice versa, get could have the brackets with a 'return', making the two consistent formats.
Oh I see. Yeah, I wrote two different "text formats" to represent the same thing.
Of course, this overrides having same or mixed formats it seems
Yes, you can think of the set as the expanded way to write out a method with the whole method body shown.
Exactly. Anyway, I wanted to cover this so that you understand what is going on here. I plan to use a lot of =>, so it's important to know that you can open up those lines and put a whole method worth of logic in there if you wanted.
Sounds good
With that said, you should change your code to this:
public string Text => textObject.text; // Contains both a set and get method in a short definition```
And understand that Text doesn't hold data, it's just an access point for two methods that you can't see.
public string Text = "";
^ This is a field. It holds data inside it.
public string Text => "";
^ This is a property, it would return "" and can't be written to.
Fields vs properties.
Gotcha
One final note. You can take it a step further and create a property with an implicit "backer field":
public string Text {get; set;}
This defines a default get and set for a new field, so now Text can actually hold data.
Basically, it writes in the private string _text automatically for you, but calls it something like _Text3278198321738211 in the compiler.
public string Text {get; set;} = "Hello, world!";
This is two methods and a string field all wrapped up in one line.
So when running code, will this print the Hello World or would you still need Console.WriteLine?
What I wrote is an assignment.
If you want to write a thing to a console, you can use a method that writes things to a console, yes.
Okay, so that was just a bit of housekeeping to cover properties. What I wanted to do was start building the DialogPanel as an object that we can use just like the text object that you were using before. Now, you understand how if we want do add some logic when assigning text, we can open up that line and add more instructions. We'll do more than just change the text later.
Is your game played with mouse and keyboard?
Yes
Okay, I noticed that you were using GetMouseDown() in another script before, and you probably aren't super attached to this being the interaction point in your game I would gather.
Does the player have to click on anything in particular to trigger the text to display, or are you only trying to listen for a click anywhere in the game window?
Each object is meant to have dialog with it and a different character profile expression to the left
In the videos shown the other day, I was using starting with the photo image of two characters
This one is a little special as it has a separate script to make it grow big when the polayer clicks on it. Other objects don't do this
Right, okay. Does it matter what is under the player's cursor when they click? I was getting visual novel vibes from your graphics.
The objects themselves when clicked on.
Okay.
Hmm, then if you're using UnityMessages to receive clicks, let's maybe stick with that. Do you still have ObjectsDialog (or whatever it is) in your scene so we can test?
For example, there are 3 paper balls that need to be drag/drop to the trash can icon to throw away (this is already scripted). But clicking separate paper has different dialog.
Paper 1: "Ugh, this kid is always a mess."
Paper 2: [none]
Paper 3: "I'm afraid to open this."
Yes, ObjectsDialog is still available and currently attached to the photo
Okay. The photo triggers the text like a button then... (this wasn't entirely clear before).
For Photo, One click expands size && should begin dialog (complete with background box, character profile and typing text). When player hits the "Close" button, dialog goes away and photo shrinks.
Okay. Can I see the script that you have making the photo grow currently?
I'm trying to get a sense of how we should start your dialog system to keep it organized.
Okay! I don't know how long you were planning on us talking, but we can really clean this up a bit. I figure that might set you on a good path for the rest of your game, if that works for you.
Sure thing, I'm down for clean-up, but will have to get off in about an hour. I have early mornings, but am available every night about the same time and more on weekends.
Okay.
First of all, what is public static GameObject Photo;? Seeing that marked static stands out as strange to me.
Better yet, why is that static? Do you understand what static data is?
I think is has to do with utilization for all gameobjects, like a constant. I realize now that it probably isn't necessary since I'm only applying to photo.
This was from a video tutorial and I remember I think I had to switch the bool in order to get it to work properly for some reason.
Well, it's a bit confusing here, because this means that ClickToBig has a Photo object that it points to. I don't mean one instance of the component, I mean the idea of ClickToBig has a reference to some specific GameObject in the scene.
Yeah, the Photo
Technically, what this does is not build the reference to the GameObject on the component (on a specific monobehaviour), it builds it on the class definition.
If you have two ClickToBig components in your scene, they will both point to exactly one and only one GameObject.
In Unity, I renamed Photo_Sprite for canvas reasons, though that's not really needed at this point
I see. In a way that might be beneficial since I dont want others to grow, but I can see it might not be necessary
So, because you're referencing transform.localScale in OnMouseDown(), I don't think you need a GameObject reference at all unless this script is supposed to go on something that isn't the same as whatever is growing/shrinking. You aren't using that reference anyway.
It's not an enforcement that I see you needing. More importantly, we could leverage static data for your dialog system if that's something that your intended design could benefit from.
What I'm saying is, it could be helpful to use static data for your dialog, but this ain't it. 😄
Try this:
- Remove that line since it's doing nothing.
Ok, I removed the static and saved and the photo still functions the same
- Let's leverage what we learned about properties to make a bool that activates the grown state of your object.
Ok, yep still works
Create a new public bool IsBig with a get and set section with curly braces. Note that I'm using a capital I. It's typical to name properties with a starting capital letter for clarity.
This is at the top above private vector, correct?
It could actually go anywhere you want, but I like to put properties below fields and above methods, personally. I would put it above Start, but you can experiment and see that it doesn't matter so long as it's at the same level as everything else indented right now.
I'm assuming we can remove the private bool?
Ugh, we actually need to keep it. I would personally rename it to _isBig, but there is no rule here. That's going to be our backer field.
Then, make the following change:
[SerializeField] private Vector3 _bigScale = new Vector3(20f, 20f, 20f);
[SerializeField] private Vector3 _smallScale = new Vector3(5f, 5f, 5f);
This will expose the values so you can tweak them in the editor if you want. You can declare values inline at compile-time so long as they are structs, so using Start() for this is unnecessary.
Also, you can set [SerializeField] private bool _isBig = false; in your definition, too.
You can completely delete Start();
Okay, look at IsBig though, that is a field. You need to add the get and set parts to mark it as a property.
I added underscores to bottom isBigs
public bool IsBig {
get {
}
set {
}
}
It isn't a property until you expand it or use a shorter form.
The big difference is that ; at the end of it. That completely determines whether it's a shorthand for methods or a field.
Perfect! get isn't happy because it has to return something, but set is happy to be empty because doing literally nothing is perfectly valid.
So, consider the following code:
public bool IsBig {
get => _isBig;
set {
_isBig = value; // value is special and is set for us
if (_isBig) {
transform.localScale = _bigScale;
} else {
transform.localScale = _smallScale;
}
}
}
Since we discussed properties in DialogPanel, do you think you understand what this does?
I think the 'get' is targeting the '_isBig' obejects, 'set'ting the value to transform objects 'if' they are '_is big' objects. Otherwise, they remain the same. The transformation values come from the vector lines at top of the script.
Hope that made sense
I think I get you.
You can remove private Vector3 bigScale, smallScale; since that's doubled up now. Replace OnMouseDown() with this:
private void OnMouseDown() => IsBig = !_isBig;
(Note this is the same as ```cs
private void OnMouseDown() {
IsBig = !_isBig;
}
... but if it's only one line, we can shrink it using expression form rather than block form.
Once you've done that, add this new code to the file:
private void OnValidate() => IsBig = _isBig; // Pay attention: there is no ! this time
I'll get to explaining what this does when you're ready.
Great! Now, go into your editor and click on the object with this component on it.
Grows and shrinks with each click 🙂
Yeah, so this demonstrates what happens when you change that bool.
Now, just like with the text object, you see something happen immediately when you set true or false.
What OnValidate does is assign _isBig to IsBig every time something in the inspector changes.
OnValidate does nothing on a shipped game, but provides some editor functionality.
So now, when you want to expand the object, you would do something like:
myClickToBigComponent.IsBig = true;
And it'll grow right away.
Like in another script?
You can also edit the scale vectors in real-time, if you haven't tried yet. Any change to any value in the spector will call OnValidate.
Yes. As long as IsBig is public.
So, I think this is a good example to show what we were doing earlier in DialogPanel.
Do you have any questions about what each of these parts do, or does it sum up well so far?
I think I get it. the "myClick..." you just mention I think is what we'll use ro simultaneously grow the object and display the dialog box?
That is an example of how you could have another object grow or shrink the object. In fact, the whole property provides a streamlined presentation for controlling this. In this case, OnMouseDown() is causing the change to happen, but you could replace that with a different system that could control the size of the object if you wanted to.
Probably, you'll want to change that before your game is finished. What we did was make it modular so it's easy to grow or shrink the object from somewhere else.
Now, when the photo grows, you want to display some text, right?
Makes sense. In Game Dev, I imagine finished products could be submitted to editors for refining/fresh eyes?
Yeah and when clciking the "Close" button on the dialog box, it will simultaneously shrink.
Ugh, if you're suggesting that you could hire a programmer to clean things up, you certainly could. I'm not sure that would be economical, but that's another discussion lol.
Okay. Do you want the text to be displayed assigned in the ClickToBig component?
And to be clear, the close button is a child of the DialogPanel, if I'm not mistaken?
Yes to Close Button.
As for the assignment, I was aiming for the dialog script to be separate so that it is more universal with objects. Most objects won't grow throughout game, especially in this scene where only the 1 object does.
Okay, I am leading us to making the dialog system "decoupled", so that it's sort of universally available.
So, let's add a new public string to your ClickToBig component, since clicking on it is what chooses what the text to show will be. You can always change how this is stored later. Call it whatever you want and write a message into it in the inspector.
This will be the text that prints when the picture grows and the DialogPanel is opened.
Yep.
Okay. Now, let's go over to DialogController... we haven't looked at that file in a while.
Remind me that this is set up, or make sure:
DialogController is on some object in your scene. It exists globally and so doesn't need any specific place in your scene hierarchy - it only needs to exist. I personally like to put controllers like this under an object called "Systems" or something.
Also, what error are you getting on dialogPanel? I assume that's an old photo, because your ClickToBig file wouldn't compile if it was still a problem.
Ok, previously I had DialogManager as an object. Will this replace that?
Yeah. We're not relying on any of your old scripts anymore.
NullReferenceException: Object reference not set to an instance of an object
ObjectsDialog.OnMouseDown () (at Assets/Scripts/ObjectsDialog.cs:11)
UnityEngine.SendMouseEvents:DoSendMouseEvents(Int32)
Ok, I'll delete the object and make a new one. Create Em,pty-> Image should be fine?
Why an image? Just an empty gameobject is most directly what you need.
Yeah true. So I made that Down bottom.
Okay. Once you're ready, you'll have to comment out or assign something to a null in the script with the error.
It's referring to ObjectsDialog which I think we're also scrapping?
We might - just comment out that line then and we'll get to that later if we need to.
Actually, ObjectsDialog and DialogController look like the same thing basically. You can scrap it - we're just rebuilding this.
DialogController, as a name, actually says what it does.
Controller is synonymous with Manager I believe. ObjectsDialog was a support piece that was assigned to objects
Actually, a manager manages other things and a controller provides functionality, usually to one thing. Consider a manager of buttons or a controller to a button.
When you have one controller that controls a system with one object, the definition can become a bit more nebulous - there it is technically most accurate to name a script a controller.
Yeah, but I mean the names were probably inaccurate somewhat
Okay. What we're going to do is create something called a singleton. There are plenty of ways to build this thing, and some people will tell you that using singletons in Unity is a bad practice.
What a singleton does is check to see if it is the only one of its kind. If it is the first to arrive in memory, it registers itself as the one true copy. If it finds that something is already registered, it destroys itself.
But anyhow, ObjectsDialog and DialogManager scripts are now removed. So we have DialogController and DialogPanel now
The truth about whether it is bad practice or not is simple: if you don't use it right, then it is not the thing to use. If you use it correctly, it is the correct thing to use.
Before we begin, I want to make a proof of concept. Copy your gameobject with the DialogController component so that there are two of it in the scene. This is going to be what we consider an invalid use of the class and we're going to have it fix this when the game starts.
Just select it and hit Ctrl+D.
Then, in DialogController, add the following to the top of the class definition:
private static DialogController _instance;
public static DialogController Instance {
get => _instance;
set {
if (_instance == null) {
_instance = value;
}
}
}
Consider for a moment what that will do.
Let me know if you think you understand what this does. If we're good to go on, I'll actually write you a better version of it.
The if statment is changing something null to a value. The Static aspect is affecting everything apart of the DialogController
Static makes it live in the class definition and not in any instanced object of the class. So, we don't have to create a component to access DialogController.Instance ... it exists as a data store for the whole program even if there is no instantiated object.
So... here's a more advanced version of the same thing:
private static DialogController _instance;
public static DialogController Instance {
get {
_instance ??= FindObjectOfType(typeof(DialogController), true); // <- Changed this
return _instance;
},
set {
_instance ??= value;
}
}
When Instance is read (through get), we check to see if there is a value there. If it is null, we search the hierarchy for the first DialogController that we can find and set that to _instance. Then, we return it.
??= is called null coalescence. It is a shorthand for:
if (someValue == null) {
someValue = someThing;
}```
If _instance is null, we assign to it. If it's not null, we leave it alone.
Parenthesis line up so not sure why it's unhappy
Sorry, I'm dumb. What is the specific error here?
Oh, add a ) after DialogController. My bad... I'll edit my message above. **See my edit
red for both
As long as this class is a MonoBehaviour and you spell it the way I just did in the yellow lettering, it should work.
Is DialogController supposed to be string?
I'll need to get ready for bed. Continue tomorrow?
Sure! Quick thing if you have time. Click on FindAnyObjectByType and press Ctrl+period.
Any suggestions come up to fix?
Okay, you just created a new method by that name. That's not going to help.
Try explicitly using the GameObject namespace (if you're still up):
_instance ??= GameObject.FindAnyObjectByType(typeof(DialogController), true);
What version of unity are you using?
2019.2.6f1 because Unity 6 was not vibing with VS Code on my comp for some reason. Though I think since I'm just Visual Studio, it was Code that was the problem, but I didn't switch back.
Ah, okay. You're using a 5 year old version of the engine, so you might be okay with a method from the old API, but I'll leave it to you to research what that is if you want to do that.
Otherwise, just delete that line and stick with return _instance;
Alright, will do. Thanks for your help tonight
@ornate hare Hello, hope you're doing well. I'll be on most of the day if you're available, but I totally understand if you're busy.
Works well for me, if you're still down.
Np. Shall we continue where we left off?
Sure!
Okay, so just get rid of that line beginning with _instance ??=. It's up to you to build a proper singleton if you're going to use an editor version that's 5 years old.
Add a new method
private void Awake() {
_instance ??= this;
if (_instance != this) {
Destroy(gameObject);
}
}
Remove the set method of Instance entirely -> let's not allow that to be set after all.
Should the Static Instance be _instance?
Do you think you can take a look at this and try work out some of the errors?
Try working it bavkwards and see if you can learn a bit what is going on here.
Ok, since we removed the get/set, I realized that we're not working inside the public static, so brackets need to be removed and private void needs to line up as it's own variable type.
Looks better! We need to put back the get element of Instance, because I have no idea why you removed that. Since we don't need set, you can use the shorthand: public static Instance => _instance;
Now, we don't really need ShowDialog() to exist on the object anymore, since we're turning this class into something globally accessible, but let's leave that in so that it can be used by reference. Add a new method: public static void ShowDialog(string text) => _instance.ShowDialog(text);
This will put an alias method with the same name on the class directly.
What gameobject is getting destroyed by the way? Is this to remove the dialog panel after reading?
Instance needs a type. I forgot to write that in my code. Think that you can fix that?
Because you just deleted the other method ShowDIalog, it can't find it anymore. Why did you do that?
Getting confused. Do we want the Hello World line?
public class DialogController : MonoBehaviour {
[SerializeField] DialogPanel dialogPanel;
private static DialogController _instance;
public static DialogController Instance => _instance;
private void Awake() {
_instance ??= this;
if (_instance != this) {
Destroy(gameObject);
}
}
public static void ShowDialog(string text) => _instance.ShowDialog(text);
public void ShowDialog(string text) {
dialogPanel.Text = "Hello world!";
}
}
Just copy this and study it later.
I think you need to take a basic C# course because I get the impression you don't understand much of what's going on.
Ok
Is dialogPanel.Text sill raising an error?
Ahhh... okay this related to the version of C# that you are using. Just change the second declaration as follows:
public static void ShowDialog(string text) => _instance.ShowDialogInternal(text);
private void ShowDialogInternal(string text) {
dialogPanel.Text = text;
}
Doesn't like using the same method name twice, even if one is declared static.
I changed "hello world" because we have a parameter we can use. That string literal is outdated now.
Right. What's the issue with dialogPanel.Text?
Ah, can you post the source for DialogPanel? I might have forgotten what we were doing in there.
Ah got it, this is due to your C# version again.
So in this case, public string Text => textObject.text; only provides an alias for get, so you have to expand that out to the longer version with a separate get => and set => . It's a good thing we went over this the other day, because you should know how to do that.
I think we even started from the longer version and shortened that code. It turns out that because you're 5 years behind, you need the longer version.
Ok, at this point I think I should do the absolute longest version possible with get/set
You just need to supply both those keywords, actually, so you could keep both wrapped with => if you wanted to. Whatever you want is fine, though.
Not sure why get/set not being recognized now
ShowDialogInternal() is a method. Aren't you supposed to be making changes inside of DialogPanel?
dialogPanel.Text was not writable. Let's think about how to make that property writeable.
That's looking pretty close!
Take a look at this state of your singleton class earlier, and try to figure out why this property was valid (ignoring the call to a method that doesn't exist in your version).
Hint: set clauses have no return type.
You got it!
Does that error on dialogPanel.Text now reinforce what get and set do? I mean, now that we know that you can't rely on the shorthand for set behaviour.
Cool. Can you post your "embiggening" script for that photo that we were looking at before? It's time to hook stuff up.
Also, is your DialogPanel object in your hierarchy enabled or disabled when the game starts?
Okay, DialogPanel enabled or disabled on game start?
Currently disabled pre-game start. Can't start game because controller is not happy apparently.
Assets\Scripts\DialogController.cs(13,21): error CS1525: Invalid expression term '='
Well that's dumb; I have to assume it's due to your version.
Bottom even says "No issues found" so really confusing
Replace it with if (_instance == null) _instance = this;
I think I know the reason, being how Unity likes to override null internally, but that's a false-positive.
No, {} is only needed for multiline blocks.
if (true) {
DoSomething();
}```
These are equivalent, as long as the contents of `{}` would contain only one statement.
...is the same as...
if (true) {
DoA();
}
DoB();```
Here's a counter-example to show you what happens to the second statement on the line.
Well of course they're never assigned to, you're assigning them in the inspector.
You need to fix your Visual Studio installation later so these stop showing up.
Ok, just making sure it wasn't a "Because it's old" situation
You might be able to add = null; to the declarations at the top of your file if you want to shut that up, but I'm personally not a fan of that approach.
I'll leave it be for now
Looks good.
ClickToBig you asked for
In ClickToBig... under if (_isBig) { add DialogController.ShowDialog(Text);
Ok, that was accepted
Then, go to DialogController.ShowDialogInternal() and add the following line either before or after the current contents:
dialogPanel.gameObject.SetActive(true);
Once done, you should be able to run your game and try clicking on your photo.
Where is DialogController.ShowDialogInternal?
Right there.
The short answer is inside DialogController, like the name would suggest.
You have to write code inside a method, not in the class definition.
If you check your errors, your IDE will tell you this.
Yeah, I was a bit confused by that, but though your instruction were saying outside the public/private voids since the had the word internal included
Great! I'm deliberately trying not to do the work for you and am using plain english to describe these changes in order to encourage that, so that's somewhat intentional on my part.
So now, you should be able to run your game and test.
Ok, So game played and when I clicked photo, it became large and made sound like normal, but a pop-up about TextMesh Pro came up. The console has the following error.
MissingReferenceException: The object of type 'DialogPanel' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
DialogController.ShowDialogInternal (System.String text) (at Assets/Scripts/DialogController.cs:24)
DialogController.ShowDialog (System.String text) (at Assets/Scripts/DialogController.cs:20)
ClickToBig.set_IsBig (System.Boolean value) (at Assets/Scripts/ClickToBig.cs:21)
ClickToBig.OnMouseDown () (at Assets/Scripts/ClickToBig.cs:31)
UnityEngine.SendMouseEvents:DoSendMouseEvents(Int32)
Ahh, got it. So this is due to the object being destroyed on Awake. Remember how you made a copy of the DialogController object in your hierarchy so that there were two of them?
We can debug this later, but for now, just delete the second DialogController object and try again.
A copy from DialogManager which is delted now?
Yes, last time we spoke, you clicked on your DialogController object in your hierarchy and pressed Ctrl+D to make a copy of it.
Ah yes
Delete that copy; we were going to protect against there being multiple copies of the same object before realizing that you're using a very old foundation.
I think it's already deleted
I notice that this controller though doesn't have components. Should it?
Okay, at the top of your scene hierarchy, type this into the search bar:
Those are the objects that each have a copy of DialogController added to them. Delete those components until there is only one in your scene.
You can only have one DialogController.
First I took it off photo and tried leaving it on DialogPanel, but the ClickToBig didn't work on photo. Now I have them switched and I have this.
Do you not have a gameobject called DialogController with the only copy of the DialogController component on it?
Ok, switched and looks the same. Note I assigned the DialogPanel from hierarchy to the respective field in component.
Great. You should never have had 3 different dialog systems in your project. This is why we have code to destroy objects in Awake()... if the monobehaviour script that is running isn't the one stored in _instance, it deletes itself.
That's not an error. You need to add the essential package files to your project for TMPro to work.
Like the dialog itself I mean, not coding
Read what the text in the window says, then click on that button when you understand what you're reading.
Ok, I imported both and now partially seeing the text (the white object is blocking some). The button function doesn't work, though the Click list section of the Button component is blank (I forget if originally it was DialogManager was here and if so, that would explain it being blank).
Okay, so you've started your game and clicked on your ClickToBig object, and you're telling me that it's not doing anything?
It's doing some
Oh, okay. I can see "...idiot..." in very bright green text. You're concerned only about the close button, is that right?
I'm going to assume that the dialog panel opens correctly when you click on the photo, even though you aren't showing that in your video.
Correct, the background pops up once the photo is clicked on the first time
Good. Let's make a way to hide that now.
In DialogController, add a new method:
public static void HideDialog() => _instance.HideDialogInternal();
private void HideDialogInternal() {
dialogPanel.gameObject.SetActive(false);
}
This will perform the opposite operation from ShowDialog.
Looks straight forward (show/hide and true/false)
Next, if you want to use your TMP button script and unityevents to close the dialog panel, drag your DialogController object onto the "click" event in your button script. You'll have to remind me if this works, since we're made DialogController a singleton... but you should be able to find the static method HideDialog() in the list of methods that you can call on click.
If you can't find the method HideDialog, just change HideDialogInternal to public and it should show up in that list.
I found it, but button isn't closing out
The word "Missing" does not mean that you have found a thing.
Open that dropdown menu and try to find your method.
Nice. Click on HideDialog()
Yes, that is what I did before
Is <Missing DialogController.HideDialog> replaced in the method drop-down on your component?
Yes
Okay, if that text no longer contains "missing" then you have done it now.
Test your game when ready.
No, I'm saying I had already did these selections when I said the button wasn't working when I sent 1st screenshot
No problem, I get that. Is the text "<Missing...> still showing in your dropdown right now?
Yes
Okay. Do you have a compilation error in your console?
Assuming the error filter is enabled, the answer is no.
Did you delete that event handler in OnClick() and add a new one, or did you use the old reference? If you didn't delete it, click on the little [-] in the bottom right corner of the drawer and then create a new event handler.
Great - drop your DialogController object into where it says [None (Object)] and see if you can find DialogController.HideDialog
It's a video
Again, if you can't, change HideDialogInternal to public and look for that method instead. I don't remember how that version responds to static methods.
OOH sorry.
That's a little bizarre, but try what I said regarding the access modifier on the internal version.
No problem - there's something up with your project domain which is manifesting here.
You mean beside the old version?
I don't know, it's before the scope of building a dialog system.
So anyway, I think we're now at a point where your system is pretty robust, but doesn't do any animation.
If I'm right about that, I want to ask you: do you understand what we have done up to this point?
I understand that the Controller is like a universal remote and the DialogPanel is focused on the panal (black background) and the text itself, making this a subpart of the controller
I believe now we need a new script that will be another subpart of the Controller that controls the character profile animations that instantiate to the left of the dialog box.
Well, the Controller is accessible from anywhere without non-static references to it. You can now call DialogController.ShowDialog("some text") from anywhere to make the dialog panel appear with some text. You can also use DialogController.HidePanel() from anywhere to make it disappear. DialogPanel represents the actual panel that shows up in your game, including the text property which obfuscates the TMPro object underneath.
As an exception, we pointed that button to HideDialogInternatl() because the button script requires a non-static method. We made the "internal" class public so that you could link to it directly.
So now before we continue, does the system work without issues? I mentioned that I think it does, but I'd like for you to confirm that I'm not just projecting and that it does in fact work as you would expect up to this point.
Right, tracking that. One concern is I noticed I can change the text of the panel "...idiot..." within the ClickToBig component. While this regarding the photo is fine, other objects will not have the ClickToBig component.
Will I be able to copy/paste line 21 into another script assign to non-growing objects to adjust text?
Yep, that's the intended use.
That line will work universally in your whole project. Same thing with DialogController.HidePanel().
Ok, that sounds good then. In previous version, I had text type out like a type writer effect. I don't have the code handy, but I imagine that portion can be applied to ClickToBig and the hypothetical other script, right?
So actually, ClickToBig should have no control over that. That's a concern for DialogPanel, since it owns the text object.
This is where I was able to change
Right. That should change what the text is that shows up in your dialog panel, right?
Like if I change "...idiot..." here to "anything", then yes, "anything" will show up in-game.
Good. That's your end point for game logic. Now whether or not this script remains how you want to structure your game logic in the future is a matter for another time, but I imagine that you'll be opening and closing the dialog panel thousands of times over the course of your game.
Yeah, probably. I'm gonna have to jump off for an hour or two for errands, but could be on tonight if you're ok with that.
Sure.
So, the logic behind how to write text in your dialog panel should only be covered in one place: inside your DialogPanel. You don't ever need to duplicate that code. Just use DialogController to start drawing text and hide the text.
ClickToBig should not control how the text is displayed, except through parameters in DialogContoller.
Hypothetically, let's say you wanted to be able to select a rate to print the text at, you would only want to expose the parameters to ClickToBig that it should be concerned with. For example, DialogController.ShowDialog("my text", 0.1f, Color.red); Here is an example of providing the text to type, the speed to type it at and the colour to change the text to. This is an example, of course.
The part of your code that you will repeat constantly as you build your game should be as simple as possible, leaving the technical details to the system to make happen.
So when you're ready, we will add to DialogPanel to describe in greater detail what it means to change text, that being to write stuff out over time instead of all at once.
Hello, sorry for being quiet a week. I got back in today and made some comments within the scripts to keep track of my understanding (hopefully these look correct).
@ornate hare
Hey! In your first photo, your first 4 comments aren't right:
dialogPanel -> the on-screen object
_instance -> the private storage mechanism for the static DialogController object in memory, as in which object DialogController uses indirectly when called in other scripts
Instance the public reference for _istance. This is a property, not a lambda expression. @frigid trench
It took a moment for me to realize that "Create field" in your comments refers to the line of code literally defining a field on the object. Usually, "create" means something gets instantiated, but this fits given your use of the word.
As a reminder for the last image: IsBig is a property, so performs an action when its value is set. The methods further down in the file OnMouseDown and OnValidate are just routing to it to trigger it when those Unity events get triggered. The reason for ! in the first method is to flip the value from true to false and vice-versa, so creating a toggle on click, but the OnValidate method doesn't have this, else the opposite would happen than whatever is showing in the inspector.
I was going to recommend before continuing on with this script to rename a few things, if you prefer this kind of style:
Rename:
DialogController to Dialog
DialogController.ShowDialog(string text) to Write(text)
DialogController.HideDialog() to Hide(text)
This would be completely optional, but the reasons for these changes would be to make the static function read like some of Unity's internal classes, such as Debug.Log or Input.GetKey. If you're familiar with those other static classes, then DialogController is meant to work the same way. Removing the word "Controller" from the name could possibly represent that better.
Thanks for the corrections, I updated the comments. As for the renaming, it's probably a good idea, but I'm gonna keep the terms as is for the moment to help keep overall consistency and prevent breaking something because I forgot to switch something somewhere.
Currently the background image is set to it's own canvas object, but when we began redoing this dialog system, the hierarchy had shrunk the image and objects. I was wondering instead of reordering things, would assigning the image to "ShowDialog" in the hierarchy be problematic with other functions? I would remove the image from the canvas, but leave the canvas to exist in the hierarchy.
@ornate hare
I converted the file to Unity 6 to fix the scripting errors we've run into. As you can see, there seems to be issues of things not transferring over cleanly and I guess I'll have to reassign images and such.