A game framework written with osu! in mind.
at master 298 lines 10 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System.Collections.Generic; 5using System.Linq; 6using osu.Framework.Bindables; 7using osu.Framework.Extensions.Color4Extensions; 8using osu.Framework.Graphics; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Lines; 11using osu.Framework.Graphics.Shapes; 12using osu.Framework.Graphics.Sprites; 13using osu.Framework.Graphics.Transforms; 14using osu.Framework.Input.Events; 15using osu.Framework.Utils; 16using osuTK; 17using osuTK.Graphics; 18 19namespace osu.Framework.Tests.Visual.Drawables 20{ 21 public class TestSceneCustomEasingCurve : FrameworkTestScene 22 { 23 public TestSceneCustomEasingCurve() 24 { 25 Add(new CurveVisualiser 26 { 27 Anchor = Anchor.Centre, 28 Origin = Anchor.Centre, 29 Size = new Vector2(400), 30 }); 31 } 32 33 private class CurveVisualiser : CompositeDrawable 34 { 35 private readonly BindableList<Vector2> easingVertices = new BindableList<Vector2>(); 36 37 private readonly SmoothPath path; 38 private readonly Container<ControlPointVisualiser> controlPointContainer; 39 private readonly SpriteIcon sideTracker; 40 private readonly Box verticalTracker; 41 private readonly Box horizontalTracker; 42 43 private readonly CustomEasingFunction easingFunction; 44 45 public CurveVisualiser() 46 { 47 easingFunction = new CustomEasingFunction { EasingVertices = { BindTarget = easingVertices } }; 48 49 Container gridContainer; 50 51 InternalChildren = new Drawable[] 52 { 53 new Container 54 { 55 RelativeSizeAxes = Axes.Both, 56 Masking = true, 57 BorderColour = Color4.White, 58 BorderThickness = 2, 59 Child = new Box 60 { 61 RelativeSizeAxes = Axes.Both, 62 Alpha = 0, 63 AlwaysPresent = true 64 } 65 }, 66 gridContainer = new Container { RelativeSizeAxes = Axes.Both }, 67 path = new SmoothPath 68 { 69 PathRadius = 1 70 }, 71 controlPointContainer = new Container<ControlPointVisualiser> { RelativeSizeAxes = Axes.Both }, 72 sideTracker = new SpriteIcon 73 { 74 Anchor = Anchor.TopRight, 75 Origin = Anchor.BottomCentre, 76 RelativePositionAxes = Axes.Y, 77 Size = new Vector2(10), 78 X = 2, 79 Colour = Color4.SkyBlue, 80 Rotation = 90, 81 Icon = FontAwesome.Solid.MapMarker, 82 }, 83 verticalTracker = new Box 84 { 85 Origin = Anchor.CentreLeft, 86 RelativeSizeAxes = Axes.X, 87 RelativePositionAxes = Axes.Y, 88 Height = 1, 89 Colour = Color4.SkyBlue 90 }, 91 horizontalTracker = new Box 92 { 93 Origin = Anchor.TopCentre, 94 RelativeSizeAxes = Axes.Y, 95 RelativePositionAxes = Axes.X, 96 Width = 1, 97 Colour = Color4.SkyBlue 98 } 99 }; 100 101 for (int i = 0; i <= 10; i++) 102 { 103 gridContainer.Add(new Box 104 { 105 Origin = Anchor.CentreLeft, 106 RelativeSizeAxes = Axes.X, 107 RelativePositionAxes = Axes.Y, 108 Height = 2, 109 Y = 0.1f * i, 110 Colour = Color4.White.Opacity(0.1f) 111 }); 112 113 gridContainer.Add(new Box 114 { 115 Origin = Anchor.TopCentre, 116 RelativeSizeAxes = Axes.Y, 117 RelativePositionAxes = Axes.X, 118 Width = 2, 119 X = 0.1f * i, 120 Colour = Color4.White.Opacity(0.1f) 121 }); 122 } 123 124 controlPointContainer.Add(new ControlPointVisualiser 125 { 126 PointPosition = { Value = new Vector2(100, 100) } 127 }); 128 } 129 130 protected override void LoadComplete() 131 { 132 base.LoadComplete(); 133 134 sideTracker.MoveToY(1) 135 .Then().MoveToY(0, 2000, easingFunction) 136 .Then().Delay(200) 137 .Loop(); 138 139 verticalTracker.MoveToY(1) 140 .Then().MoveToY(0, 2000, easingFunction) 141 .Then().Delay(200) 142 .Loop(); 143 144 horizontalTracker.MoveToX(0) 145 .Then().MoveToX(1, 2000) 146 .Then().Delay(200) 147 .Loop(); 148 } 149 150 protected override void Update() 151 { 152 base.Update(); 153 154 ControlPointVisualiser[] ordered = controlPointContainer.OrderBy(p => p.PointPosition.Value.X).ToArray(); 155 156 for (int i = 0; i < ordered.Length; i++) 157 { 158 ordered[i].Last = i > 0 ? ordered[i - 1] : null; 159 ordered[i].Next = i < ordered.Length - 1 ? ordered[i + 1] : null; 160 } 161 162 var vectorPath = new List<Vector2> { new Vector2(0, DrawHeight) }; 163 vectorPath.AddRange(ordered.Select(p => p.PointPosition.Value)); 164 vectorPath.Add(new Vector2(DrawWidth, 0)); 165 166 var bezierPath = PathApproximator.ApproximateBezier(vectorPath.ToArray()); 167 path.Vertices = bezierPath; 168 path.Position = -path.PositionInBoundingBox(Vector2.Zero); 169 170 easingVertices.Clear(); 171 easingVertices.AddRange(bezierPath.Select(p => Vector2.Divide(p, DrawSize)).Select(p => new Vector2(p.X, 1 - p.Y))); 172 } 173 174 protected override bool OnMouseDown(MouseDownEvent e) 175 { 176 controlPointContainer.Add(new ControlPointVisualiser 177 { 178 PointPosition = { Value = ToLocalSpace(e.ScreenSpaceMousePosition) } 179 }); 180 181 return true; 182 } 183 } 184 185 private class ControlPointVisualiser : CompositeDrawable 186 { 187 public readonly Bindable<Vector2> PointPosition = new Bindable<Vector2>(); 188 189 public ControlPointVisualiser Last; 190 public ControlPointVisualiser Next; 191 192 private readonly SmoothPath path; 193 194 public ControlPointVisualiser() 195 { 196 RelativeSizeAxes = Axes.Both; 197 198 InternalChildren = new Drawable[] 199 { 200 path = new SmoothPath 201 { 202 PathRadius = 1, 203 Colour = Color4.Yellow.Opacity(0.5f) 204 }, 205 new PointHandle 206 { 207 PointPosition = { BindTarget = PointPosition } 208 } 209 }; 210 } 211 212 protected override void Update() 213 { 214 base.Update(); 215 216 path.ClearVertices(); 217 218 path.AddVertex(Last?.PointPosition.Value ?? new Vector2(0, DrawHeight)); 219 path.AddVertex(PointPosition.Value); 220 221 if (Next == null) 222 path.AddVertex(new Vector2(DrawWidth, 0)); 223 224 path.Position = -path.PositionInBoundingBox(Vector2.Zero); 225 } 226 } 227 228 private class PointHandle : Circle 229 { 230 public readonly Bindable<Vector2> PointPosition = new Bindable<Vector2>(); 231 232 public PointHandle() 233 { 234 Origin = Anchor.Centre; 235 Size = new Vector2(10); 236 237 Colour = Color4.Yellow; 238 Alpha = 0.5f; 239 } 240 241 protected override void Update() 242 { 243 base.Update(); 244 245 Position = PointPosition.Value; 246 } 247 248 private bool isDragging; 249 250 protected override bool OnHover(HoverEvent e) 251 { 252 updateColour(); 253 return true; 254 } 255 256 protected override void OnHoverLost(HoverLostEvent e) => updateColour(); 257 258 protected override bool OnMouseDown(MouseDownEvent e) 259 { 260 isDragging = true; 261 return true; 262 } 263 264 protected override void OnMouseUp(MouseUpEvent e) 265 { 266 isDragging = false; 267 updateColour(); 268 } 269 270 protected override bool OnDragStart(DragStartEvent e) => true; 271 272 protected override void OnDrag(DragEvent e) => PointPosition.Value += e.Delta; 273 274 private void updateColour() => Alpha = IsHovered || isDragging ? 1f : 0.5f; 275 } 276 277 private class CustomEasingFunction : IEasingFunction 278 { 279 public readonly BindableList<Vector2> EasingVertices = new BindableList<Vector2>(); 280 281 public double ApplyEasing(double time) 282 { 283 for (int i = 0; i < EasingVertices.Count; i++) 284 { 285 if (EasingVertices[i].X < time) 286 continue; 287 288 Vector2 last = EasingVertices[i - 1]; 289 Vector2 next = EasingVertices[i]; 290 291 return Interpolation.ValueAt(time, last.Y, next.Y, last.X, next.X); 292 } 293 294 return 0; 295 } 296 } 297 } 298}