Add support for multiple media events on audio, postfx and camera tracks in Scene Animations

#519
This commit is contained in:
Wojtek Figat
2021-09-21 17:21:39 +02:00
parent f547e44d3d
commit 0ec16de569
16 changed files with 479 additions and 331 deletions

View File

@@ -114,6 +114,11 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public event Action StartFrameChanged;
/// <summary>
/// Gets the end frame of the media (start + duration).
/// </summary>
public int EndFrame => _startFrame + _durationFrames;
/// <summary>
/// Gets or sets the total duration of the media event in the timeline sequence frames amount.
/// </summary>
@@ -175,6 +180,11 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public object PropertiesEditObject;
/// <summary>
/// Gets a value indicating whether this media can be split.
/// </summary>
public bool CanSplit;
/// <summary>
/// Initializes a new instance of the <see cref="Media"/> class.
/// </summary>
@@ -258,6 +268,22 @@ namespace FlaxEditor.GUI.Timeline
Width = Duration * Timeline.UnitsPerSecond * _timeline.Zoom;
}
/// <summary>
/// Splits the media at the specified frame.
/// </summary>
/// <param name="frame">The frame to split at.</param>
/// <returns>The another media created after this media split.</returns>
public virtual Media Split(int frame)
{
var clone = (Media)Activator.CreateInstance(GetType());
clone.StartFrame = frame;
clone.DurationFrames = EndFrame - frame;
DurationFrames = DurationFrames - clone.DurationFrames;
Track?.AddMedia(clone);
Timeline?.MarkAsEdited();
return clone;
}
/// <inheritdoc />
public override void GetDesireClientArea(out Rectangle rect)
{

View File

@@ -104,8 +104,9 @@ namespace FlaxEditor.GUI.Timeline
}
break;
}
case 2:
case 3:
case 2: // [Deprecated in 2020 expires on 03.09.2023]
case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
case 4:
{
// Load properties
FramesPerSecond = stream.ReadSingle();

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Timeline
new KeyValuePair<float, string>(0, "Custom"),
};
internal const int FormatVersion = 3;
internal const int FormatVersion = 4;
/// <summary>
/// The base class for timeline properties proxy objects.
@@ -1584,6 +1584,39 @@ namespace FlaxEditor.GUI.Timeline
}
}
/// <summary>
/// Splits the media (all or selected only) at the given frame.
/// </summary>
/// <param name="frame">The frame to split at.</param>
public void Split(int frame)
{
List<IUndoAction> actions = null;
foreach (var track in _tracks)
{
byte[] trackData = null;
for (int i = track.Media.Count - 1; i >= 0; i--)
{
if (track.Media.Count >= track.MaxMediaCount)
break;
var media = track.Media[i];
if (media.CanSplit && media.StartFrame < frame && media.EndFrame > frame)
{
if (Undo != null && Undo.Enabled)
trackData = EditTrackAction.CaptureData(track);
media.Split(frame);
}
}
if (trackData != null)
{
if (actions == null)
actions = new List<IUndoAction>();
actions.Add(new EditTrackAction(this, track, trackData, EditTrackAction.CaptureData(track)));
}
}
if (actions != null)
Undo.AddAction(new MultiUndoAction(actions, "Split"));
}
/// <summary>
/// Called once to setup the drag drop handling for the timeline (lazy init on first drag action).
/// </summary>
@@ -1979,6 +2012,9 @@ namespace FlaxEditor.GUI.Timeline
return true;
}
break;
case KeyboardKeys.S:
Split(CurrentFrame);
return true;
}
return false;

View File

@@ -171,6 +171,16 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public bool Loop;
/// <summary>
/// The minimum amount of media items for this track.
/// </summary>
public int MinMediaCount = 0;
/// <summary>
/// The maximum amount of media items for this track.
/// </summary>
public int MaxMediaCount = 1024;
/// <summary>
/// The track archetype.
/// </summary>

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FlaxEditor.Utilities;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Viewport.Previews;
@@ -15,7 +16,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents an audio clip media event.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
public class AudioMedia : SingleMediaAssetMedia
public class AudioMedia : Media
{
/// <summary>
/// True if loop track, otherwise audio clip will stop on the end.
@@ -25,19 +26,31 @@ namespace FlaxEditor.GUI.Timeline.Tracks
get => Track.Loop;
set
{
if (Track.Loop != value)
{
Track.Loop = value;
Preview.DrawMode = value ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
}
if (Loop == value)
return;
Track.Loop = value;
Preview.DrawMode = value ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
Timeline?.MarkAsEdited();
}
}
/// <summary>
/// Playback offset of the audio (in seconds).
/// </summary>
public float Offset
{
get => Preview.ViewOffset;
set
{
if (Mathf.NearEqual(Preview.ViewOffset, value))
return;
Preview.ViewOffset = value;
Timeline?.MarkAsEdited();
}
}
private sealed class Proxy : ProxyBase<AudioTrack, AudioMedia>
{
/// <summary>
/// Gets or sets the audio clip to play.
/// </summary>
[EditorDisplay("General"), EditorOrder(10), Tooltip("The audio clip to play.")]
public AudioClip Audio
{
@@ -45,17 +58,21 @@ namespace FlaxEditor.GUI.Timeline.Tracks
set => Track.Asset = value;
}
/// <summary>
/// Gets or sets the audio clip looping mode.
/// </summary>
[EditorDisplay("General"), EditorOrder(20), Tooltip("If checked, the audio clip will loop when playback exceeds its duration. Otherwise it will stop play.")]
public bool Loop
{
get => Track.TrackLoop;
set => Track.TrackLoop = value;
get => Media.Loop;
set => Media.Loop = value;
}
[EditorDisplay("General"), EditorOrder(30), Tooltip("Playback offset of the audio (in seconds).")]
[Limit(0, float.MaxValue, 0.01f)]
public float Offset
{
get => Media.Offset;
set => Media.Offset = value;
}
/// <inheritdoc />
public Proxy(AudioTrack track, AudioMedia media)
: base(track, media)
{
@@ -70,6 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc />
public AudioMedia()
{
CanSplit = true;
Preview = new AudioClipPreview
{
AnchorPreset = AnchorPresets.StretchAll,
@@ -79,12 +97,30 @@ namespace FlaxEditor.GUI.Timeline.Tracks
};
}
/// <inheritdoc />
protected override void OnStartFrameChanged()
{
base.OnStartFrameChanged();
if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
volumeTrack.UpdateCurve();
}
/// <inheritdoc />
protected override void OnDurationFramesChanged()
{
base.OnDurationFramesChanged();
if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
volumeTrack.UpdateCurve();
}
/// <inheritdoc />
public override void OnTimelineChanged(Track track)
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as AudioTrack, this);
PropertiesEditObject = track != null ? new Proxy((AudioTrack)track, this) : null;
}
/// <inheritdoc />
@@ -94,6 +130,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
Preview.ViewScale = Timeline.UnitsPerSecond / AudioClipPreview.UnitsPerSecond * Timeline.Zoom;
}
/// <inheritdoc />
public override Media Split(int frame)
{
var offset = Offset + ((float)(frame - StartFrame) / DurationFrames) * Duration;
var clone = (AudioMedia)base.Split(frame);
clone.Preview.ViewOffset = offset;
clone.Preview.Asset = Preview.Asset;
clone.Preview.DrawMode = Preview.DrawMode;
return clone;
}
}
/// <summary>
@@ -123,46 +170,46 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var e = (AudioTrack)track;
Guid id = stream.ReadGuid();
e.Asset = FlaxEngine.Content.LoadAsync<AudioClip>(id);
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
if (version <= 3)
{
// [Deprecated on 03.09.2021 expires on 03.09.2023]
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
m.Preview.ViewOffset = 0.0f;
m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
}
else
{
var count = stream.ReadInt32();
while (e.Media.Count > count)
e.RemoveMedia(e.Media.Last());
while (e.Media.Count < count)
e.AddMedia(new AudioMedia());
for (int i = 0; i < count; i++)
{
var m = (AudioMedia)e.Media[i];
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
m.Preview.ViewOffset = stream.ReadSingle();
m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
m.Preview.Asset = e.Asset;
}
}
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (AudioTrack)track;
var assetId = e.Asset?.ID ?? Guid.Empty;
stream.Write(assetId.ToByteArray());
if (e.Media.Count != 0)
stream.WriteGuid(ref e.AssetID);
var count = e.Media.Count;
stream.Write(count);
for (int i = 0; i < count; i++)
{
var m = e.TrackMedia;
var m = (AudioMedia)e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
}
else
{
stream.Write(0);
stream.Write(track.Timeline.DurationFrames);
}
}
/// <summary>
/// Gets or sets the audio clip looping mode.
/// </summary>
public bool TrackLoop
{
get => TrackMedia.Loop;
set
{
AudioMedia media = TrackMedia;
if (media.Loop == value)
return;
media.Loop = value;
Timeline?.MarkAsEdited();
stream.Write(m.Offset);
}
}
@@ -172,6 +219,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public AudioTrack(ref TrackCreateOptions options)
: base(ref options)
{
MinMediaCount = 1;
// Add button
const float buttonSize = 14;
_addButton = new Button
@@ -219,7 +268,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnAssetChanged();
TrackMedia.Preview.Asset = Asset;
foreach (AudioMedia m in Media)
m.Preview.Asset = Asset;
}
}
@@ -280,7 +330,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
private AudioMedia _audioMedia;
private const float CollapsedHeight = 20.0f;
private const float ExpandedHeight = 64.0f;
private Label _previewValue;
@@ -381,17 +430,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
private bool GetRangeFrames(out int startFrame, out int endFrame)
{
if (ParentTrack != null && ParentTrack.Media.Count != 0)
{
startFrame = ParentTrack.Media[0].StartFrame;
endFrame = ParentTrack.Media[0].EndFrame;
for (int i = 1; i < ParentTrack.Media.Count; i++)
{
endFrame = Mathf.Max(endFrame, ParentTrack.Media[i].EndFrame);
}
return true;
}
startFrame = endFrame = 0;
return false;
}
private bool GetRangeMedia(out Media startMedia, out Media endMedia)
{
if (ParentTrack != null && ParentTrack.Media.Count != 0)
{
startMedia = endMedia = ParentTrack.Media[0];
for (int i = 1; i < ParentTrack.Media.Count; i++)
{
if (ParentTrack.Media[i].EndFrame >= endMedia.EndFrame)
endMedia = ParentTrack.Media[i];
}
return true;
}
startMedia = endMedia = null;
return false;
}
/// <inheritdoc />
protected override void OnContextMenu(ContextMenu.ContextMenu menu)
{
base.OnContextMenu(menu);
if (_audioMedia == null || Curve == null)
if (!GetRangeFrames(out var startFrame, out _) || Curve == null)
return;
menu.AddSeparator();
menu.AddButton("Copy Preview Value", () =>
{
var time = (Timeline.CurrentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
var time = (Timeline.CurrentFrame - startFrame) / Timeline.FramesPerSecond;
Curve.Evaluate(out var value, time, false);
Clipboard.Text = FlaxEngine.Utils.RoundTo2DecimalPlaces(Mathf.Saturate(value)).ToString("0.00");
}).LinkTooltip("Copies the current track value to the clipboard").Enabled = Timeline.ShowPreviewValues;
@@ -400,15 +481,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc />
public override bool GetNextKeyframeFrame(float time, out int result)
{
if (_audioMedia != null)
if (GetRangeFrames(out var startFrame, out var endFrame))
{
var mediaTime = time - _audioMedia.StartFrame / Timeline.FramesPerSecond;
var mediaTime = time - startFrame / Timeline.FramesPerSecond;
for (int i = 0; i < Curve.Keyframes.Count; i++)
{
var k = Curve.Keyframes[i];
if (k.Time > mediaTime)
{
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
return true;
}
}
@@ -419,13 +500,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnAddKeyClicked(Image image, MouseButton button)
{
var currentFrame = Timeline.CurrentFrame;
if (button == MouseButton.Left && _audioMedia != null && currentFrame >= _audioMedia.StartFrame && currentFrame < _audioMedia.StartFrame + _audioMedia.DurationFrames)
if (button == MouseButton.Left && GetRangeFrames(out var startFrame, out var endFrame) && currentFrame >= startFrame && currentFrame < endFrame)
{
var time = (currentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
var time = (currentFrame - startFrame) / Timeline.FramesPerSecond;
for (int i = Curve.Keyframes.Count - 1; i >= 0; i--)
{
var k = Curve.Keyframes[i];
var frame = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
var frame = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
if (frame == Timeline.CurrentFrame)
{
// Already added
@@ -449,15 +530,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc />
public override bool GetPreviousKeyframeFrame(float time, out int result)
{
if (_audioMedia != null)
if (GetRangeFrames(out var startFrame, out _))
{
var mediaTime = time - _audioMedia.StartFrame / Timeline.FramesPerSecond;
var mediaTime = time - startFrame / Timeline.FramesPerSecond;
for (int i = Curve.Keyframes.Count - 1; i >= 0; i--)
{
var k = Curve.Keyframes[i];
if (k.Time < mediaTime)
{
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
return true;
}
}
@@ -467,17 +548,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void UpdatePreviewValue()
{
if (_audioMedia == null || Curve == null || Timeline == null)
if (!GetRangeFrames(out var startFrame, out _) || Curve == null || Timeline == null)
return;
var time = (Timeline.CurrentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
var time = (Timeline.CurrentFrame - startFrame) / Timeline.FramesPerSecond;
Curve.Evaluate(out var value, time, false);
_previewValue.Text = FlaxEngine.Utils.RoundTo2DecimalPlaces(Mathf.Saturate(value)).ToString("0.00");
}
private void UpdateCurve()
internal void UpdateCurve()
{
if (_audioMedia == null || Curve == null || Timeline == null)
if (!GetRangeMedia(out var startMedia, out var endMedia) || Curve == null || Timeline == null)
return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible;
@@ -489,7 +570,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
Curve.KeyframesEditorContext = Timeline;
Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Curve.Bounds = new Rectangle(_audioMedia.X, Y + 1.0f, _audioMedia.Width, Height - 2.0f);
Curve.Bounds = new Rectangle(startMedia.X, Y + 1.0f, endMedia.Right - startMedia.Left, Height - 2.0f);
var expanded = IsExpanded;
if (expanded)
{
@@ -542,19 +623,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnParentTrackChanged(parent);
if (_audioMedia != null)
if (parent != null)
{
_audioMedia.StartFrameChanged -= UpdateCurve;
_audioMedia.DurationFramesChanged -= UpdateCurve;
_audioMedia = null;
}
if (parent is AudioTrack audioTrack)
{
var media = audioTrack.TrackMedia;
media.StartFrameChanged += UpdateCurve;
media.DurationFramesChanged += UpdateCurve;
_audioMedia = media;
UpdateCurve();
UpdatePreviewValue();
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -33,6 +34,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public CameraCutMedia()
{
ClipChildren = true;
CanSplit = true;
}
/// <summary>
@@ -292,7 +294,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as CameraCutTrack, this);
PropertiesEditObject = track != null ? new Proxy((CameraCutTrack)track, this) : null;
UpdateThumbnails();
}
@@ -673,26 +675,41 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
var e = (CameraCutTrack)track;
e.ActorID = stream.ReadGuid();
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
if (version <= 3)
{
// [Deprecated on 03.09.2021 expires on 03.09.2023]
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
}
else
{
var count = stream.ReadInt32();
while (e.Media.Count > count)
e.RemoveMedia(e.Media.Last());
while (e.Media.Count < count)
e.AddMedia(new CameraCutMedia());
for (int i = 0; i < count; i++)
{
var m = (CameraCutMedia)e.Media[i];
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
}
}
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (CameraCutTrack)track;
stream.Write(e.ActorID.ToByteArray());
if (e.Media.Count != 0)
stream.WriteGuid(ref e.ActorID);
var count = e.Media.Count;
stream.Write(count);
for (int i = 0; i < count; i++)
{
var m = e.TrackMedia;
var m = e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
}
else
{
stream.Write(0);
stream.Write(track.Timeline.DurationFrames);
}
}
private Image _pilotCamera;
@@ -702,10 +719,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// </summary>
public Camera Camera => Actor as Camera;
/// <summary>
/// Gets the camera track media.
/// </summary>
public CameraCutMedia TrackMedia
private CameraCutMedia TrackMedia
{
get
{
@@ -731,6 +745,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public CameraCutTrack(ref TrackCreateOptions options)
: base(ref options, false)
{
MinMediaCount = 1;
Height = CameraCutThumbnailRenderer.Height + 8;
// Pilot Camera button
@@ -794,12 +809,18 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
private void UpdateThumbnails()
{
foreach (CameraCutMedia media in Media)
media.UpdateThumbnails();
}
/// <inheritdoc />
protected override void OnObjectExistenceChanged(object obj)
{
base.OnObjectExistenceChanged(obj);
TrackMedia.UpdateThumbnails();
UpdateThumbnails();
}
/// <inheritdoc />

View File

@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a nested scene animation media event.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
public class NestedSceneAnimationMedia : SingleMediaAssetMedia
public class NestedSceneAnimationMedia : Media
{
private sealed class Proxy : ProxyBase<NestedSceneAnimationTrack, NestedSceneAnimationMedia>
{
@@ -48,7 +48,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as NestedSceneAnimationTrack, this);
PropertiesEditObject = track != null ? new Proxy((NestedSceneAnimationTrack)track, this) : null;
}
}
@@ -87,10 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (NestedSceneAnimationTrack)track;
var assetId = e.Asset?.ID ?? Guid.Empty;
stream.Write(assetId.ToByteArray());
stream.WriteGuid(ref e.AssetID);
if (e.Media.Count != 0)
{
var m = e.TrackMedia;
@@ -123,6 +120,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public NestedSceneAnimationTrack(ref TrackCreateOptions options)
: base(ref options)
{
MinMediaCount = 1;
MaxMediaCount = 1;
}
/// <inheritdoc />

View File

@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a particle miter playback media event.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
public class ParticleEmitterMedia : SingleMediaAssetMedia
public class ParticleEmitterMedia : Media
{
private sealed class Proxy : ProxyBase<ParticleEmitterTrack, ParticleEmitterMedia>
{
@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as ParticleEmitterTrack, this);
PropertiesEditObject = track != null ? new Proxy((ParticleEmitterTrack)track, this) : null;
}
}
@@ -78,11 +78,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (ParticleEmitterTrack)track;
var emitterId = e.Asset?.ID ?? Guid.Empty;
stream.Write(emitterId.ToByteArray());
stream.WriteGuid(ref e.AssetID);
stream.Write(((ParticleSystemTimeline)track.Timeline).Emitters.IndexOf(e));
if (e.Media.Count != 0)
{
var m = e.TrackMedia;
@@ -108,6 +105,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public ParticleEmitterTrack(ref TrackCreateOptions options)
: base(ref options)
{
MinMediaCount = 1;
MaxMediaCount = 1;
}
/// <inheritdoc />

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using FlaxEditor.Utilities;
using FlaxEngine;
@@ -11,13 +12,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a post-process material media event.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
public class PostProcessMaterialMedia : SingleMediaAssetMedia
public class PostProcessMaterialMedia : Media
{
private sealed class Proxy : ProxyBase<PostProcessMaterialTrack, PostProcessMaterialMedia>
{
/// <summary>
/// Gets or sets the post process material to show.
/// </summary>
[EditorDisplay("General"), EditorOrder(10), Tooltip("The post process material to show.")]
public MaterialBase PostProcessMaterial
{
@@ -32,12 +30,20 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PostProcessMaterialMedia"/> class.
/// </summary>
public PostProcessMaterialMedia()
{
CanSplit = true;
}
/// <inheritdoc />
public override void OnTimelineChanged(Track track)
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as PostProcessMaterialTrack, this);
PropertiesEditObject = track != null ? new Proxy((PostProcessMaterialTrack)track, this) : null;
}
}
@@ -68,29 +74,41 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var e = (PostProcessMaterialTrack)track;
Guid id = stream.ReadGuid();
e.Asset = FlaxEngine.Content.LoadAsync<MaterialBase>(id);
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
if (version <= 3)
{
// [Deprecated on 03.09.2021 expires on 03.09.2023]
var m = e.TrackMedia;
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
}
else
{
var count = stream.ReadInt32();
while (e.Media.Count > count)
e.RemoveMedia(e.Media.Last());
while (e.Media.Count < count)
e.AddMedia(new PostProcessMaterialMedia());
for (int i = 0; i < count; i++)
{
var m = e.Media[i];
m.StartFrame = stream.ReadInt32();
m.DurationFrames = stream.ReadInt32();
}
}
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (PostProcessMaterialTrack)track;
var materialId = e.Asset?.ID ?? Guid.Empty;
stream.Write(materialId.ToByteArray());
if (e.Media.Count != 0)
stream.WriteGuid(ref e.AssetID);
var count = e.Media.Count;
stream.Write(count);
for (int i = 0; i < count; i++)
{
var m = e.TrackMedia;
var m = e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
}
else
{
stream.Write(0);
stream.Write(track.Timeline.DurationFrames);
}
}
/// <summary>
@@ -100,6 +118,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public PostProcessMaterialTrack(ref TrackCreateOptions options)
: base(ref options)
{
MinMediaCount = 1;
}
}
}

View File

@@ -81,7 +81,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
PropertiesEditObject = new Proxy(Track as ScreenFadeTrack, this);
PropertiesEditObject = track != null ? new Proxy((ScreenFadeTrack)track, this) : null;
}
/// <inheritdoc />

View File

@@ -8,18 +8,6 @@ using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Timeline.Tracks
{
/// <summary>
/// The timeline media that represents a media event with an asset reference.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
public class SingleMediaAssetMedia : Media
{
/// <summary>
/// The asset id.
/// </summary>
public Guid Asset;
}
/// <summary>
/// The base class for timeline tracks that use single media with an asset reference.
/// </summary>
@@ -28,26 +16,29 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
public abstract class SingleMediaAssetTrack<TAsset, TMedia> : SingleMediaTrack<TMedia>
where TAsset : Asset
where TMedia : SingleMediaAssetMedia, new()
where TMedia : Media, new()
{
/// <summary>
/// The asset reference picker control.
/// </summary>
protected readonly AssetPicker _picker;
/// <summary>
/// The asset id.
/// </summary>
public Guid AssetID;
/// <summary>
/// Gets or sets the asset.
/// </summary>
public TAsset Asset
{
get => FlaxEngine.Content.LoadAsync<TAsset>(TrackMedia.Asset);
get => FlaxEngine.Content.LoadAsync<TAsset>(AssetID);
set
{
TMedia media = TrackMedia;
if (media.Asset == value?.ID)
if (AssetID == value?.ID)
return;
media.Asset = value?.ID ?? Guid.Empty;
AssetID = value?.ID ?? Guid.Empty;
_picker.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();

View File

@@ -44,9 +44,16 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc />
public override void OnSpawned()
{
// Ensure to have valid media added
// ReSharper disable once UnusedVariable
var media = TrackMedia;
// Ensure to have minimum valid media count
for (int i = Media.Count; i < MinMediaCount; i++)
{
var m = new TMedia
{
StartFrame = 0,
DurationFrames = Timeline?.DurationFrames ?? 60,
};
AddMedia(m);
}
base.OnSpawned();
}