-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTiletest.vb
293 lines (219 loc) · 12.3 KB
/
Tiletest.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
Imports System.IO
Public Class TileTest
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents Clock As System.Windows.Forms.Timer
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.components = New System.ComponentModel.Container
Me.Clock = New System.Windows.Forms.Timer(Me.components)
'
'Clock
'
'
'TileTest
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(440, 469)
Me.Name = "TileTest"
Me.Text = "Tile Map"
End Sub
#End Region
Dim Tileset As Bitmap 'Holds the tile pictures.
Dim Backup As Bitmap 'Backup picture holds the map that we've just drawn.
Dim GFX As Graphics 'Draws to the backup picture.
Dim FormGFX As Graphics 'Draws to the form.
Dim Map(,) As Integer 'The map is a 2D array of integers.
Dim Charpics(3) As Bitmap 'Hold each frame in one of the array elements.
Dim Cyc As Integer 'This number tells which frame to draw.
Dim CharacterLocation As Point
Dim SymbolicTileloc As PointF 'Holds the tile position now: since the character can be partially off of a tile.
'This is a PointF so that we can have decimal values representing portions of a tile.
Const Tilesize As Integer = 32
Const ViewTileCount As Integer = 7
Const NumofTilesBetweenCharacterAndScreenEdge As Integer = (ViewTileCount * 2 - 1) \ 2
Const Mapsizeintiles As Integer = Tilesize * 2 + 8 ' 24
Public Sub SetDefaults()
Me.CharacterLocation = New Point(16, 16) 'Character location now represents a pixel location on the map.
Me.SymbolicTileloc = New PointF(1.0F, 1.0F) 'The point 16,16 corresponds to Tile 1,1
'The formula for converting location to tile location would be multiplying the location's x and y by 16 and put them into
'the symbolictilelocation.
Charpics(0) = New Bitmap("0.png")
Charpics(1) = New Bitmap("1.png")
Charpics(2) = New Bitmap("2.png")
Charpics(3) = New Bitmap("1.png")
'Load each frame one by one into the array.
End Sub
Private Sub TileTest_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
SetDefaults()
Tileset = New Bitmap("tileset.bmp") 'Put the bitmap into the tileset picture.
Backup = New Bitmap(Tilesize * ViewTileCount, Tilesize * ViewTileCount) 'Backup will be initialzed to an empty bitmap
GFX = Graphics.FromImage(Backup) 'GFX draws onto the backup picture.
FormGFX = Me.CreateGraphics() 'Draw to the form.
Dim SR As StreamReader = New StreamReader("map.txt")
Dim Ln As String
Dim LX, LY As Integer
Dim Chs As String
'The map in the file is now 24x24, so the array must be initialized as:
Map = New Integer(23, 23) {}
For LY = 0 To 23
Ln = SR.ReadLine() 'One row of tiles from the file.
For LX = 0 To 23 'Goes through each column.
Chs = Ln.Substring(LX, 1) 'Get a string with one character in it.
If Chs = "*" Then
Me.CharacterLocation = New Point(LX * Tilesize, LY * Tilesize)
Chs = "0"
End If
Map(LX, LY) = Integer.Parse(Chs) 'Convert this string into its matching number.
'"0" -> 0, "1" -> 1 ...
'The number is put into the map.
Next
Next
SR.Close()
'Allow the keypresses to go directly to the form.
Me.KeyPreview = True
'Enable the clock.
Clock.Start()
Draw() 'Draw the map.
End Sub
Private Sub Draw() 'Draw map to the backup.
'Drawing the map requires nested for loops.
Dim LX, LY As Integer
Dim Thisindex As Integer 'The index of the map at the point LX,LY.
Dim Tile As Rectangle 'Holds the rectangle defining which tile we will draw.
'There are a few things that we need to consider about the scrolling of the map before we apply these two things.
'Since the map is scrolling, when a player is partially to another tile, there will be more than 7x7 tiles on the screen.
'A partial row/column will be on both ends of the screen and they need to be drawn as well.
'Also note that the screen should only scroll when the camera is not at one of the end.
'To facilitate the scrolling, our camera will also be the pixel location of the upper left of the map.
Dim CamX, CamY As Integer
'The player will always be 3 tiles * 16 (tile size) = 48 from the left, so the Camera will always be the
'player's location minus 48 (except of course, when the player is at the ends of the map.
CamX = Me.CharacterLocation.X - NumofTilesBetweenCharacterAndScreenEdge * Tilesize
'The camera must also remain in the range.
If CamX < 0 Then
CamX = 0
ElseIf CamX > (Mapsizeintiles - ViewTileCount) * Tilesize Then
CamX = (Mapsizeintiles - ViewTileCount) * Tilesize 'This is the calculated value of the camera's maximum location in the map.
End If 'The View width is subtracted from the map width
CamY = Me.CharacterLocation.Y - 192
If CamY < 0 Then
CamY = 0
ElseIf CamY > (Mapsizeintiles - ViewTileCount) * Tilesize Then
CamY = (Mapsizeintiles - ViewTileCount) * Tilesize
End If
Dim StartX, StartY, EndX, EndY As Integer
'This is the tile that corresponds to the upper left corner on the display.
StartX = CamX \ Tilesize
StartY = CamY \ Tilesize
Dim OffX, OffY As Integer
'If the camera is 4 greater than a value that is the upperleft corner of a tile, then the tile
'will be drawn -4 away from the tile position that would fit neatly within the map.
OffX = CamX Mod Tilesize
OffY = CamY Mod Tilesize
If OffX = 0 Then
EndX = StartX + ViewTileCount - 1 'the last tile on the end is six tiles past the beginning tile.
Else
EndX = StartX + ViewTileCount 'here it would be 7.
End If
If OffY = 0 Then
EndY = StartY + ViewTileCount - 1
Else
EndY = StartY + ViewTileCount
End If
For LY = StartY To EndY
For LX = StartX To EndX
Thisindex = Map(LX, LY)
'Get the index from the map.
'The values of LX and LY already depend on the camera position, so no need to add it again.
Tile = New Rectangle(Thisindex * Tilesize, 0, Tilesize, Tilesize)
'Get the rectangle defining the tile. (same)
GFX.DrawImage(Tileset, (LX - StartX) * Tilesize - OffX, (LY - StartY) * Tilesize - OffY, Tile, GraphicsUnit.Pixel)
'And then draw the tile onto the backup.
Next
Next
'Map code.
GFX.DrawImage(Charpics(Cyc), (CharacterLocation.X - CamX), (CharacterLocation.Y - CamY))
'Draw the character to the display at the location... this also changes depending on the camera location, so
'the camera location is subtracted from the character location. The character only appears to remain centered.
'At this point the whole map should reside on the backup bitmap. The bitmap is not visible, so it must be made visible.
FormGFX.DrawImage(Backup, Me.ClientRectangle())
End Sub
Private Sub Clock_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Clock.Tick
Cyc = (Cyc + 1) Mod 4 'Cyc cycles through 0, 1, 2, 3, 0, 1, 2, 3, 0, 1 ...
Draw() 'Draw.
End Sub
Private Sub TileTest_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
'FormGFX.SetClip(Me.ClientRectangle)
FormGFX.DrawImage(Backup, Me.ClientRectangle())
End Sub
Private Sub TileTest_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
Dim Collided As Boolean
'The character now moves four pixels instead of by a whole tile (16).
'Collision detection always comes after this movement caused by the user.
If e.KeyCode = Keys.Left Then
CharacterLocation.Offset(-Tilesize, 0)
ElseIf e.KeyCode = Keys.Up Then
CharacterLocation.Offset(0, -Tilesize)
ElseIf e.KeyCode = Keys.Right Then
CharacterLocation.Offset(Tilesize, 0)
ElseIf e.KeyCode = Keys.Down Then
CharacterLocation.Offset(0, Tilesize)
End If
'The character can be on, at most, four tiles at once. So, in addition to the map boundary checks,
'we'll also have to check the four tiles that the character is on for collision.
'The upper left tile being, of course, the characterlocation \ tilesize (for X and Y).
'however, we can use the SymbolicTileLoc to get the tile location as well, rounding up and rounding down with
'Floor and Ceiling to get the tiles that the character is on.
SymbolicTileloc.X = Convert.ToSingle(CharacterLocation.X / Tilesize) '16
SymbolicTileloc.Y = Convert.ToSingle(CharacterLocation.Y / Tilesize) '16
'Check for collisions after moving the character.
If CharacterLocation.X < 0 OrElse CharacterLocation.X > Map.GetUpperBound(0) * 16 OrElse CharacterLocation.Y < 0 OrElse CharacterLocation.Y > Map.GetUpperBound(1) * 16 Then
Collided = True
'Don't ask why Ceiling and Floor return doubles.
ElseIf Map(Convert.ToInt32(Math.Floor(SymbolicTileloc.X)), Convert.ToInt32(Math.Floor(SymbolicTileloc.Y))) = 3 Then
Collided = True
ElseIf Map(Convert.ToInt32(Math.Ceiling(SymbolicTileloc.X)), Convert.ToInt32(Math.Floor(SymbolicTileloc.Y))) = 3 Then
Collided = True
ElseIf Map(Convert.ToInt32(Math.Floor(SymbolicTileloc.X)), Convert.ToInt32(Math.Ceiling(SymbolicTileloc.Y))) = 3 Then
Collided = True
ElseIf Map(Convert.ToInt32(Math.Ceiling(SymbolicTileloc.X)), Convert.ToInt32(Math.Ceiling(SymbolicTileloc.Y))) = 3 Then
Collided = True
End If
'If we have collded, then the character needs to move back immediately.
If Collided Then
'Take all of the keycodes and reverse their operation.
If e.KeyCode = Keys.Left Then
CharacterLocation.Offset(Tilesize, 0)
ElseIf e.KeyCode = Keys.Up Then
CharacterLocation.Offset(0, Tilesize)
ElseIf e.KeyCode = Keys.Right Then
CharacterLocation.Offset(-Tilesize, 0)
ElseIf e.KeyCode = Keys.Down Then
CharacterLocation.Offset(0, -Tilesize)
End If
SymbolicTileloc.X = Convert.ToSingle(CharacterLocation.X / Tilesize) '16)
SymbolicTileloc.Y = Convert.ToSingle(CharacterLocation.Y / Tilesize) ' 16)
End If
'Note that the keydown event can fire more than once over the 100 ms that the timer is not drawing.
End Sub
End Class