An application that simulates a gun turret, allowing for rotation and firing projectiles.
Welcome to the detailed walkthrough of the Gun Turret code! In this lesson, we will go through the code line by line, explaining each part in a way that is easy to understand. This project simulates a turret that can rotate and fire projectiles. Let’s dive in!
Public Structure Turret
Here, we define a structure named Turret
. A structure is a value type that can hold data and related functionality. This structure will encapsulate all the properties and methods related to our turret.
Public Pen As Pen
Public UnderlightPen As Pen
Public UnderlightBrush As Brush
Public Center As Point
Public Length As Integer
Public AngleInDegrees As Single
Public TimeToNextFire As TimeSpan
Public LastFireTime As DateTime
- Pen: This variable defines the style (color, width) used when drawing the turret.
- UnderlightPen: Similar to
Pen
, but for drawing an under-light effect. - UnderlightBrush: A brush used for filling the under-light effect.
- Center: A
Point
structure that holds the coordinates of the turret's center. - Length: An integer representing the length of the turret's barrel.
- AngleInDegrees: A single-precision floating-point number that specifies the angle of the turret in degrees.
- TimeToNextFire: A
TimeSpan
representing the time until the turret can fire again. - LastFireTime: The last time the turret fired a projectile.
Public Sub New(pen As Pen,
center As Point,
length As Integer,
angleInDegrees As Single,
reloadTime As TimeSpan)
Me.Pen = pen
Me.Center = center
Me.Length = length
Me.AngleInDegrees = angleInDegrees
Me.TimeToNextFire = reloadTime
UnderlightPen = New Pen(Color.FromArgb(128, Color.Blue), 23)
UnderlightBrush = New SolidBrush(Color.FromArgb(128, Color.Blue))
End Sub
This constructor initializes the turret’s properties:
- Me: Refers to the current instance of the structure.
- The parameters allow us to set the initial values for the
Pen
,Center
,Length
,AngleInDegrees
, andTimeToNextFire
. - The
UnderlightPen
andUnderlightBrush
are initialized with a semi-transparent blue color.
Public Sub Draw(g As Graphics)
' Draw a line of given length from the given center point at a given angle.
Dim Diameter As Integer = 75
Dim UnderLightRectangle As New Rectangle(Center.X - Diameter / 2,
Center.Y - Diameter / 2,
Diameter, Diameter)
UnderLightRectangle.Inflate(2, 2)
g.FillEllipse(UnderlightBrush,
UnderLightRectangle)
DrawEllipseWithRadialGradient(g,
Center,
Diameter)
DrawLineFromCenterGivenLenghtAndAngle(g,
UnderlightPen,
Center,
Length,
AngleInDegrees)
DrawLineFromCenterGivenLenghtAndAngle(g,
Pen,
Center,
Length,
AngleInDegrees)
End Sub
- Draw: This method is responsible for rendering the turret on the screen.
- Diameter: Defines the size of the turret's base.
- UnderLightRectangle: Creates a rectangle that represents the area for the under-light effect, centered at the turret's center.
- g.FillEllipse: Fills the under-light rectangle with the specified brush.
- DrawEllipseWithRadialGradient: Calls another method to draw a gradient effect on the turret.
- DrawLineFromCenterGivenLenghtAndAngle: Draws the turret's barrel based on its center, length, and angle.
Public Sub DrawEllipseWithRadialGradient(g As Graphics,
Center As Point,
Diameter As Integer)
' Create the path for the ellipse
Dim path As New GraphicsPath()
Dim GradRect As New Rectangle(Center.X - Diameter / 2,
Center.Y - Diameter / 2,
Diameter, Diameter)
GradRect.Inflate(4, 4)
path.AddEllipse(GradRect)
' Create the radial gradient brush
Dim brush As New PathGradientBrush(path)
' Set the center color (highlight)
brush.CenterColor = Color.White
brush.SurroundColors = New Color() {Color.Black}
brush.CenterPoint = New PointF(Center.X - Diameter / 4,
Center.Y - Diameter / 4)
' Fill the ellipse with the radial gradient brush
g.FillEllipse(brush, New Rectangle(Center.X - Diameter / 2,
Center.Y - Diameter / 2,
Diameter, Diameter))
End Sub
- DrawEllipseWithRadialGradient: This method creates a radial gradient effect for the turret's base.
- GraphicsPath: Used to define the shape of the ellipse that will be filled with a gradient.
- PathGradientBrush: A brush that allows for a gradient fill, with a center color and surrounding colors.
- g.FillEllipse: Fills the ellipse with the gradient brush.
Private Sub DrawLineFromCenterGivenLenghtAndAngle(g As Graphics,
pen As Pen,
center As PointF,
length As Integer,
angleInDegrees As Single)
' Draw a line of given length from the given center point at a given angle.
' Convert to the angle in degrees to radians.
Dim angleInRadians As Single = angleInDegrees * (Math.PI / 180)
Dim EndPoint As PointF
' Calculate the endpoint of the line using trigonometry
EndPoint = New PointF(center.X + length * Cos(angleInRadians),
center.Y + length * Sin(angleInRadians))
' Draw the line.
g.DrawLine(pen, center, EndPoint)
End Sub
- DrawLineFromCenterGivenLengthAndAngle: This method draws a line representing the turret's barrel.
- angleInRadians: Converts the angle from degrees to radians, which is necessary for trigonometric calculations.
- EndPoint: Calculates where the line will end based on the length and angle using cosine and sine functions.
- g.DrawLine: Draws the line from the center of the turret to the calculated endpoint.
Public Structure ProjectileManager
The ProjectileManager
structure manages the projectiles fired from the turret.
Public Brush As Brush
Public BarrelLength As Integer
Public MuzzleVelocity As Single
Public Size As Size
Public LifeTimeInSeconds As Integer
- Brush: The brush used for drawing projectiles.
- BarrelLength: Length of the turret's barrel.
- MuzzleVelocity: Speed at which projectiles are fired.
- Size: Size of the projectiles.
- LifeTimeInSeconds: How long the projectiles will exist before being removed.
Public Sub New(brush As Brush, size As Size, muzzleVelocity As Single, barrelLength As Integer, lifeTimeInSeconds As Integer)
Me.Brush = brush
Me.BarrelLength = barrelLength
Me.MuzzleVelocity = muzzleVelocity
Me.Size = size
Me.LifeTimeInSeconds = lifeTimeInSeconds
End Sub
This constructor initializes the projectile manager's properties using the provided parameters.
Private Structure Projectile
Defines a structure for individual projectiles.
Public X, Y, Width, Height As Double
Public Velocity As PointF
Public Brush As Brush
Public Center As PointF
Public BarrelLength As Integer
Public AngleInDegrees As Single
Public Creation As DateTime
- X, Y: Position of the projectile.
- Width, Height: Dimensions of the projectile.
- Velocity: Speed and direction of the projectile.
- Brush: Used to fill the projectile.
- Center: The center point of the projectile.
- BarrelLength: Length of the barrel from which the projectile is fired.
- AngleInDegrees: The angle at which the projectile is fired.
- Creation: The time when the projectile was created.
Public Sub New(brush As Brush,
width As Double,
height As Double,
velocity As Single,
center As PointF,
length As Integer,
angleInDegrees As Single)
Me.Brush = brush
Me.Width = width
Me.Height = height
Me.Center = center
Me.BarrelLength = length
Me.AngleInDegrees = angleInDegrees
' Convert angle from degrees to radians.
Dim AngleInRadians As Single = angleInDegrees * (Math.PI / 180)
' Set initial position based on the angle and length
X = center.X + length * Cos(AngleInRadians) - Me.Width / 2
Y = center.Y + length * Sin(AngleInRadians) - Me.Height / 2
' Set velocity based on angle
Me.Velocity = New PointF(Cos(AngleInRadians) * velocity,
Sin(AngleInRadians) * velocity)
Creation = Now()
End Sub
- This constructor initializes the projectile's properties and calculates its initial position and velocity based on the angle and length.
Public Sub UpdateMovement(ByVal deltaTime As TimeSpan)
' Move our projectile horizontally.
X += Velocity.X * deltaTime.TotalSeconds 'Δs = V * Δt
' Displacement = Velocity x Delta Time
' Move our projectile vertically.
Y += Velocity.Y * deltaTime.TotalSeconds 'Δs = V * Δt
' Displacement = Velocity x Delta Time
End Sub
- Updates the position of the projectile based on its velocity and the elapsed time since the last update.
Public Function NearestX() As Integer
Return RoundToNearest(X)
End Function
Public Function NearestY() As Integer
Return RoundToNearest(Y)
End Function
Public Function NearestWidth() As Integer
Return RoundToNearest(Width)
End Function
Public Function NearestHeight() As Integer
Return RoundToNearest(Height)
End Function
Private Function RoundToNearest(ByVal value As Double) As Integer
Return CInt(Math.Round(value))
End Function
These functions round the projectile's attributes to the nearest integer. This is useful for rendering the projectiles accurately on the screen.
Public Function Rectangle() As Rectangle
Return New Rectangle(NearestX(), NearestY(), NearestWidth(), NearestHeight())
End Function
This method returns a Rectangle
object representing the projectile's bounding box, which is useful for collision detection.
Public Sub FireProjectile(CenterOfFire As PointF, AngleInDegrees As Single)
AddProjectile(CenterOfFire, AngleInDegrees)
End Sub
This method is called to fire a new projectile from a specified center point and angle.
Public Sub UpdateProjectiles(deltaTime As TimeSpan)
If Projectiles IsNot Nothing Then
RemoveProjectilesPastTheirLifeTime()
For Index As Integer = 0 To Projectiles.Length - 1
Projectiles(Index).UpdateMovement(deltaTime)
Next
End If
End Sub
This method updates the position of each projectile and removes any that have exceeded their lifetime.
Public Sub DrawProjectiles(graphics As Graphics)
If Projectiles IsNot Nothing Then
For Each projectile In Projectiles
graphics.FillRectangle(projectile.Brush, projectile.Rectangle())
Next
End If
End Sub
This method iterates through all active projectiles and draws them on the screen.
Private Sub AddProjectile(centerOfFire As PointF, angleInDegrees As Single)
If Projectiles Is Nothing Then
ReDim Projectiles(0)
Else
Array.Resize(Projectiles, Projectiles.Length + 1)
End If
Projectiles(Projectiles.Length - 1) = New Projectile(Brush,
Size.Width,
Size.Height,
MuzzleVelocity,
centerOfFire,
BarrelLength,
angleInDegrees)
End Sub
This method adds a new projectile to the projectiles array, initializing it with the given parameters.
Public Function IsColliding(rectangle As Rectangle) As Boolean
If Projectiles IsNot Nothing Then
Return Projectiles.Any(Function(p) p.Rectangle().IntersectsWith(rectangle))
End If
Return False
End Function
This function checks if any projectiles are colliding with a specified rectangle.
Public Sub RemoveCollidingProjectiles(rectangle As Rectangle)
Projectiles = Projectiles.Where(Function(p) Not p.Rectangle().IntersectsWith(rectangle)).ToArray()
End Sub
This method removes any projectiles that collide with the specified rectangle.
Private Sub RemoveProjectilesPastTheirLifeTime()
Dim lifeTime As Integer = LifeTimeInSeconds
Projectiles = Projectiles.Where(Function(p) (Date.Now - p.Creation).TotalSeconds < lifeTime).ToArray()
End Sub
This method removes projectiles that have exceeded their lifetime to prevent memory issues and slowdowns.
Public Class Form1
The Form1
class is the main class for the graphical user interface of the simulation.
Private OffScreen As New BufferManager(Me, BackColor)
Private Player As AudioPlayer
Private Turret As Turret
Private Projectiles As New ProjectileManager(Brushes.Red, New Drawing.Size(10, 10), 200, 100, 9)
Private DeltaTime As DeltaTimeStructure
Private ClientCenter As Point
Private XDown As Boolean
Private LeftArrowDown As Boolean
Private RightArrowDown As Boolean
Private TimeToNextRotation As TimeSpan = TimeSpan.FromMilliseconds(1)
Private LastRotationTime As DateTime = Now
Private Target As New Rectangle(0, 0, 100, 100)
Private TargetBrush As Brush = Brushes.Black
Private InstructionsFont As New Font("Segoe UI", 12)
Private InstructionsLocation As New PointF(0, 0)
Private InstructionsText As New String("Use left or right arrow keys to rotate turret." &
Environment.NewLine &
"Press X to fire, hold for automatic.")
- OffScreen: Manages off-screen rendering to reduce flickering.
- Player: Responsible for audio playback.
- Turret: An instance of the
Turret
structure. - Projectiles: An instance of the
ProjectileManager
structure. - DeltaTime: Used to track the time between frames.
- ClientCenter: The center point of the client area of the form.
- XDown, LeftArrowDown, RightArrowDown: Boolean flags to track key presses.
- TimeToNextRotation: Determines how fast the turret rotates.
- LastRotationTime: Records the last time the turret was rotated.
- Target: Represents the target area for projectiles.
- TargetBrush: The brush used to draw the target.
- InstructionsFont, InstructionsLocation, InstructionsText: Used to display instructions on the screen.
Public Sub New()
InitializeComponent()
InitializeForm()
CreateSoundFiles()
InitializeSounds()
ClientCenter = New Point(ClientSize.Width / 2, ClientSize.Height / 2)
Turret = New Turret(New Pen(Color.Black, 20), ClientCenter, 100, 0, TimeSpan.FromMilliseconds(100))
Player.LoopSound("ambientnoise")
InitializeTimer()
End Sub
- This constructor initializes the form and its components.
- Calls methods to set up the form, sounds, and the turret.
- The turret is created with a black pen, centered in the form, with a specified length and reload time.
- The ambient noise is played in a loop.
Private Sub InitializeForm()
CenterToScreen()
SetStyle(ControlStyles.UserPaint, True)
SetStyle(ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint, True)
Text = "Gun Turret - Code with Joe"
WindowState = FormWindowState.Maximized
End Sub
- Centers the form on the screen.
- Enables double buffering to reduce flickering.
- Sets the title of the window.
- Maximizes the window.
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
DeltaTime.Update()
HandleKeyPresses()
Projectiles.UpdateProjectiles(DeltaTime.ElapsedTime)
HandleCollisions()
Invalidate()
End Sub
- This method is called at each tick of the timer.
- Updates the delta time, handles key presses, updates projectiles, checks for collisions, and requests a redraw of the form.
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
DrawFrame()
OffScreen.Buffered?.Render(e.Graphics)
OffScreen.EraseFrame()
End Sub
- This method handles the painting of the form.
- Calls the
DrawFrame
method to draw all elements. - Renders the off-screen buffer to the form.
Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
MyBase.OnKeyDown(e)
If e.KeyCode = Keys.Right Then
RightArrowDown = True
End If
If e.KeyCode = Keys.Left Then
LeftArrowDown = True
End If
If e.KeyCode = Keys.X Then
XDown = True
End If
End Sub
- This method handles key presses to track when the left or right arrow keys and the 'X' key are pressed.
Protected Overrides Sub OnKeyUp(e As KeyEventArgs)
MyBase.OnKeyUp(e)
If e.KeyCode = Keys.Right Then
RightArrowDown = False
End If
If e.KeyCode = Keys.Left Then
LeftArrowDown = False
End If
If e.KeyCode = Keys.X Then
XDown = False
End If
End Sub
- This method handles key releases, setting the corresponding boolean flags to
False
when the left or right arrow keys and the 'X' key are released.
Private Sub Form1_Resize(sender As Object, e As EventArgs) Handles Me.Resize
If Not WindowState = FormWindowState.Minimized Then
Turret.Center = New Point(ClientSize.Width / 2, ClientSize.Height / 2)
Target.Y = ClientSize.Height / 2 - Target.Height / 2
OffScreen.DisposeBuffer()
Timer1.Enabled = True
Else
Timer1.Enabled = False
End If
End Sub
- This method is called when the form is resized.
- It re-centers the turret and the target rectangle based on the new size of the form.
- Disposes of the off-screen buffer to prepare for a redraw.
- Enables or disables the timer based on whether the window is minimized.
Private Sub DrawFrame()
OffScreen.AllocateBuffer(Me)
OffScreen.Buffered.Graphics.DrawString(InstructionsText, InstructionsFont, Brushes.Black, InstructionsLocation)
OffScreen.Buffered.Graphics.FillRectangle(TargetBrush, Target)
Projectiles.DrawProjectiles(OffScreen.Buffered.Graphics)
Turret.Draw(OffScreen.Buffered.Graphics)
End Sub
- This method prepares the off-screen buffer for drawing.
- Displays the instructions on the screen using the specified font and location.
- Fills the target rectangle with the current brush color.
- Calls the
DrawProjectiles
method to draw all active projectiles. - Calls the
Draw
method of the turret to render it on the screen.
Private Sub HandleKeyPresses()
' Handle key presses to rotate the turret or fire projectiles.
If XDown Then
FireProjectile()
End If
If LeftArrowDown Then
RotateTurretCounterClockwise()
End If
If RightArrowDown Then
RotateTurretClockwise()
End If
End Sub
- This method checks the state of the key press flags.
- If the 'X' key is pressed, it calls the
FireProjectile
method. - If the left arrow key is pressed, it calls the
RotateTurretCounterClockwise
method. - If the right arrow key is pressed, it calls the
RotateTurretClockwise
method.
Private Sub RotateTurretClockwise()
Dim ElapsedTime As TimeSpan = Now - LastRotationTime
If ElapsedTime > TimeToNextRotation Then
If Turret.AngleInDegrees < 360 Then
Turret.AngleInDegrees += 1.5 ' Rotate clockwise
Else
Turret.AngleInDegrees = 0
End If
LastRotationTime = Now
End If
End Sub
- This method rotates the turret clockwise.
- It calculates the elapsed time since the last rotation.
- If enough time has passed, it increases the turret's angle by 1.5 degrees.
- If the angle exceeds 360 degrees, it resets to 0.
Private Sub RotateTurretCounterClockwise()
Dim ElapsedTime As TimeSpan = Now - LastRotationTime
If ElapsedTime > TimeToNextRotation Then
If Turret.AngleInDegrees > 0 Then
Turret.AngleInDegrees -= 1.5 ' Rotate counterclockwise
Else
Turret.AngleInDegrees = 360
End If
LastRotationTime = Now
End If
End Sub
- This method rotates the turret counterclockwise.
- Similar to the clockwise rotation, it checks the elapsed time and decreases the angle by 1.5 degrees.
- If the angle goes below 0, it sets it to 360.
Private Sub FireProjectile()
' Is it time to shoot my shot?
If Now - Turret.LastFireTime > Turret.TimeToNextFire Then
' Yes, it's time to shoot your shot.
Player.PlayOverlapping("gunshot")
Projectiles.FireProjectile(Turret.Center, Turret.AngleInDegrees)
Turret.LastFireTime = Now
End If
End Sub
- This method checks if it is time to fire a projectile.
- If enough time has passed since the last shot, it plays the gunshot sound.
- It calls the
FireProjectile
method of theProjectileManager
to create a new projectile at the turret's center and angle. - Updates the
LastFireTime
to the current time.
Private Sub HandleCollisions()
If Projectiles.IsColliding(Target) Then
Player.PlayOverlapping("explosion")
TargetBrush = Brushes.Red
Projectiles.RemoveCollidingProjectiles(Target)
Else
TargetBrush = Brushes.Black
End If
End Sub
- This method checks for collisions between projectiles and the target rectangle.
- If a collision is detected, it plays the explosion sound and changes the target's color to red.
- It removes any colliding projectiles from the game.
- If no collision is detected, it sets the target color back to black.
Private Sub InitializeTimer()
Timer1.Interval = 15
Timer1.Start()
End Sub
- This method sets up the timer to tick every 15 milliseconds, which controls the update rate of the game.
- Starts the timer to begin the game loop.
Private Sub InitializeSounds()
Dim FilePath As String = Path.Combine(Application.StartupPath, "gunshot.mp3")
Player.AddOverlapping("gunshot", FilePath)
Player.SetVolumeOverlapping("gunshot", 1000)
FilePath = Path.Combine(Application.StartupPath, "ambientnoise.mp3")
Player.AddSound("ambientnoise", FilePath)
Player.SetVolume("ambientnoise", 5)
FilePath = Path.Combine(Application.StartupPath, "explosion.mp3")
Player.AddOverlapping("explosion", FilePath)
Player.SetVolumeOverlapping("explosion", 700)
End Sub
- This method initializes the sounds used in the game.
- It sets up the file paths for the sound files and adds them to the audio player.
- Adjusts the volume levels for each sound.
Private Sub CreateSoundFiles()
Dim FilePath As String = Path.Combine(Application.StartupPath, "gunshot.mp3")
CreateFileFromResource(FilePath, My.Resources.Resource1.gunshot003)
FilePath = Path.Combine(Application.StartupPath, "ambientnoise.mp3")
CreateFileFromResource(FilePath, My.Resources.Resource1.ambientnoise)
FilePath = Path.Combine(Application.StartupPath, "explosion.mp3")
CreateFileFromResource(FilePath, My.Resources.Resource1.explosion)
End Sub
- This method creates sound files from embedded resources if they do not already exist.
- It sets the file paths and calls
CreateFileFromResource
for each sound.
Private Sub CreateFileFromResource(filepath As String, resource As Byte())
Try
If Not IO.File.Exists(filepath) Then
IO.File.WriteAllBytes(filepath, resource)
End If
Catch ex As Exception
Debug.Print($"Error creating file: {ex.Message}")
End Try
End Sub
- This method writes the byte array resource to a file at the specified path.
- It checks if the file already exists to avoid overwriting.
- If an error occurs, it prints the error message to the debug console.
This code represents a simple yet effective simulation of a turret that can rotate and fire projectiles. Each structure and method is designed to encapsulate specific functionality, making the code modular and easier to understand. By breaking down the code line by line, we can see how each part contributes to the overall behavior of the turret and projectile manager. Feel free to experiment with the code, change parameters, and see how it affects the behavior of the turret and its projectiles. Happy coding!