Skip to content

Instantly share code, notes, and snippets.

@ssshake
Last active March 4, 2024 17:39
Show Gist options
  • Save ssshake/576fa871870e61a8c5bcda42a3ea34f9 to your computer and use it in GitHub Desktop.
Save ssshake/576fa871870e61a8c5bcda42a3ea34f9 to your computer and use it in GitHub Desktop.
Unity NPC Dialog with Branching Nodes and Unlimited Statements and Responses
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace Daggasoft {
[System.Serializable]
public class Statement {
[TextArea(1, 10)]
public string name;
public AudioClip voiceOver;
public Action action;
public List<Response> responses;
}
[System.Serializable]
public class Response {
[TextArea(1, 10)]
public string name;
public List<Statement> statements;
}
[System.Serializable]
public class Action {
public GameObject prefab;
public Transform spawnTransform;
public Vector3 offset;
}
[System.Serializable]
public class NPCDialog : MonoBehaviour
{
[Header("Dialog Branches")]
public List<Statement> statements;
Statement currentStatement;
Stack<Statement> statementsStack;
[Header("UI References")]
public TMP_Text statementText;
public Transform responsePanel;
public GameObject responseButtonPrefab;
AudioSource audioSource;
void Start()
{
statementsStack = new Stack<Statement>();
audioSource = GetComponent<AudioSource>();
AddStatementsToStack(statements);
ProcessStack();
}
void AddStatementsToStack(List<Statement> newStatements) {
newStatements.Reverse();
foreach (Statement statement in newStatements)
{
statementsStack.Push(statement);
}
}
public void ProcessStack() {
if (statementsStack.Count == 0) {
statementText.text = "Dialog Over";
DeleteResponseButtons();
return;
}
currentStatement = statementsStack.Pop();
statementText.text = currentStatement.name;
if (currentStatement.voiceOver && audioSource) {
audioSource.PlayOneShot(currentStatement.voiceOver);
}
if (currentStatement.action.prefab)
{
GameObject actionItem = Instantiate(currentStatement.action.prefab);
actionItem.transform.position = currentStatement.action.spawnTransform.position + currentStatement.action.offset;
}
BuildResponseButtons();
}
void DeleteResponseButtons()
{
foreach (Transform child in responsePanel)
{
Destroy(child.gameObject);
}
}
void BuildResponseButtons() {
DeleteResponseButtons();
if (currentStatement.responses.Count <= 0)
{
BuildResponseButton("...").onClick.AddListener(Done);
return;
}
int index = 0;
foreach (Response response in currentStatement.responses)
{
int indexCopy = index;
BuildResponseButton(response.name).onClick.AddListener(() => HandleResponse(indexCopy));
index++;
}
}
Button BuildResponseButton(string text) {
GameObject buttonGameObject = (GameObject)Instantiate(responseButtonPrefab);
buttonGameObject.transform.SetParent(responsePanel);
buttonGameObject.transform.GetChild(0).GetComponent<TMP_Text>().text = text;
return buttonGameObject.GetComponent<Button>();
}
void Done() {
Debug.Log("Done");
ProcessStack();
}
public void HandleResponse(int index) {
Response response = currentStatement.responses[index];
if (response?.statements.Count > 0) {
AddStatementsToStack(response.statements);
}
ProcessStack();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment