Adventure Creator Wikia
No edit summary
Tag: Visual edit
(Removed need to manually assign Quest IDs)
Tag: Visual edit
Line 8: Line 8:
   
 
To use this script:
 
To use this script:
# In the Inventory Manager's Quest tab, name your sub-Objectives using the convention "QuestTitle/SubObjectiveTitle" (no quotes).  The script works through naming-convention - so a sub-Objective will be automatically classes as such so long as another Objective named "QuestTitle" (or whatever was used to name the sub-Objective) is found.
+
# In the Inventory Manager's Quest tab, name your sub-Objectives using the convention "QuestTitle/SubObjectiveTitle" (no quotes).  The script works through naming-convention - so a sub-Objective will be automatically classes as such so long as another Objective named "QuestTitle" (or whatever was used to name the sub-Objective) is found. Any Objective without a forward-slash (/) character will be treated as a Quest.
 
# For each Objective, enter in the text to display into the '''Description''' box.
 
# For each Objective, enter in the text to display into the '''Description''' box.
 
# Define a new Global String Variable in the Variables Manager
 
# Define a new Global String Variable in the Variables Manager
 
# Paste in the code below into a C# script named QuestSystem.cs
 
# Paste in the code below into a C# script named QuestSystem.cs
 
# Add a new GameObject to your game's first scene and attach the new Quest System component to it
 
# Add a new GameObject to your game's first scene and attach the new Quest System component to it
# In the Quest System Inspector, enter in the number of Quests you want - and enter each Quest's ID number into each index entry.  A Quests ID number can be found to the left of its name in the Inventory Manager.
 
 
# Also in the Inspector, enter in the ID number of your new String variable.  This too will be to the left of its name in the Variables Manager.
 
# Also in the Inspector, enter in the ID number of your new String variable.  This too will be to the left of its name in the Variables Manager.
 
# (Optional) Create a new Menu with an '''Appear type''' set to '''During Gameplay''', position it to the side of the screen, and give it a single Label element.
 
# (Optional) Create a new Menu with an '''Appear type''' set to '''During Gameplay''', position it to the side of the screen, and give it a single Label element.
Line 31: Line 30:
 
#region Variables<br>
 
#region Variables<br>
 
<br>
 
<br>
[SerializeField] private Quest[] quests;<br>
+
private Quest[] quests;<br>
 
[SerializeField] private int stringVariableID;<br>
 
[SerializeField] private int stringVariableID;<br>
 
<br>
 
<br>
Line 42: Line 41:
 
{<br>
 
{<br>
 
DontDestroyOnLoad (gameObject);<br>
 
DontDestroyOnLoad (gameObject);<br>
  +
GenerateQuests ();<br>
<br>
 
foreach (Quest quest in quests)<br>
 
{<br>
 
quest.GenerateSubIDs ();<br>
 
}<br>
 
 
<br>
 
<br>
 
EventManager.OnObjectiveUpdate += OnObjectiveUpdate;<br>
 
EventManager.OnObjectiveUpdate += OnObjectiveUpdate;<br>
Line 61: Line 56:
 
<br>
 
<br>
 
#region PrivateFunctions<br>
 
#region PrivateFunctions<br>
 
<br>
  +
private void GenerateQuests ()<br>
  +
{<br>
  +
List<Quest> questsList = new List<Quest>();<br>
 
foreach (Objective objective in KickStarter.inventoryManager.objectives)<br>
 
{<br>
  +
if (!objective.Title.Contains ("/"))<br>
 
{<br>
 
Quest quest = new Quest (objective);<br>
  +
questsList.Add (quest);<br>
 
}<br>
 
}<br>
 
<br>
  +
quests = questsList.ToArray ();<br>
 
}<br>
  +
<br>
 
<br>
 
<br>
 
private void OnObjectiveUpdate (Objective objective, ObjectiveState state)<br>
 
private void OnObjectiveUpdate (Objective objective, ObjectiveState state)<br>
Line 105: Line 116:
 
 
 
<br>
 
<br>
[SerializeField] private int mainObjectiveID;<br>
+
private int mainObjectiveID;<br>
 
private List<int> subObjectiveIDs = new List<int>();<br>
 
private List<int> subObjectiveIDs = new List<int>();<br>
 
 
 
<br>
 
<br>
 
#endregion<br>
 
#endregion<br>
 
 
<br>
 
<br>
 
 
<br>
 
<br>
#region PublicFunctions<br>
+
#region Constructors<br>
 
<br>
 
<br>
public void GenerateSubIDs ()<br>
+
public Quest (Objective mainObjective)<br>
 
{<br>
 
{<br>
  +
mainObjectiveID = mainObjective.ID;<br>
Objective mainObjective = KickStarter.inventoryManager.GetObjective (mainObjectiveID);<br>
 
if (mainObjective == null)<br>
+
subObjectiveIDs = new List<int>();<br>
{<br>
 
return;<br>
 
}<br>
 
 
<br>
 
<br>
 
string requiredPrefix = mainObjective.Title + "/";<br>
 
string requiredPrefix = mainObjective.Title + "/";<br>
subObjectiveIDs.Clear ();<br>
 
<br>
 
 
foreach (Objective objective in KickStarter.inventoryManager.objectives)<br>
 
foreach (Objective objective in KickStarter.inventoryManager.objectives)<br>
 
{<br>
 
{<br>
Line 137: Line 141:
 
}<br>
 
}<br>
 
<br>
 
<br>
  +
#endregion<br>
 
 
  +
<br>
 
  +
<br>
  +
#region PublicFunctions<br>
 
<br>
 
<br>
 
public void OnObjectiveUpdate (int objectiveID)<br>
 
public void OnObjectiveUpdate (int objectiveID)<br>
Line 333: Line 342:
 
<br>
 
<br>
 
}
 
}
  +
  +
 
[[Category:General]]
 
[[Category:General]]

Revision as of 08:47, 6 April 2020

AC v1.70 introduced Objectives, which offer a way to provide feedback on the player's progress performing the game's tasks.

Objectives can have multiple states, but themselves are independent from one another.  This script allows you to classify specific Objectives as "Quests", which then depend on sub-Objectives for their completion state.  If all sub-Objectives are complete, the Quest Objective will be complete.  Similarly, if all sub-Objectives are failed, the Quest Objective will be failed.

This behaviour might not be desirable for all situations, so the script is intended to act as a demonstration for how the Objective system can be expanded upon through scripting.

Additionally, the state of all active Quests - and their sub-Objectives - will be condensed into a formatted Global String variable, which can then be e.g. displayed in a Label menu element for player feedback.

To use this script:

  1. In the Inventory Manager's Quest tab, name your sub-Objectives using the convention "QuestTitle/SubObjectiveTitle" (no quotes).  The script works through naming-convention - so a sub-Objective will be automatically classes as such so long as another Objective named "QuestTitle" (or whatever was used to name the sub-Objective) is found. Any Objective without a forward-slash (/) character will be treated as a Quest.
  2. For each Objective, enter in the text to display into the Description box.
  3. Define a new Global String Variable in the Variables Manager
  4. Paste in the code below into a C# script named QuestSystem.cs
  5. Add a new GameObject to your game's first scene and attach the new Quest System component to it
  6. Also in the Inspector, enter in the ID number of your new String variable.  This too will be to the left of its name in the Variables Manager.
  7. (Optional) Create a new Menu with an Appear type set to During Gameplay, position it to the side of the screen, and give it a single Label element.
  8. (Optional) Set that Label element's Label type to Global Variable, and select the String variable created in step 3.  Increase the size of the element so that it's large enough to display the list of objectives

QuestSystem.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace AC
{

public class QuestSystem : MonoBehaviour
{

#region Variables

private Quest[] quests;
[SerializeField] private int stringVariableID;

#endregion


#region UnityStandards

private void OnEnable ()
{
DontDestroyOnLoad (gameObject);
GenerateQuests ();

EventManager.OnObjectiveUpdate += OnObjectiveUpdate;
}


private void OnDisable ()
{
EventManager.OnObjectiveUpdate -= OnObjectiveUpdate;
}

#endregion


#region PrivateFunctions

private void GenerateQuests ()
{
List<Quest> questsList = new List<Quest>();
foreach (Objective objective in KickStarter.inventoryManager.objectives)
{
if (!objective.Title.Contains ("/"))
{
Quest quest = new Quest (objective);
questsList.Add (quest);
}
}

quests = questsList.ToArray ();
}


private void OnObjectiveUpdate (Objective objective, ObjectiveState state)
{
foreach (Quest quest in quests)
{
quest.OnObjectiveUpdate (objective.ID);
}

UpdateActiveQuestsString ();
}


private void UpdateActiveQuestsString ()
{
GVar stringVariable = GlobalVariables.GetVariable (stringVariableID);

if (stringVariable == null)
{
return;
}

string result = string.Empty;

foreach (Quest quest in quests)
{
result += quest.GetActiveString ();
result += "\n";
}

stringVariable.SetStringValue (result);
}

#endregion


[System.Serializable]
private class Quest
{

#region Variables

private int mainObjectiveID;
private List<int> subObjectiveIDs = new List<int>();

#endregion


#region Constructors

public Quest (Objective mainObjective)
{
mainObjectiveID = mainObjective.ID;
subObjectiveIDs = new List<int>();

string requiredPrefix = mainObjective.Title + "/";
foreach (Objective objective in KickStarter.inventoryManager.objectives)
{
if (objective != mainObjective && objective.Title.StartsWith (requiredPrefix))
{
// Is a sub-objective
subObjectiveIDs.Add (objective.ID);
}
}
}

#endregion


#region PublicFunctions

public void OnObjectiveUpdate (int objectiveID)
{
if (!subObjectiveIDs.Contains (objectiveID) ||
!IsValid ())
{
return;
}

AutoUpdateMainState ();
}


public string GetActiveString ()
{
if (!IsValid ())
{
return string.Empty;
}

ObjectiveInstance mainObjectiveInstance = KickStarter.runtimeObjectives.GetObjective (mainObjectiveID);
if (mainObjectiveInstance == null || mainObjectiveInstance.CurrentState.stateType != ObjectiveStateType.Active)
{
return string.Empty;
}

int language = Options.GetLanguage ();
string result = "<b>" + mainObjectiveInstance.Objective.GetDescription (language) + ":</b>";
result += "\n";

foreach (int subObjectiveID in subObjectiveIDs)
{
ObjectiveInstance subObjectiveInstance = KickStarter.runtimeObjectives.GetObjective (subObjectiveID);
if (subObjectiveInstance == null)
{
continue;
}

result += " - " + subObjectiveInstance.Objective.GetDescription (language);

switch (subObjectiveInstance.CurrentState.stateType)
{
case ObjectiveStateType.Fail:
result += " (FAILED)";
break;

case ObjectiveStateType.Complete:
result += " (COMPLETE)";
break;

default:
break;
}

result += "\n";
}

return result;
}

#endregion


#region PrivateFunctions

private void AutoUpdateMainState ()
{
int completedSubObjectives = 0;
int failedSubObjectives = 0;
int activeSubObjectives = 0;

foreach (int subObjectiveID in subObjectiveIDs)
{
ObjectiveState subObjectiveState = KickStarter.runtimeObjectives.GetObjectiveState (subObjectiveID);
if (subObjectiveState == null)
{
continue;
}

switch (subObjectiveState.stateType)
{
case ObjectiveStateType.Active:
activeSubObjectives ++;
break;

case ObjectiveStateType.Complete:
completedSubObjectives ++;
break;

case ObjectiveStateType.Fail:
failedSubObjectives ++;
break;

default:
break;
}
}

int totalSubObjectives = subObjectiveIDs.Count;
Objective mainObjective = KickStarter.inventoryManager.GetObjective (mainObjectiveID);

if (completedSubObjectives == totalSubObjectives)
{
// Completed all sub-objectives, main is completed
SetObjectiveStateType (mainObjective, ObjectiveStateType.Complete);
}
else if (failedSubObjectives > 0)
{
// Failed a sub-objectives, main is failed
SetObjectiveStateType (mainObjective, ObjectiveStateType.Fail);
}
else if (activeSubObjectives > 0)
{
// Main is active
SetObjectiveStateType (mainObjective, ObjectiveStateType.Active);
}
else
{
// Main is inactive
}
}


private void SetObjectiveStateType (Objective objective, ObjectiveStateType stateType)
{
int failedStateID = GetFirstObjectiveStateType (objective, stateType);
if (failedStateID >= 0)
{
KickStarter.runtimeObjectives.SetObjectiveState (objective.ID, failedStateID);
}
}


private int GetFirstObjectiveStateType (Objective objective, ObjectiveStateType stateType)
{
foreach (ObjectiveState state in objective.states)
{
if (state.stateType == stateType)
{
return state.ID;
}
}
return -1;
}


private bool IsValid ()
{
if (mainObjectiveID < 0 ||
subObjectiveIDs.Contains (mainObjectiveID) ||
subObjectiveIDs.Count == 0)
{
return false;
}
return true;
}

#endregion

}

}

}