I am trying to create a Air Traffic Control game, in WPF using c#, and I have plane sprites that move by a vector (0-360 degrees), which I do with the following code which is in a timer which runs every millisecond. The problem I am having is it will jump straight to the new location, however I need it to move from its current position turn in the direction and move there, rather than jumping straight there. How can I achieve this? Do I need to work outs it current position or something?
Thanks in advance for the help
distance = 1;
if (Heading[i] > 0 && Heading[i] < 90)
{
X_Length = (((Math.Sin((Heading[i] * (Math.PI / 180))) * distance)));
Y_Length = -( ((Math.Cos(Heading[i] * (Math.PI / 180)) * distance)));
}
else if (Heading[i] > 90 && 180 > Heading[i])
{
X_Length = (((Math.Sin((Heading[i] * (Math.PI / 180))) * distance)));
Y_Length = (((Math.Cos(Heading[i] * (Math.PI / 180)) * distance)));
}
else if (Heading[i] > 180 && 270 > Heading[i])
{
X_Length = -(((Math.Sin((Heading[i] * (Math.PI / 180))) * distance)));
Y_Length = (((Math.Cos(Heading[i] * (Math.PI / 180)) * distance)));
}
else if (Heading[i] > 270 && 360 > Heading[i])
{
X_Length = -(((Math.Sin((Heading[i] * (Math.PI / 180))) * distance)));
Y_Length = -(((Math.Cos(Heading[i] * (Math.PI / 180)) * distance)));
}
Plane_callsigns[i].RenderTransform = new TranslateTransform(X_Length, Y_Length);
Plane_callsigns[i].LayoutTransform = new RotateTransform(Heading[i]);
CodePudding user response:
The overall approach should be
var newPosition = oldPosition direction * speed * deltaTime;
So you need to keep track of the position from the previous frame, as well as define a speed of your airplanes. The delta time would be the time since the last update. The most accurate way to get this is with a stopwatch, simply create one and reset it in the end of every update.
You might also need to add things like maximal turn rate, and possibly even how fast you can change the turn rate. You might also want the possibility to direct an airplane towards a position, or to fly along a specified line. But to get a accurate simulation you need to study things like Control Theory.
Note that 'vector' in computer graphics usually means a composite type of two or more values, and can describe things like a position or direction. It seems like you are using an angle to describe a 2D direction, and that is fine, but you will need to convert it to a vector representation sooner or later.
If you are starting out I would highly recommend using appropriate types rather than individual floats/doubles everywhere. For example System.Numerics.Vector2 to describe a vector, Create your own angle type to deal with conversions between degrees, radians, vectors etc. Use TimeSpan to describe time, perhaps even seprate wrappers around a vector to describe position and directions, since these behave differently, adding two positions for example would be meaningless.
CodePudding user response:
i used animations to smooth the movement. It assumes that the updates have a similar interval which is at most 2 seconds.
public class Position
{
public Position(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
TimeStamp = DateTime.Now;
}
public double X { get; }
public double Y { get; }
public double Z { get; }
public DateTime TimeStamp { get; }
public override string ToString()
{
return $"{X},{Y},{Z} {TimeStamp}";
}
public static implicit operator Point3D(Position position) => new Point3D(position.X, position.Y, position.Z);
}
public class Rotation
{
public Rotation(double value, double originX, double originY)
{
Value = value;
OriginX = originX;
OriginY = originY;
TimeStamp = DateTime.Now;
}
public double Value { get; }
public double OriginX { get; }
public double OriginY { get; }
public DateTime TimeStamp { get; }
}
public static class Animate2D
{
public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached("Position", typeof(Position), typeof(Animate2D), new PropertyMetadata(PositionChanged));
public static Position GetPosition(DependencyObject obj) => (Position)obj.GetValue(PositionProperty);
public static void SetPosition(DependencyObject obj, Position value) => obj.SetValue(PositionProperty, value);
private static void PositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newValue = (Position)e.NewValue;
if (newValue == null)
return;
var oldValue = (Position)e.OldValue ?? newValue;
var duration = newValue.TimeStamp - oldValue.TimeStamp;
if (duration.TotalSeconds <= 0 || duration.TotalSeconds > 2)
duration = TimeSpan.FromSeconds(2);
StartAnimation(d, Canvas.LeftProperty, duration, oldValue.X, newValue.X);
StartAnimation(d, Canvas.TopProperty, duration, oldValue.Y, newValue.Y);
}
public static readonly DependencyProperty RotationProperty = DependencyProperty.RegisterAttached("Rotation", typeof(Rotation), typeof(Animate2D), new PropertyMetadata(RotationChanged));
public static Rotation GetRotation(DependencyObject obj) => (Rotation)obj.GetValue(RotationProperty);
public static void SetRotation(DependencyObject obj, Rotation value) => obj.SetValue(RotationProperty, value);
private static void RotationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newValue = (Rotation)e.NewValue;
if (newValue == null)
return;
var oldValue = (Rotation)e.OldValue ?? newValue;
var duration = newValue.TimeStamp - oldValue.TimeStamp;
if (duration.TotalSeconds <= 0 || duration.TotalSeconds > 2)
duration = TimeSpan.FromSeconds(2);
Control control = (Control)d;
var renderTransform = control.RenderTransform;
if (!(renderTransform is RotateTransform rotateTransform) || rotateTransform.CenterX != newValue.OriginX || rotateTransform.CenterY != newValue.OriginY)
{
control.RenderTransform = new RotateTransform { CenterX = newValue.OriginX, CenterY = newValue.OriginY };
}
StartAnimation(control, new PropertyPath("RenderTransform.Angle"), duration, (double)renderTransform.GetValue(RotateTransform.AngleProperty), newValue.Value);
}
private static void StartAnimation(DependencyObject target, DependencyProperty dp, TimeSpan duration, double fallback, double newValue)
{
var value = (double)target.GetValue(dp);
StartAnimation((FrameworkElement)target, new PropertyPath(dp), duration, double.IsNaN(value) ? fallback : value, newValue);
}
private static void StartAnimation(FrameworkElement target, PropertyPath path, TimeSpan duration, double oldValue, double newValue)
{
var animation = new DoubleAnimation
{
Duration = duration,
From = oldValue,
To = newValue,
FillBehavior = FillBehavior.HoldEnd,
};
Storyboard.SetTarget(animation, target);
Storyboard.SetTargetProperty(animation, path);
new Storyboard { Children = { animation } }.Begin(target);
}
}
<Viewbox>
<ListBox ItemsSource="{Binding Planes}" Width="{Binding SizeX}" Height="{Binding SizeY}" SelectedItem="{Binding SelectedPlane}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="a:Animate2D.Position" Value="{Binding Position}"/>
<Setter Property="a:Animate2D.Rotation" Value="{Binding Rotation}"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="BorderBrush" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Blue"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Viewbox>