tank_island 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +58 -0
  7. data/Rakefile +2 -0
  8. data/bin/tank_island +30 -0
  9. data/lib/entities/box.rb +28 -0
  10. data/lib/entities/bullet.rb +26 -0
  11. data/lib/entities/camera.rb +113 -0
  12. data/lib/entities/components/ai/gun.rb +114 -0
  13. data/lib/entities/components/ai/tank_chasing_state.rb +30 -0
  14. data/lib/entities/components/ai/tank_fighting_state.rb +47 -0
  15. data/lib/entities/components/ai/tank_fleeing_state.rb +50 -0
  16. data/lib/entities/components/ai/tank_motion_fsm.rb +102 -0
  17. data/lib/entities/components/ai/tank_motion_state.rb +84 -0
  18. data/lib/entities/components/ai/tank_navigating_state.rb +34 -0
  19. data/lib/entities/components/ai/tank_roaming_state.rb +83 -0
  20. data/lib/entities/components/ai/tank_stuck_state.rb +45 -0
  21. data/lib/entities/components/ai/vision.rb +109 -0
  22. data/lib/entities/components/ai_input.rb +70 -0
  23. data/lib/entities/components/box_graphics.rb +39 -0
  24. data/lib/entities/components/bullet_graphics.rb +13 -0
  25. data/lib/entities/components/bullet_physics.rb +65 -0
  26. data/lib/entities/components/bullet_sounds.rb +15 -0
  27. data/lib/entities/components/component.rb +32 -0
  28. data/lib/entities/components/damage_graphics.rb +20 -0
  29. data/lib/entities/components/explosion_graphics.rb +43 -0
  30. data/lib/entities/components/explosion_sounds.rb +16 -0
  31. data/lib/entities/components/health.rb +87 -0
  32. data/lib/entities/components/player_input.rb +100 -0
  33. data/lib/entities/components/player_sounds.rb +16 -0
  34. data/lib/entities/components/powerup_graphics.rb +22 -0
  35. data/lib/entities/components/powerup_sounds.rb +15 -0
  36. data/lib/entities/components/tank_graphics.rb +46 -0
  37. data/lib/entities/components/tank_health.rb +32 -0
  38. data/lib/entities/components/tank_physics.rb +179 -0
  39. data/lib/entities/components/tank_sounds.rb +43 -0
  40. data/lib/entities/components/tree_graphics.rb +69 -0
  41. data/lib/entities/damage.rb +26 -0
  42. data/lib/entities/explosion.rb +34 -0
  43. data/lib/entities/game_object.rb +54 -0
  44. data/lib/entities/hud.rb +79 -0
  45. data/lib/entities/map.rb +183 -0
  46. data/lib/entities/object_pool.rb +59 -0
  47. data/lib/entities/powerups/fire_rate_powerup.rb +14 -0
  48. data/lib/entities/powerups/health_powerup.rb +12 -0
  49. data/lib/entities/powerups/powerup.rb +35 -0
  50. data/lib/entities/powerups/powerup_respawn_queue.rb +23 -0
  51. data/lib/entities/powerups/repair_powerup.rb +14 -0
  52. data/lib/entities/powerups/tank_speed_powerup.rb +14 -0
  53. data/lib/entities/radar.rb +62 -0
  54. data/lib/entities/score_display.rb +35 -0
  55. data/lib/entities/tank.rb +64 -0
  56. data/lib/entities/tree.rb +18 -0
  57. data/lib/game_states/demo_state.rb +49 -0
  58. data/lib/game_states/game_state.rb +27 -0
  59. data/lib/game_states/menu_state.rb +60 -0
  60. data/lib/game_states/pause_state.rb +61 -0
  61. data/lib/game_states/play_state.rb +119 -0
  62. data/lib/misc/axis_aligned_bounding_box.rb +33 -0
  63. data/lib/misc/game_window.rb +30 -0
  64. data/lib/misc/names.rb +13 -0
  65. data/lib/misc/quad_tree.rb +91 -0
  66. data/lib/misc/stats.rb +55 -0
  67. data/lib/misc/stereo_sample.rb +96 -0
  68. data/lib/misc/utils.rb +145 -0
  69. data/media/armalite_rifle.ttf +0 -0
  70. data/media/boxes_barrels.json +60 -0
  71. data/media/boxes_barrels.png +0 -0
  72. data/media/bullet.png +0 -0
  73. data/media/c_dot.png +0 -0
  74. data/media/country_field.png +0 -0
  75. data/media/crash.ogg +0 -0
  76. data/media/damage1.png +0 -0
  77. data/media/damage2.png +0 -0
  78. data/media/damage3.png +0 -0
  79. data/media/damage4.png +0 -0
  80. data/media/decor.json +516 -0
  81. data/media/decor.png +0 -0
  82. data/media/decor.psd +0 -0
  83. data/media/explosion.mp3 +0 -0
  84. data/media/explosion.png +0 -0
  85. data/media/fire.mp3 +0 -0
  86. data/media/ground.json +492 -0
  87. data/media/ground.png +0 -0
  88. data/media/ground_units.json +900 -0
  89. data/media/ground_units.png +0 -0
  90. data/media/menu_music.mp3 +0 -0
  91. data/media/metal_interaction2.wav +0 -0
  92. data/media/names.txt +279 -0
  93. data/media/pickups.json +68 -0
  94. data/media/pickups.png +0 -0
  95. data/media/powerup.mp3 +0 -0
  96. data/media/respawn.wav +0 -0
  97. data/media/tank_driving.mp3 +0 -0
  98. data/media/top_secret.ttf +0 -0
  99. data/media/trees.png +0 -0
  100. data/media/trees_packed.json +388 -0
  101. data/media/trees_packed.png +0 -0
  102. data/media/water.png +0 -0
  103. data/spec/misc/aabb_spec.rb +85 -0
  104. data/spec/misc/quad_tree_spec.rb +137 -0
  105. data/tank_island.gemspec +29 -0
  106. metadata +223 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d532027c65a76a3a675c7bddbd7ed6699bf74880
4
+ data.tar.gz: 90459a44a1e0e4f91796ac0188e54f75ac64277a
5
+ SHA512:
6
+ metadata.gz: bd1d1bd1afed378b9655e77d6b7ee5972d7c37aa0e904cecdbee7f2c35b93075c145b4ef37ef5241026f994da48e98149df7f925c9538fed83eb336c4df1d657
7
+ data.tar.gz: b85eed9ea5b0fd7e7c9ba709b502a3e1a489c92399bd0f5daea7f2c47202f5be8b09f71a537edc8bf8ee3b6d6f630390836cde81a5ac54df31869f631fe8811b
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1 @@
1
+ 2.1.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tank_island.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tomas Varaneckas
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ # Tank Island
2
+
3
+ Tank Island is an open source 2D top down shooter game that was created with Ruby using
4
+ [Gosu](http://www.libgosu.org) game development library while writing
5
+ [this book](https://leanpub.com/developing-games-with-ruby/read).
6
+
7
+ ## Screenshots
8
+
9
+ ![Tank Island Game](https://dl.dropboxusercontent.com/u/176100/tank_island/screen1.png)
10
+
11
+ ![Tank Island Game](https://dl.dropboxusercontent.com/u/176100/tank_island/screen2.png)
12
+
13
+ [Gameplay video on YouTube](https://www.youtube.com/watch?v=PpbnCUBrXXU)
14
+
15
+ ## Book: Developing Games With Ruby
16
+
17
+ Complete process of building this game is described step by step in this free to read book:
18
+
19
+ [Developing Games With Ruby](https://leanpub.com/developing-games-with-ruby/read)
20
+
21
+ ## Installation
22
+
23
+ To install it, run
24
+
25
+ $ gem install tank_island
26
+
27
+ On Mac OSX you will have to `brew install imagemagick` to make it work.
28
+
29
+ ## Starting the game
30
+
31
+ There are several ways to start the game.
32
+
33
+ ### Running in 800x600 window mode
34
+
35
+ $ tank_island
36
+
37
+ ### Running with custom resolution
38
+
39
+ $ w=1600 h=1200 tank_island
40
+
41
+ ### Running full screen with custom resolution
42
+
43
+ $ fs=1 w=1200 h=800 tank_island
44
+
45
+ ## Controls
46
+
47
+ ### Gameplay
48
+
49
+ - `W` `A` `S` `D` moves your tank.
50
+ - Mouse `left click` shoots.
51
+ - `ESC` goes into menu and away from it.
52
+
53
+ ### Debugging
54
+
55
+ - `R` respawns your tank.
56
+ - `T` spawns an enemy tank under mouse cursor.
57
+ - `F1` enters debug mode.
58
+ - `F2` toggles profiling
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gosu'
4
+ require 'gosu_texture_packer'
5
+ require 'perlin_noise'
6
+
7
+ root_dir = File.expand_path(File.join(
8
+ File.dirname(File.dirname(__FILE__)), 'lib'))
9
+ require_pattern = File.join(root_dir, '**/*.rb')
10
+ @failed = []
11
+
12
+ # Dynamically require everything
13
+ Dir.glob(require_pattern).each do |f|
14
+ begin
15
+ require f
16
+ rescue
17
+ # May fail if parent class not required yet
18
+ @failed << f
19
+ end
20
+ end
21
+
22
+ # Retry unresolved requires
23
+ @failed.each do |f|
24
+ require f.gsub("#{root_dir}/", '')
25
+ end
26
+
27
+ $debug = false
28
+ $window = GameWindow.new
29
+ GameState.switch(MenuState.instance)
30
+ $window.show
@@ -0,0 +1,28 @@
1
+ class Box < GameObject
2
+ attr_reader :health, :graphics, :angle
3
+
4
+ def initialize(object_pool, x, y)
5
+ super
6
+ @graphics = BoxGraphics.new(self)
7
+ @health = Health.new(self, object_pool, 10, true)
8
+ @angle = rand(-15..15)
9
+ end
10
+
11
+ def on_collision(object)
12
+ return unless object.physics.speed > 1.0
13
+ move(*Utils.point_at_distance(@x, @y, object.direction, 2))
14
+ @box = nil
15
+ end
16
+
17
+ def box
18
+ return @box if @box
19
+ w = @graphics.width / 2
20
+ h = @graphics.height / 2
21
+ # Bounding box adjusted to trim shadows
22
+ @box = [x - w + 4, y - h + 8,
23
+ x + w , y - h + 8,
24
+ x + w , y + h,
25
+ x - w + 4, y + h]
26
+ @box = Utils.rotate(@angle, @x, @y, *@box)
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ class Bullet < GameObject
2
+ attr_accessor :target_x, :target_y, :source, :speed, :fired_at
3
+
4
+ def initialize(object_pool, source_x, source_y, target_x, target_y)
5
+ super(object_pool, source_x, source_y)
6
+ @target_x, @target_y = target_x, target_y
7
+ BulletPhysics.new(self, object_pool)
8
+ BulletGraphics.new(self)
9
+ BulletSounds.play(self, object_pool.camera)
10
+ end
11
+
12
+ def box
13
+ [@x, @y]
14
+ end
15
+
16
+ def explode
17
+ Explosion.new(object_pool, @x, @y, @source)
18
+ mark_for_removal
19
+ end
20
+
21
+ def fire(source, speed)
22
+ @source = source
23
+ @speed = speed
24
+ @fired_at = Gosu.milliseconds
25
+ end
26
+ end
@@ -0,0 +1,113 @@
1
+ class Camera
2
+ attr_accessor :x, :y, :zoom
3
+ attr_reader :target
4
+
5
+ def target=(target)
6
+ @target = target
7
+ @x, @y = target.x, target.y
8
+ @zoom = 1
9
+ end
10
+
11
+ def desired_spot
12
+ if @target.physics.moving?
13
+ Utils.point_at_distance(
14
+ @target.x, @target.y,
15
+ @target.direction,
16
+ @target.physics.speed.ceil * 25)
17
+ else
18
+ [@target.x, @target.y]
19
+ end
20
+ end
21
+
22
+ def mouse_coords
23
+ x, y = target_delta_on_screen
24
+ mouse_x_on_map = @target.x +
25
+ (x + $window.mouse_x - ($window.width / 2)) / @zoom
26
+ mouse_y_on_map = @target.y +
27
+ (y + $window.mouse_y - ($window.height / 2)) / @zoom
28
+ [mouse_x_on_map, mouse_y_on_map].map(&:round)
29
+ end
30
+
31
+ def update
32
+ des_x, des_y = desired_spot
33
+ shift = Utils.adjust_speed(
34
+ @target.physics.speed).floor *
35
+ @target.speed_modifier + 1
36
+ if @x < des_x
37
+ if des_x - @x < shift
38
+ @x = des_x
39
+ else
40
+ @x += shift
41
+ end
42
+ elsif @x > des_x
43
+ if @x - des_x < shift
44
+ @x = des_x
45
+ else
46
+ @x -= shift
47
+ end
48
+ end
49
+ if @y < des_y
50
+ if des_y - @y < shift
51
+ @y = des_y
52
+ else
53
+ @y += shift
54
+ end
55
+ elsif @y > des_y
56
+ if @y - des_y < shift
57
+ @y = des_y
58
+ else
59
+ @y -= shift
60
+ end
61
+ end
62
+
63
+ zoom_delta = @zoom > 0 ? 0.01 : 1.0
64
+ zoom_delta = Utils.adjust_speed(zoom_delta)
65
+ if $window.button_down?(Gosu::KbUp)
66
+ @zoom -= zoom_delta unless @zoom < 0.7
67
+ elsif $window.button_down?(Gosu::KbDown)
68
+ @zoom += zoom_delta unless @zoom > 10
69
+ else
70
+ target_zoom = @target.physics.speed > 1.1 ? 0.75 : 1.0
71
+ if @zoom <= (target_zoom - 0.01)
72
+ @zoom += zoom_delta / 3
73
+ elsif @zoom > (target_zoom + 0.01)
74
+ @zoom -= zoom_delta / 3
75
+ end
76
+ end
77
+ end
78
+
79
+ def to_s
80
+ "FPS: #{Gosu.fps}. " <<
81
+ "#{@x}:#{@y} @ #{'%.2f' % @zoom}. " <<
82
+ 'WASD to move, arrows to zoom.'
83
+ end
84
+
85
+ def target_delta_on_screen
86
+ [(@x - @target.x) * @zoom, (@y - @target.y) * @zoom]
87
+ end
88
+
89
+ def draw_crosshair
90
+ factor = 0.5
91
+ x = $window.mouse_x
92
+ y = $window.mouse_y
93
+ c = crosshair
94
+ c.draw(x - c.width * factor / 2,
95
+ y - c.height * factor / 2,
96
+ 1000, factor, factor)
97
+ end
98
+
99
+ def viewport
100
+ x0 = @x - ($window.width / 2) / @zoom
101
+ x1 = @x + ($window.width / 2) / @zoom
102
+ y0 = @y - ($window.height / 2) / @zoom
103
+ y1 = @y + ($window.height / 2) / @zoom
104
+ [x0, x1, y0, y1]
105
+ end
106
+
107
+ private
108
+
109
+ def crosshair
110
+ @crosshair ||= Gosu::Image.new(
111
+ $window, Utils.media_path('c_dot.png'), false)
112
+ end
113
+ end
@@ -0,0 +1,114 @@
1
+ class AiGun
2
+ DECISION_DELAY = 300
3
+ attr_reader :target, :desired_gun_angle
4
+
5
+ def initialize(object, vision)
6
+ @object = object
7
+ @vision = vision
8
+ @desired_gun_angle = rand(0..360)
9
+ @retarget_speed = rand(1..5)
10
+ @accuracy = rand(0..10)
11
+ @aggressiveness = rand(1..5)
12
+ end
13
+
14
+ def adjust_angle
15
+ adjust_desired_angle
16
+ adjust_gun_angle
17
+ end
18
+
19
+ def update
20
+ if @vision.in_sight.any?
21
+ if @vision.closest_tank != @target
22
+ change_target(@vision.closest_tank)
23
+ end
24
+ else
25
+ @target = nil
26
+ end
27
+
28
+ if @target
29
+ if (0..30 - rand(0..@accuracy)).include?(
30
+ (@desired_gun_angle - @object.gun_angle).abs.round)
31
+ distance = distance_to_target
32
+ if distance - 50 <= BulletPhysics::MAX_DIST
33
+ target_x, target_y = Utils.point_at_distance(
34
+ @object.x, @object.y, @object.gun_angle,
35
+ distance + 10 - rand(0..@accuracy))
36
+ if can_make_new_decision? && @object.can_shoot? &&
37
+ should_shoot?
38
+ @object.shoot(target_x, target_y)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def draw(viewport)
46
+ if $debug
47
+ color = Gosu::Color::BLUE
48
+ x, y = @object.x, @object.y
49
+ t_x, t_y = Utils.point_at_distance(x, y, @desired_gun_angle,
50
+ BulletPhysics::MAX_DIST)
51
+ $window.draw_line(x, y, color, t_x, t_y, color, 1001)
52
+ color = Gosu::Color::RED
53
+ t_x, t_y = Utils.point_at_distance(x, y, @object.gun_angle,
54
+ BulletPhysics::MAX_DIST)
55
+ $window.draw_line(x, y, color, t_x, t_y, color, 1000)
56
+ end
57
+ end
58
+
59
+ def distance_to_target
60
+ Utils.distance_between(
61
+ @object.x, @object.y, @target.x, @target.y)
62
+ end
63
+
64
+
65
+ def should_shoot?
66
+ rand * @aggressiveness > 0.3
67
+ end
68
+
69
+ def can_make_new_decision?
70
+ now = Gosu.milliseconds
71
+ if now - (@last_decision ||= 0) > DECISION_DELAY
72
+ @last_decision = now
73
+ true
74
+ end
75
+ end
76
+
77
+ def adjust_desired_angle
78
+ @desired_gun_angle = if @target
79
+ Utils.angle_between(
80
+ @object.x, @object.y, @target.x, @target.y)
81
+ else
82
+ @object.direction
83
+ end
84
+ end
85
+
86
+ def change_target(new_target)
87
+ @target = new_target
88
+ adjust_desired_angle
89
+ end
90
+
91
+ def adjust_gun_angle
92
+ actual = @object.gun_angle
93
+ desired = @desired_gun_angle
94
+ if actual > desired
95
+ if actual - desired > 180 # 0 -> 360 fix
96
+ @object.gun_angle = (actual + @retarget_speed) % 360
97
+ if @object.gun_angle < desired
98
+ @object.gun_angle = desired # damp
99
+ end
100
+ else
101
+ @object.gun_angle = [actual - @retarget_speed, desired].max
102
+ end
103
+ elsif actual < desired
104
+ if desired - actual > 180 # 360 -> 0 fix
105
+ @object.gun_angle = (360 + actual - @retarget_speed) % 360
106
+ if @object.gun_angle > desired
107
+ @object.gun_angle = desired # damp
108
+ end
109
+ else
110
+ @object.gun_angle = [actual + @retarget_speed, desired].min
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,30 @@
1
+ class TankChasingState < TankMotionState
2
+ def initialize(object, vision, gun)
3
+ super(object, vision)
4
+ @object = object
5
+ @vision = vision
6
+ @gun = gun
7
+ end
8
+
9
+ def update
10
+ change_direction if should_change_direction?
11
+ drive
12
+ end
13
+
14
+ def change_direction
15
+ @object.physics.change_direction(
16
+ @gun.desired_gun_angle -
17
+ @gun.desired_gun_angle % 45)
18
+
19
+ @changed_direction_at = Gosu.milliseconds
20
+ @will_keep_direction_for = turn_time
21
+ end
22
+
23
+ def drive_time
24
+ 10000
25
+ end
26
+
27
+ def turn_time
28
+ rand(300..600)
29
+ end
30
+ end