An easy to use Unity 3D library for creating illustrated Interactive Fiction games and more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
6.2 KiB

using UnityEngine;
using UnityEngine.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Fungus
{
[ExecuteInEditMode]
[RequireComponent(typeof(Flowchart))]
[AddComponentMenu("")]
public class Block : Node
{
public enum ExecutionState
{
Idle,
Executing,
}
[NonSerialized]
public ExecutionState executionState;
[HideInInspector]
public int itemId = -1; // Invalid flowchart item id
[FormerlySerializedAs("sequenceName")]
[Tooltip("The name of the block node as displayed in the Flowchart window")]
public string blockName = "New Block";
[TextArea(2, 5)]
[Tooltip("Description text to display under the block node")]
public string description = "";
[Tooltip("An optional Event Handler which can execute the block when an event occurs")]
public EventHandler eventHandler;
[HideInInspector]
[System.NonSerialized]
public Command activeCommand;
// Index of last command executed before the current one
// -1 indicates no previous command
[HideInInspector]
[System.NonSerialized]
public int previousActiveCommandIndex = -1;
[HideInInspector]
[System.NonSerialized]
public float executingIconTimer;
[HideInInspector]
public List<Command> commandList = new List<Command>();
protected int executionCount;
/**
* Duration of fade for executing icon displayed beside blocks & commands.
*/
public const float executingIconFadeTime = 0.5f;
/**
* Controls the next command to execute in the block execution coroutine.
*/
[NonSerialized]
public int jumpToCommandIndex = -1;
protected virtual void Awake()
{
// Give each child command a reference back to its parent block
// and tell each command its index in the list.
int index = 0;
foreach (Command command in commandList)
{
if (command == null)
{
continue;
}
command.parentBlock = this;
command.commandIndex = index++;
}
}
#if UNITY_EDITOR
// The user can modify the command list order while playing in the editor,
// so we keep the command indices updated every frame. There's no need to
// do this in player builds so we compile this bit out for those builds.
void Update()
{
int index = 0;
foreach (Command command in commandList)
{
if (command == null) // Null entry will be deleted automatically later
{
continue;
}
command.commandIndex = index++;
}
}
#endif
public virtual Flowchart GetFlowchart()
{
return GetComponent<Flowchart>();
}
public virtual bool HasError()
{
foreach (Command command in commandList)
{
if (command.errorMessage.Length > 0)
{
return true;
}
}
return false;
}
public virtual bool IsExecuting()
{
return (executionState == ExecutionState.Executing);
}
public virtual int GetExecutionCount()
{
return executionCount;
}
public virtual bool Execute(Action onComplete = null)
{
if (executionState != ExecutionState.Idle)
{
return false;
}
executionCount++;
StartCoroutine(ExecuteBlock(onComplete));
return true;
}
protected virtual IEnumerator ExecuteBlock(Action onComplete = null)
{
Flowchart flowchart = GetFlowchart();
executionState = ExecutionState.Executing;
#if UNITY_EDITOR
// Select the executing block & the first command
flowchart.selectedBlock = this;
if (commandList.Count > 0)
{
flowchart.ClearSelectedCommands();
flowchart.AddSelectedCommand(commandList[0]);
}
#endif
int i = 0;
while (true)
{
// Executing commands specify the next command to skip to by setting jumpToCommandIndex using Command.Continue()
if (jumpToCommandIndex > -1)
{
i = jumpToCommandIndex;
jumpToCommandIndex = -1;
}
// Skip disabled commands, comments and labels
while (i < commandList.Count &&
(!commandList[i].enabled ||
commandList[i].GetType() == typeof(Comment) ||
commandList[i].GetType() == typeof(Label)))
{
i = commandList[i].commandIndex + 1;
}
if (i >= commandList.Count)
{
break;
}
// The previous active command is needed for if / else / else if commands
if (activeCommand == null)
{
previousActiveCommandIndex = -1;
}
else
{
previousActiveCommandIndex = activeCommand.commandIndex;
}
Command command = commandList[i];
activeCommand = command;
if (flowchart.gameObject.activeInHierarchy)
{
// Auto select a command in some situations
if ((flowchart.selectedCommands.Count == 0 && i == 0) ||
(flowchart.selectedCommands.Count == 1 && flowchart.selectedCommands[0].commandIndex == previousActiveCommandIndex))
{
flowchart.ClearSelectedCommands();
flowchart.AddSelectedCommand(commandList[i]);
}
}
command.isExecuting = true;
// This icon timer is managed by the FlowchartWindow class, but we also need to
// set it here in case a command starts and finishes execution before the next window update.
command.executingIconTimer = Time.realtimeSinceStartup + executingIconFadeTime;
command.Execute();
// Wait until the executing command sets another command to jump to via Command.Continue()
while (jumpToCommandIndex == -1)
{
yield return null;
}
#if UNITY_EDITOR
if (flowchart.stepPause > 0f)
{
yield return new WaitForSeconds(flowchart.stepPause);
}
#endif
command.isExecuting = false;
}
executionState = ExecutionState.Idle;
activeCommand = null;
if (onComplete != null)
{
onComplete();
}
}
public virtual void Stop()
{
// This will cause the execution loop to break on the next iteration
jumpToCommandIndex = int.MaxValue;
}
public virtual List<Block> GetConnectedBlocks()
{
List<Block> connectedBlocks = new List<Block>();
foreach (Command command in commandList)
{
if (command != null)
{
command.GetConnectedBlocks(ref connectedBlocks);
}
}
return connectedBlocks;
}
public virtual System.Type GetPreviousActiveCommandType()
{
if (previousActiveCommandIndex >= 0 &&
previousActiveCommandIndex < commandList.Count)
{
return commandList[previousActiveCommandIndex].GetType();
}
return null;
}
}
}