Hang on to your hats boys and girls! Knife Thrower is sort of a 2D knife throwing game, only there are no levels, points, or moving up in the game... Just endless (and pointless!) GDI+ knife throwing bliss...
Please note* In order to fully experience this example, you would need to download the complete example project, located at the link at the bottom of this page. While you're there, be sure to take the time to vote 5 stars! This example/article will cover some topics in the following areas (but may not cover every constituent of that topic!) 1.) GDI+ 2.) Transformation of polygons.
This section contains shortcuts to key MSDN library entries of some topics mentioned in this article. Graphics Class
GDI+ stands for "Graphics Device Interface". Simply put, GDI+ is an easy way to create graphics in Visual Basic and other languages. GDI+ could be used to modify anything from which you can retrieve a "Graphics" object. This example will be using the Graphics Object located in the "Protected Overrides Sub OnPaint". The reason we will use that one instead of the Paint event handler is because this will allow our painting to be processed before the "Control.OnPaint" method is invoked. This will ensure our painting is finished before all the other resultant events in the event chain are raised thus handling our graphics routine first. Let me back up... Maybe I went too far ahead.... A little more information on GDI+, rather than what we will be doing. GDI+ classes, methods, and objects can be accessed from the following namespaces:
System.Drawing System.Drawing.Configuration System.Drawing.Design System.Drawing.Drawing2D System.Drawing.Imaging System.Drawing.Printing System.Drawing.Text
The graphics object is located in the System.Drawing namespace. Once a graphics object is obtained from an (Image, Bitmap, or Control), there are many drawing methods at your disposal, awaiting your command on how to modify the appearance of that (Image, Bitmap, or Control). There are so many methods in the Graphics Class alone! The graphics class is absolutely essential for doing anything with GDI+. I suggest familiarizing yourself with those methods, described at the following link:
A polygon is a shape made up of connected lines. In Visual Basic, a polygon is an array of points, those points are later connected using GDI. I would like to clarify that my example will utilize the following object that I have created. The name of this object is Polygon, but this is only because I use this object to transform the rotation of the actual polygon points, so in a sense, it is a polygon, but still the polygon is the actual points connected with GDI. Here is the polygon class I have created for this example (Don't worry, I will explain it later....).
001.
Option
Strict
On
002.
Public
Class
Polygon
003.
Points
As
Point()
004.
Origin
Point
005.
006.
Private
sPoints
007.
tsPoints
008.
Sub
New
(Points
Point(), Origin
Point, SpecialPoints
Point())
009.
Me
.Points = Points
010.
.Origin = Origin
011.
.sPoints = SpecialPoints
012.
Angle = 0
013.
End
014.
_Angle
Single
015.
UpdateOrigin(NewOrigin
Point)
016.
Dim
Diff
017.
Diff.X = NewOrigin.X - Origin.X
018.
Diff.Y = NewOrigin.Y - Origin.Y
019.
.Origin = NewOrigin
020.
AdjustPoints(Diff.X, Diff.Y)
021.
022.
AdjustPoints(Xadj
Integer
, Yadj
)
023.
For
I
= 0
To
.Points.Count - 1
024.
.Points(I).X += Xadj
025.
.Points(I).Y += Yadj
026.
Next
027.
.Polygon.Count - 1
028.
.Polygon(I).X += Xadj
029.
.Polygon(I).Y += Yadj
030.
031.
.sPoints.Count - 1
032.
.sPoints(I).X += Xadj
033.
.sPoints(I).Y += Yadj
034.
035.
036.
ReadOnly
Property
ContainerRectangle(Pad
Rectangle
037.
Get
038.
Left
=
.Polygon(0).X
039.
Right
040.
Top
.Polygon(0).Y
041.
Bottom
042.
Each
P
In
.Polygon
043.
If
P.X < Left
Then
Left = P.X
044.
P.X > Right
Right = P.X
045.
P.Y < Top
Top = P.Y
046.
P.Y > Bottom
Bottom = P.Y
047.
048.
Left -= Pad
049.
Right += Pad
050.
Top -= Pad
051.
Bottom += Pad
052.
R
053.
R.Location =
Point(Left, Top)
054.
R.Width = Right - Left
055.
R.Height = Bottom - Top
056.
Return
057.
058.
059.
Angle
060.
061.
062.
063.
Set
(value
064.
_Angle = value
065.
_Angle = 360
_Angle = 0
066.
_Angle = -360
067.
_Angle > 360
068.
Do
069.
_Angle -= 360
070.
Loop
Until
_Angle <= 360
071.
072.
_Angle < -360
073.
074.
_Angle += 360
075.
_Angle >= 0
076.
077.
078.
079.
_Angle < 0
080.
RotateToAngle(_Angle)
081.
082.
083.
RotateToAngle(Angle
084.
List(Of Point)
085.
.Points
086.
Points.Add(RotatePoint(P,
.Origin, Angle))
087.
088.
sppts
089.
p
.sPoints
090.
sppts.Add(RotatePoint(p,
091.
092.
.Polygon = Points.ToArray
093.
.tsPoints = sppts.ToArray
094.
095.
Function
RotatePoint(
ByRef
pPoint
Point,
pOrigin
ByVal
Degrees
096.
Result
097.
Result.X =
CInt
(pOrigin.X + (Math.Cos(Rad(Degrees)) * (pPoint.X - pOrigin.X) - Math.Sin(Rad(Degrees)) * (pPoint.Y - pOrigin.Y)))
098.
Result.Y =
(pOrigin.Y + (Math.Sin(Rad(Degrees)) * (pPoint.X - pOrigin.X) + Math.Cos(Rad(Degrees)) * (pPoint.Y - pOrigin.Y)))
099.
100.
101.
Rad(
Degree
102.
Degree / 180 *
CSng
(Math.PI)
103.
104.
Clone()
105.
TestPoly
106.
TestPoly.SetAll(
.Points,
.Origin,
.Polygon,
.sPoints,
.tsPoints,
.Angle)
107.
108.
109.
SetAll(Points
Point, Polygon
Point(), sPoints
Point(), tsPoints
Point(), Angle
110.
ReDim
.Points(Points.Count - 1)
111.
Points.CopyTo(
.Points, 0)
112.
.Origin =
Point(Origin.X, Origin.Y)
113.
.Polygon(Polygon.Count - 1)
114.
Polygon.CopyTo(
.Polygon, 0)
115.
.sPoints(sPoints.Count - 1)
116.
sPoints.CopyTo(
.sPoints, 0)
117.
.tsPoints(tsPoints.Count - 1)
118.
tsPoints.CopyTo(
119.
._Angle = Angle
120.
121.
KnifeDirection
Direction
122.
123.
Select
Case
True
124.
.ContainerRectangle(0).Width >
.ContainerRectangle(0).Height
' Knife is horizontal
125.
126.
.Polygon(0).X >
.Polygon(11).X
127.
Direction.Left
128.
Else
129.
Direction.Right
130.
131.
.ContainerRectangle(0).Width <
'Knife is vertical
132.
Direction.Vertical
133.
134.
Direction.PerfectDiagnol
135.
136.
137.
138.
Enum
139.
Up
140.
Down
141.
142.
143.
Unknown
144.
Horizontal
145.
Vertical
146.
PerfectDiagnol
147.
148.
()
149.
150.
When you want to rotate a polygon, you're really just rotating a set of points around an origin. Therefore you could use a simple one dimensional array of points. You then use the rotation algorithm to rotate each point in that array. The way this works is to take the following equation (Visual Basic Function):
1.
2.
3.
4.
5.
6.
This is where we will begin. When I create an application, I always try to keep 'Option Strict On'. This is to help prevent type cast errors. In my Form1 (the main form) I have created some class level variables for settings and whatnot. I will explain what each of those variables is for:
Chronologically speaking, this will basically be our first Event, where all the magic starts.... So let's check out what we need to do in Form1's Load event:
RandomTarget()
Width
= 100
Height
= R.
(100, 200)
(TargetLimitX, My.Computer.Screen.Bounds.Width - Width)
(0, My.Computer.Screen.Bounds.Height - Height)
Rectangle(Left, Top, Width, Height)
ConstructKnife()
KnifePts
X
Y
= 200
KnifePts.Add(
Point(
((X) * iScale),
(Y * iScale)))
((X + 20) * iScale),
((Y - 20) * iScale)))
((X + 30) * iScale),
((Y - 8) * iScale)))
((X + 70) * iScale),
((Y - 10) * iScale)))
((X + 80) * iScale),
((Y - 60) * iScale)))
((X + 75) * iScale),
((Y - 65) * iScale)))
((X + 85) * iScale),
((Y - 75) * iScale)))
((X + 95) * iScale),
((X + 90) * iScale),
'---
((X + 250) * iScale),
((Y - 18) * iScale)))
((X + 300) * iScale),
'Knife Tip
((Y + 18) * iScale)))
((Y + 10) * iScale)))
((Y + 60) * iScale)))
((Y + 65) * iScale)))
((Y + 75) * iScale)))
((Y + 8) * iScale)))
((Y + 20) * iScale)))
SpecialPoints
SpecialPoints.Add(
Point(KnifePts(9).X, KnifePts(11).Y))
Origin =
((KnifePts(0).X + 200) * iScale), KnifePts(0).Y)
OriginalOrigin =
Knife =
Polygon(KnifePts.ToArray, Origin, SpecialPoints.ToArray)
Somewhere along the line, you may have heard or read that Visual Basic is an Event-Drivin language. What does this mean? This means that unless your application is in the middle of processing an event, then your application is waiting for an event to happen. This is exactly what it is doing when it is done processing your FormLoad event, it begins waiting for events to fire.
Form1_KeyDown(sender
Object
, e
KeyEventArgs)
Handles
.KeyDown
sSpeed
= 15
oSpeed
= 10
e.KeyCode
Keys.Up
TestAngle
= Knife.Angle
TestOrigin
TestOrigin.Y -= oSpeed
AllowMove(TestOrigin, TestAngle)
Origin.Y -= oSpeed
Knife.UpdateOrigin(Origin)
Keys.Down
TestOrigin.Y += oSpeed
Origin.Y += oSpeed
Keys.Left
TestOrigin.X -= oSpeed
Origin.X -= oSpeed
Keys.Right
TestOrigin.X += oSpeed
Origin.X += oSpeed
Keys.W
'up
= Knife.Angle + sSpeed
Knife.Angle += sSpeed
Keys.S
'down
= Knife.Angle - sSpeed
Knife.Angle -= sSpeed
Keys.Oemplus
SpinSpeed =
(SpinSpeed + 0.5)
SpinSpeed > 10
SpinSpeed = 5
.Invalidate()
Keys.OemMinus
(SpinSpeed - 0.5)
SpinSpeed < 0.5
SpinSpeed = 0.5
Keys.Q
Velocity = Velocity - 1
Velocity < 1
Velocity = 1
Keys.E
Velocity = Velocity + 1
Velocity > 15
Velocity = 15
Keys.H
Message
System.Text.StringBuilder
Message.Append(
"Knife Thrower Help:"
& vbCrLf)
"Press the arrow keys to move the knife"
"to where you want to throw it from."
Message.Append(vbCrLf)
"Press 'w' or 's' to rotate the knife before throwing."
"Press '+' or '-' to adjust the speed at which the knife spins"
"Press 'q' or 'e' to adjust the velocity of the knife"
"Press 'h' to open Knife Thrower help menu"
"Press 'Space' to throw the knife or to reset the knife after sticking it."
MsgBox(Message.ToString)
Keys.Space
Not
Thrown
Thrown =
Throwing
Throwing =
ThrowKnife()
False
CurrentTarget = RandomTarget()
ResetKnife()
Keys.J
My.Computer.Audio.Play(My.Resources.knifethrowerjingle, AudioPlayMode.Background)
Keys.Z
WireFrameOnly =
WireFrameOnly
Keys.Escape
Knife.Angle = Knife.Angle
AllowMove(TestOrigin
Point, TestAngle
Boolean
ClonePoly
Polygon = Knife.Clone
ClonePoly.UpdateOrigin(TestOrigin)
ClonePoly.Angle = TestAngle
CandidateRectangle
Rectangle = ClonePoly.ContainerRectangle(0)
CandidateRectangle.Right > LimitX
CandidateRectangle.Left < 0
CandidateRectangle.Top < 0
CandidateRectangle.Bottom >
.ClientRectangle.Height
+
Knife.Angle
= sSpeed
-
(SpinSpeed
0.5)
Velocity = Velocity
1
My.Computer.Audio.Play(My.Resources.flyingKnife3, AudioPlayMode.BackgroundLoop)
FlewOffScreen
TargetHit
Origin.X += Velocity
Knife.Angle += SpinSpeed
Application.DoEvents()
TargetHit = RectIntersectsWithPoly(CurrentTarget, Knife.Polygon)
'CurrentTarget.IntersectsWith(Poly1.ContainerRectangle(0))
FlewOffScreen = Knife.ContainerRectangle(0).Left >
.ClientRectangle.Width
Exit
My.Computer.Audio.
Stop
Knife.KnifeDirection = Polygon.Direction.Right
Origin.X += Velocity * 5
Knife.Angle += SpinSpeed * 5
My.Computer.Audio.Play(My.Resources.stick, AudioPlayMode.Background)
Origin.X = OriginalOrigin.X
Origin.Y = OriginalOrigin.Y
The Direction Enum, and knife direction property, is somewhat specific to the knife polygon, but could be removed or reused with non-knife polygons, with little issue.
I hope you find this helpful! Hits On Pages