Skip to content

Latest commit

 

History

135 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔵 Bouncing Ball

This application displays a single ball that moves smoothly across the screen using delta‑time‑based animation.

005

When the program starts, the ball is placed at the exact center of the window. A random direction is chosen, and the ball begins traveling across the client area at a constant speed.

The movement is updated inside a high‑frequency timer, which calculates how much real time has passed between frames. This ensures the animation stays smooth and consistent even if the frame rate changes.

As the ball travels, it checks for collisions with the edges of the window. When the ball reaches any boundary - left, right, top, or bottom - it “bounces” by reversing the appropriate velocity component. This creates natural, physics‑style motion where the ball ricochets around the screen indefinitely.

The app also includes a simple FPS counter drawn directly onto the form. It updates once per second and shows how many frames were rendered, giving a clear visual indicator of performance.

System Summary

Code Walkthrough



🎓 Learning Objectives

This project is designed to teach:

  • How animation works
  • How physics loops work
  • How rendering loops work
  • How to avoid flicker
  • How to optimize without complexity
  • How to structure real-time code cleanly

It’s a introduction to game programming concepts using Windows Forms and VB.NET.





⚙️ System Summary

A complete, high‑level overview of how the animation engine works, why each subsystem exists, and how they fit together.

This project is a miniature real‑time graphics engine built inside Windows Forms.
It demonstrates smooth animation, delta‑time physics, motion trails, FPS tracking, and high‑quality GDI+ rendering all using simple, readable code.

Below is the full architecture broken into its major subsystems.


Engine State ( Core Variables )

The engine stores everything it needs to simulate and draw the ball:

  • ballPos - the ball’s current position
  • velX / velY - the ball’s velocity components
  • ballDiameter - the size of the ball
  • speed - the magnitude of the velocity
  • trail - a list of past positions for the motion trail
  • trailSizes / trailOffsets - precomputed geometry for trail circles
  • frameCount / fps - FPS tracking
  • physicsTimer / fpsTimer / stopwatch - timing systems

These variables form the backbone of the animation.

Core Variables Walkthrough


Initialization ( Constructor + OnLoad )

The engine initializes itself in two phases:

Constructor (New)

  • Enables double‑buffering
  • Sets high‑quality painting flags
  • Centers the ball
  • Generates a random direction
  • Starts the physics and FPS timers

Constructor Walkthrough

OnLoad

  • Creates all GDI+ resources ( brushes, fonts )
  • Preallocates trail brushes
  • Precomputes trail sizes and offsets
  • Starts the physics loop

This ensures the engine is fully prepared before the first frame is drawn.

OnLoad Walkthrough


Physics Loop ( Fixed Timestep )

The physics loop runs every 15 ms ( ~ 66 FPS ):

PhysicsTick

  • Measures delta‑time (dt)
  • Clamps dt to avoid teleporting during lag
  • Updates ball position
  • Handles collisions
  • Updates the trail
  • Calls Invalidate() to trigger a redraw

This loop is the heartbeat of the animation.

PhysicsTick Walkthrough


Collision Handling

The engine checks for collisions with all four window edges:

  • If the ball hits a wall, snap it back inside
  • Reverse the velocity component
  • Use Math.Abs to guarantee correct bounce direction

This creates clean, predictable bouncing behavior.

HandleCollisions Walkthrough


Trail System

The trail system records the ball’s movement history:

UpdateTrail

  • Adds the current ball position
  • Removes the oldest entry when the list exceeds its max length

UpdateTrail Walkthrough

DrawTrail

  • Uses precomputed sizes and offsets
  • Applies exponential alpha fading
  • Draws each trail circle behind the ball

The result is a smooth, tapered comet‑like trail.

DrawTrail Walkthrough


Rendering Pipeline

Rendering happens inside OnPaint:

Graphics Quality Settings

  • Anti‑aliasing
  • High‑quality interpolation
  • High‑quality pixel offset
  • SourceOver compositing

These ensure the animation looks crisp and professional.

Draw Order

  1. DrawTrail
  2. DrawBall
  3. DrawFPS

This layering keeps the trail behind the ball and the FPS counter on top.

OnPaint Walkthrough

OnPaintBackground

  • Suppressed to eliminate flicker
  • Lets double‑buffering handle the entire frame

This is essential for smooth animation.

OnPaintBackground Walkthrough


FPS Counter

The FPS system measures how many frames occur per second:

UpdateFPS

  • Increment frame count
  • If one second passed:
    • Set fps
    • Reset counter
    • Restart stopwatch

UpdateFPS Walkthrough

DrawFPS

  • Draws the FPS text in the top‑left corner

This gives real‑time performance feedback.

DrawFPS Walkthrough


Resize Handling

When the window is resized:

  • Ignore resize until resources exist
  • Clamp the ball inside the new bounds
  • Recompute trail offsets
  • Invalidate the form

This keeps the engine stable and visually correct during window resizing.

OnResize Walkthrough


Cleanup

When the form closes:

  • Dispose all brushes
  • Dispose the font
  • Dispose the timer
  • Dispose each trail brush

This prevents memory leaks and cleans up unmanaged GDI resources.

Cleanup Walkthrough


Why This Engine Is So Effective

This animation engine works beautifully because it follows real graphics‑engine principles:

  • Fixed timestep physics
  • Delta‑time movement
  • High‑quality rendering modes
  • Double‑buffering everywhere
  • Suppressed background painting
  • Preallocated GDI resources
  • Precomputed geometry
  • Clean separation of responsibilities

It’s small, readable, and perfect for learning how real‑time animation works.





Code Walkthrough

A line‑by‑line explanation of how this animation works and why we built it this way.

This project shows how to animate a bouncing ball in Windows Forms using smooth motion, trails, and an FPS counter.
The goal is to teach you how real-time graphics work, using simple, readable code.


Imports System.Drawing.Drawing2D
  • This line brings in the Drawing2D namespace from .NET’s GDI+ drawing library.
  • It gives you access to advanced rendering features such as:
    • SmoothingMode
    • InterpolationMode
    • PixelOffsetMode
    • CompositingMode
  • Without this import, you’d have to fully qualify those types (e.g., System.Drawing.Drawing2D.SmoothingMode.AntiAlias).

In short: This import enables high‑quality graphics rendering options for your form.


Public Class Form1
  • This begins the definition of your Windows Forms class.
  • Form1 inherits from System.Windows.Forms.Form ( implicitly ).
  • Everything that follows: fields, methods, event handlers, belongs to this form.
  • When the program runs, this class becomes the window the user sees.

In short: This is the main window of your application, and all your animation logic lives inside it.

System Summary



Core Variables

' -------------------------------
'  Engine State
' -------------------------------
Private ballPos As PointF
Private ballDiameter As Integer = 80

Private velX As Double
Private velY As Double
Private speed As Double = 450

Private physicsTimer As New Timer()
Private sw As New Stopwatch()
  • Comment block (Engine State): Just a visual separator in the code, labeling the next section as “Engine State”.
  • Private ballPos As PointF: Declares a private field that stores the ball’s current position as a 2D point (X, Y).
  • Private ballDiameter As Integer = 80: Declares the ball’s diameter in pixels and initializes it to 80.
  • Private velX As Double: Declares the horizontal velocity component of the ball ( pixels per second ).
  • Private velY As Double: Declares the vertical velocity component of the ball ( pixels per second ).
  • Private speed As Double = 450: Declares a base speed value ( magnitude ) for the ball’s movement and initializes it to 450.
  • Private physicsTimer As New Timer(): Creates a Timer that will drive the physics updates ( the fixed‑timestep loop ).
  • Private sw As New Stopwatch(): Creates a Stopwatch used to measure elapsed time between physics ticks ( for dt).

' -------------------------------
'  FPS Tracking
' -------------------------------
Private frameCount As Integer = 0
Private fps As Integer = 0
Private fpsTimer As New Stopwatch()
  • Comment block ( FPS Tracking ): Labels this section as related to frames‑per‑second tracking.
  • Private frameCount As Integer = 0: Counts how many frames have been rendered in the current one‑second window.
  • Private fps As Integer = 0: Stores the calculated FPS value that will be displayed on screen.
  • Private fpsTimer As New Stopwatch(): Measures time to know when one second has passed so FPS can be updated.

' -------------------------------
'  GDI Resources
' -------------------------------
Private ballBrush As SolidBrush
Private fpsBrush As SolidBrush
Private fpsFont As Font
Private trailBrushes As SolidBrush()
  • Comment block ( GDI Resources ): Marks the section for drawing resources.
  • Private ballBrush As SolidBrush: Brush used to fill the ball when drawing.
  • Private fpsBrush As SolidBrush: Brush used to draw the FPS text.
  • Private fpsFont As Font: Font used for the FPS text rendering.
  • Private trailBrushes As SolidBrush(): An array of brushes used to draw each segment of the trail with varying alpha.

' -------------------------------
'  Trail System
' -------------------------------
Private trail As New List(Of PointF)
Private trailLength As Integer = 25
Private trailSizes As Integer()
Private trailOffsets As Single()
  • Comment block ( Trail System ): Labels the section for the motion trail behind the ball.
  • Private trail As New List(Of PointF): A list storing past positions of the ball to render the trail.
  • Private trailLength As Integer = 25: Maximum number of trail points ( segments ) to keep and draw.
  • Private trailSizes As Integer(): An array holding precomputed sizes ( diameters ) for each trail segment.
  • Private trailOffsets As Single(): An array holding precomputed offsets so each trail ellipse is centered relative to the main ball.

System Summary



Constructor

Public Sub New()

This is the form’s constructor. It runs once, the moment the form is created.


InitializeComponent()

Loads everything designed in the Windows Forms Designer: controls, properties, layout, etc.
Every WinForms form calls this first.


Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
            ControlStyles.UserPaint Or
            ControlStyles.OptimizedDoubleBuffer,
            True)

This line enables three important rendering flags:

  • AllPaintingInWmPaint
    Prevents background erasing, reducing flicker.

  • UserPaint
    Tells WinForms that you will handle all painting manually (via OnPaint).

  • OptimizedDoubleBuffer
    Enables double‑buffering at the control level, eliminating flicker during animation.

Setting them to True applies the combined flags.


Me.DoubleBuffered = True

A second layer of double‑buffering.
Even though you already set OptimizedDoubleBuffer, this property ensures the form itself uses a back‑buffer.


Me.BackColor = Color.Black

Sets the form’s background color to black — the canvas for your animation.


🎯 Center ball

ballPos = New PointF((ClientSize.Width - ballDiameter) / 2, (ClientSize.Height - ballDiameter) / 2)

This computes the centered position:

  • ClientSize.Width - ballDiameter → remaining horizontal space
  • Divide by 2 → center horizontally
  • Same logic vertically

Creates a PointF representing the ball’s starting position.


🎲 Random direction

Dim rnd As New Random()

Creates a random number generator.

Dim angle As Double = rnd.NextDouble() * Math.PI * 2

Generates a random angle between 0 and 2π (full circle).

velX = Math.Cos(angle) * speed

Computes the horizontal velocity based on the angle.

velY = Math.Sin(angle) * speed

Computes the vertical velocity based on the angle.

Together, these give the ball a random direction with the same speed magnitude.


⚙️ Physics at ~60 FPS

physicsTimer.Interval = 15

Sets the timer to tick every 15 ms, which is ~66 updates per second.

AddHandler physicsTimer.Tick, AddressOf PhysicsTick

Connects the timer’s Tick event to your physics update method.

Every tick → PhysicsTick() runs.


⏱️ Start timing systems

sw.Start()

Starts the stopwatch used to measure delta‑time (dt) between physics updates.

fpsTimer.Start()

Starts the stopwatch used for FPS measurement.


End Sub

Marks the end of the constructor.

System Summary



OnLoad - Preparing all graphics resources before the animation begins

OnLoad runs once, right after the form is created and just before it becomes visible.
This is the perfect place to initialize brushes, fonts, trail arrays, and anything that depends on the form’s size.


Protected Overrides Sub OnLoad(e As EventArgs)

This overrides the form’s built‑in OnLoad method.
It fires when the form is fully constructed and ready to initialize runtime resources.


MyBase.OnLoad(e)

Calls the base class version of OnLoad.
This ensures WinForms performs its normal setup before your custom logic runs.


🎨 Core GDI Resources

ballBrush = New SolidBrush(Color.DeepSkyBlue)
fpsBrush = New SolidBrush(Color.White)
fpsFont = New Font("Segoe UI", 14, FontStyle.Bold)

These are the core drawing resources:

  • ballBrush — fills the ball with a bright blue color
  • fpsBrush — draws the FPS text in white
  • fpsFont — sets the font used for the FPS counter

In short: These are the main tools used to draw your scene.


🟦 Preallocate Trail Brushes

trailBrushes = New SolidBrush(trailLength - 1) {}

Creates an array of SolidBrush objects with trailLength entries.

This avoids allocating brushes during animation, which keeps performance smooth.


For i As Integer = 0 To trailLength - 1
    trailBrushes(i) = New SolidBrush(Color.FromArgb(0, 0, 191, 255))
Next

Initializes each brush with a fully transparent blue color:

  • Alpha = 0
  • RGB = (0, 191, 255)

The alpha will be changed dynamically during drawing to create the fading trail effect.

In short: Every trail segment gets its own brush, ready to have its alpha adjusted.


📏 Precompute Trail Sizes and Offsets

trailSizes = New Integer(trailLength - 1) {}
trailOffsets = New Single(trailLength - 1) {}

Creates two arrays:

  • trailSizes — the diameter of each trail circle
  • trailOffsets — how much each circle must be shifted to stay centered

Precomputing these values avoids doing math inside the render loop.


For i As Integer = 0 To trailLength - 1
    Dim size As Integer = ballDiameter - (trailLength - i) * 2
    If size < 10 Then size = 10

This loop calculates the size of each trail segment:

  • Older trail segments (lower i) get smaller circles
  • Newer trail segments (higher i) get larger circles
  • Minimum size is clamped to 10 pixels

This creates a tapered, comet‑like trail behind the ball.


    trailSizes(i) = size
    trailOffsets(i) = CSng((ballDiameter - size) / 2)
Next

Stores:

  • the computed size
  • the offset needed to center the smaller circle inside the ball’s position

The offset is (ballDiameter - size) / 2, which centers the ellipse perfectly.

In short: Each trail circle is smaller and centered relative to the ball.


▶️ Start the Physics Engine

physicsTimer.Start()

Begins the fixed‑timestep physics loop.

From this moment on:

  • the ball moves
  • collisions are checked
  • the trail updates
  • the form invalidates and redraws

This is the moment the animation officially starts.


End Sub

Marks the end of the OnLoad method.

System Summary



PhysicsTick — The heartbeat of the animation

This method runs every 15 ms (≈66 times per second).
It updates the ball’s position, handles collisions, updates the trail, and triggers a redraw.


Private Sub PhysicsTick(sender As Object, e As EventArgs)

This is the event handler for the timer’s Tick event.
Every time the timer fires, this method executes one physics update.


⏱️ Measure Delta‑Time (dt)

Dim dt As Double = sw.Elapsed.TotalSeconds
sw.Restart()
  • sw.Elapsed.TotalSeconds
    Reads how much time has passed since the last physics tick.
  • sw.Restart()
    Resets and starts the stopwatch again for the next tick.

This gives you a precise delta‑time, which makes movement smooth even if the timer drifts slightly.

In short: dt = time since last update.


🛑 Clamp dt (Safety Against Lag Spikes)

dt = Math.Min(dt, 0.05)

If the app freezes or lags for a moment, dt could become very large.
Large dt → ball teleports across the screen → breaks physics.

Clamping dt to 0.05 seconds (50 ms) prevents runaway movement.

In short: Never allow dt to exceed a safe maximum.


🎯 Update Ball Position

ballPos.X += CSng(velX * dt)
ballPos.Y += CSng(velY * dt)

This applies velocity to position:

  • velX * dt → how far the ball moves horizontally this frame
  • velY * dt → how far the ball moves vertically this frame

CSng() converts the result to Single because PointF uses Single values.

In short: Move the ball based on velocity and elapsed time.


🧱 Handle Collisions

HandleCollisions()

Checks whether the ball hit any of the window edges:

  • left
  • right
  • top
  • bottom

If so, it adjusts position and reverses velocity.

In short: Bounce the ball off the walls.


🟦 Update Trail

UpdateTrail()

Adds the ball’s current position to the trail list and removes old entries.

This creates the fading motion trail behind the ball.

In short: Record the ball’s movement history.


🎨 Request a Redraw

Invalidate()

Tells WinForms:

“The screen is out of date — please repaint the form.”

This triggers OnPaint, which draws:

  • the trail
  • the ball
  • the FPS counter

In short: PhysicsTick updates the world; OnPaint draws it.


End Sub

Marks the end of the physics update loop.

System Summary



HandleCollisions - Keeping the ball inside the window and bouncing cleanly

This method checks whether the ball has hit any of the four edges of the window.
If it has, the ball is repositioned and its velocity is flipped so it bounces back.


Private Sub HandleCollisions()

Begins the collision‑handling routine.
This is called once per physics update.


🧱 Horizontal Bounce (Left & Right Walls)

If ballPos.X <= 0 Then
    ballPos.X = 0
    velX = Math.Abs(velX)

Left wall collision

  • ballPos.X <= 0
    The ball’s left edge has reached or passed the left boundary.
  • ballPos.X = 0
    Snap the ball back inside the window so it doesn’t drift off‑screen.
  • velX = Math.Abs(velX)
    Ensures the horizontal velocity becomes positive, sending the ball to the right.

In short: If the ball hits the left wall, push it right.


ElseIf ballPos.X >= ClientSize.Width - ballDiameter Then
    ballPos.X = ClientSize.Width - ballDiameter
    velX = -Math.Abs(velX)
End If

Right wall collision

  • ballPos.X >= ClientSize.Width - ballDiameter
    The ball’s right edge has reached the right boundary.
  • ballPos.X = ClientSize.Width - ballDiameter
    Snap it back inside the window.
  • velX = -Math.Abs(velX)
    Ensures the horizontal velocity becomes negative, sending the ball left.

In short: If the ball hits the right wall, push it left.


🧱 Vertical Bounce (Top & Bottom Walls)

If ballPos.Y <= 0 Then
    ballPos.Y = 0
    velY = Math.Abs(velY)

Top wall collision

  • ballPos.Y <= 0
    The ball’s top edge has reached the top boundary.
  • ballPos.Y = 0
    Snap it back inside.
  • velY = Math.Abs(velY)
    Ensures vertical velocity becomes positive, sending the ball downward.

In short: If the ball hits the top wall, push it down.


ElseIf ballPos.Y >= ClientSize.Height - ballDiameter Then
    ballPos.Y = ClientSize.Height - ballDiameter
    velY = -Math.Abs(velY)
End If

Bottom wall collision

  • ballPos.Y >= ClientSize.Height - ballDiameter
    The ball’s bottom edge has reached the bottom boundary.
  • ballPos.Y = ClientSize.Height - ballDiameter
    Snap it back inside.
  • velY = -Math.Abs(velY)
    Ensures vertical velocity becomes negative, sending the ball upward.

In short: If the ball hits the bottom wall, push it up.


End Sub

Ends the collision‑handling routine.


🧠 Why this works so well

This collision system is:

  • simple — no complex physics
  • predictable — always snaps the ball inside the bounds
  • stable — avoids tunneling thanks to dt clamping
  • clean — uses Math.Abs to guarantee correct bounce direction

It’s exactly the kind of logic that is easy to understand while still feeling “real” in motion.

System Summary



UpdateTrail — Recording the ball’s movement history

The trail system works by storing the ball’s previous positions in a list.
Each frame, you add the newest position and remove the oldest one once the list reaches its maximum length.

This creates a clean, tapered motion trail behind the ball.


Private Sub UpdateTrail()

Begins the trail‑update routine.
This is called once per physics tick, right after the ball moves.


🟦 Add the Current Ball Position

trail.Add(New PointF(ballPos.X, ballPos.Y))

This line records the ball’s current position:

  • Creates a new PointF using the ball’s current X and Y.
  • Appends it to the end of the trail list.

In short: Each physics update adds one new trail point.


✂️ Trim the Trail to a Fixed Length

If trail.Count > trailLength Then
    trail.RemoveAt(0)
End If

This ensures the trail never grows beyond the configured length:

  • If the list has more entries than trailLength (25 by default)
  • Remove the oldest entry at index 0

This keeps the trail:

  • lightweight
  • predictable
  • visually consistent

In short: Oldest positions are removed so the trail stays exactly the right size.


End Sub

Ends the trail update routine.


🧠 Why this design works so well

This trail system is intentionally simple:

  • No complex math
  • No interpolation
  • No heavy data structures
  • Just a clean FIFO (first‑in, first‑out) list

Combined with our precomputed sizes and alpha fading, it produces a smooth, professional‑looking motion trail with minimal CPU overhead.

System Summary



OnPaint — Drawing the entire scene every frame

OnPaint is the core rendering function.
Every time the form needs to redraw (because Invalidate() was called), WinForms calls this method and gives you a Graphics object to draw with.


Protected Overrides Sub OnPaint(e As PaintEventArgs)

Overrides the form’s built‑in painting method.
This is where all custom drawing for your animation happens.


MyBase.OnPaint(e)

Calls the base class version of OnPaint.
This ensures WinForms performs its normal painting behavior before your custom graphics run.

Even though you suppress background painting elsewhere, calling the base method is still good practice.


🎨 Prepare the Graphics Object

Dim g = e.Graphics

Retrieves the Graphics object from the PaintEventArgs.
This object is your drawing canvas — everything you draw goes through g.


g.CompositingMode = CompositingMode.SourceOver

Sets how overlapping graphics are blended.

  • SourceOver means new pixels are drawn over existing ones.
  • This is ideal for trails, transparency, and layered effects.

In short: Allows smooth alpha blending.


g.SmoothingMode = SmoothingMode.AntiAlias

Enables anti‑aliasing:

  • Smooth edges
  • No jagged circles
  • Professional‑looking graphics

Perfect for drawing ellipses and curved shapes.


g.PixelOffsetMode = PixelOffsetMode.HighQuality

Improves pixel alignment:

  • Reduces tiny rendering artifacts
  • Makes circles look cleaner
  • Helps sub‑pixel drawing look smoother

This is especially useful because your ball moves in floating‑point space.


g.InterpolationMode = InterpolationMode.HighQualityBicubic

Controls how scaled images are rendered.

Even though you’re not scaling images here, setting this mode ensures:

  • smooth gradients
  • smooth alpha transitions
  • high‑quality rendering overall

It’s part of a “best practice” set of rendering flags for GDI+ animation.


🎨 Draw the Scene

DrawTrail(g)
DrawBall(g)
DrawFPS(g)

These three calls render the entire frame:

DrawTrail(g)

Draws the fading motion trail behind the ball.

DrawBall(g)

Draws the main ball at its current position.

DrawFPS(g)

Draws the FPS counter in the corner.

009

The order matters:

  1. Trail first → behind the ball
  2. Ball second → on top
  3. FPS last → UI overlay

This layering creates a clean, readable scene.


End Sub

Ends the rendering routine.


🧠 Why this rendering pipeline works

Our rendering setup is:

  • clean - separated into small draw functions
  • efficient - uses preallocated brushes and precomputed sizes
  • high‑quality - uses all the right GDI+ smoothing modes
  • flicker‑free - thanks to double‑buffering and suppressed background painting

This is exactly how you build a smooth real‑time animation in WinForms.

System Summary



DrawTrail — Rendering the fading motion trail behind the ball

The trail is drawn before the ball so it appears underneath it.
Each trail segment is a smaller, more transparent circle, creating a smooth fading effect.

008
Private Sub DrawTrail(g As Graphics)

Begins the trail‑rendering routine.
This method draws all trail segments using the precomputed sizes, offsets, and brushes.


📏 Determine How Many Trail Points to Draw

Dim count As Integer = Math.Min(trail.Count, trailLength)
  • trail.Count = how many positions are currently stored
  • trailLength = maximum allowed trail segments

Math.Min ensures you never draw more than the configured trail length.

In short: Draw only the valid portion of the trail.


🔁 Loop Through Each Trail Segment

For i As Integer = 0 To count - 1

Iterates through each trail entry:

  • i = 0 → oldest trail point
  • i = count - 1 → newest trail point

This ordering is important because the fade effect depends on i.


🌫️ Smooth Exponential Fade

Dim t As Double = i / trailLength
Dim alpha As Integer = CInt(32 * t * t)
If alpha > 255 Then alpha = 255

This computes the transparency ( alpha ) for each trail segment.

How it works:

  • t = i / trailLength
    Gives a value between 0.0 ( oldest ) and 1.0 ( newest ).

  • t * t
    Squares the value, creating an exponential curve.
    This makes the fade smoother and more natural.

  • 32 * t * t
    Scales the alpha into a usable range.

  • Clamp to 255
    Ensures alpha never exceeds the valid maximum.

In short: Older trail segments are more transparent; newer ones are more visible.


🎨 Apply the Alpha to the Brush

trailBrushes(i).Color = Color.FromArgb(alpha, 0, 191, 255)

Updates the brush color for this trail segment:

  • alpha = computed transparency
  • RGB = (0, 191, 255) → same blue as the ball

Each trail segment gets its own brush with its own alpha.

In short: Every trail circle fades smoothly based on its age.


📍 Retrieve Precomputed Geometry

Dim p As PointF = trail(i)
Dim size As Integer = trailSizes(i)
Dim offset As Single = trailOffsets(i)

These values were precomputed in OnLoad:

  • p → the recorded position of the ball
  • size → the diameter of this trail circle
  • offset → how much to shift the circle so it stays centered

In short: Each trail circle is smaller and centered relative to the ball.


🟦 Draw the Trail Segment

g.FillEllipse(trailBrushes(i),
              p.X + offset,
              p.Y + offset,
              size,
              size)

Draws the actual circle:

  • Uses the brush with the correct alpha
  • Uses the precomputed size
  • Adds the offset so the circle is centered

This creates the layered, fading trail effect.


Next

Ends the loop - all trail segments have now been drawn.


End Sub

Ends the trail‑rendering routine.


🧠 Why this trail looks so good

Your trail system combines:

  • exponential alpha fading → smooth, natural fade
  • shrinking circle sizes → tapered comet effect
  • centered offsets → visually aligned trail
  • preallocated brushes → no runtime allocations
  • precomputed geometry → fast rendering

This is exactly how you build a professional‑looking motion trail in WinForms.

System Summary



DrawBall - Rendering the main ball at its current position

This method draws the ball itself.
It’s intentionally simple: one brush, one ellipse, one position.
All the complexity ( physics, trail, smoothing ) happens elsewhere - this is the final visual step.

007
Private Sub DrawBall(g As Graphics)

Begins the ball‑rendering routine.
The Graphics object g is the drawing surface for this frame.


g.FillEllipse(ballBrush,
              ballPos.X,
              ballPos.Y,
              ballDiameter,
              ballDiameter)

This single line draws the ball:

ballBrush

  • The solid brush created in OnLoad
  • Uses the color DeepSkyBlue
  • No transparency - the ball is fully opaque

ballPos.X / ballPos.Y

  • The current position of the ball
  • Updated every physics tick
  • Stored as floating‑point values for smooth motion

ballDiameter

  • Width and height of the ellipse
  • Because width = height, the ellipse is a perfect circle

What this line does visually:

  • Draws a filled circle
  • At the ball’s current position
  • Using the ball’s color
  • With the ball’s size

In short: This is the ball you see bouncing around the screen.


End Sub

Ends the ball‑rendering routine.


🧠 Why this method is so simple

All the heavy lifting happens elsewhere:

  • PhysicsTick moves the ball
  • HandleCollisions keeps it inside the window
  • UpdateTrail records its history
  • DrawTrail renders the fading trail
  • OnPaint sets up high‑quality rendering modes

By the time DrawBall runs, everything is ready to draw the final circle.

This separation of responsibilities is exactly what makes our animation engine clean and easy to extend.

System Summary



DrawFPS - Rendering the frames‑per‑second counter

This method draws the FPS counter in the top‑left corner of the screen.
It updates the FPS value once per second, then renders it using our preloaded font and brush.


Private Sub DrawFPS(g As Graphics)

Begins the FPS‑rendering routine.
The Graphics object g is the drawing surface for this frame.


⏱️ Update the FPS Value

UpdateFPS()

Calls our FPS‑tracking method, which:

  • increments the frame counter
  • checks whether one second has passed
  • updates the fps field
  • resets the counter for the next second

This ensures the FPS number is always accurate and refreshed once per second.

In short: Before drawing the FPS text, make sure the value is up‑to‑date.


🎨 Draw the FPS Text

g.DrawString($"FPS: {fps}", fpsFont, fpsBrush, 10, 10)

This line draws the FPS counter:

"FPS: {fps}"

  • A formatted string showing the current FPS value
  • Example: "FPS: 64"

fpsFont

  • The font created in OnLoad
  • "Segoe UI", 14pt, Bold
  • Clean and readable for UI overlays

fpsBrush

  • A solid white brush
  • Ensures the text stands out against the black background

(10, 10)

  • Draws the text at coordinates (10, 10)
  • Top‑left corner of the window
  • Slight padding so it doesn’t touch the edges

In short: This draws a clean, readable FPS counter in the corner of the screen.


End Sub

Ends the FPS‑rendering routine.


🧠 Why this FPS system works well

Our FPS counter is:

  • lightweight — only updates once per second
  • accurate — uses a stopwatch instead of relying on frame timing
  • non‑intrusive — drawn last so it overlays the scene
  • beginner‑friendly — easy to understand and extend

It’s a perfect example of how real‑time diagnostics work in animation engines.

System Summary



OnPaintBackground - Preventing flicker

Windows Forms normally repaints the background before drawing the foreground.
For static apps, that’s fine.
For real‑time animation, it causes flicker — especially when drawing fast‑moving objects.

This override disables that behavior.


Protected Overrides Sub OnPaintBackground(pevent As PaintEventArgs)

This overrides the form’s built‑in background‑painting method.
WinForms calls this before OnPaint unless you suppress it.


' Suppress background flicker

A comment explaining the purpose of this override:

  • Prevent background clearing
  • Prevent unnecessary redraws
  • Prevent flicker during animation
  • Allow our own double‑buffering to handle everything

This is a classic technique used in game loops and custom renderers.


End Sub

Ends the override.


🧠 Why suppressing background painting matters

By default, WinForms does this every frame:

  1. Clear background
  2. Paint background
  3. Paint controls
  4. Paint your custom graphics

When animating:

  • Step 1 creates a flash
  • Step 2 creates a second flash
  • Step 4 draws your ball
  • The result is visible flicker, especially at high speeds

Our animation engine already uses:

  • OptimizedDoubleBuffer
  • DoubleBuffered = True
  • custom OnPaint
  • no child controls

So background painting is not needed.

Suppressing it gives you:

  • perfectly smooth animation
  • no flashing
  • no tearing
  • no redundant work

This is exactly how professional WinForms animation engines handle rendering.

System Summary



UpdateFPS — Measuring and updating the frames‑per‑second value

This method keeps track of how many frames were drawn in the last second.
It’s called once per frame (inside DrawFPS), and updates the FPS value once every 1000 ms.


Private Sub UpdateFPS()

Begins the FPS‑update routine.
This method is called every time the screen is redrawn.


🔢 Count Each Frame

frameCount += 1

Every time UpdateFPS runs, one frame has just been rendered.
So we increment the frame counter.

  • If the app is running at 60 FPS → this line runs 60 times per second
  • If the app is running at 120 FPS → it runs 120 times per second

In short: This counts how many frames occurred in the current one‑second window.


⏱️ Check If One Second Has Passed

If fpsTimer.ElapsedMilliseconds >= 1000 Then

fpsTimer is a stopwatch that started in the constructor.

This line checks:

Has at least 1000 ms (1 second) passed since the last FPS update?

If yes, it’s time to compute a new FPS value.


📊 Update the FPS Value

fps = frameCount

The number of frames counted in the last second becomes the FPS value.

Examples:

  • If frameCount = 64 → FPS is 64
  • If frameCount = 120 → FPS is 120

This gives a real‑time measurement of how fast the animation is running.


🔄 Reset for the Next Second

frameCount = 0
fpsTimer.Restart()

Two things happen:

1. Reset the frame counter

Start counting frames for the next one‑second window.

2. Restart the stopwatch

Begin timing the next second.

In short: Every second, the FPS counter resets and starts fresh.


End If
End Sub

Ends the FPS‑update routine.


🧠 Why this FPS system is ideal

Our FPS counter is:

  • simple — easy to understand
  • accurate — uses a stopwatch instead of relying on frame timing
  • efficient — updates only once per second
  • clean — no heavy math or averaging needed

It’s exactly the kind of diagnostic tool you want in a real‑time animation.

System Summary



OnResize - Keeping the ball visible when the window changes size

This method runs every time the form is resized — whether the user drags the window edges, maximizes it, or restores it.
Its job is to keep the ball inside the new bounds and ensure the trail geometry stays correct.


Protected Overrides Sub OnResize(e As EventArgs)

Overrides the form’s built‑in resize handler.
This fires whenever the form’s client area changes size.


MyBase.OnResize(e)

Calls the base class version of OnResize.
This ensures WinForms performs its normal layout and resize behavior before your custom logic runs.


🛑 Ignore Resize Until Resources Exist

If trailSizes Is Nothing OrElse trailOffsets Is Nothing Then
    Return
End If

This prevents errors during startup.

Why this matters:

  • OnResize can fire before OnLoad
  • At that moment, trailSizes and trailOffsets are still Nothing
  • Accessing them would cause a crash

So this guard simply exits early until the arrays are initialized.

In short: Don’t run resize logic until the trail system is ready.


🎯 Clamp Ball Inside New Bounds

When the window shrinks, the ball might suddenly be outside the visible area.
These checks snap it back inside.


If ballPos.X > ClientSize.Width - ballDiameter Then
    ballPos.X = ClientSize.Width - ballDiameter
End If

Horizontal clamp

  • If the ball’s right edge is outside the new width
  • Move it back inside so it remains visible

If ballPos.Y > ClientSize.Height - ballDiameter Then
    ballPos.Y = ClientSize.Height - ballDiameter
End If

Vertical clamp

  • Same logic, but for the bottom edge

In short: Resizing the window never hides the ball.


📏 Recompute Trail Offsets

For i As Integer = 0 To trailLength - 1
    Dim size As Integer = trailSizes(i)
    trailOffsets(i) = CSng((ballDiameter - size) / 2)
Next

Each trail circle is smaller than the main ball.
To keep them centered, you compute an offset:

(ballDiameter - size) / 2

This ensures:

  • every trail circle stays perfectly centered
  • resizing the window doesn’t distort the trail alignment
  • the visual taper remains correct

In short: Recalculate the centering math for each trail segment.


🎨 Request a Redraw

Invalidate()

Tells WinForms:

“The window changed — redraw everything.”

This ensures the ball and trail update immediately after resizing.


End Sub

Ends the resize‑handling routine.


🧠 Why this resize logic is important

Our animation engine stays stable because:

  • it avoids crashes during early resize events
  • it clamps the ball inside the new bounds
  • it recalculates trail geometry
  • it triggers a redraw so the scene updates instantly

This is exactly how you build a robust real‑time animation system in WinForms.

System Summary



Cleanup — Disposing graphics resources when the form closes

Windows Forms uses GDI+ objects (Brushes, Fonts, Pens, etc.) that must be manually disposed.
If you don’t dispose them, they remain in memory until the process exits — which is fine for tiny apps, but bad practice for real‑time graphics.

This cleanup method ensures our animation engine shuts down cleanly.


Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing

This method runs right before the form closes.

The Handles Me.FormClosing part wires it to the form’s closing event.

This is our chance to release resources and stop timers.


🧹 Dispose Core GDI Resources

ballBrush?.Dispose()
fpsBrush?.Dispose()
fpsFont?.Dispose()
physicsTimer?.Dispose()

Each of these objects holds unmanaged resources inside Windows:

ballBrush

Used to draw the main ball.

fpsBrush

Used to draw the FPS text.

fpsFont

Used to render the FPS counter.

physicsTimer

Stops the physics loop and releases the timer’s internal handles.

The ?. operator ensures disposal only happens if the object is not Nothing.

In short: Dispose everything that was created in OnLoad or the constructor.


🟦 Dispose Trail Brushes

If trailBrushes IsNot Nothing Then
    For Each b In trailBrushes
        b?.Dispose()
    Next
End If

Our trail uses an array of brushes — one per trail segment.

This loop:

  1. Checks if the array exists
  2. Iterates through each brush
  3. Disposes it safely

This prevents dozens of small unmanaged objects from lingering after the form closes.

In short: Every trail brush is cleaned up properly.


End Sub

Ends the cleanup routine.


🧠 Why this cleanup matters

Even though the app closes immediately afterward, disposing GDI objects is:

  • good practice
  • professional
  • important in long‑running apps
  • important in apps that recreate brushes/fonts often
  • a great example

It shows that real‑time graphics require careful resource management.

System Summary





About

This app displays a single ball that moves smoothly across the screen using delta‑time‑based animation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages