The Tile Engine: The Ins and Outs

squaring it all

The GameSprite class here is not that different from the ones I used in other games. If you read the H.E.R.O tutorial you know exactly how this works.

This is the class which is extended by every moving sprite in the game, namely, the tanks and the bullets. And so it contains properties to store information regarding current position, next position and speeds. I’ve added a Render stage to the sprite, so that now the sprite is updated, moved, and rendered. And the values for X and Y are stored in the properties _nowX and _nowY. These properties will make sense in the next tutorial when I turn this game into an isometric game.

The different methods here are connected with the Maze collision logic and are:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function getPointLeft ():int {
	return _gameData.maze.getPointLeft(this, _speed + int(this.width/_gameData.maze.tileWidth));
 
}
protected function getPointRight ():int {
	return _gameData.maze.getPointRight(this, _speed +  int(this.width/_gameData.maze.tileWidth));
 
}
protected function getPointUp ():int {
	return _gameData.maze.getPointUp(this, _speed + int(this.height/_gameData.maze.tileHeight));
 
}
protected function getPointDown ():int {
	return _gameData.maze.getPointDown(this, _speed + int(this.height/_gameData.maze.tileHeight));
}

These methods call the Maze object and grabs the position of a probable collision point. If these methods return -1, it means there is no collision with the maze.

These methods are called from here:

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
protected function reachedTile ():Boolean {
	var result:Boolean = false;
	var limitX:int;
	var limitY:int;
	var reachedBase:Boolean = false;
 
	if (_nextY + this.height >= GameConstants.BASE_Y - GameConstants.BASE_HEIGHT) {
		reachedBase = true;
	}
	if (_vx > 0) {
		limitX = getPointRight();
		if (limitX > 0 && (_nextX >= limitX )) {
 
			if (reachedBase) checkBaseCollision(_nextX + this.width/2);
			_nextX = limitX;
			result = true;
		}
	} else if (_vx < 0) {
		limitX = getPointLeft();
		if (limitX > 0 && (_nextX <= limitX )) {
 
			if (reachedBase) checkBaseCollision(_nextX - this.width/2);
			_nextX = limitX;
			result = true;
		}
	}
	if (_vy > 0) {
		limitY = getPointDown();
		if (limitY > 0 && (_nextY >= limitY) ) {
 
			_nextY = limitY;
			if (reachedBase) checkBaseCollision(_nextX);
			result = true;
		}
	} else if (_vy < 0) {
		limitY = getPointUp();
		if (limitY > 0 && (_nextY <= limitY)) {
 
			_nextY = limitY;
			if (reachedBase) checkBaseCollision(_nextX );
			result = true;
		}
	}
	return result;
}

The method checks in which direction the sprite is moving, checks the collision point for that movement, and if there is one and the next position does overlap with it, then the next movement is adjusted so that the solid cell is touched but not crossed.

And finally the checkBaseCollision method which checks to see if the sprite has reached the HQ. It would make sense to have this logic as only part of the enemy tanks, but the enemy bullets need to check this too, so I’ve decided to have it in the super class of the sprites.

1
2
3
4
5
6
7
8
9
10
protected function checkBaseCollision (newX:int):void {
	if (newX >= GameConstants.BASE_X - GameConstants.BASE_WIDTH/2 - this.width/2
		&& newX <= GameConstants.BASE_X + GameConstants.BASE_WIDTH/2 + this.width/2) {
		if (this is AiBullet || this is AiTank) {
			//GAME OVER
			GameController.instance.gameOver();
		}
	}
 
}

The GameSprite class

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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package tiletank.dynamic {
 
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.geom.Point;
 
 
	import tiletank.*;
	import tiletank.map.*;
	import tiletank.tank.*;
	import tiletank.bullet.*;
 
 
	/**
	* The base class for every moving object in the game
	*/
	public class GameSprite extends Sprite {
 
 
		protected var _manager:ScreenManager;
		protected var _stageElements:StageElements;
		protected var _active:Boolean = true;
		protected var _trashme:Boolean;
		protected var _vx:int = 0;
		protected var _vy:int = 0;
		protected var _nextX:int;
		protected var _nextY:int;
		protected var _nowX:int;
		protected var _nowY:int;
		protected var _gameStage:GameStage;
		protected var _gameData:GameData;
		protected var _hit:Boolean;
 
		protected var _speed:int;
 
 
		function GameSprite () {
			cacheAsBitmap = true;
			_manager = ScreenManager.instance;
			_stageElements = StageElements.instance;
			_gameData = GameData.instance;
			_gameStage = _stageElements.getElementByClass(GameConstants.GAME_STAGE) as GameStage;
 
			addEventListener(Event.REMOVED_FROM_STAGE, removeMe, false, 0, true);
			addEventListener(Event.ADDED_TO_STAGE, initMe, false, 0, true);
 
			//the ScreenManager Events to Update next Poisition of element and then Render the element
			_manager.addEventListener(GameConstants.UPDATE_SCREEN, onUpdate, false, 0, true);
			_manager.addEventListener(GameConstants.SHOW_SCREEN, onShowScreen, false, 0, true);
 
			_gameStage.addEventListener(GameConstants.GAME_PAUSED, onGamePause);
		}
 
		public function set active (value:Boolean):void {
			_active = value;
		}
		public function get active ():Boolean {
			return _active;
		}
		public function set trashme (value:Boolean):void {
			_trashme  = value;
		}
		public function get trashme  ():Boolean {
			return _trashme;
		}
 
		public function get vy ():int {
			return _vy;
		}
		public function get vx ():int {
			return _vx;
		}
 
		public function set vy (value:int):void {
			_vy = value;
		}
		public function set vx (value:int):void {
			_vx = value;
		}
 
		public function get nextY ():int {
			return _nextY;
		}
		public function get nextX ():int {
			return _nextX;
		}
 
		public function set nextY (value:int):void {
			_nextY = value;
		}
		public function set nextX (value:int):void {
			_nextX = value;
		}
 
 
		public function get left ():int {
			return _nowX - width/2;
		}
		public function get right ():int {
			return _nowX + width/2;
		}
 
		public function get top ():int {
			return _nowY - height/2;
		}
		public function get bottom ():int {
			return _nowY + height/2;
		}
 
		public function get next_left ():int {
			return _nextX - width/2;
		}
		public function get next_right ():int {
			return _nextX + width/2;
		}
 
		public function get next_top ():int {
			return _nextY - height/2;
		}
		public function get next_bottom ():int {
			return _nextY + height/2;
		}
 
		public function get nowY ():int {
			return _nowY;
		}
		public function get nowX ():int {
			return _nowX;
		}
 
		public function set nowY (value:int):void {
			_nowY = value;
		}
		public function set nowX (value:int):void {
			_nowX = value;
		}
 
		override public function get width ():Number {
			return this.getBounds(stage).width;
 
		}
 
		override public function get height ():Number {
			return this.getBounds(stage).height;
 
		}
 
		public function get speed ():int {
			return _speed;
		}
 
		public function get box ():Rectangle {
			return this.getBounds(stage);
		}
		public function get next_box ():Rectangle {
			var nextOrigin:Point = localToGlobal(new Point(_nextX, _nextY));
			return new Rectangle(nextOrigin.x - width/2, nextOrigin.y - height/2, width, height);
 
		}
 
		public function move ():void {
			if (_gameData.gameMode == GameConstants.PAUSE) return;
			_nowX = _nextX;
			_nowY = _nextY;
			setState();
			render();
		}
 
		public function render ():void {
			x = _nowX;
			y = _nowY;
		}
 
		public function update ():void {
			_nextX = _nowX + _vx;
			_nextY = _nowY + _vy;
		}
		/**
		* Clear positioning data
		*
		*/
		public function reset ():void {
			_vx = _vy = 0;
			_nextX = _nowX = x;
			_nextY = _nowY = y;
		}
		public function setState ():void {
		}
 
		public function destroy ():void {
			_active = false;
		}
		/**
		* Collision Logic for current position
		* @param rect Rectangle of the object checked against collision
		* @returns Boolean, whether or not collision is occurring
		*/
		public function isIntersecting (rect:Rectangle):Boolean {
			return (this.box.intersection(rect).width > 0);
		}
		/**
		* Collision Logic for next iteration
		* @param rect Rectangle of the object checked against collision
		* @returns Boolean, whether or not collision is occurring
		*/
		public function willBeIntersecting (rect:Rectangle):Boolean {
			return (this.next_box.intersection(rect).width > 0);
		}
 
		/**
		* Between updates of the tank, the next position is checked against collision with the maze
		* if collision with the maze is detected, the method reachedTile returns true
		* reachedTile is declared in GameSprite
		*/
		public function checkMazeCollision ():void {
			reachedTile();
		}
 
		protected function getPointLeft ():int {
			return _gameData.maze.getPointLeft(this, _speed + int(this.width/_gameData.maze.tileWidth));
 
		}
		protected function getPointRight ():int {
			return _gameData.maze.getPointRight(this, _speed +  int(this.width/_gameData.maze.tileWidth));
 
		}
		protected function getPointUp ():int {
			return _gameData.maze.getPointUp(this, _speed + int(this.height/_gameData.maze.tileHeight));
 
		}
		protected function getPointDown ():int {
			return _gameData.maze.getPointDown(this, _speed + int(this.height/_gameData.maze.tileHeight));
		}
 
		protected function reachedTile ():Boolean {
			var result:Boolean = false;
			var limitX:int;
			var limitY:int;
			var reachedBase:Boolean = false;
 
			if (_nextY + this.height >= GameConstants.BASE_Y - GameConstants.BASE_HEIGHT) {
				reachedBase = true;
			}
			if (_vx > 0) {
				limitX = getPointRight();
				if (limitX > 0 && (_nextX >= limitX )) {
 
					if (reachedBase) checkBaseCollision(_nextX + this.width/2);
					_nextX = limitX;
					result = true;
				}
			} else if (_vx < 0) {
				limitX = getPointLeft();
				if (limitX > 0 && (_nextX <= limitX )) {
 
					if (reachedBase) checkBaseCollision(_nextX - this.width/2);
					_nextX = limitX;
					result = true;
				}
			}
			if (_vy > 0) {
				limitY = getPointDown();
				if (limitY > 0 && (_nextY >= limitY) ) {
 
					_nextY = limitY;
					if (reachedBase) checkBaseCollision(_nextX);
					result = true;
				}
			} else if (_vy < 0) {
				limitY = getPointUp();
				if (limitY > 0 && (_nextY <= limitY)) {
 
					_nextY = limitY;
					if (reachedBase) checkBaseCollision(_nextX );
					result = true;
				}
			}
			return result;
		}
 
 
		protected function checkBaseCollision (newX:int):void {
			if (newX >= GameConstants.BASE_X - GameConstants.BASE_WIDTH/2 - this.width/2
				&& newX <= GameConstants.BASE_X + GameConstants.BASE_WIDTH/2 + this.width/2) {
				if (this is AiBullet || this is AiTank) {
					//GAME OVER
 
					GameController.instance.gameOver();
				}
			}
 
		}
		/**
		* The UPDATE SCREEN event dispatched by ScreenManager
		*
		*/
		protected function onUpdate (event:Event):void {
			if (!_trashme) update();
		}
 
		/**
		* The SHOW SCREEN event dispatched by ScreenManager
		*
		*/
		protected function onShowScreen (event:Event):void {
			if (_active) move();
		}
 
 
		/**
		* REMOVED_FROM_STAGE Event
		*
		*/
		protected function removeMe (event:Event):void {
			_manager.removeEventListener(GameConstants.UPDATE_SCREEN, onUpdate);
			_manager.removeEventListener(GameConstants.SHOW_SCREEN, onShowScreen);
			removeEventListener(Event.ADDED_TO_STAGE, initMe);
			removeEventListener(Event.REMOVED_FROM_STAGE, removeMe);
 
		}
 
		/**
		* ADDED_TO_STAGE Event
		*
		*/
		protected function initMe (event:Event):void {
			reset();
 
		}
 
		protected function onGamePause (event:Event):void {}
 
	}
}

Leave a Comment