Module: GameClient

Includes:
EntityConstants
Included in:
GameWindow
Defined in:
lib/game_2d/game_client.rb

Overview

We put as many methods here as possible, so we can test them without instantiating an actual Gosu window

Constant Summary collapse

SCREEN_WIDTH =

in pixels

640
SCREEN_HEIGHT =

in pixels

480
DEFAULT_PORT =
4321
DEFAULT_KEY_SIZE =
1024

Constants included from EntityConstants

EntityConstants::CELL_WIDTH_IN_PIXELS, EntityConstants::MAX_VELOCITY, EntityConstants::PIXEL_WIDTH, EntityConstants::WIDTH

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#animationObject (readonly)

Returns the value of attribute animation.



49
50
51
# File 'lib/game_2d/game_client.rb', line 49

def animation
  @animation
end

#fontObject (readonly)

Returns the value of attribute font.



49
50
51
# File 'lib/game_2d/game_client.rb', line 49

def font
  @font
end

#player_idObject

Returns the value of attribute player_id.



50
51
52
# File 'lib/game_2d/game_client.rb', line 50

def player_id
  @player_id
end

#top_menuObject (readonly)

Returns the value of attribute top_menu.



49
50
51
# File 'lib/game_2d/game_client.rb', line 49

def top_menu
  @top_menu
end

Instance Method Details

#_make_client_connection(*args) ⇒ Object



82
83
84
# File 'lib/game_2d/game_client.rb', line 82

def _make_client_connection(*args)
  ClientConnection.new(*args)
end

#build_top_menuObject



106
107
108
# File 'lib/game_2d/game_client.rb', line 106

def build_top_menu
  @top_menu = MenuItem.new('Click for menu', self, @font) { main_menu }
end

#button_down(id) ⇒ Object



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
# File 'lib/game_2d/game_client.rb', line 209

def button_down(id)
  case id
    when Gosu::KbEnter, Gosu::KbReturn then
      if @dialog
        @dialog.enter
        @conn.start(@dialog.password_hash)
        @dialog = nil
      end
    when Gosu::KbP then
      @conn.send_ping unless @dialog
    when Gosu::KbEscape then @menu = @top_menu
    when Gosu::MsLeft then # left-click
      if new_menu = @menu.handle_click
        # If handle_click returned anything, the menu consumed the click
        # If it returned a menu, that's the new one we display
        @menu = (new_menu.respond_to?(:handle_click) ? new_menu : @top_menu)
      else
        send_fire
      end
    when Gosu::MsRight then # right-click
      if button_down?(Gosu::KbRightShift) || button_down?(Gosu::KbLeftShift)
        @create_npc_proc.call
      else
        toggle_grab
      end
    when Gosu::KbB then @create_npc_proc.call
    when Gosu::Kb1 then @create_npc_proc = make_block_npc_proc( 5).call
    when Gosu::Kb2 then @create_npc_proc = make_block_npc_proc(10).call
    when Gosu::Kb3 then @create_npc_proc = make_block_npc_proc(15).call
    when Gosu::Kb4 then @create_npc_proc = make_block_npc_proc(20).call
    when Gosu::Kb5 then @create_npc_proc = make_block_npc_proc(25).call
    when Gosu::Kb6 then @create_npc_proc = make_block_npc_proc( 0).call
    when Gosu::Kb7 then @create_npc_proc = make_teleporter_npc_proc.call
    else @pressed_buttons << id unless @dialog
  end
end

#clear_messageObject



102
103
104
# File 'lib/game_2d/game_client.rb', line 102

def clear_message
  @message = nil
end

#display_message(*lines) ⇒ Object



86
87
88
89
90
91
92
# File 'lib/game_2d/game_client.rb', line 86

def display_message(*lines)
  if @message
    @message.lines = lines
  else
    @message = Message.new(self, @font, lines)
  end
end

#display_message!(*lines) ⇒ Object

Ensure the message is drawn at least once



97
98
99
100
# File 'lib/game_2d/game_client.rb', line 97

def display_message!(*lines)
  display_message(*lines)
  sleep 0.01 until message_drawn?
end

#grab_specific(registry_id) ⇒ Object



311
312
313
# File 'lib/game_2d/game_client.rb', line 311

def grab_specific(registry_id)
  @grabbed_entity_id = registry_id
end

#handle_inputObject

Dequeue an input event



330
331
332
333
334
# File 'lib/game_2d/game_client.rb', line 330

def handle_input
  return if player.falling? || @dialog
  move = move_for_keypress
  @conn.send_move move # also creates a delta in the engine
end

#initialize_from_hash(opts = {}) ⇒ Object



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
# File 'lib/game_2d/game_client.rb', line 52

def initialize_from_hash(opts = {})
  player_name = opts[:name]
  hostname = opts[:hostname]
  port = opts[:port] || DEFAULT_PORT
  key_size = opts[:key_size] || DEFAULT_KEY_SIZE
  profile = opts[:profile] || false

  @conn_update_total = @engine_update_total = 0.0
  @conn_update_count = @engine_update_count = 0
  @profile = profile

  self.caption = "Game 2D"

  @pressed_buttons = []

  @snap_to_grid = false

  @create_npc_proc = make_block_npc_proc(5)

  @grabbed_entity_id = nil

  @run_start = Time.now.to_f
  @update_count = 0

  @conn = _make_client_connection(hostname, port, self, player_name, key_size)
  @engine = @conn.engine = ClientEngine.new(self)
  @menu = build_top_menu
  @dialog = PasswordDialog.new(self, @font)
end


110
111
112
113
114
115
# File 'lib/game_2d/game_client.rb', line 110

def main_menu
  Menu.new('Main menu', self, @font,
    MenuItem.new('Object creation', self, @font) { object_creation_menu },
    MenuItem.new('Quit!', self, @font) { shutdown }
  )
end

#make_block_npc_proc(hp) ⇒ Object



276
277
278
279
# File 'lib/game_2d/game_client.rb', line 276

def make_block_npc_proc(hp)
  type = hp.zero? ? 'Entity::Titanium' : 'Entity::Block'
  proc { send_create_npc type, :hp => hp }
end

#make_destination_npc_procObject



287
288
289
290
291
292
# File 'lib/game_2d/game_client.rb', line 287

def make_destination_npc_proc
  proc do |teleporter|
    send_create_npc 'Entity::Destination', :owner => teleporter.registry_id,
      :on_create => make_grab_destination_proc
  end
end

#make_grab_destination_procObject



294
295
296
297
298
# File 'lib/game_2d/game_client.rb', line 294

def make_grab_destination_proc
  proc do |destination|
    grab_specific destination.registry_id
  end
end

#make_teleporter_npc_procObject



281
282
283
284
285
# File 'lib/game_2d/game_client.rb', line 281

def make_teleporter_npc_proc
  proc do
    send_create_npc 'Entity::Teleporter', :on_create => make_destination_npc_proc
  end
end

#media(filename) ⇒ Object



149
150
151
# File 'lib/game_2d/game_client.rb', line 149

def media(filename)
  "#{File.dirname __FILE__}/../../media/#{filename}"
end

#message_drawn?Boolean

Returns:

  • (Boolean)


94
# File 'lib/game_2d/game_client.rb', line 94

def message_drawn?; @message.try(:drawn?); end

#mouse_coordsObject

X/Y position of the mouse (center of the crosshairs), adjusted for camera



255
256
257
258
259
260
261
# File 'lib/game_2d/game_client.rb', line 255

def mouse_coords
  # For some reason, Gosu's mouse_x/mouse_y return Floats, so round it off
  [
    (mouse_x.round + @camera_x) * PIXEL_WIDTH,
    (mouse_y.round + @camera_y) * PIXEL_WIDTH
  ]
end

#mouse_entity_locationObject



263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/game_2d/game_client.rb', line 263

def mouse_entity_location
  x, y = mouse_coords

  if @snap_to_grid
    # When snap is on, we want the upper-left corner of the cell we point at
    return (x / WIDTH) * WIDTH, (y / HEIGHT) * HEIGHT
  else
    # When snap is off, we are pointing at the entity's center, not
    # its upper-left corner
    return x - WIDTH / 2, y - HEIGHT / 2
  end
end

#move_for_keypressObject

Check keyboard, mouse, and pressed-button queue Return a motion symbol or nil



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/game_2d/game_client.rb', line 338

def move_for_keypress
  # Generated once for each keypress
  until @pressed_buttons.empty?
    button = @pressed_buttons.shift
    case button
      when Gosu::KbUp, Gosu::KbW
        return (player.building?) ? :rise_up : :flip
    end
  end

  # Continuously-generated when key held down
  case
    when button_down?(Gosu::KbLeft), button_down?(Gosu::KbA)
      :slide_left
    when button_down?(Gosu::KbRight), button_down?(Gosu::KbD)
      :slide_right
    when button_down?(Gosu::KbRightControl), button_down?(Gosu::KbLeftControl)
      :brake
    when button_down?(Gosu::KbDown), button_down?(Gosu::KbS)
      :build
  end
end

#move_grabbed_entity(divide_by = ClientConnection::ACTION_DELAY) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/game_2d/game_client.rb', line 197

def move_grabbed_entity(divide_by = ClientConnection::ACTION_DELAY)
  return unless @grabbed_entity_id
  return unless grabbed = space[@grabbed_entity_id]
  dest_x, dest_y = mouse_entity_location
  vel_x = Entity.constrain_velocity((dest_x - grabbed.x) / divide_by)
  vel_y = Entity.constrain_velocity((dest_y - grabbed.y) / divide_by)
  @conn.send_update_entity(
    :registry_id => grabbed.registry_id,
    :velocity => [vel_x, vel_y],
    :moving => true)
end

#object_creation_menuObject



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/game_2d/game_client.rb', line 117

def object_creation_menu
  snap_text = lambda do |item|
    @snap_to_grid ? "Turn snap off" : "Turn snap on"
  end

  Menu.new('Object creation', self, @font,
    MenuItem.new('Object type', self, @font) { object_type_menu },
    MenuItem.new(snap_text, self, @font) do
      @snap_to_grid = !@snap_to_grid
    end,
    MenuItem.new('Save!', self, @font) { @conn.send_save }
  )
end

#object_type_menuObject



131
132
133
# File 'lib/game_2d/game_client.rb', line 131

def object_type_menu
  Menu.new('Object type', self, @font, *object_type_submenus)
end

#object_type_submenusObject



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/game_2d/game_client.rb', line 135

def object_type_submenus
  [
    ['Dirt',        make_block_npc_proc( 5)],
    ['Brick',       make_block_npc_proc(10)],
    ['Cement',      make_block_npc_proc(15)],
    ['Steel',       make_block_npc_proc(20)],
    ['Unlikelium',  make_block_npc_proc(25)],
    ['Titanium',    make_block_npc_proc( 0)],
    ['Teleporter',  make_teleporter_npc_proc],
  ].collect do |type_name, p|
    MenuItem.new(type_name, self, @font) { @create_npc_proc = p }
  end
end

#playerObject



161
162
163
# File 'lib/game_2d/game_client.rb', line 161

def player
  space[@player_id]
end

#send_create_npc(type, args) ⇒ Object



300
301
302
303
304
305
306
307
308
309
# File 'lib/game_2d/game_client.rb', line 300

def send_create_npc(type, args)
  args.merge!(
    :class => type,
    :position => mouse_entity_location,
    :velocity => [0, 0],
    :angle => 0,
    :moving => true
  )
  @conn.send_create_npc(args)
end

#send_fireObject



246
247
248
249
250
251
252
# File 'lib/game_2d/game_client.rb', line 246

def send_fire
  return unless @player_id
  x, y = mouse_coords
  x_vel = (x - (player.x + WIDTH / 2)) / PIXEL_WIDTH
  y_vel = (y - (player.y + WIDTH / 2)) / PIXEL_WIDTH
  @conn.send_move :fire, :x_vel => x_vel, :y_vel => y_vel
end

#shutdownObject



324
325
326
327
# File 'lib/game_2d/game_client.rb', line 324

def shutdown
  @conn.disconnect
  close
end

#spaceObject



153
154
155
# File 'lib/game_2d/game_client.rb', line 153

def space
  @engine.space
end

#tickObject



157
158
159
# File 'lib/game_2d/game_client.rb', line 157

def tick
  @engine.tick
end

#toggle_grabObject



315
316
317
318
319
320
321
322
# File 'lib/game_2d/game_client.rb', line 315

def toggle_grab
  if @grabbed_entity_id
    @conn.send_snap_to_grid(space[@grabbed_entity_id]) if @snap_to_grid
    return @grabbed_entity_id = nil
  end

  @grabbed_entity_id = space.near_to(*mouse_coords).nullsafe_registry_id
end

#updateObject



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
# File 'lib/game_2d/game_client.rb', line 165

def update
  @update_count += 1

  return unless @conn.online?

  # Handle any pending ENet events
  before_t = Time.now.to_f
  @conn.update
  if @profile
    @conn_update_total += (Time.now.to_f - before_t)
    @conn_update_count += 1
    $stderr.puts "@conn.update() averages #{@conn_update_total / @conn_update_count} seconds each" if (@conn_update_count % 60) == 0
  end
  return unless @engine.world_established?

  before_t = Time.now.to_f
  @engine.update
  if @profile
    @engine_update_total += (Time.now.to_f - before_t)
    @engine_update_count += 1
    $stderr.puts "@engine.update() averages #{@engine_update_total / @engine_update_count} seconds" if (@engine_update_count % 60) == 0
  end

  # Player at the keyboard queues up a command
  # @pressed_buttons is emptied by handle_input
  handle_input if @player_id

  move_grabbed_entity

  $stderr.puts "Updates per second: #{@update_count / (Time.now.to_f - @run_start)}" if @profile
end