This project showcases the fundamentals of creating smooth animations. The application features a moving rectangle that glides across the screen, illustrating key concepts such as frame independence and real-time rendering.
In this app, you'll learn how to manage timing, handle graphics rendering, and implement basic animation principles. Whether you're a beginner looking to understand the basics of animation or an experienced developer seeking a refresher, this project provides a hands-on approach to mastering animation techniques.
Welcome to the Animation C# project! In this lesson, we will explore the code step by step, breaking down each part to understand how it works. This project demonstrates the fundamentals of creating smooth animations in a Windows Forms application using C#. By the end of this walkthrough, you will have a solid understanding of the code and the principles behind animation.
Animation is the art of creating the illusion of motion by displaying a series of static images in quick succession. In our app, we use animation to make it appear as though our rectangle is moving towards the right. To ensure that our animation runs smoothly on all devices, we have designed it to be frame-independent. This means that our animation is not affected by changes in the frame rate, ensuring a consistent and seamless experience for all users.
Let's dive into the code line by line.
using System.Diagnostics;
This line imports the System.Diagnostics
namespace, which provides classes for debugging and tracing. This will help us print debug messages to the console.
namespace Animation_CS
{
Here, we define a namespace called Animation_CS
. Namespaces are used to organize code and avoid naming conflicts with other parts of the program.
public partial class Form1 : Form
{
We define a class named Form1
that inherits from Form
. This means that Form1
is a type of window in our application. The partial
keyword allows us to define the class across multiple files if needed.
private BufferedGraphicsContext Context = new();
private BufferedGraphics? Buffer;
private readonly Size MinimumMaxBufferSize = new(1280, 720);
private Color BackgroundColor = Color.Black;
private readonly String FpsIdentifier = new(" FPS");
- BufferedGraphicsContext Context: This manages the buffering for smoother graphics rendering.
- BufferedGraphics? Buffer: This holds our graphics buffer, which is used to draw graphics off-screen before displaying them.
- Size MinimumMaxBufferSize: Sets a minimum size for the buffer to 1280x720 pixels.
- Color BackgroundColor: Defines the background color of the form, set to black.
- String FpsIdentifier: A string to identify the FPS (frames per second) display on the screen.
public struct RectangleDouble
{
public double X, Y, Width, Height, Velocity;
public Brush Brush;
public RectangleDouble(double x, double y, double width, double height,
double velocity, Brush brush)
{
X = x;
Y = y;
Width = width;
Height = height;
Velocity = velocity;
Brush = brush;
}
This structure represents a rectangle with double-precision coordinates and dimensions. It includes:
- Constructor: Initializes the rectangle's position, size, velocity, and color (brush).
public readonly int GetNearestX()
{
return (int)Math.Round(X);
}
These methods round the rectangle's attributes to the nearest integer values for drawing. This is important because pixel coordinates must be whole numbers.
public void MoveRight(TimeSpan deltaTime)
{
X += Velocity * deltaTime.TotalSeconds;
}
This method updates the rectangle's position based on its velocity and the time elapsed since the last frame (deltaTime
). The formula used is:
- Displacement = Velocity x Delta Time (Δs = V * Δt)
public void Wraparound(Rectangle clientRectangle)
{
if (X > clientRectangle.Right)
{
X = clientRectangle.Left - Width;
}
}
This method checks if the rectangle has exited the right side of the client area. If it has, it repositions the rectangle to the left side of the screen, creating a wraparound effect.
public void MoveRightAndWraparound(Rectangle clientRectangle,
TimeSpan deltaTime)
{
MoveRight(deltaTime);
Wraparound(clientRectangle);
}
This method combines moving the rectangle to the right and checking for wraparound in one call.
public void CenterVertically(Rectangle clientRectangle)
{
Y = clientRectangle.Height / 2 - Height / 2;
}
This method centers the rectangle vertically in the client area of the form.
private RectangleDouble Rectangle = new(0.0f, 0.0f, 256.0f, 256.0f, 32.0f,
new SolidBrush(Color.Chartreuse));
Here, we create an instance of RectangleDouble
, starting at position (0, 0) with a width and height of 256 pixels, a velocity of 32 pixels per second, and a color of chartreuse.
private struct DeltaTimeStructure
{
public DateTime CurrentFrame;
public DateTime LastFrame;
public TimeSpan ElapsedTime;
public DeltaTimeStructure(DateTime currentFrame, DateTime lastFrame,
TimeSpan elapsedTime)
{
CurrentFrame = currentFrame;
LastFrame = lastFrame;
ElapsedTime = elapsedTime;
}
This structure keeps track of the time between frames, which is crucial for smooth animations. It includes:
- CurrentFrame: The time of the current frame.
- LastFrame: The time of the last frame.
- ElapsedTime: The time difference between the two frames.
public void Update()
{
CurrentFrame = DateTime.Now;
ElapsedTime = CurrentFrame - LastFrame;
LastFrame = CurrentFrame;
}
This method updates the current frame time, calculates the elapsed time since the last frame, and updates the last frame's time for the next iteration.
private struct DisplayStructure
{
public Point Location;
public string Text;
public Font Font;
public Brush Brush;
public DisplayStructure(Point location, string text, Font font,
Brush brush)
{
Location = location;
Text = text;
Font = font;
Brush = brush;
}
}
This structure is used to display text (like FPS) on the screen. It includes:
- Location: The position where the text will be displayed.
- Text: The actual text to display.
- Font: The font used for the text.
- Brush: The color of the text.
private struct FrameCounterStructure
{
public int FrameCount;
public DateTime StartTime;
public TimeSpan TimeElapsed;
public double SecondsElapsed;
public FrameCounterStructure(int frameCount, DateTime startTime,
TimeSpan timeElapsed, double secondsElapsed)
{
FrameCount = frameCount;
StartTime = startTime;
TimeElapsed = timeElapsed;
SecondsElapsed = secondsElapsed;
}
}
This structure tracks the number of frames rendered and the elapsed time, helping us calculate the FPS.
private void Form1_Load(object sender, EventArgs e)
{
InitializeApp();
Debug.Print($"Running...{DateTime.Now}");
}
This method is called when the form loads. It initializes the application and prints a debug message to the console.
private void Form1_Resize(object sender, EventArgs e)
{
if (WindowState != FormWindowState.Minimized)
{
ResizeFPS();
Rectangle.CenterVertically(ClientRectangle);
DisposeBuffer();
}
}
This method is triggered when the form is resized. It adjusts the FPS display and rectangle size, and disposes of the buffer if the window is not minimized.
private void timer1_Tick(object sender, EventArgs e)
{
if (WindowState != FormWindowState.Minimized)
{
UpdateFrame();
Invalidate(); // Calls OnPaint
}
}
This event is triggered at regular intervals (every 10 ms). It updates the frame and calls the OnPaint
method to redraw the form.
protected override void OnPaint(PaintEventArgs e)
{
AllocateBuffer();
DrawFrame();
Buffer?.Render(e.Graphics);
UpdateFrameCounter();
base.OnPaint(e);
}
This method is called whenever the form needs to be redrawn. It allocates the buffer, draws the current frame, renders it on the form, and updates the frame counter.
private void UpdateFrame()
{
DeltaTime.Update();
Rectangle.MoveRightAndWraparound(ClientRectangle,
DeltaTime.ElapsedTime);
}
This method updates the time since the last frame and moves the rectangle.
private void InitializeBuffer()
{
Context = BufferedGraphicsManager.Current;
if (Screen.PrimaryScreen != null)
{
Context.MaximumBuffer = Screen.PrimaryScreen.WorkingArea.Size;
}
else
{
Context.MaximumBuffer = MinimumMaxBufferSize;
Debug.Print($"Primary screen not detected.");
}
AllocateBuffer();
}
This method sets up the graphics buffer based on the screen size and allocates the buffer.
private void AllocateBuffer()
{
if (Buffer == null)
{
Buffer = Context.Allocate(CreateGraphics(), ClientRectangle);
Buffer.Graphics.CompositingMode =
System.Drawing.Drawing2D.CompositingMode.SourceOver;
Buffer.Graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
}
}
This method allocates the buffer for drawing graphics, ensuring that the graphics are rendered smoothly.
private void DrawFrame()
{
Buffer?.Graphics.Clear(BackgroundColor);
Buffer?.Graphics.FillRectangle(Rectangle.Brush,
Rectangle.GetNearestX(),
Rectangle.GetNearestY(),
Rectangle.GetNearestWidth(),
Rectangle.GetNearestHeight());
Buffer?.Graphics.DrawString(FpsDisplay.Text,
FpsDisplay.Font,
FpsDisplay.Brush,
FpsDisplay.Location);
}
This method clears the buffer, draws the rectangle, and displays the current FPS.
private void DisposeBuffer()
{
if (Buffer != null)
{
Buffer.Dispose();
Buffer = null; // Set to null to avoid using a disposed object
}
}
This method disposes of the graphics buffer to free up resources.
private void InitializeApp()
{
InitializeForm();
Timer1.Interval = 10;
Timer1.Start();
}
This method initializes the form and starts the timer for regular updates.
private void InitializeForm()
{
CenterToScreen();
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
Text = "Animation C# - Code with Joe";
WindowState = FormWindowState.Maximized;
}
This method sets up the form's appearance and behavior, centering it on the screen and maximizing the window.
private void UpdateFrameCounter()
{
FrameCounter.TimeElapsed = DateTime.Now - FrameCounter.StartTime;
FrameCounter.SecondsElapsed = FrameCounter.TimeElapsed.TotalSeconds;
if (FrameCounter.SecondsElapsed < 1)
{
FrameCounter.FrameCount += 1;
}
else
{
FpsDisplay.Text = $"{FrameCounter.FrameCount}{FpsIdentifier}";
FrameCounter.FrameCount = 0;
FrameCounter.StartTime = DateTime.Now;
}
}
This method updates the frame counter, calculating the FPS and updating the display text accordingly.
private void ResizeFPS()
{
FpsDisplay.Location = new Point(FpsDisplay.Location.X,
ClientRectangle.Bottom - 75);
}
This method positions the FPS display at the bottom of the client area.
public Form1()
{
InitializeComponent();
Context = BufferedGraphicsManager.Current;
if (Screen.PrimaryScreen != null)
{
Context.MaximumBuffer = Screen.PrimaryScreen.WorkingArea.Size;
}
else
{
Context.MaximumBuffer = MinimumMaxBufferSize;
Debug.Print($"Primary screen not detected.");
}
Buffer = Context.Allocate(CreateGraphics(), ClientRectangle);
Buffer.Graphics.CompositingMode =
System.Drawing.Drawing2D.CompositingMode.SourceOver;
Buffer.Graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
}
The constructor initializes the form and sets up the graphics context and buffer.
Congratulations! You’ve just completed a detailed walkthrough of the Animation C# project. We explored each part of the code, understanding how it works together to create a smooth animation of a rectangle moving across the screen. This project serves as a solid foundation for learning more about animation techniques and graphics programming in C#.
Feel free to experiment with different values and see how they affect the animation! Happy coding!
Here are some exercises to enhance your understanding of the Animation C# project by modifying various parameters:
-
Change the Rectangle's Color
- Task: Modify the
RectangleBrush
variable to change the color of the rectangle. - Instructions:
private readonly Brush RectangleBrush = new SolidBrush(Color.Red); // Change Color.Chartreuse to Color.Red
- Task: Modify the
-
Change the Rectangle's Size
- Task: Adjust the dimensions of the rectangle by modifying the
Rectangle
instance. - Instructions:
private RectangleDouble Rectangle = new(0, 0, 128, 128); // Change width and height to 128
- Task: Adjust the dimensions of the rectangle by modifying the
-
Change the Rectangle's Velocity
- Task: Modify the
Velocity
variable to change how fast the rectangle moves across the screen. - Instructions:
private readonly double Velocity = 100.0; // Change from 64.0 to 100.0 for faster movement
- Task: Modify the
-
Add a Random Color Change on Wraparound
- Task: Implement functionality to change the rectangle's color randomly on Wraparound.
- Instructions:
private void MoveRectangle() { // Move the rectangle to the right. Rectangle.X += Velocity * DeltaTime.ElapsedTime.TotalSeconds; // Displacement = Velocity x Delta Time ( Δs = V * Δt ) // Wraparound // When the rectangle exits the right side of the client area. if (Rectangle.X > ClientRectangle.Right) { // The rectangle reappears on the left side the client area. Rectangle.X = ClientRectangle.Left - Rectangle.Width; // Change color randomly Random rand = new Random(); RectangleBrush = new SolidBrush(Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))); } }
-
Implement Rectangle Resizing on Key Press
- Task: Allow the user to resize the rectangle using keyboard input (e.g., increase size with the Up arrow and decrease with the Down arrow).
- Instructions:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Up) { Rectangle.Width += 10; Rectangle.Height += 10; } else if (keyData == Keys.Down) { Rectangle.Width -= 10; Rectangle.Height -= 10; } return base.ProcessCmdKey(ref msg, keyData); }
These exercises will help you explore the flexibility of the Animation C# project and deepen your understanding of graphics programming in C#. Feel free to experiment with different values and see how they affect the animation!
DeltaTime refers to the time difference between the current frame and the last frame in a game or animation loop. It is crucial for creating smooth and consistent animations, especially in real-time applications where frame rates can vary.
-
Frame Rate Independence:
- Without DeltaTime, animations would run differently on machines with varying frame rates. By using DeltaTime, animations can be adjusted to maintain a consistent speed regardless of how many frames are rendered per second.
-
Smooth Motion:
- DeltaTime allows for smoother motion by ensuring that movement calculations are based on the actual time elapsed rather than relying solely on frame counts.
-
Timing Events:
- It helps in timing events accurately, ensuring that actions occur at the right moment relative to real-world time.
-
Capture Time:
- At the beginning of each frame, capture the current time (e.g., using
DateTime.Now
in C#).
- At the beginning of each frame, capture the current time (e.g., using
-
Calculate DeltaTime:
- Subtract the last captured time from the current time to get the elapsed time for that frame.
TimeSpan elapsedTime = currentFrameTime - lastFrameTime;
-
Update Last Frame Time:
- Store the current time as the last frame time for the next iteration.
lastFrameTime = currentFrameTime;
-
Use DeltaTime in Calculations:
- Use the calculated DeltaTime to adjust movement speeds, animations, and other time-dependent calculations.
position += velocity * elapsedTime.TotalSeconds;
Here’s a simple example of how DeltaTime might be implemented in a game loop:
private DateTime lastFrameTime;
private double velocity = 100.0; // pixels per second
private double position = 0.0;
private void GameLoop()
{
DateTime currentFrameTime = DateTime.Now;
TimeSpan elapsedTime = currentFrameTime - lastFrameTime;
lastFrameTime = currentFrameTime;
// Update position based on velocity and elapsed time
position += velocity * elapsedTime.TotalSeconds;
// Render the new position
Render(position);
}
-
Consistent Units:
- Always ensure that DeltaTime is in a consistent unit (usually seconds) to avoid scaling issues.
-
Cap DeltaTime:
- Optionally cap DeltaTime to prevent large jumps in time due to frame drops, which can lead to erratic behavior.
if (elapsedTime.TotalSeconds > maxDeltaTime) elapsedTime = TimeSpan.FromSeconds(maxDeltaTime);
-
Use Fixed Time Steps for Physics:
- For physics calculations, consider using a fixed timestep to maintain stability.
DeltaTime is a fundamental concept in game development and animation that allows for smooth, frame-rate-independent motion. By accurately calculating and utilizing DeltaTime, developers can create more responsive and visually appealing applications. Understanding and implementing DeltaTime correctly is essential for any developer working in real-time graphics and animation.
This project serves as a direct port of the original Animation project created in VB.NET, which you can also explore for a different perspective on the same concepts. For more information and to access the complete code, visit the Animation Repository and the Animation C# Repository. Happy coding!
MIT License
Copyright (c) 2025 Joseph W. Lumbley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...