다이얼로그 작업중

This commit is contained in:
skrwns304@gmail.com
2026-06-09 15:51:29 +09:00
parent 54c6ddee0a
commit 404921f815
172 changed files with 9854 additions and 26 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 318e27bd31adda9468bbc46ace95b9c6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: d860f3521bf608c468e4863c7cc232ac
TextScriptImporter:
guid: f4d6b8db1a5ed1343accb75293597025
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 103ef77e0ab79bf4bb00e69a4f3fbb11
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b5dd56c374fcdca41927418e4717e370
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
using System;
[Serializable]
public class DialogChoice
{
public DialogNode DestinationNode;
public string ChoiceText;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bd5ea8e7c904dd24e967d91b86004efb

View File

@@ -0,0 +1,8 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Communication/Dialog Group")]
public class DialogGroup : ScriptableObject
{
public string DialogGroupName;
public DialogNode StartNode;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 72ef984dbd5eb29498ece3d2dae297ea

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine;
[CreateAssetMenu(menuName = "Communication/Dialog Node")]
public class DialogNode : ScriptableObject
{
[Header("Speaker")]
public CharacterData Speaker;
[Header("Content")]
[TextArea(2,5)] public string TalkText;
public GestureData Gesture;
public ExpressionData Expression;
public VoiceClip Voice;
public float LineDuration; //자동 넘김 시간
//LineDuration=0 → 플레이어 입력 대기 (수동)
//Voice 있음 → 클립 길이만큼 대기
//Voice 없음 → LineDuration 대기
[Header("Behavior")]
public bool LookAtPlayer;
[Header("Flow")]
public DialogNode Next; // 선택지 없을 때 자동으로 갈 노드
public List<DialogChoice> Choices; // 있으면 플레이어 선택 대기
[Header("ChoiceQuestion")]
[TextArea(2,5)] public string ChoiceQuestion;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fb30abdb4b5671b458096d48d11c7f27

View File

@@ -0,0 +1,196 @@
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine;
[RequireComponent(typeof(CharacterVoiceObject))]
public class DialogPlayer : MonoBehaviour
{
[SerializeField] private List<DialogGroup> _dialogGroups;
private Dictionary<string, DialogGroup> _dialogGroupMap;
private Animator _animator;
private int _initialGestureHash;
private int _initialExpressionHash;
private bool _hasInitialExpression;
private readonly Dictionary<Transform, Quaternion> _originalRotations = new();
public bool IsPlaying { get; private set; }
private void Awake()
{
_dialogGroupMap = new Dictionary<string, DialogGroup>();
foreach (var g in _dialogGroups)
_dialogGroupMap[g.DialogGroupName] = g;
_animator = GetComponentInChildren<Animator>();
if (_animator != null)
{
_initialGestureHash = _animator.GetCurrentAnimatorStateInfo(0).fullPathHash;
if (_animator.layerCount > 1)
{
_initialExpressionHash = _animator.GetCurrentAnimatorStateInfo(1).fullPathHash;
_hasInitialExpression = true;
}
}
}
public void Play()
{
if(_dialogGroups.Count > 0)
_ = Play(_dialogGroups[0].DialogGroupName);
}
public async Awaitable Play(string groupName)
{
if (IsPlaying) return;
if (!_dialogGroupMap.TryGetValue(groupName, out var group))
{
Debug.LogWarning($"[DialogPlayer] 그룹 없음: {groupName}");
return;
}
IsPlaying = true;
try
{
var node = group.StartNode;
while (node != null)
{
await PlayNode(node);
if (node.Choices != null && node.Choices.Count > 0)
{
int picked = await WaitForChoice(node);
node = node.Choices[picked].DestinationNode;
}
else
{
node = node.Next;
}
}
}
finally
{
IsPlaying = false;
RestoreDefaultAnimations();
RestoreRotations();
}
Debug.Log("[DialogPlayer] 대화 종료");
}
private void RestoreDefaultAnimations()
{
if (_animator == null) return;
_animator.CrossFade(_initialGestureHash, 0.3f, 0, normalizedTimeOffset: 0f);
if (_hasInitialExpression)
_animator.CrossFade(_initialExpressionHash, 0.3f, 1, normalizedTimeOffset: 0f);
}
private async Awaitable RotateTowardPlayer(Transform target)
{
if (Camera.main == null) return;
var playerCam = Camera.main.transform;
float duration = 0.5f;
float elapsed = 0f;
while (elapsed < duration)
{
Vector3 dir = playerCam.position - target.position;
dir.y = 0f;
if (dir.sqrMagnitude > 0.0001f)
{
var targetRot = Quaternion.LookRotation(dir);
target.rotation = Quaternion.Slerp(target.rotation, targetRot, 10f * Time.deltaTime);
}
elapsed += Time.deltaTime;
await Awaitable.NextFrameAsync();
}
}
private void RestoreRotations()
{
foreach (var kvp in _originalRotations)
{
if (kvp.Key != null)
_ = RotateToRotation(kvp.Key, kvp.Value);
}
_originalRotations.Clear();
}
private async Awaitable RotateToRotation(Transform target, Quaternion targetRotation)
{
float duration = 0.5f;
float elapsed = 0f;
while (elapsed < duration)
{
if (target == null) return;
target.rotation = Quaternion.Slerp(target.rotation, targetRotation, 10f * Time.deltaTime);
elapsed += Time.deltaTime;
await Awaitable.NextFrameAsync();
}
}
private async Awaitable PlayNode(DialogNode node)
{
var speakerName = node.Speaker != null ? node.Speaker.Name : "(누구?)";
Debug.Log($"[{speakerName}] {node.TalkText}");
// 보이스 재생
if (node.Voice != null && node.Speaker != null)
{
var voiceObj = CharacterVoiceObject.Find(node.Speaker);
if (voiceObj != null && node.Voice.Clip != null)
voiceObj.Play(node.Voice.Clip);
}
// 플레이어 향해 회전
if (node.LookAtPlayer && node.Speaker != null)
{
var voiceObj = CharacterVoiceObject.Find(node.Speaker);
if (voiceObj != null)
{
_originalRotations.TryAdd(voiceObj.transform, voiceObj.transform.rotation);
_ = RotateTowardPlayer(voiceObj.transform);
}
}
if (node.Gesture != null)
_animator.CrossFade(node.Gesture.StateName, node.Gesture.CrossFadeDuration, node.Gesture.AnimationLayer);
if (node.Expression != null)
_animator.CrossFade(node.Expression.StateName, node.Expression.CrossFadeDuration, node.Expression.AnimationLayer);
// 대기 시간 결정
float wait = 0f;
if (node.Voice != null && node.Voice.Clip != null)
wait = node.Voice.Clip.length;
else
wait = node.LineDuration;
if (wait > 0f)
await Awaitable.WaitForSecondsAsync(wait);
else
await WaitForAdvanceInput(); // 수동 진행
}
private async Awaitable<int> WaitForChoice(DialogNode node)
{
//선택을 기다리는 함수 수정해서 사용할것
/*
if (ChoiceHud.Instance == null)
{
Debug.LogWarning("[DialogPlayer] ChoiceHud 없음 — 0번 자동 선택");
return 0;
}
return await ChoiceHud.Instance.Show(node.Speaker, node.ChoiceQuestion ,node.Choices);
*/
return 0;
}
private async Awaitable WaitForAdvanceInput()
{
// TODO: VR 컨트롤러 버튼 입력 대기. 일단은 1초 대기
await Awaitable.WaitForSecondsAsync(1f);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9838e0e0a3edefa4e92ddbb98aaa3ce5

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 98959040e708aa04891672a6c21a9479
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using UnityEngine;
public class CharacterVoiceObject : MonoBehaviour
{
public CharacterData Character;
public AudioSource VoiceSource;
private static readonly Dictionary<CharacterData, CharacterVoiceObject> _registry = new();
private void OnEnable() => _registry[Character] = this;
private void OnDisable() => _registry.Remove(Character);
public static CharacterVoiceObject Find(CharacterData data)
=> _registry.TryGetValue(data, out var obj) ? obj : null;
public void Play(AudioClip clip) => VoiceSource.PlayOneShot(clip);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: aa8a1a66f46e7ef4f99770437e10ae34

View File

@@ -0,0 +1,109 @@
using System;
using UnityEngine;
// 보이스 진폭에 따라 입 관련 블렌드셰이프 그룹의 weight를 직접 제어
// LateUpdate에서 갱신해 Animator가 같은 프레임에 0으로 세팅한 값을 덮어씀
[RequireComponent(typeof(CharacterVoiceObject))]
public class LipSync : MonoBehaviour
{
[Serializable]
private struct LipShape
{
public string Name;
[Range(0f, 100f)] public float MaxWeight; // amplitude=1일 때 도달할 weight
}
[Header("Refs")]
[SerializeField] private SkinnedMeshRenderer _meshRenderer;
// BMAC_OpenMouse_Big 클립의 입 관련 셰이프 프리셋
[Header("Mouth Preset (입 최대 시 weight)")]
[SerializeField] private LipShape[] _shapes =
{
new() { Name = "Expression_SurpriesedMouth", MaxWeight = 50f },
new() { Name = "Expression_MouthSad_L", MaxWeight = 10f },
new() { Name = "Expression_MouthSad_R", MaxWeight = 10f },
new() { Name = "Expression_MouthWide_L", MaxWeight = 30f },
new() { Name = "Expression_MouthWide_R", MaxWeight = 30f },
new() { Name = "Expression_LipsOh", MaxWeight = 100f },
new() { Name = "Expression_LipsO", MaxWeight = 5f },
};
[Header("Tuning")]
[SerializeField, Range(0f, 20f)] private float _amplitudeScale = 6f; // RMS → 0~1 매핑 배수
[SerializeField, Range(0f, 0.05f)] private float _noiseFloor = 0.005f;
[SerializeField, Range(0f, 30f)] private float _smoothingSpeed = 15f;
[SerializeField] private int _sampleSize = 256;
private AudioSource _audioSource;
private int[] _indices;
private float[] _sampleBuffer;
private float _currentAmplitude;
private void Awake()
{
var voiceObj = GetComponent<CharacterVoiceObject>();
_audioSource = voiceObj != null ? voiceObj.VoiceSource : null;
// 메시 자동 탐색 — 첫 번째 셰이프 이름을 가진 SkinnedMeshRenderer 사용
if (_meshRenderer == null && _shapes.Length > 0)
{
string probe = _shapes[0].Name;
foreach (var smr in GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
if (smr.sharedMesh != null && smr.sharedMesh.GetBlendShapeIndex(probe) >= 0)
{
_meshRenderer = smr;
break;
}
}
}
// 인덱스 캐시
_indices = new int[_shapes.Length];
if (_meshRenderer != null && _meshRenderer.sharedMesh != null)
{
var mesh = _meshRenderer.sharedMesh;
for (int i = 0; i < _shapes.Length; i++)
{
_indices[i] = mesh.GetBlendShapeIndex(_shapes[i].Name);
if (_indices[i] < 0)
Debug.LogWarning($"[LipSync] 블렌드셰이프 없음: {_shapes[i].Name}", this);
}
}
else
{
for (int i = 0; i < _indices.Length; i++) _indices[i] = -1;
}
if (_audioSource == null)
Debug.LogWarning("[LipSync] CharacterVoiceObject.VoiceSource 미할당", this);
_sampleBuffer = new float[_sampleSize];
}
private void LateUpdate()
{
if (_audioSource == null || _meshRenderer == null) return;
// PlayOneShot도 잡히도록 항상 샘플링 — 무음은 노이즈 플로어로 컷
_audioSource.GetOutputData(_sampleBuffer, 0);
float sumSq = 0f;
for (int i = 0; i < _sampleBuffer.Length; i++)
sumSq += _sampleBuffer[i] * _sampleBuffer[i];
float rms = Mathf.Sqrt(sumSq / _sampleBuffer.Length);
rms = Mathf.Max(0f, rms - _noiseFloor);
float target = Mathf.Clamp01(rms * _amplitudeScale);
_currentAmplitude = Mathf.Lerp(_currentAmplitude, target, Time.deltaTime * _smoothingSpeed);
// Animator가 같은 프레임에 0으로 덮은 값을 LateUpdate에서 다시 씌움
for (int i = 0; i < _shapes.Length; i++)
{
if (_indices[i] < 0) continue;
_meshRenderer.SetBlendShapeWeight(_indices[i], _currentAmplitude * _shapes[i].MaxWeight);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ae0d9abbe32fe5b4e8e4886143e1e5e2

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f15371153c317454585018e424b7df5f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f7bdce60fa4722c409858d6a5a234ec3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Character/CharacterData")]
public class CharacterData : ScriptableObject
{
public string Id;
public string Name;
public Sprite Portrait;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c44a4226e5f3e344a96eac21f829952f

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f31eda293b7f19046b229ac17d081f81
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 864a90870193471458445ecc75905891
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Communication/Expression")]
public class ExpressionData : ScriptableObject
{
[HideInInspector] public int AnimationLayer = 1;
public string StateName;
public float CrossFadeDuration = 0.2f;
public AnimationClip AnimClip;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b705522cfca24d141b3d560aeffb2c14

View File

@@ -0,0 +1,10 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Communication/Gesture")]
public class GestureData : ScriptableObject
{
[HideInInspector] public int AnimationLayer = 0;
public string StateName;
public float CrossFadeDuration = 0.2f;
public AnimationClip AnimClip;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7287df3d52d586a4085d412938dae461

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e5b1fe96389ab554db24396abacdc278
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Communication/VoiceClip")]
public class VoiceClip : ScriptableObject
{
public string VoiceCode; //해당 음성 고유코드
public AudioClip Clip;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fc306ebaeba77b94dbb631e9fdc0e864

View File

@@ -1 +0,0 @@
지울것

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 92e8daf2a9ba20a49a16bb225f8d77d3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 141467d7781315e48add8aa98cc3f898
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b8aa4d76be40e104a8092d81a3ec5514
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 950e0361db9a0a745b0ed9ad142f100f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 61a783976a5fa5b42bc285ab42cb6163
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1f943b2edd3f1d14bac0dd498d02472b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f645ce42811120c4e96f476777bdfdb8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 389924088ebe3cb4c9eba808e6930cbd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 31dd7fd2b9c6180449ac56389b32d9dc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 66f8495fd4406e64da3cc6b32bbd81a6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1 +0,0 @@
지울것

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 87758d7304fe4e048a2ff5af7c1d0ad1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8ea9a14d170e8fb44b2d23996f207323
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0ed698945ae02b742902aee392bc1f5b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6fddd458db0b7c14f9ccd96f22b9ee24
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32a9038ef099d5a4d87f583d4b99d0b4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
using System.Linq;
using Unity.GraphToolkit.Editor;
using UnityEditor.AssetImporters;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// TextureMakerImporter is a <see cref="ScriptedImporter"/> responsible for creating the texture and adding the
/// texture to the asset, so the graph can be used directly as a texture in the scene.
/// It contains all the Texture logic of the tool.
/// </summary>
[ScriptedImporter(1, TextureMakerGraph.AssetExtension)]
internal class TextureMakerImporter : ScriptedImporter
{
/// <summary>
/// The name of the texture identifier in the asset.
/// </summary>
private const string OutputTextureAssetIdName = "OutputTexture";
/// <summary>
/// Gets or sets the output Texture2D.
/// </summary>
private Texture2D OutputTexture { get; set; }
/// <summary>
/// Called when the asset is imported. This method processes the graph object and creates the texture.
/// </summary>
/// <param name="ctx">The asset import context.</param>
public override void OnImportAsset(AssetImportContext ctx)
{
// The loaded graph may be null if the `Graph.LoadForImporter` method fails to load the asset from the
// specified `ctx.assetPath`.
// This can occur under the following circumstances:
// - The asset path is incorrect, or the asset does not exist at the specified location.
// - The asset located at the specified path is not of type `TextureMakerGraph`.
// - The asset file itself is problematic, such as being corrupted or stored in an unsupported format.
//
// Best practice when dealing with serialization is to account for potential deserialization issues
// by thoroughly validating and safeguarding against impaired or incomplete data.
var graph = GraphDatabase.LoadGraphForImporter<TextureMakerGraph>(ctx.assetPath);
if (graph == null)
{
Debug.LogError($"Failed to load texture maker graph object: {ctx.assetPath}");
return;
}
CompileGraph(graph);
// Adding the texture to the graph object and setting it to be the main asset.
// This allows the generated texture to be used directly in the project and referenced in a material or a scene.
if (OutputTexture != null)
{
ctx.AddObjectToAsset(OutputTextureAssetIdName, OutputTexture);
ctx.SetMainObject(OutputTexture);
}
}
/// <summary>
/// Compiles the provided texture maker graph and updates the output texture based on its evaluated nodes.
/// </summary>
/// <param name="graph">The texture maker graph to compile.</param>
private void CompileGraph(TextureMakerGraph graph)
{
// Get the first CreateTexture Node
// (Only using the first one is a tool design simplification we made for this sample)
var createTextureNode = graph.GetNodes().OfType<CreateTextureNode>().FirstOrDefault();
if (createTextureNode == null)
{
// No need to log an error here, as an error is already logged in the console (see the `OnGraphChanged` method
// overriden in the TextureMakerGraph class).
return;
}
// Initialize the output texture to a default texture
// This enables the graph object to use a default texture when a valid texture evaluator node is not yet available.
OutputTexture = new Texture2D(CreateTextureNode.OutputWidth, CreateTextureNode.OutputHeight);
OutputTexture.name = OutputTextureAssetIdName;
// Get the input port of the CreateTextureNode
var inputPort = createTextureNode.GetInputPortByName(CreateTextureNode.TextureInputName);
// Resolve the texture from the input port
var texture = TextureMakerGraph.ResolvePortValue<Texture2D>(inputPort);
if (texture != null)
{
OutputTexture = new Texture2D(texture.width, texture.height);
OutputTexture.SetPixels(texture.GetPixels());
} // else can happen when the input port is not connected and no embedded value is set
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e14cbdfe3bad40dc9e5cf791df184d43
timeCreated: 1740674216

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 51ff29566e157cc42a82d110da3fa57f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2145001d8b7b79d4d90138544650514d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System;
using JetBrains.Annotations;
using Unity.GraphToolkit.Editor;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// Represents the result node in a Texture Maker Graph.
/// </summary>
/// <remarks>
/// The result node serves as the end node in the texture graph, where the final texture is evaluated.
/// </remarks>
[Serializable]
[UsedImplicitly]
internal class CreateTextureNode : Node
{
/// Using constants for port ids provides type safety and ensures consistent references across the implementation
internal const string TextureInputName = "InTexture";
/// <summary>
/// The width of the output texture.
/// </summary>
public const int OutputWidth = 16;
/// <summary>
/// The height of the output texture.
/// </summary>
public const int OutputHeight = 16;
/// <summary>
/// Defines the input for the node.
/// </summary>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
context.AddInputPort<Texture2D>(TextureInputName).Build();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5bbd857d07214e2b97256039bbd713f7
timeCreated: 1740675943

View File

@@ -0,0 +1,22 @@
using Unity.GraphToolkit.Editor;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// Defines an interface for evaluating Texture ports.
/// </summary>
/// <remarks>
/// This interface is defined by nodes with the ability to output a Texture2D.
/// Implementations of this interface should provide the logic to evaluate a given port model and return the resulting Texture.
/// </remarks>
internal interface ITextureEvaluatorNode
{
/// <summary>
/// Evaluates the specified port and returns the resulting Texture.
/// </summary>
/// <param name="port">The port to evaluate.</param>
/// <returns>The result of the evaluation.</returns>
public Texture2D EvaluateTexturePort(IPort port);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 184980cc4753403d96db0011e12a78b2
timeCreated: 1742204057

View File

@@ -0,0 +1,75 @@
using System;
using JetBrains.Annotations;
using Unity.GraphToolkit.Editor;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// Represents a node that combines two input textures and produces an output texture where pixel values are the mean
/// of the two input values at the same position.
/// </summary>
[Serializable]
[UsedImplicitly]
internal class MeanColorNode : Node, ITextureEvaluatorNode
{
/// Using constants for port ids provides type safety and ensures consistent references across the implementation
internal const string Texture1InputName = "InTexture1";
internal const string Texture2InputName = "InTexture2";
internal const string TextureOutputName = "OutTexture";
/// <summary>
/// Defines the input and output port for the Mean node.
/// This node takes two textures as input and outputs a Texture2D.
/// </summary>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
context.AddInputPort<Texture2D>(Texture1InputName).Build();
context.AddInputPort<Texture2D>(Texture2InputName).Build();
context.AddOutputPort<Texture2D>(TextureOutputName).Build();
}
/// <summary>
/// Evaluates the specified port and returns the result.
/// </summary>
/// <param name="port">The port model to evaluate.</param>
/// <returns>The result of the evaluation.</returns>
public Texture2D EvaluateTexturePort(IPort port)
{
// Only the output port can be evaluated for this node.
if (port != GetOutputPortByName(TextureOutputName))
return null;
// Get the textures from the ports or the embedded value
Texture2D firstTexture = TextureMakerGraph.ResolvePortValue<Texture2D>(GetInputPortByName(Texture1InputName));
Texture2D secondTexture = TextureMakerGraph.ResolvePortValue<Texture2D>(GetInputPortByName(Texture2InputName));
if (firstTexture != null && secondTexture != null)
{
// Using min dimensions to prevent out-of-bounds access; simplified for learning (production code would handle varying texture sizes)
int width = Math.Min(firstTexture.width, secondTexture.width);
int height = Math.Min(firstTexture.height, secondTexture.height);
Texture2D resultTexture = new Texture2D(width, height);
for (var j = 0; j < height; j++)
{
for (var i = 0; i < width; i++)
{
// Set the color of each pixel in the texture
Color firstColor = firstTexture.GetPixel(i, j);
Color secondColor = secondTexture.GetPixel(i, j);
Color color = (firstColor + secondColor) / 2;
resultTexture.SetPixel(i, j, color);
}
}
return resultTexture;
}
// If one of the input ports have no resolved value, return a default texture
return new Texture2D(CreateTextureNode.OutputWidth, CreateTextureNode.OutputHeight);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2866fff3cefe46638f8fe6860cb86b5e
timeCreated: 1742380071

View File

@@ -0,0 +1,57 @@
using System;
using JetBrains.Annotations;
using Unity.GraphToolkit.Editor;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// Represents a node that generates a uniform texture from a specified color.
/// </summary>
[Serializable]
[UsedImplicitly]
internal class UniformNode : Node, ITextureEvaluatorNode
{
/// Using constants for port ids provides type safety and ensures consistent references across the implementation
internal const string ColorInputName = "InColor";
internal const string TextureOutputName = "OutTexture";
/// <summary>
/// Defines the input and output port for the Uniform node.
/// This nodes takes a color as input and outputs a Texture2D.
/// </summary>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
context.AddInputPort<Color>(ColorInputName).Build();
context.AddOutputPort<Texture2D>(TextureOutputName).Build();
}
/// <summary>
/// Evaluates the specified port and returns the result.
/// </summary>
/// <param name="port">The port model to evaluate.</param>
/// <returns>The result of the evaluation.</returns>
public Texture2D EvaluateTexturePort(IPort port)
{
// Only the Texture output port can be evaluated for this node.
if (port != GetOutputPortByName(TextureOutputName))
return null;
// Gets the color port.
var colorPort = GetInputPortByName(ColorInputName);
// Get the color from the connected port or the embedded value
var color = TextureMakerGraph.ResolvePortValue<Color>(colorPort);
var texture = new Texture2D(CreateTextureNode.OutputWidth, CreateTextureNode.OutputHeight);
// Set the color of each pixel in the texture
for (var j = 0; j < CreateTextureNode.OutputHeight; j++)
{
for (var i = 0; i < CreateTextureNode.OutputWidth; i++)
{
texture.SetPixel(i, j, color);
}
}
return texture;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b8ad780581240939565c81dd3d40ce5
timeCreated: 1740675943

View File

@@ -0,0 +1,120 @@
using System;
using System.Linq;
using Unity.GraphToolkit.Editor;
using UnityEditor;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.TextureMaker.Editor
{
/// <summary>
/// Represents the Texture Maker graph.
/// </summary>
/// <remarks> This is the entry point for the Texture Maker tool, it represents a Texture Maker graph.
/// This class extends the Graph Toolkit <see cref="Graph"/> class, which provides a range of default behaviors,
/// reducing the amount of code required to quickly implement a basic graph tool.
/// It also provides customization by allowing you to override specific methods and can be decorated by attributes to
/// tailor the tool's functionality.
/// For example, in this class, we override the OnGraphChanged method to add our custom handling for graph errors and warnings.
/// We also decorated it with the <see cref="GraphAttribute"/>, which specifies the asset extension.
/// </remarks>
[Graph(AssetExtension)]
[Serializable]
internal class TextureMakerGraph : Graph
{
/// <summary>
/// The file extension for Texture Maker graph assets.
/// </summary>
/// <remarks> In Unity, the extension is used to select the right importer, so it must be unique.</remarks>
internal const string AssetExtension = "texmkr";
/// <summary>
/// Create a texture maker asset file.
/// </summary>
[MenuItem("Assets/Create/Graph Toolkit Samples/Texture Maker Graph", false)]
static void CreateAssetFile()
{
GraphDatabase.PromptInProjectBrowserToCreateNewAsset<TextureMakerGraph>();
}
/// <summary>
/// Checks the graph for errors and warnings and adds them to the result object.
/// </summary>
/// <param name="infos">Object implementing <see cref="GraphLogger"/> interface and containing
/// collected errors and warnings</param>
/// <remarks>Errors and warnings are reported by adding them to the GraphLogger object,
/// which is the default reporting mechanism for a Graph Toolkit tool. </remarks>
private void CheckGraphErrors(GraphLogger infos)
{
// Get all CreateTextureNode instances in the graph
var createTextureNodes = GetNodes().OfType<CreateTextureNode>().ToList();
switch (createTextureNodes.Count)
{
case 0:
// This is a tool design choice to log an error if no CreateTextureNode is found in the graph.
// This will result in an error message being logged in the console.
infos.LogError("Add a CreateTextureNode in your Texture graph.");
break;
case > 1:
{
foreach (var createTextureNode in createTextureNodes.Skip(1))
{
// This will result in a warning message being logged in the console and a warning marker being displayed on the node
infos.LogWarning($"TextureMaker only supports one {nameof(CreateTextureNode)} by graph. " +
"Only the first created one will be used.", createTextureNode);
}
break;
}
}
}
/// <summary>
/// Called when the graph changes.
/// </summary>
/// <param name="infos">The GraphLogger object to which errors and warnings are added.</param>
/// <remarks>
/// This method is triggered whenever the graph is modified. It calls `CheckGraphErrors` to validate the graph
/// and report any issues.
/// </remarks>
public override void OnGraphChanged(GraphLogger infos)
{
CheckGraphErrors(infos);
}
/// <summary>
/// Resolves the value from the given port by determining the type of node connected.
/// For constant, variable, and texture evaluator nodes, it extracts the corresponding value.
/// If no connected source is available, it attempts to resolve an embedded value from the port.
/// </summary>
/// <typeparam name="T">The expected type of the port value.</typeparam>
/// <param name="port">The port from which to resolve the value.</param>
/// <returns>
/// The resolved value of type <typeparamref name="T"/> if successful; otherwise, the default value of <typeparamref name="T"/>.
/// </returns>
public static T ResolvePortValue<T>(IPort port)
{
// Get the source port providing input to "port" (null if no connection exists)
var sourcePort = port.firstConnectedPort;
switch (sourcePort?.GetNode())
{
case IConstantNode node:
node.TryGetValue(out T constantValue);
return constantValue;
case IVariableNode node:
node.variable.TryGetDefaultValue(out T variableValue);
return variableValue;
case ITextureEvaluatorNode textureEvaluatorNode:
if (typeof(T).IsAssignableFrom(typeof(Texture2D)))
{
return (T)(object)textureEvaluatorNode.EvaluateTexturePort(sourcePort);
}
break;
case null:
// If no connection exists, try to get "port" 's embedded value (returns type default if unavailable)
port.TryGetValue(out T embeddedValue);
return embeddedValue;
}
return default;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed470a2448996694b915fbb1456339f6
timeCreated: 1647523327

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dac0961372db2d04fbe83f6429dcbc1c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 93047d44cb250814093971723f2c02b8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c33a34c7388f5240a69a3ace09379ad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 8c636d999f90e614d85cf993f7211c1d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 4096
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 2d9bbc299867e244c98e4a3cb6e7a4bf
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: ae8b150e3c088b84f879db093e49f80e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 4096
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 938e70ce972aab048a4ffac6f77386bb
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f482b7cbfff70ad468d867cdee4cb3b3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: c94e9de1d1d086f41bfeaafb64d32436
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 91ebf5c54c1ef1a478545d01bed02985
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,362 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &1
MonoBehaviour:
m_ObjectHideFlags: 61
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 790b4d75d92f4b0984310a268dbd952f, type: 3}
m_Name: FishGraph
m_EditorClassIdentifier: Unity.GraphToolkit.Editor::Unity.GraphToolkit.Editor.Implementation.GraphObjectImp
m_GraphModel:
rid: 7374727060909719746
references:
version: 2
RefIds:
- rid: -2
type: {class: , ns: , asm: }
- rid: 7374727060909719746
type: {class: GraphModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: Unity.GraphToolkit.Editor}
data:
m_Guid:
m_Value0: 10871790756099748146
m_Value1: 18346613123235132978
m_HashGuid:
serializedVersion: 2
Hash: 3271b369175ce0963236f3596e439cfe
m_Name: FishGraph
m_GraphNodeModels:
- rid: 7374727060909719749
- rid: 7374727060909719757
- rid: 7374727060909719761
m_GraphWireModels:
- rid: 7374727060909719760
- rid: 7374727060909719762
m_GraphStickyNoteModels: []
m_GraphPlacematModels: []
m_GraphVariableModels:
- rid: 7374727060909719770
m_GraphPortalModels: []
m_SectionModels:
- rid: 7374727060909719747
m_LocalSubgraphs: []
m_LastKnownBounds:
serializedVersion: 2
x: 588
y: 170.66666
width: 882.6666
height: 188.00003
m_GraphElementMetaData:
- m_Guid:
m_Value0: 16114223607233698066
m_Value1: 9777206141510115540
m_HashGuid:
serializedVersion: 2
Hash: 12d96074fe38a1dfd4607a102f9daf87
m_Category: 0
m_Index: 0
- m_Guid:
m_Value0: 17642536195234030393
m_Value1: 14841915658528706059
m_HashGuid:
serializedVersion: 2
Hash: 3963e50532e1d6f40b4ee9d4a713f9cd
m_Category: 0
m_Index: 1
- m_Guid:
m_Value0: 5435177532580037469
m_Value1: 15680088436607675287
m_HashGuid:
serializedVersion: 2
Hash: 5d039e482ba16d4b97ebb53e5bdd9ad9
m_Category: 2
m_Index: 0
- m_Guid:
m_Value0: 885725688160263005
m_Value1: 3538362528306852737
m_HashGuid:
serializedVersion: 2
Hash: 5d1b3b13d2ba4a0c817348f2f8c91a31
m_Category: 0
m_Index: 2
- m_Guid:
m_Value0: 12130015399313110953
m_Value1: 4354400247149229639
m_HashGuid:
serializedVersion: 2
Hash: a97713f4bc7856a847f63345f4ef6d3c
m_Category: 2
m_Index: 1
- m_Guid:
m_Value0: 15287734564713934406
m_Value1: 9766309808735964307
m_HashGuid:
serializedVersion: 2
Hash: 462e3cb696f128d493cc1ee406e78887
m_Category: 1
m_Index: 0
m_EntryPoint:
rid: 7374727060909719749
m_Graph:
rid: 7374727060909719748
- rid: 7374727060909719747
type: {class: SectionModel, ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Guid:
m_Value0: 3666221662041836785
m_Value1: 8866686477437553489
m_HashGuid:
serializedVersion: 2
Hash: f18cd8592d09e13251cb6c9e5fcc0c7b
m_Version: 2
m_Items:
- rid: 7374727060909719770
m_Title:
- rid: 7374727060909719748
type: {class: VisualNovelDirectorGraph, ns: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, asm: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor}
data:
- rid: 7374727060909719749
type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: Unity.GraphToolkit.Editor}
data:
m_Guid:
m_Value0: 16114223607233698066
m_Value1: 9777206141510115540
m_HashGuid:
serializedVersion: 2
Hash: 12d96074fe38a1dfd4607a102f9daf87
m_Version: 2
m_Position: {x: 588.00006, y: 170.66666}
m_Title:
m_Tooltip:
m_NodePreviewModel:
rid: -2
m_State: 0
m_InputConstantsById:
m_KeyList: []
m_ValueList: []
m_InputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_OutputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_Collapsed: 0
m_CurrentModeIndex: 0
m_ElementColor:
m_Color: {r: 0, g: 0, b: 0, a: 0}
m_HasUserColor: 0
m_Node:
rid: 7374727060909719750
- rid: 7374727060909719750
type: {class: StartNode, ns: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, asm: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor}
data:
- rid: 7374727060909719757
type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: Unity.GraphToolkit.Editor}
data:
m_Guid:
m_Value0: 17642536195234030393
m_Value1: 14841915658528706059
m_HashGuid:
serializedVersion: 2
Hash: 3963e50532e1d6f40b4ee9d4a713f9cd
m_Version: 2
m_Position: {x: 788.9752, y: 176.91309}
m_Title:
m_Tooltip:
m_NodePreviewModel:
rid: -2
m_State: 0
m_InputConstantsById:
m_KeyList:
- Background
m_ValueList:
- rid: 7374727060909719758
m_InputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_OutputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_Collapsed: 0
m_CurrentModeIndex: 0
m_ElementColor:
m_Color: {r: 0, g: 0, b: 0, a: 0}
m_HasUserColor: 0
m_Node:
rid: 7374727060909719759
- rid: 7374727060909719758
type: {class: 'Constant`1[[UnityEngine.Sprite, UnityEngine.CoreModule]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value: {fileID: 0}
- rid: 7374727060909719759
type: {class: SetBackgroundNode, ns: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, asm: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor}
data:
- rid: 7374727060909719760
type: {class: WireModel, ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Guid:
m_Value0: 5435177532580037469
m_Value1: 15680088436607675287
m_HashGuid:
serializedVersion: 2
Hash: 5d039e482ba16d4b97ebb53e5bdd9ad9
m_Version: 2
m_FromPortReference:
m_NodeModelGuid:
m_Value0: 16114223607233698066
m_Value1: 9777206141510115540
m_NodeModelHashGuid:
serializedVersion: 2
Hash: 12d96074fe38a1dfd4607a102f9daf87
m_UniqueId: ExecutionPort
m_PortDirection: 2
m_PortOrientation: 0
m_Title:
m_ToPortReference:
m_NodeModelGuid:
m_Value0: 17642536195234030393
m_Value1: 14841915658528706059
m_NodeModelHashGuid:
serializedVersion: 2
Hash: 3963e50532e1d6f40b4ee9d4a713f9cd
m_UniqueId: ExecutionPort
m_PortDirection: 1
m_PortOrientation: 0
m_Title:
- rid: 7374727060909719761
type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: Unity.GraphToolkit.Editor}
data:
m_Guid:
m_Value0: 885725688160263005
m_Value1: 3538362528306852737
m_HashGuid:
serializedVersion: 2
Hash: 5d1b3b13d2ba4a0c817348f2f8c91a31
m_Version: 2
m_Position: {x: 1168, y: 174.66663}
m_Title:
m_Tooltip:
m_NodePreviewModel:
rid: -2
m_State: 0
m_InputConstantsById:
m_KeyList:
- ActorName
- ActorSprite
- ActorLocation
- Dialogue
m_ValueList:
- rid: 7374727060909719763
- rid: 7374727060909719764
- rid: 7374727060909719765
- rid: 7374727060909719766
m_InputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_OutputPortInfos:
expandedPortsById:
m_KeyList: []
m_ValueList:
m_Collapsed: 0
m_CurrentModeIndex: 0
m_ElementColor:
m_Color: {r: 0, g: 0, b: 0, a: 0}
m_HasUserColor: 0
m_Node:
rid: 7374727060909719767
- rid: 7374727060909719762
type: {class: WireModel, ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Guid:
m_Value0: 12130015399313110953
m_Value1: 4354400247149229639
m_HashGuid:
serializedVersion: 2
Hash: a97713f4bc7856a847f63345f4ef6d3c
m_Version: 2
m_FromPortReference:
m_NodeModelGuid:
m_Value0: 17642536195234030393
m_Value1: 14841915658528706059
m_NodeModelHashGuid:
serializedVersion: 2
Hash: 3963e50532e1d6f40b4ee9d4a713f9cd
m_UniqueId: ExecutionPort
m_PortDirection: 2
m_PortOrientation: 0
m_Title:
m_ToPortReference:
m_NodeModelGuid:
m_Value0: 885725688160263005
m_Value1: 3538362528306852737
m_NodeModelHashGuid:
serializedVersion: 2
Hash: 5d1b3b13d2ba4a0c817348f2f8c91a31
m_UniqueId: ExecutionPort
m_PortDirection: 1
m_PortOrientation: 0
m_Title:
- rid: 7374727060909719763
type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value:
- rid: 7374727060909719764
type: {class: 'Constant`1[[UnityEngine.Sprite, UnityEngine.CoreModule]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value: {fileID: 0}
- rid: 7374727060909719765
type: {class: EnumConstant, ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value:
m_EnumType:
m_Identification: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor.SetDialogueNode+Location,
Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=null
m_Value: 0
m_EnumType:
m_Identification: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor.SetDialogueNode+Location,
Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=null
- rid: 7374727060909719766
type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value:
- rid: 7374727060909719767
type: {class: SetDialogueNode, ns: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor, asm: Unity.GraphToolkit.Samples.VisualNovelDirector.Editor}
data:
- rid: 7374727060909719770
type: {class: VariableDeclarationModel, ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Guid:
m_Value0: 15287734564713934406
m_Value1: 9766309808735964307
m_HashGuid:
serializedVersion: 2
Hash: 462e3cb696f128d493cc1ee406e78887
m_Version: 2
m_Name: Fish1
m_UniqueId:
m_DataType:
m_Identification: System.String, System.Private.CoreLib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
m_IsExposed: 0
m_Scope: 0
m_ShowOnInspectorOnly: 0
m_Tooltip:
m_InitializationValue:
rid: 7374727060909719771
m_Modifiers: 1
m_VariableFlags: 0
- rid: 7374727060909719771
type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor}
data:
m_Value:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6582b238d52902d49834d98156b8eeff
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 5cc0cd77ad7bd1646b4bbaa06b9ca408, type: 3}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2eef90b0729576f48acf0e5a8dccd047
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 5cc0cd77ad7bd1646b4bbaa06b9ca408, type: 3}

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: 38acbdf5a2c207e41a9cbc000d617486
TextScriptImporter:
guid: aabc1a18e22649544aa44ef45945cf54
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: adff2def1fbe63d4bbf736ae549a38b3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25c0bc052eb686040a094b80e9456edc
timeCreated: 1647540495

View File

@@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.GraphToolkit.Editor;
using UnityEditor.AssetImporters;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// VisualNovelDirectorImporter is a <see cref="ScriptedImporter"/> that imports the <see cref="VisualNovelDirectorGraph"/>
/// and builds the corresponding <see cref="VisualNovelRuntimeGraph"/>.
/// </summary>
[ScriptedImporter(1, VisualNovelDirectorGraph.AssetExtension)]
internal class VisualNovelDirectorImporter : ScriptedImporter
{
/// <summary>
/// Unity calls this method when the editor imports the asset. This method then processes the imported <see cref="VisualNovelDirectorGraph"/>.
/// </summary>
/// <param name="ctx">The asset import context.</param>
public override void OnImportAsset(AssetImportContext ctx)
{
var graph = GraphDatabase.LoadGraphForImporter<VisualNovelDirectorGraph>(ctx.assetPath);
// The `graph` may be null if the `GraphDatabase.LoadGraphForImporter` method
// fails to load the asset from the specified `ctx.assetPath`.
// This can occur under the following circumstances:
// - The asset path is incorrect, or the asset does not exist at the specified location.
// - The asset located at the specified path is not of type `VisualNovelDirectorGraph`.
// - The asset file itself is problematic. For example, it is corrupted, or stored in an unsupported format.
//
// Best practice to deal with serialization is to thoroughly validate and safeguard against
// impaired or incomplete data, to account for potential deserialization issues.
if (graph == null)
{
Debug.LogError($"Failed to load Visual Novel Director graph asset: {ctx.assetPath}");
return;
}
// Get the first Start Node
// (Only using the first node is a simplification we made for this sample)
var startNodeModel = graph.GetNodes().OfType<StartNode>().FirstOrDefault();
if (startNodeModel == null)
{
// No need to log an error here, as the VisualNovelDirectorGraphProcessor is already logging an error in the console
// See VisualNovelDirectorGraph.CheckGraphErrors(GraphLogger).
return;
}
// Build the runtime asset by walking the graph and adding the relevant nodes.
var runtimeAsset = ScriptableObject.CreateInstance<VisualNovelRuntimeGraph>();
BuildRuntimeGraph(startNodeModel, runtimeAsset);
// Add the runtime object to the graph asset and set it to be the main asset.
// This allows the same asset to be used in inspectors wherever a runtime asset is expected.
// Refer to the BasicVisualNovelCanvas.prefab for an example of this.
ctx.AddObjectToAsset("RuntimeAsset", runtimeAsset);
ctx.SetMainObject(runtimeAsset);
}
/// <summary>
/// Builds the runtime graph by traversing all reachable nodes from the start node.
/// Supports both linear and branching paths.
/// </summary>
/// <param name="startNode">The start node of the graph</param>
/// <param name="runtimeAsset">The runtime asset to populate</param>
static void BuildRuntimeGraph(INode startNode, VisualNovelRuntimeGraph runtimeAsset)
{
// Map from editor nodes to their starting index in the runtime nodes list
var nodeToRuntimeIndex = new Dictionary<INode, int>();
// Queue for breadth-first traversal
var nodesToProcess = new Queue<INode>();
// Start with the first node after the start node
var firstNode = GetNextNode(startNode);
if (firstNode != null)
{
nodesToProcess.Enqueue(firstNode);
}
// Process all reachable nodes
while (nodesToProcess.Count > 0)
{
var currentNode = nodesToProcess.Dequeue();
// Skip if we've already processed this node
if (nodeToRuntimeIndex.ContainsKey(currentNode))
continue;
// Record the starting index for this node's runtime nodes
var startIndex = runtimeAsset.Nodes.Count;
nodeToRuntimeIndex[currentNode] = startIndex;
// Convert the editor node to runtime node(s)
var runtimeNodes = TranslateNodeModelToRuntimeNodes(currentNode);
runtimeAsset.Nodes.AddRange(runtimeNodes);
// Enqueue connected nodes based on node type
if (currentNode is TwoOptionNode dialogueOptionNode)
{
// For branching nodes, enqueue both branches
var option1Node = GetNextNodeFromPort(dialogueOptionNode, TwoOptionNode.OUT_PORT_OPTION1_NAME);
var option2Node = GetNextNodeFromPort(dialogueOptionNode, TwoOptionNode.OUT_PORT_OPTION2_NAME);
if (option1Node != null)
nodesToProcess.Enqueue(option1Node);
if (option2Node != null)
nodesToProcess.Enqueue(option2Node);
}
else
{
// For linear nodes, enqueue the next node
var nextNode = GetNextNode(currentNode);
if (nextNode != null)
nodesToProcess.Enqueue(nextNode);
}
}
// Second pass: set up NextNodeIndex references
SetupNodeReferences(runtimeAsset, nodeToRuntimeIndex, startNode);
}
/// <summary>
/// Sets up the NextNodeIndex references for all runtime nodes.
/// </summary>
static void SetupNodeReferences(VisualNovelRuntimeGraph runtimeAsset, Dictionary<INode, int> nodeToRuntimeIndex, INode startNode)
{
var processedNodes = new HashSet<INode>();
var nodesToProcess = new Queue<INode>();
var firstNode = GetNextNode(startNode);
if (firstNode != null)
{
nodesToProcess.Enqueue(firstNode);
}
while (nodesToProcess.Count > 0)
{
var currentNode = nodesToProcess.Dequeue();
if (!processedNodes.Add(currentNode))
continue;
if (!nodeToRuntimeIndex.TryGetValue(currentNode, out var currentRuntimeIndex))
continue;
// Handle different node types
if (currentNode is TwoOptionNode optionNode)
{
// For branching nodes, set both branch indices
var option1Node = GetNextNodeFromPort(optionNode, TwoOptionNode.OUT_PORT_OPTION1_NAME);
var option2Node = GetNextNodeFromPort(optionNode, TwoOptionNode.OUT_PORT_OPTION2_NAME);
var twoOptionRuntimeNode = runtimeAsset.Nodes[currentRuntimeIndex] as TwoOptionRuntimeNode;
if (twoOptionRuntimeNode != null)
{
twoOptionRuntimeNode.Option1NextNodeIndex = option1Node != null && nodeToRuntimeIndex.TryGetValue(option1Node, out var idx1) ? idx1 : -1;
twoOptionRuntimeNode.Option2NextNodeIndex = option2Node != null && nodeToRuntimeIndex.TryGetValue(option2Node, out var idx2) ? idx2 : -1;
}
// Enqueue both branches for processing
if (option1Node != null)
nodesToProcess.Enqueue(option1Node);
if (option2Node != null)
nodesToProcess.Enqueue(option2Node);
}
else
{
// For linear nodes, set NextNodeIndex
var nextNode = GetNextNode(currentNode);
// Get all runtime nodes created from this editor node
var runtimeNodes = TranslateNodeModelToRuntimeNodes(currentNode);
for (int i = 0; i < runtimeNodes.Count; i++)
{
var runtimeNodeIndex = currentRuntimeIndex + i;
var runtimeNode = runtimeAsset.Nodes[runtimeNodeIndex];
// If this is the last runtime node from this editor node
if (i == runtimeNodes.Count - 1)
{
// Point to the next editor node's first runtime node
runtimeNode.NextNodeIndex = nextNode != null && nodeToRuntimeIndex.TryGetValue(nextNode, out var nextIdx) ? nextIdx : -1;
}
else
{
// Point to the next runtime node in the sequence
runtimeNode.NextNodeIndex = runtimeNodeIndex + 1;
}
}
if (nextNode != null)
nodesToProcess.Enqueue(nextNode);
}
}
}
/// <summary>
/// Gets the node connected to a specific output port.
/// </summary>
/// <param name="currentNode">The node to get the output from</param>
/// <param name="portName">The name of the output port</param>
/// <returns>The connected node, or null if not connected</returns>
static INode GetNextNodeFromPort(INode currentNode, string portName)
{
var outputPort = currentNode.GetOutputPortByName(portName);
var nextNodePort = outputPort?.firstConnectedPort;
return nextNodePort?.GetNode();
}
/// <summary>
/// Gets the node that is executed after the given node.
/// </summary>
/// <param name="currentNode">The current node</param>
/// <returns>The next node in the graph</returns>
static INode GetNextNode(INode currentNode)
{
var outputPort = currentNode.GetOutputPortByName(VisualNovelNode.EXECUTION_PORT_DEFAULT_NAME);
var nextNodePort = outputPort.firstConnectedPort;
var nextNode = nextNodePort?.GetNode();
return nextNode;
}
/// <summary>
/// Converts a <see cref="VisualNovelNode"/> to a list of one or more runtime <see cref="VisualNovelRuntimeNode"/>s.
/// </summary>
/// <param name="nodeModel">The <see cref="VisualNovelNode"/> to convert.</param>
/// <returns>
/// A list of <see cref="VisualNovelRuntimeNode"/>s that represent the runtime behavior of the input node.
/// Multiple runtime nodes may be generated from a single input <see cref="VisualNovelNode"/>.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown if the <see cref="NodeModel"/> passed in is unsupported and cannot be converted.
/// </exception>
/// <remarks>
/// This conversion is not always 1:1. For example: the <see cref="SetDialogueNode"/> node is converted to
/// a <see cref="SetDialogueRuntimeNode"/> and a <see cref="WaitForInputRuntimeNode"/>. This is so that the
/// runtime pauses execution and waits for player input after a dialogue is displayed. This approach allows
/// more complex behaviour to be composed of multiple simpler runtime nodes.
/// <br/><br/>
/// </remarks>
static List<VisualNovelRuntimeNode> TranslateNodeModelToRuntimeNodes(INode nodeModel)
{
var returnedNodes = new List<VisualNovelRuntimeNode>();
switch (nodeModel)
{
case SetBackgroundNode setBackgroundNodeModel:
returnedNodes.Add(new SetBackgroundRuntimeNode
{
BackgroundSprite = GetInputPortValue<Sprite>(setBackgroundNodeModel.GetInputPortByName(SetBackgroundNode.IN_PORT_BACKGROUND_NAME))
});
// Note: We deliberately don't add a WaitForInputRuntimeNode here to enable updating multiple
// visual novel elements (the background, music, dialogue, etc) all at once. This creates a seamless
// transition involving more than one element.
break;
case SetDialogueNode setDialogueNodeModel:
returnedNodes.Add(new SetDialogueRuntimeNode
{
ActorName = GetInputPortValue<string>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_ACTOR_NAME_NAME)),
ActorSprite = GetInputPortValue<Sprite>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_ACTOR_SPRITE_NAME)),
LocationIndex = (int)GetInputPortValue<SetDialogueNode.Location>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_LOCATION_NAME)),
DialogueText = GetInputPortValue<string>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_DIALOGUE_NAME))
});
// Insert a WaitForInputNode after dialogue to create the expected visual novel behaviour.
// This ensures narrative flow pauses until the player signals readiness to continue.
returnedNodes.Add(new WaitForInputRuntimeNode());
break;
case WaitForInputNode _:
returnedNodes.Add(new WaitForInputRuntimeNode());
break;
case TwoOptionNode twoOptionNodeModel:
returnedNodes.Add(new TwoOptionRuntimeNode
{
Option1Text = GetInputPortValue<string>(twoOptionNodeModel.GetInputPortByName(TwoOptionNode.IN_PORT_OPTION1_NAME)),
Option2Text = GetInputPortValue<string>(twoOptionNodeModel.GetInputPortByName(TwoOptionNode.IN_PORT_OPTION2_NAME))
});
break;
default:
throw new ArgumentException($"Unsupported node model type: {nodeModel.GetType()}");
}
return returnedNodes;
}
/// <summary>
/// Gets the value of an input port on a node.
/// <br/><br/>
/// The value is obtained from (in priority order):<br/>
/// 1. Connections to the port (variable nodes, constant nodes, wire portals)<br/>
/// 2. Embedded value on the port<br/>
/// 3. Default value of the port<br/>
/// </summary>
static T GetInputPortValue<T>(IPort port)
{
T value = default;
// If port is connected to another node, get value from connection
if (port.isConnected)
{
switch (port.firstConnectedPort.GetNode())
{
case IVariableNode variableNode:
variableNode.variable.TryGetDefaultValue<T>(out value);
return value;
case IConstantNode constantNode:
constantNode.TryGetValue<T>(out value);
return value;
default:
break;
}
}
else
{
// If port has embedded value, return it.
// Otherwise, return the default value of the port
port.TryGetValue(out value);
}
return value;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5cc0cd77ad7bd1646b4bbaa06b9ca408
timeCreated: 1740674216

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 603dae51e7f0bd74199c3ec0dcc55a8f
timeCreated: 1741946956

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5704810c9af26f5448573e38148dbb5f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Set Background Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// Is converted to a <see cref="SetBackgroundRuntimeNode"/> for the runtime.
/// </remarks>
[Serializable]
internal class SetBackgroundNode : VisualNovelNode
{
public static readonly string IN_PORT_BACKGROUND_NAME = "Background";
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
AddInputOutputExecutionPorts(context);
context.AddInputPort<Sprite>(IN_PORT_BACKGROUND_NAME);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fcad573acd6e41f68c6e963a725d1853
timeCreated: 1743698947

View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Set Dialogue Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// Is converted to a <see cref="SetDialogueRuntimeNode"/> for the runtime.
/// </remarks>
[Serializable]
internal class SetDialogueNode : VisualNovelNode
{
public const string IN_PORT_ACTOR_NAME_NAME = "ActorName";
public const string IN_PORT_ACTOR_SPRITE_NAME = "ActorSprite";
public const string IN_PORT_LOCATION_NAME = "ActorLocation";
public const string IN_PORT_DIALOGUE_NAME = "Dialogue";
public enum Location
{
Left = 0,
Right = 1
}
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
AddInputOutputExecutionPorts(context);
context.AddInputPort<string>(IN_PORT_ACTOR_NAME_NAME)
.WithDisplayName("Actor Name")
.Build();
context.AddInputPort<Sprite>(IN_PORT_ACTOR_SPRITE_NAME)
.WithDisplayName("Actor Sprite")
.Build();
context.AddInputPort<Location>(IN_PORT_LOCATION_NAME)
.WithDisplayName("Actor Location")
.Build();
context.AddInputPort<string>(IN_PORT_DIALOGUE_NAME)
.Build();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b615d15a39ee45b18faded43e7ebb6a3
timeCreated: 1743699457

Some files were not shown because too many files have changed in this diff Show More