summaryrefslogtreecommitdiffhomepage
path: root/samples/02_collision_03_entities/app/main.rb
blob: afbc3dffa1c3db79777db23c222d4f28c1b7ebdf (plain)
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
=begin

 Reminders:

 - map: Ruby method used to transform data; used in arrays, hashes, and collections.
   Can be used to perform an action on every element of a collection, such as multiplying
   each element by 2 or declaring every element as a new entity.

 - reject: Removes elements from a collection if they meet certain requirements.
   For example, you can derive an array of odd numbers from an original array of
   numbers 1 through 10 by rejecting all elements that are even (or divisible by 2).

 - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
   In this sample app, new_entity is used to define the properties of enemies and bullets.
   (Remember, you can use state to define ANY property and it will be retained across frames.)

 - args.outputs.labels: An array. The values generate a label on the screen.
   The parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]

 - ARRAY#intersect_rect?: Returns true or false depending on if the two rectangles intersect.

 - args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.

=end

# This sample app shows enemies that contain an id value and the time they were created.
# These enemies can be removed by shooting at them with bullets.

# Calls all methods necessary for the game to function properly.
def tick args
  tick_instructions args, "Sample app shows how to use args.state.new_entity along with collisions. CLICK to shoot a bullet."
  defaults args
  render args
  calc args
  process_inputs args
end

# Sets default values
# Enemies and bullets start off as empty collections
def defaults args
  args.state.enemies ||= []
  args.state.bullets ||= []
end

# Provides each enemy in enemies collection with rectangular border,
# as well as a label showing id and when they were created
def render args
  # When you're calling a method that takes no arguments, you can use this & syntax on map.
  # Numbers are being added to x and y in order to keep the text within the enemy's borders.
  args.outputs.borders << args.state.enemies.map(&:rect)
  args.outputs.labels  << args.state.enemies.flat_map do |enemy|
    [
      [enemy.x + 4, enemy.y + 29, "id: #{enemy.entity_id}", -3, 0],
      [enemy.x + 4, enemy.y + 17, "created_at: #{enemy.created_at}", -3, 0] # frame enemy was created
    ]
  end

  # Outputs bullets in bullets collection as rectangular solids
  args.outputs.solids << args.state.bullets.map(&:rect)
end

# Calls all methods necessary for performing calculations
def calc args
  add_new_enemies_if_needed args
  move_bullets args
  calculate_collisions args
  remove_bullets_of_screen args
end

# Adds enemies to the enemies collection and sets their values
def add_new_enemies_if_needed args
  return if args.state.enemies.length >= 10 # if 10 or more enemies, enemies are not added
  return unless args.state.bullets.length == 0 # if user has not yet shot bullet, no enemies are added

  args.state.enemies += (10 - args.state.enemies.length).map do # adds enemies so there are 10 total
    args.state.new_entity(:enemy) do |e| # each enemy is declared as a new entity
      e.x = 640 + 500 * rand # each enemy is given random position on screen
      e.y = 600 * rand + 50
      e.rect = [e.x, e.y, 130, 30] # sets definition for enemy's rect
    end
  end
end

# Moves bullets across screen
# Sets definition of the bullets
def move_bullets args
  args.state.bullets.each do |bullet| # perform action on each bullet in collection
    bullet.x += bullet.speed # increment x by speed (bullets fly horizontally across screen)

    # By randomizing the value that increments bullet.y, the bullet does not fly straight up and out
    # of the scope of the screen. Try removing what follows bullet.speed, or changing 0.25 to 1.25 to
    # see what happens to the bullet's movement.
    bullet.y += bullet.speed.*(0.25).randomize(:ratio, :sign)
    bullet.rect = [bullet.x, bullet.y, bullet.size, bullet.size] # sets definition of bullet's rect
  end
end

# Determines if a bullet hits an enemy
def calculate_collisions args
  args.state.bullets.each do |bullet| # perform action on every bullet and enemy in collections
    args.state.enemies.each do |enemy|
      # if bullet has not exploded yet and the bullet hits an enemy
      if !bullet.exploded && bullet.rect.intersect_rect?(enemy.rect)
        bullet.exploded = true # bullet explodes
        enemy.dead = true # enemy is killed
      end
    end
  end

  # All exploded bullets are rejected or removed from the bullets collection
  # and any dead enemy is rejected from the enemies collection.
  args.state.bullets = args.state.bullets.reject(&:exploded)
  args.state.enemies = args.state.enemies.reject(&:dead)
end

# Bullets are rejected from bullets collection once their position exceeds the width of screen
def remove_bullets_of_screen args
  args.state.bullets = args.state.bullets.reject { |bullet| bullet.x > 1280 } # screen width is 1280
end

# Calls fire_bullet method
def process_inputs args
  fire_bullet args
end

# Once mouse is clicked by the user to fire a bullet, a new bullet is added to bullets collection
def fire_bullet args
  return unless args.inputs.mouse.click # return unless mouse is clicked
  args.state.bullets << args.state.new_entity(:bullet) do |bullet| # new bullet is declared a new entity
    bullet.y = args.inputs.mouse.click.point.y # set to the y value of where the mouse was clicked
    bullet.x = 0 # starts on the left side of the screen
    bullet.size = 10
    bullet.speed = 10 * rand + 2 # speed of a bullet is randomized
    bullet.rect = [bullet.x, bullet.y, bullet.size, bullet.size] # definition is set
  end
end

def tick_instructions args, text, y = 715
  return if args.state.key_event_occurred
  if args.inputs.mouse.click ||
     args.inputs.keyboard.directional_vector ||
     args.inputs.keyboard.key_down.enter ||
     args.inputs.keyboard.key_down.space ||
     args.inputs.keyboard.key_down.escape
    args.state.key_event_occurred = true
  end

  args.outputs.debug << [0, y - 50, 1280, 60].solid
  args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
  args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
end