using System.Collections;
using UnityEngine;
using TMPro;
public class NPCscript : Interactable {
public GameObject dialougeObj;
public TMP_Text dialouge;
public int Size;
public string dialougeSt;
public override void Interact () {
base.Interact ();
dialougeObj.SetActive (true);
NPCInteraction();
}
void OnTriggerExit(Collider other) {
dialougeObj.SetActive(false);
}
void NPCInteraction(){
dialouge.SetText(dialougeSt);
}
}
Please tell how can I have an array or list in which I can do use my size variable and loop it until all dialogues are said.
I tried but mine doesn't work.
HELP!
A Dialogue system in itself is a very broad topic. However, I don't think you will be able to pull it off with just a List of strings. If you are talking about giving the player choices to choose from then you will need to create a few custom classes.
First, you will need a class to define your Character's Options. This will be a very simple class. It will contain the string response and an integer that will represent a destination node.
public class DialogueOption {
public string Text;
public int DestinationNodeID;
}
Next, you will need a DialogueNode class. Consider this what the NPC is telling the character and then it also contains a list of, you guessed it, Dialogue Options. The other thing it will have is a NodeID. This is an integer that gives us a place to send us to from our Options. It will look a little like this:
public class DialogueNode {
public int NodeID = -1; //I use -1 as a way to exit a conversation.
//NodeId should be a positive number
public string Text;
public List<DialogueOption> Options;
}
Finally, you will need to create your Dialogue class, which is the simplest of them all, just a list of DialogueNodes and looks something like this:
public class Dialogue {
public List<DialogueNode> Nodes = new List<DialogueNode>();
}
You can have it where your character has just one dialogue script but, I did it differently where each character then had another class called DialogueList, which basically is a list of dialogues and it also contained the character's name that I could display and depending on the situation I could pick which dialogue I wanted my character to have with the player at the time.
public class DialogueList
{
public string name;
public List<Dialogue> dialogues = new List<Dialogue>();
}
This also has the added benefit of being easily converted to a Dictionary using the name as a key returning a list of dialogues if you wanted to set it up in that direction.
And somewhere else in your project you will need a DialogueManager class that will control everything. I typically make this a Singleton just so I can easily call it from anywhere. I won't show you all of it but I will show you the displaying part the rest is just setting the text and turning on and off objects.
public void RunDialogue(string name, Dialogue dia)
{
nameText.text = name;
StartCoroutine(run(dia));
}
IEnumerator run(Dialogue dia)
{
DialoguePanel.SetActive(true);
//start the convo
int node_id = 0;
//if the node is equal to -1 end the conversation
while (node_id != -1 )
{
//display the current node
DisplayNode(dia.Nodes[node_id]);
//reset the selected option
selected_option = -2;
//wait here until a selection is made by button click
while (selected_option == -2)
{
yield return new WaitForSeconds(0.25f);
}
//get the new id since it has changed
node_id = selected_option;
}
//the user exited the conversation
EndDialogue(node_id);
}
My buttons basically just had this simple method attached to them in their OnClick event that I set under the DisplayNode method. Basically, I take my buttons and give them this method and their parameter is whatever their DialogueOption.DestinationId is
public void SetSelectedOption(int x)
{
selected_option = x;
}
Hmm if you are asking for a Dialogue System it is very easy to create on Unity using UGUI.
1.) Create a canvas with a plane and a text I guess will do
*scale the plane ofcourse to your satisfaction and put it infront of the camera
*and about the text you must put it infront of the plane like the plane is on the back
*and the text is infront of the plane.
2.) Now setup your heirarchy like this
Canvas
Dialogue //Empty GameObject
Plane
Text
I'll make it pop out whenever you collides into an NPC something like that
[SerializeField] GameObject dialogueObject; //here drag the canvas
[SerializeField] GameObject text; //we will get its component for changing the text later on
void Start(){
text = GetComponent<Text>().text = "Sample Dialogue";
dialogueObject.setActive(false);
}
void OnCollisionEnter(collision collide){
if(collide.gameObject.name == "NPC"){
dialogue.setActive(true);
}
}
Simple as that . I hope it helps
Related
I'm new to coding and Unity, I'm working on a simple click style game to learn the basics of both. I've created several scenes: MainMenu, UI, 1st level and 2nd level. After pressing 'Start' in main menu i'm loading UI and 1st level additively.
In UI layer I have a shop UI and other bits that I never want to unload. On the 1st and 2nd level i have the bits that i want to have only on those scenes.
So, what I'm trying to do is when i purchase an item or upgrade for the 1st level i want a GameObject (sprite) to be set as active.
What I've tried to do is to call a function in one script that is attached to GameObject in 1st level from script that is attached to a purchase button in UI scene, but from what I was able to understand from messing with it will set that game object active if it's assigned in the UI scene - so the whole thing is basicly pointless and i can do it much easier.
Code in script attached to GameObject in 1st level scene
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Huberd()
{
hubert12.SetActive(true);
}
}
Code in script attached to GameObject in UI scene
public class Shop : MonoBehaviour
{
public GameObject buyHubertOnebutton;
public GameObject test;
public void UnlockHubert1()
{
if (Global_Cash.CashCount >= 20)
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button>().interactable = false;
Work_Button sn = test.GetComponent<Work_Button>();
sn.Huberd();
}
}
}
If you have any remarks anout how i've spit scenes or anything else they will be more than welcome!
Thanks!
There's something you need to be aware of, and that is that you can't reference an object from one scene, in another. You CAN reference the same Prefab though, but that's a slightly different issue.
One way I find effective is to do something like this:
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Awake ( )
{
Shop.Register ( this );
}
public void Huberd ( )
{
hubert12.SetActive ( true );
}
}
Then you can have a Shop manager that has instance and static components.
public class Shop : MonoBehaviour
{
// Static variables
private static Work_Button _workButton;
// Instance variables
public GameObject buyHubertOnebutton;
public static void Register ( Work_Button workButton )
{
_workButton = workButton;
}
public void UnlockHubert1 ( )
{
if ( Global_Cash.CashCount >= 20 )
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button> ( ).interactable = false;
if ( _workButton != null )
_workButton.Huberd ( );
}
}
}
This works by having your button register with the shop. And because we're using the static variable here, we don't need to "find" the Shop object. The instance method UnlockHubert1 will check to make sure there is a button registered.
This represents just one of many ways to accomplish this. It's also the most basic of basic implementations. But as of right now I'm fond of this method. Extending this, you could store the registered items in a Dictionary collection, and you could do some cleanup/deregister when a button is destroyed (i.e. OnDestroy ).
The assumption here is that there is only one Work_Button in the scene, otherwise you'll need a way to differentiate them (i.e. a Dictionary with am identifier as the key).
SOLVED
thank you for taking the time to view my question.
I am currently working on a project using Unity3D and have ran into a bit of difficulty whilst creating the inventory system. Upon doing some research I have decided that it would be in my best interest to use a List of ScriptableObjects (Item) to create this inventory system due to the ease of management and the expand-ability of Lists.
Just as an example I have an Item titled "Skull." When the player points towards the skull my code prompts the user to press F to pick up the item which in turn should remove the item instance from the game-world and add an instance of that Item into the inventory.
The part that is causing me the most trouble is my InventoryEditor script which controls the RayCast feature I mentioned previously.
Here is the code in my InventoryEditor Script with the exception of Update and Start methods as they are working as intended
using UnityEngine;
public class InventoryEditor : MonoBehaviour
{
private bool isOpen = false;
public TMPro.TMP_Text itemIDTM;
public GameObject currentObject;
public GameObject inventoryUI;
new Inventory playerInventory;
public void CurrentObject()
{
RaycastHit itemHit;
Ray itemFinder = new Ray(this.transform.position, transform.TransformDirection(Vector3.forward));
if (Physics.Raycast(itemFinder, out itemHit))
{
if (itemHit.collider.tag == "Item")
{
// Rather than itemHit.collider.name I would prefer if it uses the Identifier of the
//Item ScriptableObject V
itemIDTM.text = "F - Pick up: " + itemHit.collider.name;
if (Input.GetKeyDown(KeyCode.F))
{
/*
I need to Add an Item When F is pressed
Something along the lines of:
playerInventory.AddItem(Item that is hit by RayCast);
*/
}
}
else
{
itemIDTM.text = "";
}
}
}
}
Here is my ScriptableObject which has not been causing me any issues
using UnityEngine;
[CreateAssetMenu]
public class Item : ScriptableObject
{
public string identifier;
public GameObject model;
public int value;
public double weight;
Item(string identifierStr)
{
this.identifier = identifierStr;
}
}
Here is my Inventory Class
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : List<Item>
{
public List<Item> itemList = new List<Item>();
// Default Constructor
Inventory()
{
}
public override string ToString()
{
string itemListString = " ";
for(int i = 0; i < itemList.Count; i++)
{
itemListString = itemListString += itemList[i].name;
}
return itemListString;
}
public void AddItem(Item itemToAdd)
{
for (int i = 0; i < itemList.Count + 1; i++)
{
if (itemList[i] == null)
{
itemList[i] = itemToAdd;
}
}
}
// Remove Item Not Finished
public void RemoveItem(Item itemToRemove)
{
for (int i = 0; i < itemList.Count; i++)
{
if (itemList[i] == itemToRemove)
{
itemList.Remove(itemToRemove);
}
}
}
}
The only solution that I can think of is to use ScriptableObject.CreateInstance to populate my game-world with the items that I wish to add to the inventory but even then I am unsure how to both: reference those Item Instances in code and, use a RayCast to determine that an Item Instance is being hit. Is it possible to create instances of a ScriptableObject in unity without code or will I have to populate my world through code?
Any help would be greatly appreciated I have been struggling with this for quite some time now.
So as with most things there are many ways to skin a cat. There are some brilliant and complicated inventory systems in the asset store that you can use if you feel like this is taking too much time. If it were me settings up this structure i would make an interface called something like "IInteractable" for you items that you want to the player to have the ability to pick up.That interface would look something like this
public interface IInteractable
{
//once a player has reference to this object they call this
//method to get an item back (which is your scriptable object)
Item GetItemInfo();
}
So in your OnCollision method instead of checking for a tag you could instead check against an interface like this.
IInteractable item = itemhit.collider.gameobject.GetComponent<IInteractable>();
if (item != null)
{
//Then you have found an item that needs to be picked up.
string name = item.GetItemInfo().identifier;
}
From here there are several ways you can go depending on what effects you want your inventory to have on your character. With some inventory you want items to have immediate effect while with others you want the effect only to be active while equipped.As far as spawning these items it would probably be easiest to just make prefabs with SO's attached and spawn those. Let me know if you have any other questions.
well, I'm working on a specific part of a game in which I need to create a list of lists, a list of Game levels and in each list node a list of triggers (The level lists and each list inside it are variable in size).
This is all in the script LevelController.cs.
This is the Trigger class (it's simple like this for now)
[System.Serializable]
public class TriggerClass
{
public GameObject Trigger;
public bool Active;
public int Type;
public TriggerClass(GameObject obje, bool active = false, int type = 1)
{
Trigger = obje;
Active = active;
Type = type;
}
}
And a wrapper class to create the Levels List
[System.Serializable]
public class WrapperClass
{
public List<TriggerClass> Triggers = new List<TriggerClass>();
}
And of course
public List<WrapperClass> Levels = new List<WrapperClass>();
I serialized both and they show ok in standard Inspector draw in Unity, but I need to customize the Inspector showing this list, so I made a InspectorCustomizer.cs and a EditorList.cs in Editor folder.
For now I'm just trying to simulate the default draw that Unity does with the lists (an expand arrow, a size field that creates that amount of list nodes, and in each node the same thing, as I said, the default that happens if you uncomment the DrawDefaultInspector and comment the other 3 lines in InspectorCustomizer.cs)
The InspectorCustomizer:
[CustomEditor(typeof(LevelController))]
public class InspectorCustomizer : Editor
{
public override void OnInspectorGUI()
{
//DrawDefaultInspector();
serializedObject.Update();
EditorList.Show(serializedObject.FindProperty("Levels"));
serializedObject.ApplyModifiedProperties();
}
}
And here the EditorList.cs:
public static class EditorList
{
public static void Show (SerializedProperty list)
{
EditorGUILayout.PropertyField(list);
EditorGUI.indentLevel += 1;
if (list.isExpanded)
{
EditorGUILayout.PropertyField(list.FindPropertyRelative("Array.size"));
for (int i = 0; i < list.arraySize; i++)
{
EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i));
}
}
EditorGUI.indentLevel -= 1;
}
}
But this just shows the "Levels" list with empty elements, I click in the expande arrow in each element and nothing shows up, I know this happens because the script is iterating only through the Levels list and not each element within, but I don't know how to iterate through every element inside the Levels list to show the same thing.
I'm not really used to serialization so forgive me if this sounds easy.
I cannot do EditorList.Show(list[i]); inside the for loop because it cannot apply indexing to a serializedProperty of course, so how do I procced?
No, You don't need the EditorList.cs.
EditorGUILayout.PropertyField has a parameter includeChildren to show its children properties.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(LevelController))]
public class InspectorCustomizer : Editor
{
public override void OnInspectorGUI()
{
//DrawDefaultInspector();
serializedObject.Update();
EditorGUILayout.PropertyField(serializedObject.FindProperty("Levels"), true);
serializedObject.ApplyModifiedProperties();
}
}
So, I have this crazy idea to have enums pointing to gameobjects.
Here's what I want to do:
/* These enums would hold gameobjects instead of ints */
enum exampleEnum{
AddPanel,
ListPanel
}
public class GUIManager : MonoBehaviour {
void Start()
{
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
Is there any way to make this work? Or a workaround?
This might be possible with something other than an enum but I don't know of it if there is and I'm looking through the web for a such a solution.
This would satisfy your requirement, works for any amount of enum values or panels.
// Add this to each of your panels, the enum field can be integrated into your other behaviours as well
public class EnumPanel : MonoBehaviour
{
// configurable from the Editor, the unity way.
public ExampleEnum Type;
}
// Assign all your panles in the editor (or use FindObjectsByType<EnumPanel> in Start())
public class GUIManager : MonoBehaviour
{
// configurable from the Editor, the unity way.
public EnumPanel[] Panels;
void Start()
{
// Optionally find it at runtime, if assigning it via the editor is too much maintenance.
// Panels = FindObjectsByType<EnumPanel>();
EnablePanel(ExampleEnum.AddPanel);
}
void EnablePanel(ExampleEnum panelType)
{
foreach(var panel in Panels)
{
if(panel.Type == panelType)
EnablePanel(panel.gameObject);
}
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
I don't know why the answer from: #Paradox Forge was wrong but maybe this will help you.
System.Collections.Generic.Dictionary
I don't have a lot of time to explain the dictionary class but this is how you can use it.
This will cost some performance but has really nice readability
public class GUIManager : MonoBehaviour {
public enum exampleEnum{
AddPanel,
ListPanel
}
//For readability you can also add "using System.Collections.Generic;" on the top of your script
private System.Collections.Generic.Dictionary<exampleEnum,GameObject> exampleDictionary = new System.Collections.Generic.Dictionary<exampleEnum, GameObject>();
private GameObject SomeGameObject;
private GameObject SomeOtherGameObject;
void Start()
{
//You have to add all the enums and objects you want to use inside your GUIManager.
exampleDictionary.Add (exampleEnum.AddPanel, SomeGameObject); //Add panel will be linked to SomeGameObject
exampleDictionary.Add (exampleEnum.ListPanel, SomeOtherGameObject); //List Panel will be linked to SomeOtherGameObject
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(exampleEnum examplePanel)
{
if (!exampleDictionary.ContainsKey (examplePanel)) //If the given panel does not exist inside the dictionary
return; //Leave the method
GameObject panelToEnable = exampleDictionary [examplePanel]; //Will return the GameObject linked to given panel
panelToEnable.SetActive(true); //Enable the gameobject
}
}
If you want to know more about the Dictionary class go to: Dictionary
I want a restart menu that works for all my levels, as I load level 1 there is this script setting an integer to 1 on an empty game object.
using UnityEngine;
using System.Collections;
public class SetRestart : MonoBehaviour {
public int Setrestart = 1;
void awake ()
{
DontDestroyOnLoad (this);
}
}
When you fail the level you get to the next scene called: LostMenu. You have the option to restart that level you were playing or quit. So here I made a button to restart and attached this script to it:
using UnityEngine;
using System.Collections;
public class RestartButton : MonoBehaviour
{
public int Setrestart;
void Start()
{
if (Setrestart == 1) {
Application.LoadLevel("Main");
}
}
}
(Plan is to make 50 levels and also 50 if statements, this is just the first one for the first level called "Main", every level will have its own number).
The problem is that nothing happens when I click on the button (Screenshot: http://prntscr.com/9tf4dd) and that when I load the LostMenu screen nothing happens to the int and it stays at 0 in the inspector while in the level scene called: "Main" I said to it to give it 'int = 1.' The number 1 stays in the "Main" scene and doesn't go to the menu scene.
Level 1 is scene: "Main".
The menu to restart when you lose is scene: "LostMenu".
Am I clear? Sorry for my bad English and thank you in advance.
I use a static 'Globals' class to persist data between scenes in Unity.
You could use something like this to store the name of the last level that you played.
public static class Globals
{
public static string LastLevel {get; set;}
}
Then you could just write to this string at the start of your level and read it in your restart button handler.
In order for RestartButton to know about the Setrestart value stored in SetRestart you need to have a reference to the GO with the SetRestart component. Right now RestartButton is checking its own Setrestart value, which defaults to 0. This is why it doesn't work.
Instead you need this:
public class RestartButton : MonoBehaviour
{
GameObject persistentObject;
void Start()
{
persistentObject = GameObject.Find("NameOfGameObject");
int shouldReset = (persistentObject.GetComponent<SetRestart>() as SetRestart).Setrestart;
if (shouldReset == 1) {
Application.LoadLevel("Main");
}
}
}
GameObject.Find isn't great, performance wise, but for a once-off lookup, it's fine. There are also other ways of getting the reference, but in the case of objects that persist across scene changes, this is the only way (note that I'm saving the GameObject reference to a field, while not neccessary here it is good practice so you aren't GO.Find()ing all the time).