C# low timer interval = slow drawtime? - c#

I'm trying to make a simple pong game but I encountered this problem, each timer1 tick (with the interval set to 1ms) should move of 1 pixel the white paddle rectangle if the 'S' or 'W' button is pressed, this means theorically that the white rectangle inside my 400px in height picturebox should be moving from y = 0 to y = 400 in 0.4 - 0.8 seconds, but apparently it takes more than 4 full seconds.
I understand that timer tick events may be "skipped" if the cpu is already busy or by processing speed problems but I tried to make a snake game way more complex than these 50 lines of code and the speed of the drawn snake was actually accurate with low-time intervals
Why does it takes that much?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
pictureBox1.BackColor = Color.Black;
timer1.Interval = 1;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
PongGame.CheckIfMoving();
PongGame.DrawIt(pictureBox1);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.S)
{ PongGame.movesDown = true; }
if (e.KeyData == Keys.W)
{ PongGame.movesUp = true; }
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.S)
{ PongGame.movesDown = false; }
if (e.KeyData == Keys.W)
{ PongGame.movesUp = false; }
}
}
public class PongGame
{
public static Rectangle paddle1 = new Rectangle(14, 370, 20, 100);
public Rectangle paddle2;
public static bool movesUp;
public static bool movesDown;
public static Graphics Draw;
public static void DrawIt(PictureBox pb1)
{
Draw = pb1.CreateGraphics();
SolidBrush sb = new SolidBrush(Color.White);
Draw.Clear(Color.Black);
Draw.FillRectangle(sb, paddle1);
}
public static void CheckIfMoving()
{
if (movesUp == true)
{
paddle1.Y -= 1;
}
if (movesDown == true)
{
paddle1.Y += 1;
}
}
}
Thanks in advance for the answers (:

First of all, setting the Timer's interval to 1 produces an unnecessary overhead in this case. That's 1000 fps (well, it would be, if you could actually trust the Timer).
In DrawIt:
Draw = pb1.CreateGraphics();
SolidBrush sb = new SolidBrush(Color.White);
This, since DrawIt is called by the Timer's tick, recreates the Graphics, and the Brush, 1000 times, every second. Be careful about what you put in there.
Furtheremore, you shouldn't use the Picturebox's CreateGraphics method. Instead, override its OnPaint method, and call Refresh on it.
You can read more about this on Bob Powell's Website

The timer makes no guarantee that it will fire at the interval that you specify. In fact it can, and does, fire at an interval longer than that which you specify.
Typically the actual interval of a timer will be related to the underlying system and hardware. It's not uncommon for timers on Windows to fire no more frequently than 20Hz.
If you are going to make a timer based game, then use the timer to give your game a pulse, a regular heartbeat. When the timer fires, use the amount of time since the last timer event to update the state. You'll need to use an accurate measure of time. The Stopwatch class should suffice.
You are also using the picture box incorrectly. This is an event driven control. You are expected to paint the scene in a handler for the Paint event of the picture box.

Related

How to create smooth animation movement of shapes in C# windows forms application?

I'm making C# windows forms application in which I want to make smooth animation movement of a black bar over a white background. I tried the following code but the movement is flickering and not achieving smooth rapid movement even on 1 ms interval timer.
I tried the following code :
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 30;
timer1.Tick += Timer1_Tick;
timer1.Enabled = true;
}
int x_position = 0;
int bar_width = 40;
int speed = 1;
private void Timer1_Tick(object sender, EventArgs e)
{
x_position += speed;
if (x_position + bar_width > this.Width) x_position = 0;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
}
The result is not smooth movement at all. I found on another questions something like Invalidate(true) or DoubleBuffered = true; but they didn't solve the issue.
Waiting for help. Thank you
The Winforms API, and the native Win32 window-messaging API on which it's built, is simply not designed for "smooth animation". It will be difficult, if not impossible, to achieve perfectly smooth animation.
That said, much can be done to improve on your attempt above:
Don't use CreateGraphics(). Instead, always draw in response to the Paint event.
Don't use any of the .NET timer implementations to schedule rendering. Instead, draw as quickly as you can, taking into account time between frames to update your object position.
Applying these ideas, here's a version of your code that works much better:
const float speedXPerSecond = 1000f / 30;
const int bar_width = 40;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
float x_position = 0;
TimeSpan _lastFrameTime = TimeSpan.Zero;
Stopwatch _frameTimer = Stopwatch.StartNew();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
TimeSpan currentFrameTime = _frameTimer.Elapsed;
float distance = (float)(currentFrameTime - _lastFrameTime).TotalSeconds * speedXPerSecond;
x_position += distance;
while (x_position > this.Width) x_position -= this.Width;
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
_lastFrameTime = currentFrameTime;
Invalidate();
}
You can apply this general technique to any interactive scenario. A real game will have other elements to this general "render loop", including user input and updating game state (e.g. based on a physics model). These will add overhead to the render loop, and will necessarily reduce frame rate. But for any basic game on any reasonably recent hardware (e.g. built in the last couple of decades), the net frame rate will still be well above that needed for acceptably smooth game-play.
Do note that in the managed .NET/Winforms context, there will always be limits to the success of this approach as compared to using a lower-level API. In particular, without some extra work on your part, garbage collection will interrupt periodically, causing the frame rate to stutter slightly, as well unevenness in thread scheduling and the thread's message queue.
But it's been my experience that people asking this sort of question don't need things to be absolutely perfect. They need "good enough", so that they can move on to learning about other topics involved in game development.

Monogame C# Timer (do something for 15 seconds every 3 seconds)

I am trying to create a timer, which, for example, every 3 seconds during eg 15 seconds will perform an action.
I tried to use gameTime.ElapsedGameTime.TotalSeconds and loop, but unfortunately it doesn't work.
I have an Attack () function that reduces player statistics when an enemy attacks it. I would like that in case of one particular enemy, this function for a specified period of time would subtract player's HP, eg for every 3 seconds. I guess it should be done in the Update function to access gameTime, unfortunately, I have no idea how to do it.
public override Stats Attack()
{
attack = true;
return new Stats(0, -stats.Damage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
public override void Update(GameTime gameTime)
{
spriteDirection = Vector2.Zero; // reset input
Move(Direction); // gets the state of my keyborad
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; // make movement framerate independant
spriteDirection *= Speed; // add hero's speed to movement
position += (spriteDirection * deltaTime); // adding deltaTime to stabilize movement
totalPosition = new Vector2((int)((BottomBoundingBox.Center.X) / 32.0f), (int)((BottomBoundingBox.Center.Y) / 32.0f));
base.Update(gameTime);
}
I will make it simple, so you need to modify my code to achieve your desire result.
My best guess is that you want to have a special effect when your monsters hit your player.
First, you need to check if the monster actually hits the player (if collision is detected):
if (collision)//if it's true
{
// Apply your special effect if it is better than
// the one currently affecting the target :
if (player.PoisonModifier <= poisonModifier) {
player.PoisonModifier = poisonModifier;
player.ModifierDuration = modifierDuration;
}
//player.setColor(Color.Blue);//change color to blue
player.hitPoints -= Poision.Damage;//or enemy.PoisonDamage or whatever you define here
hit.Expire();//this can be for the arrow or bullet from your enemy or simply just a normal hit
}
In your Player class, you need:
public float ModifierDuration {
get {
return modifierDuration;
}
set {
modifierDuration = value;
modiferCurrentTime = 0;
}
}
Then in Update method of Player class:
// If the modifier has finished,
if (modiferCurrentTime > modifierDuration) {
// reset the modifier.
//stop losing HP code is here
modiferCurrentTime = 0;//set the time to zero
setColor(Color.White);//set back the color of your player
}
count += gameTime.ElapsedGameTime.TotalSeconds;//timer for actions every 3s
if (posionModifier != 0 && modiferCurrentTime <= modifierDuration) {
// Modify the hp of the enemy.
player.setHP(player.getCurrentHP() - posionDamage);
//Or change it to every 3s
//if (count > 3) {
// count = 0;
//DoSubtractHP(player);
//}
// Update the modifier timer.
modiferCurrentTime += (float) gameTime.ElapsedGameTime.TotalSeconds;
setColor(Color.Blue);//change the color to match the special effect
}
Hope this helps!
You need to store the start time, or the last time that the action was carried out. Then during each update compare the elapsed time to the stored time. If 3 seconds have passed then perform the action, store the current time and repeat the process.
I do not know monogame, but if I were doing this in one of my C# applications, I would use a timer, and pass in anything that the timer would need to modify.
There is good info here https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8 and I stole a bit of code from here and modified it as an example to demonstrate my idea. I extended the System.Timer to allow it to run for a duration and stop itself. You can set the frequency and duration and forget about it. Assuming that you are able to update this information from a timer.
class Program
{
private static FixedDurationTimer aTimer;
static void Main(string[] args)
{
// Create a timer and set a two second interval.
aTimer = new FixedDurationTimer();
aTimer.Interval = 2000;
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
// Start the timer
aTimer.StartWithDuration(TimeSpan.FromSeconds(15));
Console.WriteLine("Press the Enter key to exit the program at any time... ");
Console.ReadLine();
}
private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
FixedDurationTimer timer = source as FixedDurationTimer;
if (timer.Enabled)
{
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}
}
public class FixedDurationTimer : System.Timers.Timer
{
public TimeSpan Duration { get; set; }
private Stopwatch _stopwatch;
public void StartWithDuration(TimeSpan duration)
{
Duration = duration;
_stopwatch = new Stopwatch();
Start();
_stopwatch.Start();
}
public FixedDurationTimer()
{
Elapsed += StopWhenDurationIsReached;
}
private void StopWhenDurationIsReached(object sender, ElapsedEventArgs e)
{
if (_stopwatch != null && Duration != null)
{
if (_stopwatch.Elapsed > Duration)
{
Console.WriteLine("Duration has been met, stopping");
Stop();
}
}
}
}
}
You could see examples of how to pass objects into the timer here (#JaredPar's example) How do I pass an object into a timer event?
string theString = ...;
timer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, theString);
static void MyElapsedMethod(object sender, ElapsedEventArgs e, string theString) {
...
}
One way to do this would be to use coroutines. MonoGame does not have built-in support for them like other game engines, but they are not too complicated to implement yourself. You need some knowledge of the yield keyword and enumerators to understand them, but once abstracted away they make your game code way easier to write and understand.
Here's an example of what your gameplay logic would look using a Coroutine system like the one described below:
public void Attack(Enemy enemyAttacking)
{
if (enemyAttacking.Type == "OneParticularEnemy")
{
StartCoroutine(RunDamageOverTimeAttack());
}
}
// This coroutine starts a second coroutine that applies damage over time, it
// then waits 15 seconds before terminating the second coroutine.
public IEnumerator RunDamageOverTimeAttack()
{
var cr = StartCoroutine(ApplyDamageOverTime());
yield return 15000; // in milleseconds (ms), i.e. 15000 ms is 15 seconds
cr.IsFinished = true;
}
// This coroutine applies the damage every 3 seconds until the coroutine is finished
public IEnumerator ApplyDamageOverTime()
{
while (true)
{
ApplyDamageToPlayer();
yield return 3000;
}
}
The code reads very close to the way you described the actual problem you're trying to solve. Now for the coroutine system...
The StartCouroutine method creates a Coroutine class instance and stores it. During the Update step of the game loop you iterate through the coroutines and update them, providing gameTime to calculate when the next step of the method should run. Each step executes the code in the routine until a yield is found OR until the method ends naturally. Once the coroutine is finished you clear them out. This logic looks something like this:
private List<Coroutine> coroutines = new List<Coroutine>();
public Coroutine StartCoroutine(IEnumerator routine)
{
var cr = new Coroutine(routine);
couroutines.Add(cr);
return cr;
}
public void UpdateCoroutines(GameTime gameTime)
{
// copied in case list is modified during coroutine updates
var coroutinesToUpdate = coroutines.ToArray();
foreach (coroutine in coroutinesToUpdate)
coroutine.Update(gameTime);
coroutines.RemoveAll(c => c.IsFinished);
}
public void Update(GameTime gameTime)
{
// normal update logic that would invoke Attack(), then...
UpdateCoroutines(gameTime);
}
A Coroutine class is responsible for tracking the time remaining between steps of the routine, and tracking when the routine is finished. It looks something like this:
public class Coroutine
{
private IEnumerator routine;
private double? wait;
public Coroutine(IEnumerator routine)
{
this.routine = routine;
}
public bool IsFinished { get; set; }
public void Update(GameTime gameTime)
{
if (IsFinished) return;
if (wait.HasValue)
{
var timeRemaining = wait.Value - gameTime.ElapsedGameTime.TotalMilliseconds;
wait = timeRemaining < 0 ? null : timeRemaining;
// If wait has a value we still have time to burn before the
// the next increment, so we return here.
if (wait.HasValue) return;
}
if (!routine.MoveNext())
{
IsFinished= true;
}
else
{
wait = routine.Current as double?;
}
}
}
This may seem considerably more complex than other solutions provided here, and it may be overkill, but Coroutines allow you to forgo tracking a bunch of state in tracking variables, making complex scenarios easier to follow and cleaner to read. For example, here's a arrow spawning strategy I used Coroutines for in Ludum Dare 37. It spawns 3 arrows 600 milleseconds apart with a 3 second wait between them: https://github.com/srakowski/LD37/blob/477cf515d599eba7c4b55c3f57952865d894f741/src/LD37/GameObjects/BurstArrowSpawnBehavior.cs
If you'd like more social proof of the value of Coroutines take a look at Unity. Unity is one of the more popular game engines, and it has Coroutine support. They describe a scenario where it is useful in their documentation: https://docs.unity3d.com/Manual/Coroutines.html.
I use this for my game :
Public Async Function DelayTask(Time As Double) As Threading.Tasks.Task
Await Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time))
End Function
Converted to C# :
public async System.Threading.Tasks.Task DelayTask(double Time)
{
await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time));
}
You would use it like this in an Async Function :
Await DelayTask(1.5);
The number is in seconds, you can change this by changing the TimeSpan.whateverformat.
Considering that you'll have various things that affect your stats maybe you're better off at having an update subroutine in your Stats class that will check a list of effects that are scheduled to update after one point in time.
This would be better for performance than having each effect relying on its own thread.

How can I take a sample still image from two pictureBoxes in fullscreen every X seconds?

The code:
this.TopMost = true;
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.splitContainer1.SplitterDistance = (this.ClientSize.Width -
this.splitContainer1.SplitterWidth) / 2;
pictureBox1.Image = Image.FromFile(#"d:\gifs\RadarGifAnimatoion.gif");//pb1.Image;
pictureBox2.Image = Image.FromFile(#"d:\gifs\SatelliteGifAnimatoion.gif");//pb2.Image;
timer1.Interval = animationSpeed;
timer1.Enabled = true;
And I have a timer tick event:
private void timer1_Tick(object sender, EventArgs e)
{
}
In this example the timer interval is 80ms.
So inside the timer1 tick event I want each 80ms to take a snapshot or a screenshot of both pictureBoxes and save this shot to the hard disk.
So in the end I will have on the hard disk for example 5 images of the two pictureBoxes .
So if I edit each image of the 5 on the hard disk I will see the two pictureBoxes images.
How can I do it in the timer1 tick event ?
The update code in the timer1 tick event:
private void timer1_Tick(object sender, EventArgs e)
{
using (var still = new Bitmap(this.Width, this.Height))
{
this.DrawToBitmap(still, new Rectangle(new Point(0, 0), still.Size));
still.Save(String.Format(#"d:\GifForAnimation\still_{0}.gif", sequence++), System.Drawing.Imaging.ImageFormat.Gif);
if (sequence == 5)
{
timer1.Stop();
}
}
}
I set the timer1 interval in the constructor to the same speed of the animation when created it. The animation speed is 80ms so the timer1 is set to 80ms too.
And still instead of taking each 80ms an image of the pictureBoxes it's taking or saving the same image.
5 images that are the same.
To create a screenshot you could try this code:
int sequence = 0;
private void timer1_Tick(object sender, EventArgs e)
{
using(var still = new Bitmap(form.Width, form.Height))
{
form.DrawToBitmap(still, new Rectangle(new Point(0, 0), still.Size));
still.Save(String.Format(#"c:\still_{0}.gif", sequence++), ImageFormat.Gif);
}
}
Instead of the form you could refer to any container that contains both images.
EDIT
This screenshot does not include the start menu, just the container of the two image controls.
EDIT2
Rendering the UI to a bitmap like this might not animate the GIF's because the bitmap has no awareness of the animations. You will also get into a race condition: updating the GIF's and making the screenshots within a specific time period in the right order.
I'd use videos capturing software to record a video and select the frames I needed. That is much easier than trying to solve this issue and potential race condition

creating an advanced metronome in WPF (problems with code created animation and completed event)

Good afternoon,
over the last few weeks I have been working on a project to create an advanced metronome. the metronome is made up of the following things
a swinging arm
a light flash
a collection of dynamically created user controls that represent beats (4 of them that are either on, accented or off).
a usercontrol that displays an LCD numeric display and calculates the number of milliseconds between beats for the selected BPM (60000/BPM=milliseconds)
the user selects a BPM and presses start and the following happens
the arm swings between two angles at a rate of n milliseconds per sweep
the light flashes at the end of each arm sweep
the indicators are created and they flash in sequence (one at the end of each sweep).
now the problem
the Arm and light flash animation are created in code and added to a story board with repeat forever and auto reverse.
the indicators are created in code and need to fire an event at the end of each Arm sweep animation.
So, what I did after much messing around was create a timer that runs at the same pace as the storyboard.
the problem, over 30 seconds the timer and the storyboard go out of sync and therefore the indicators and the arm sweep are not in time (not good for a metronome!!).
I was trying to catch the completed event of the animations and use that as a trigger to stop and restart the timer, this was all I could come up with to keep the two in perfect sync.
the moving out of sync is caused by the storyboard slipping and the fact that the storyboard is invoked with begin on the line before the timer is invoked with .start, this although microseconds I think means that they start impossibly close but not at exactly the same time.
my question,
when I try to bind to the completed event of the animation it never fires. I was under the impression that completed even fires regardless of autoreverse (i.e in between each iteration). is this not the case?
can anyone think of another (more cunning) way to keep the two things in sync.
lastly, I did look to see if I could fire a method from a storyboard (which would of made my life really easy, however it would appear that this cannot be done).
if there are any suggestions I am not precious, I just want to get this finished!!
final point of interest,
the bpm can be adjusted whilst the metronome is running, this is achieved by calculating the millisecond duration on the fly (mouse down of a button) and scale the storyboard by the difference between the current speed and the new speed. obviously the timer running the indicators has to be changed at the same time (using interval).
code below is from my project so far (not the XAML just the C#)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;
namespace MetronomeLibrary
{
public partial class MetronomeLarge
{
private bool Running;
//Speed and time signature
private int _bpm = 60;
private int _beats = 4;
private int _beatUnit = 4;
private int _currentBeat = 1;
private readonly int _baseSpeed = 60000 / 60;
private readonly DispatcherTimer BeatTimer = new DispatcherTimer();
private Storyboard storyboard = new Storyboard();
public MetronomeLarge()
{
InitializeComponent();
NumericDisplay.Value = BPM;
BeatTimer.Tick += new EventHandler(TimerTick);
SetUpAnimation();
SetUpIndicators();
}
public int Beats
{
get
{
return _beats;
}
set
{
_beats = value;
SetUpIndicators();
}
}
public int BPM
{
get
{
return _bpm;
}
set
{
_bpm = value;
//Scale the story board here
SetSpeedRatio();
}
}
public int BeatUnit
{
get
{
return _beatUnit;
}
set
{
_beatUnit = value;
}
}
private void SetSpeedRatio()
{
//divide the new speed (bpm by the old speed to get the new ratio)
float newMilliseconds = (60000 / BPM);
float newRatio = _baseSpeed / newMilliseconds;
storyboard.SetSpeedRatio(newRatio);
//Set the beat timer according to the beattype (standard is quarter beats for one sweep of the metronome
BeatTimer.Interval = TimeSpan.FromMilliseconds(newMilliseconds);
}
private void TimerTick(object sender, EventArgs e)
{
MetronomeBeat(_currentBeat);
_currentBeat++;
if (_currentBeat > Beats)
{
_currentBeat = 1;
}
}
private void MetronomeBeat(int Beat)
{
//turnoff all indicators
TurnOffAllIndicators();
//Find a control by name
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[Beat-1];
//illuminate the control
theIndicator.TurnOn();
theIndicator.PlaySound();
}
private void TurnOffAllIndicators()
{
for (int i = 0; i <= gridContainer.Children.Count-1; i++)
{
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[i];
theIndicator.TurnOff();
}
}
private void SetUpIndicators()
{
gridContainer.Children.Clear();
gridContainer.ColumnDefinitions.Clear();
for (int i = 1; i <= _beats; i++)
{
MetronomeLargeIndicator theNewIndicator = new MetronomeLargeIndicator();
ColumnDefinition newCol = new ColumnDefinition() { Width = GridLength.Auto };
gridContainer.ColumnDefinitions.Add(newCol);
gridContainer.Children.Add(theNewIndicator);
theNewIndicator.Name = "Indicator" + i.ToString();
Grid.SetColumn(theNewIndicator, i - 1);
}
}
private void DisplayOverlay_MouseDown(object sender, MouseButtonEventArgs e)
{
ToggleAnimation();
}
private void ToggleAnimation()
{
if (Running)
{
//stop the animation
((Storyboard)Resources["Storyboard"]).Stop() ;
BeatTimer.Stop();
}
else
{
//start the animation
BeatTimer.Start();
((Storyboard)Resources["Storyboard"]).Begin();
SetSpeedRatio();
}
Running = !Running;
}
private void ButtonIncrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value++;
BPM = NumericDisplay.Value;
}
private void ButtonDecrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value--;
BPM = NumericDisplay.Value;
}
private void ButtonIncrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button-over.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonIncrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonDecrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button-over.png"))
};
ButtonDecrement.Background = theBrush;
}
private void ButtonDecrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button.png"))
};
ButtonDecrement.Background = theBrush;
}
private void SweepComplete(object sender, EventArgs e)
{
BeatTimer.Stop();
BeatTimer.Start();
}
private void SetUpAnimation()
{
NameScope.SetNameScope(this, new NameScope());
RegisterName(Arm.Name, Arm);
DoubleAnimation animationRotation = new DoubleAnimation()
{
From = -17,
To = 17,
Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds)),
RepeatBehavior = RepeatBehavior.Forever,
AccelerationRatio = 0.3,
DecelerationRatio = 0.3,
AutoReverse = true,
};
Timeline.SetDesiredFrameRate(animationRotation, 90);
MetronomeFlash.Opacity = 0;
DoubleAnimation opacityAnimation = new DoubleAnimation()
{
From = 1.0,
To = 0.0,
AccelerationRatio = 1,
BeginTime = TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds - 0.5),
Duration = new Duration(TimeSpan.FromMilliseconds(100)),
};
Timeline.SetDesiredFrameRate(opacityAnimation, 10);
storyboard.Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds * 2));
storyboard.RepeatBehavior = RepeatBehavior.Forever;
Storyboard.SetTarget(animationRotation, Arm);
Storyboard.SetTargetProperty(animationRotation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
Storyboard.SetTarget(opacityAnimation, MetronomeFlash);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));
storyboard.Children.Add(animationRotation);
storyboard.Children.Add(opacityAnimation);
Resources.Add("Storyboard", storyboard);
}
}
}
This might not be easily implemented with WPF animations. Instead, a good method would be a game loop. A little research should turn up lots of resources about this. The first one that jumped out at me was http://www.nuclex.org/articles/3-basics/5-how-a-game-loop-works.
In your game loop, you would follow one or the other of these basic procedures:
Calculate how much time has elapsed since the last frame.
Move your displays appropriately.
or
Calculate the current time.
Position your displays appropriately.
The advantage of a game loop is that although the timing may drift slightly (depending on what sort of timing you use), all displays will drift by the same amount.
You can prevent clock drift by calculating time by the system clock, which for practical purposes does not drift. Timers do drift, because they do not run by the system clock.
Time sync is a vaster field than you'd think.
I suggest you take a look at Quartz.NET which is renowned for scheduling/timers issues.
Syncing a WPF animation is tricky because Storyboards are not part of the logical tree, therefore you can't bind anything in them.
That's why you can't define dynamic/variable Storyboards in XAML, you have to do it in C# as you did.
I suggest you make 2 Storyboards: one for the tick to the left, the other one to the right.
In between each animation, fire a method to do your calculations/update another part of the UI, but do it in a separate Task so that the timings aren't messed up (a few µs for the calculations make up for quite some time after 30s already!)
Keep in mind that you will need to use Application.Current.Dispatcher from your Task to update the UI.
And lastly, at least set the Task flag TaskCreationOptions.PreferFairness so that Tasks run in the order they were started.
Now since that just gives a hint to the TaskScheduler and doesn't guarantees them to run in order, you may want to use a queueing system instead for full guarantee.
HTH,
Bab.
You could try 2 animations , one for the right swing and one for the left. In the animation complete on each, start the other animation (checking for cancellation flags) and update your indicators (possibly via BeginInvoke on the Dispatcher so you don't interfere with the next animation start.)
I think getting the timer to sync with an animation is difficult - it is a dispatcher based timer which is based on messages - sometimes it can skip a bit of time, ie if you click fast with the mouse a lot I think the animation timer also is dispatcher based, so they will easily get out of sync.
I would suggest to abandon the syncing and let the timer handle it. Can't you let it update a property with notification and let your metronome arm position bind to that?
To get the accelaration/deceleration you just have to use a Sine or Cosine function.

How can I rotate triangle (clock-wise ) with CSGL?

I'm trying to make a clock-wise rotating triangle, but I can not. I made a timer control but the result is the same without the timer. As a result, the below code does not show rotating triangle.
How can i rotate triangle with CSGL?
namespace WinDrawCoordinate
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private float a = 0.0f;
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
}
protected void Gosterim()
{
GL.glClear(GL.GL_COLOR_BUFFER_BIT);
GL.glLoadIdentity();
Hesapla();
Ucgen();
}
protected void ayarlar()
{
GL.glClearColor(1, 1, 1, 1);
GL.glShadeModel(GL.GL_FLAT);
}
protected void Hesapla()
{
a += 0.5f;
this.Refresh();
}
protected void Ucgen()
{
GL.glColor3f(0, 1, 1);
GL.glRotatef(a, 0, 0, -1);
GL.glBegin(GL.GL_TRIANGLES);
GL.glVertex2f(-0.2f, -0.2f);
GL.glVertex2f(0.2f, -0.2f);
GL.glVertex2f(0.0f, 0.2f);
GL.glEnd();
}
private void timer1_Tick(object sender, EventArgs e)
{
ayarlar();
Gosterim();
}
}
From the code posted I see a couple things:
You don't have any matrix set-up. While you can use the default matrices you should almost certainly specify your own to ensure the triangle is where you expect. The default matrices should work for you here as they specify an identity modelview and projection.
You don't have a call to swap buffers. You need a call after you're done drawing to swap the front and back buffers so that the triangle is displayed. For CsGL I think there is a SwapBuffer() method you can use.
Don't use a timer to pump your redrawing. Timers work unreliably as they use the windows message pump and they are hard to get good results from. Instead you should use a render loop--a loop that runs the entire time your program runs and just keeps refreshing the screen. You have to make sure you give the operating system time to handle your messages though. A very simple render loop might be:
void RenderLoop() {
while (true) {
SetUpCamera();
CreateGeometry();
SwapBuffers();
Application.DoEvents(); // this lets windows process messages
}
}
note that there are better ways to get a good message loop in C# but this is easy to set up and reason about.
Double check this line:
GL.glRotatef(a, 0, 0, -1);
Shouldn't it be?
GL.glRotatef(a, 0, 0, 1);
Looks like your increment to the rotation angle (a) should be OK....but how fast is the timer "ticking" and firing off ayarlar and Gosterim?
Also you may want to add a check in relation to the rotation angle, if it loops alot and goes over 359.5 (Since you are adding 0.5 each time through), then you are going "full circle" literally

Categories