Completed a large amount of work relating to recording.
This was all done in about 4 hours of crunch. I was just in the groove. Recording is pretty much completely working. You can record things in realtime, you can pass that data onto a playback object (haven't written that part yet but I've written the in-between), and you can save recordings to a file, either as a binary file (efficient) or as an XML file (definitely not). There's more I might want to do with the encoding in the future, namely some compression. Like if a value hasn't changed in a while, we don't need to store it every tick. I believe this is called "temporal compression." The XML data actually compresses incredibly well (I guess I shouldn't be surprised; the data is 90% the word "Property"). That might be worth looking into. Anyway, playback coming soon.
This commit is contained in:
parent
1242aef875
commit
fee5cf0efd
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.36212.18 d17.14
|
VisualStudioVersion = 17.14.36212.18
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActionRecorder", "ActionRecorder\ActionRecorder.csproj", "{ADBD19FD-F099-4422-A96B-D2D3A025C31B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActionRecorder", "ActionRecorder\ActionRecorder.csproj", "{ADBD19FD-F099-4422-A96B-D2D3A025C31B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
171
ActionRecorder/ActionManager.cs
Normal file
171
ActionRecorder/ActionManager.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
using UnityEngine;
|
||||||
|
using DisplayNameAttribute = System.ComponentModel.DisplayNameAttribute;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public static class ActionManager
|
||||||
|
{
|
||||||
|
private static readonly List<ComponentRecorderInfo> recorderTypes;
|
||||||
|
private static readonly List<ComponentRecorderBase> activeRecorders;
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, (ObjectRecorder recorder, object playback)> registeredIds;
|
||||||
|
private static readonly Dictionary<(string id, string recordname), RecordingContainer> recordingData;
|
||||||
|
|
||||||
|
static ActionManager()
|
||||||
|
{
|
||||||
|
Assembly[] toSearch = new Assembly[]
|
||||||
|
{
|
||||||
|
Assembly.GetEntryAssembly(),
|
||||||
|
Assembly.GetExecutingAssembly()
|
||||||
|
};
|
||||||
|
|
||||||
|
recorderTypes = new List<ComponentRecorderInfo>();
|
||||||
|
foreach (Assembly asm in toSearch)
|
||||||
|
{
|
||||||
|
if (asm is null) continue;
|
||||||
|
foreach (Type t in asm.GetTypes())
|
||||||
|
{
|
||||||
|
if (recorderTypes.Any(x => x.RecorderType == t)) continue;
|
||||||
|
|
||||||
|
ComponentRecorderAttribute att = t.GetCustomAttribute<ComponentRecorderAttribute>();
|
||||||
|
if (att is null || !t.HasBaseType<ComponentRecorderBase>()) continue;
|
||||||
|
|
||||||
|
// Try to get the instant name. If there isn't one, use the name
|
||||||
|
// of the recorder, minus the word "recorder"
|
||||||
|
string instantName;
|
||||||
|
DisplayNameAttribute instantNameAtt = att.instantType.GetCustomAttribute<DisplayNameAttribute>();
|
||||||
|
if (instantNameAtt is null)
|
||||||
|
{
|
||||||
|
instantName = t.Name.Replace("Recorder", "");
|
||||||
|
}
|
||||||
|
else instantName = instantNameAtt.DisplayName;
|
||||||
|
|
||||||
|
recorderTypes.Add(new ComponentRecorderInfo()
|
||||||
|
{
|
||||||
|
ComponentType = att.componentType,
|
||||||
|
RecorderType = t,
|
||||||
|
InstantType = att.instantType,
|
||||||
|
InstantDisplayName = instantName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeRecorders = new List<ComponentRecorderBase>();
|
||||||
|
registeredIds = new Dictionary<string, (ObjectRecorder recorder, object playback)>();
|
||||||
|
recordingData = new Dictionary<(string, string), RecordingContainer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ComponentRecorderInfo GetRecorderInfo(Type recorderType)
|
||||||
|
{
|
||||||
|
return recorderTypes.SingleOrDefault(x => x.RecorderType == recorderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IdTaken(string id) => registeredIds.ContainsKey(id);
|
||||||
|
public static void RegisterController(ObjectRecorder recorder)
|
||||||
|
{
|
||||||
|
if (registeredIds.ContainsKey(recorder.Id))
|
||||||
|
{
|
||||||
|
(ObjectRecorder, object) current = registeredIds[recorder.Id];
|
||||||
|
current.Item1 = recorder;
|
||||||
|
registeredIds[recorder.Id] = current;
|
||||||
|
}
|
||||||
|
else registeredIds.Add(recorder.Id, (recorder, null));
|
||||||
|
|
||||||
|
recorder.containers = new RecordingContainer[recorder.ComponentsToRecord.Length];
|
||||||
|
for (int i = 0; i < recorder.containers.Length; i++)
|
||||||
|
{
|
||||||
|
ComponentRecorderInfo info = GetRecorderInfo(recorder.recorders[i].GetType());
|
||||||
|
if (!recordingData.TryGetValue((recorder.Id, info.InstantDisplayName), out RecordingContainer data))
|
||||||
|
{
|
||||||
|
data = new RecordingContainer(info.InstantDisplayName);
|
||||||
|
recordingData.Add((recorder.Id, info.InstantDisplayName), data);
|
||||||
|
}
|
||||||
|
recorder.containers[i] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasRecorderTypeFor(Component component)
|
||||||
|
{
|
||||||
|
return recorderTypes.Any(x => x.ComponentType == component.GetType());
|
||||||
|
}
|
||||||
|
public static ComponentRecorderBase GetRecorderFor(string id, Component component)
|
||||||
|
{
|
||||||
|
Type comType = component.GetType();
|
||||||
|
|
||||||
|
// First check if a recorder with this ID already exists.
|
||||||
|
ComponentRecorderBase result = activeRecorders.SingleOrDefault(x =>
|
||||||
|
x.Controller.Id == id &&
|
||||||
|
GetComponentTypeOfRecorder(x) == comType);
|
||||||
|
if (result != null) return result;
|
||||||
|
|
||||||
|
// Otherwise make a new instance.
|
||||||
|
ComponentRecorderInfo recInfo = recorderTypes.SingleOrDefault(x => x.ComponentType == comType);
|
||||||
|
if (recInfo is null) return null;
|
||||||
|
result = (ComponentRecorderBase)Activator.CreateInstance(recInfo.RecorderType);
|
||||||
|
activeRecorders.Add(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream GetRecordingAsBinary(string id)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
BinaryWriter writer = new BinaryWriter(ms, Encoding.UTF8, true);
|
||||||
|
WriteRecordingAsBinary(id, writer);
|
||||||
|
writer.Close();
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
public static void WriteRecordingAsBinary(string id, BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(id);
|
||||||
|
IEnumerable<RecordingContainer> data = from c in recordingData
|
||||||
|
where c.Key.id == id
|
||||||
|
select c.Value;
|
||||||
|
foreach (RecordingContainer container in data)
|
||||||
|
{
|
||||||
|
writer.Write(container.DisplayName);
|
||||||
|
container.EncodeBinary(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static string GetRecordingAsXml(string id, bool indent = true)
|
||||||
|
{
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
XmlWriterSettings settings = new XmlWriterSettings();
|
||||||
|
if (indent)
|
||||||
|
{
|
||||||
|
settings.Indent = true;
|
||||||
|
settings.IndentChars = " ";
|
||||||
|
}
|
||||||
|
XmlWriter writer = XmlWriter.Create(result, settings);
|
||||||
|
WriteRecordingAsXml(id, writer);
|
||||||
|
writer.Close();
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
public static void WriteRecordingAsXml(string id, XmlWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Object");
|
||||||
|
writer.WriteAttributeString("Identifier", id);
|
||||||
|
IEnumerable<RecordingContainer> data = from c in recordingData
|
||||||
|
where c.Key.id == id
|
||||||
|
select c.Value;
|
||||||
|
foreach (RecordingContainer container in data)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Component");
|
||||||
|
writer.WriteAttributeString("Identifier", container.DisplayName);
|
||||||
|
container.EncodeXml(writer);
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Type GetComponentTypeOfRecorder(ComponentRecorderBase instance) =>
|
||||||
|
recorderTypes.Single(x => x.RecorderType == instance.GetType()).ComponentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,14 @@
|
|||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="UnityEngine">
|
<Reference Include="UnityEngine">
|
||||||
<HintPath>UnityEngine.dll</HintPath>
|
<HintPath>UnityEngine.dll</HintPath>
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace ActionRecorder
|
|
||||||
{
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
ActionRecorder/ComponentRecorderAttribute.cs
Normal file
17
ActionRecorder/ComponentRecorderAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ComponentRecorderAttribute : Attribute
|
||||||
|
{
|
||||||
|
public readonly Type componentType;
|
||||||
|
public readonly Type instantType;
|
||||||
|
|
||||||
|
public ComponentRecorderAttribute(Type componentType, Type instantType)
|
||||||
|
{
|
||||||
|
this.componentType = componentType;
|
||||||
|
this.instantType = instantType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
ActionRecorder/ComponentRecorderBase.cs
Normal file
24
ActionRecorder/ComponentRecorderBase.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public abstract class ComponentRecorderBase
|
||||||
|
{
|
||||||
|
public ObjectRecorder Controller { get; internal set; }
|
||||||
|
public Type ComponentType => ActionManager.GetComponentTypeOfRecorder(this);
|
||||||
|
|
||||||
|
public Component Component { get; internal set; }
|
||||||
|
|
||||||
|
public abstract FrequencyKind RecordFrequency { get; }
|
||||||
|
|
||||||
|
public virtual void Awake() { }
|
||||||
|
public virtual void Start() { }
|
||||||
|
|
||||||
|
public virtual void OnBeginRecording() { }
|
||||||
|
public abstract RecordingInstantBase RecordInstant();
|
||||||
|
public virtual void OnEndRecording() { }
|
||||||
|
|
||||||
|
// TODO: Return a type for containing recordings.
|
||||||
|
}
|
||||||
|
}
|
||||||
14
ActionRecorder/ComponentRecorderInfo.cs
Normal file
14
ActionRecorder/ComponentRecorderInfo.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public class ComponentRecorderInfo
|
||||||
|
{
|
||||||
|
public Type ComponentType { get; internal set; }
|
||||||
|
public Type RecorderType { get; internal set; }
|
||||||
|
public Type InstantType { get; internal set; }
|
||||||
|
public string InstantDisplayName { get; internal set; }
|
||||||
|
|
||||||
|
internal ComponentRecorderInfo() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
ActionRecorder/FrequencyKind.cs
Normal file
9
ActionRecorder/FrequencyKind.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public enum FrequencyKind
|
||||||
|
{
|
||||||
|
Manual = 0,
|
||||||
|
Update = 1,
|
||||||
|
FixedUpdate = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
19
ActionRecorder/HelperExtensions.cs
Normal file
19
ActionRecorder/HelperExtensions.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
internal static class HelperExtensions
|
||||||
|
{
|
||||||
|
public static bool HasBaseType(this Type type, Type baseType)
|
||||||
|
{
|
||||||
|
Type activeType = type.BaseType;
|
||||||
|
while (activeType != null)
|
||||||
|
{
|
||||||
|
if (activeType == baseType) return true;
|
||||||
|
else activeType = activeType.BaseType;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public static bool HasBaseType<T>(this Type type) => HasBaseType(type, typeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
138
ActionRecorder/ObjectRecorder.cs
Normal file
138
ActionRecorder/ObjectRecorder.cs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public class ObjectRecorder : MonoBehaviour
|
||||||
|
{
|
||||||
|
public bool AutoRecord;
|
||||||
|
public string Id;
|
||||||
|
public Component[] ComponentsToRecord;
|
||||||
|
|
||||||
|
public bool Recording { get; private set; }
|
||||||
|
|
||||||
|
internal ComponentRecorderBase[] recorders;
|
||||||
|
internal RecordingContainer[] containers;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Id))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("You should always give your recorders a unique identifier!");
|
||||||
|
Id = GetRandomId();
|
||||||
|
}
|
||||||
|
else if (ActionManager.IdTaken(Id))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"A recorder with the ID \"{Id}\" already exists! Please choose a different unique identifier.");
|
||||||
|
Id = GetRandomId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the list of components to record, ensure they are
|
||||||
|
// components belonging to this object, and initialize recorders
|
||||||
|
// for those components.
|
||||||
|
|
||||||
|
List<Component> valid = new List<Component>();
|
||||||
|
foreach (Component c in ComponentsToRecord)
|
||||||
|
{
|
||||||
|
if (c is null) continue;
|
||||||
|
if (c.gameObject != gameObject)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"The component {c} does not belong to this game object and thus cannot be recorded!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!ActionManager.HasRecorderTypeFor(c))
|
||||||
|
{
|
||||||
|
Debug.LogError($"No recognized recorder type for {c}! This component cannot be recorded.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
valid.Add(c);
|
||||||
|
}
|
||||||
|
ComponentsToRecord = valid.ToArray();
|
||||||
|
|
||||||
|
recorders = new ComponentRecorderBase[ComponentsToRecord.Length];
|
||||||
|
for (int i = 0; i < recorders.Length; i++)
|
||||||
|
{
|
||||||
|
ComponentRecorderBase rec = ActionManager.GetRecorderFor(Id, ComponentsToRecord[i]);
|
||||||
|
rec.Controller = this;
|
||||||
|
rec.Component = ComponentsToRecord[i];
|
||||||
|
recorders[i] = rec;
|
||||||
|
}
|
||||||
|
ActionManager.RegisterController(this);
|
||||||
|
|
||||||
|
OnAwake();
|
||||||
|
for (int i = 0; i < recorders.Length; i++) recorders[i].Awake();
|
||||||
|
}
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (AutoRecord) RecordStart();
|
||||||
|
|
||||||
|
OnStart();
|
||||||
|
for (int i = 0; i < recorders.Length; i++) recorders[i].Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnAwake() { }
|
||||||
|
protected virtual void OnStart() { }
|
||||||
|
protected virtual void OnUpdate() { }
|
||||||
|
protected virtual void OnFixedUpdate() { }
|
||||||
|
protected virtual void BeginRecording() { }
|
||||||
|
protected virtual void EndRecording() { }
|
||||||
|
public event Action OnBeginRecording = delegate { };
|
||||||
|
public event Action OnEndRecording = delegate { };
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
OnUpdate();
|
||||||
|
if (!Recording) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < recorders.Length; i++)
|
||||||
|
{
|
||||||
|
ComponentRecorderBase rec = recorders[i];
|
||||||
|
if (rec.RecordFrequency != FrequencyKind.Update) continue;
|
||||||
|
RecordingInstantBase instant = rec.RecordInstant();
|
||||||
|
containers[i].Add(instant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
OnFixedUpdate();
|
||||||
|
if (!Recording) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < recorders.Length; i++)
|
||||||
|
{
|
||||||
|
ComponentRecorderBase rec = recorders[i];
|
||||||
|
if (rec.RecordFrequency != FrequencyKind.FixedUpdate) continue;
|
||||||
|
RecordingInstantBase instant = rec.RecordInstant();
|
||||||
|
containers[i].Add(instant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordStart()
|
||||||
|
{
|
||||||
|
if (Recording) return;
|
||||||
|
Recording = true;
|
||||||
|
BeginRecording();
|
||||||
|
OnBeginRecording();
|
||||||
|
for (int i = 0; i < recorders.Length; i++) recorders[i].OnBeginRecording();
|
||||||
|
}
|
||||||
|
public void RecordStop()
|
||||||
|
{
|
||||||
|
if (!Recording) return;
|
||||||
|
Recording = false;
|
||||||
|
EndRecording();
|
||||||
|
OnEndRecording();
|
||||||
|
for (int i = 0; i < recorders.Length; i++) recorders[i].OnEndRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRandomId()
|
||||||
|
{
|
||||||
|
StringBuilder id = new StringBuilder("CHANGEME-");
|
||||||
|
System.Random rand = new System.Random();
|
||||||
|
for (int i = 0; i < 8; i++) id.Append((char)('A' + rand.Next(26)));
|
||||||
|
return id.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
ActionRecorder/Recorders/TransformRecorder.cs
Normal file
48
ActionRecorder/Recorders/TransformRecorder.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ActionRecorder.Recorders
|
||||||
|
{
|
||||||
|
[ComponentRecorder(typeof(Transform), typeof(TransformInstant))]
|
||||||
|
public class TransformRecorder : ComponentRecorderBase
|
||||||
|
{
|
||||||
|
public override FrequencyKind RecordFrequency { get; } = FrequencyKind.Update;
|
||||||
|
|
||||||
|
new public Transform Component => (Transform)base.Component;
|
||||||
|
|
||||||
|
public override RecordingInstantBase RecordInstant() => new TransformInstant()
|
||||||
|
{
|
||||||
|
Time = Time.time,
|
||||||
|
localPosition = Component.localPosition,
|
||||||
|
localRotation = Component.localRotation.eulerAngles,
|
||||||
|
localScale = Component.localScale
|
||||||
|
};
|
||||||
|
|
||||||
|
[DisplayName("Transform")]
|
||||||
|
public class TransformInstant : RecordingInstantBase
|
||||||
|
{
|
||||||
|
public Vector3 localPosition, localRotation, localScale;
|
||||||
|
|
||||||
|
public override void EncodeBinary(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(localPosition.x);
|
||||||
|
writer.Write(localPosition.y);
|
||||||
|
writer.Write(localPosition.z);
|
||||||
|
writer.Write(localRotation.x);
|
||||||
|
writer.Write(localRotation.y);
|
||||||
|
writer.Write(localRotation.z);
|
||||||
|
writer.Write(localScale.x);
|
||||||
|
writer.Write(localScale.y);
|
||||||
|
writer.Write(localScale.z);
|
||||||
|
}
|
||||||
|
public override void EncodeXml(XmlWriter writer)
|
||||||
|
{
|
||||||
|
WriteProperty(writer, nameof(localPosition), localPosition);
|
||||||
|
WriteProperty(writer, nameof(localRotation), localRotation);
|
||||||
|
WriteProperty(writer, nameof(localScale), localScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
ActionRecorder/RecordingContainer.cs
Normal file
45
ActionRecorder/RecordingContainer.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public class RecordingContainer
|
||||||
|
{
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
// TODO: Replace this with a more sophisticated method.
|
||||||
|
// Allocate using powers of 2, implement IList, etc.
|
||||||
|
public readonly List<RecordingInstantBase> instants;
|
||||||
|
|
||||||
|
internal RecordingContainer(string name)
|
||||||
|
{
|
||||||
|
DisplayName = name;
|
||||||
|
instants = new List<RecordingInstantBase>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(RecordingInstantBase instant)
|
||||||
|
{
|
||||||
|
instants.Add(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EncodeBinary(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
foreach (RecordingInstantBase instant in instants)
|
||||||
|
{
|
||||||
|
writer.Write(instant.Time);
|
||||||
|
instant.EncodeBinary(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void EncodeXml(XmlWriter writer)
|
||||||
|
{
|
||||||
|
foreach (RecordingInstantBase instant in instants)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Instant");
|
||||||
|
writer.WriteAttributeString("Time", instant.Time.ToString());
|
||||||
|
instant.EncodeXml(writer);
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
ActionRecorder/RecordingInstantBase.cs
Normal file
55
ActionRecorder/RecordingInstantBase.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ActionRecorder
|
||||||
|
{
|
||||||
|
public abstract class RecordingInstantBase
|
||||||
|
{
|
||||||
|
public float Time { get; set; }
|
||||||
|
|
||||||
|
public abstract void EncodeBinary(BinaryWriter writer);
|
||||||
|
public abstract void EncodeXml(XmlWriter writer);
|
||||||
|
|
||||||
|
protected void WriteProperty(XmlWriter writer, string name, object value)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Property");
|
||||||
|
writer.WriteAttributeString("Name", name);
|
||||||
|
writer.WriteValue(value?.ToString() ?? "");
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
protected void WriteProperty(XmlWriter writer, string name, Vector2 value)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Property");
|
||||||
|
writer.WriteAttributeString("Name", name);
|
||||||
|
|
||||||
|
WriteProperty(writer, "x", value.x);
|
||||||
|
WriteProperty(writer, "y", value.y);
|
||||||
|
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
protected void WriteProperty(XmlWriter writer, string name, Vector3 value)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Property");
|
||||||
|
writer.WriteAttributeString("Name", name);
|
||||||
|
|
||||||
|
WriteProperty(writer, "x", value.x);
|
||||||
|
WriteProperty(writer, "y", value.y);
|
||||||
|
WriteProperty(writer, "z", value.z);
|
||||||
|
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
protected void WriteProperty(XmlWriter writer, string name, Vector4 value)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("Property");
|
||||||
|
writer.WriteAttributeString("Name", name);
|
||||||
|
|
||||||
|
WriteProperty(writer, "x", value.x);
|
||||||
|
WriteProperty(writer, "y", value.y);
|
||||||
|
WriteProperty(writer, "z", value.z);
|
||||||
|
WriteProperty(writer, "w", value.w);
|
||||||
|
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user