Skip to content

JoeLumbley/Gun-Turret

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gun Turret 🔫

An application that simulates a gun turret, allowing for rotation and firing projectiles.

009


Code Walkthrough

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!

Turret Structure

Definition

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.

Member Variables

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.

Constructor

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, and TimeToNextFire.
  • The UnderlightPen and UnderlightBrush are initialized with a semi-transparent blue color.

Draw Method

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.

DrawEllipseWithRadialGradient Method

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.

DrawLineFromCenterGivenLengthAndAngle Method

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.

ProjectileManager Structure

Definition

Public Structure ProjectileManager

The ProjectileManager structure manages the projectiles fired from the turret.

Member Variables

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.

Constructor

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.

Projectile Structure

Private Structure Projectile

Defines a structure for individual projectiles.

Member Variables

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.

Constructor

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.

UpdateMovement Method

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.

Rounding Functions

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.

Rectangle Method

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.

FireProjectile Method

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.

UpdateProjectiles Method

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.

DrawProjectiles Method

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.

AddProjectile Method

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.

IsColliding Method

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.

RemoveCollidingProjectiles Method

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.

RemoveProjectilesPastTheirLifeTime Method

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.

Form1 Class

Definition

Public Class Form1

The Form1 class is the main class for the graphical user interface of the simulation.

Member Variables

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.

Constructor

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.

InitializeForm Method

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.

Timer1_Tick Method

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.

OnPaint Method

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.

OnKeyDown Method

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.

OnKeyUp Method

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.

Form1_Resize Method

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.

DrawFrame Method

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.

HandleKeyPresses Method

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.

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.

RotateTurretCounterClockwise Method

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.

FireProjectile Method

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 the ProjectileManager to create a new projectile at the turret's center and angle.
  • Updates the LastFireTime to the current time.

HandleCollisions Method

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.

InitializeTimer Method

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.

InitializeSounds Method

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.

CreateSoundFiles Method

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.

CreateFileFromResource Method

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!

Releases

No releases published

Packages

No packages published