class AuthorEngine::GameRunner

Attributes

fps[R]
game[R]
levels[R]
save_file[R]
sprites[R]
spritesheet[R]

Public Class Methods

instance() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 3
def self.instance
  @instance
end
instance=(klass) click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 6
def self.instance=(klass)
  @instance = klass
end
new(project_string) click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 14
def initialize(project_string)
  AuthorEngine::GameRunner.instance=(self)

  @save_file = AuthorEngine::SaveFile.new(nil)
  @save_file.load(false, project_string)

  @sprites = []
  @spritesheet = nil
  @spritesheet_width  = @save_file.sprites.columns
  @spritesheet_height = @save_file.sprites.rows
  @sprite_size = 16

  @game = Game.new(code: @save_file.code)
  resize_canvas

  @fps = 0
  @counted_frames = 0
  @frame_count_stated_at = 0

  @show_touch_controls = false

  @game_loaded = false

  @loader_tasks = [
    [
      "Loading levels",
      proc {
        @levels  = @save_file.levels
        @levels.each {|level| level.each {|sprite| sprite.x = sprite.x * @sprite_size; sprite.y = sprite.y * @sprite_size}}
      }
    ],

    [
      "Evaluating game",
      proc {
        @game.authorengine_eval_code
      },
    ],

    [
      "Loading sprites",
      proc {
        build_spritesheet_and_sprites_list
      },
    ],

    [
      "Setting up collision detection",
      proc {
        @collision_detection = AuthorEngine::CollisionDetection.new(@sprites, @levels, @save_file.sprites)
        @game.authorengine_collision_detection = @collision_detection
      },
    ],

    [
      "Initializing game",
      proc {
        @game.init
      },
    ],

    [
      "Setting up touch controls",
      proc {
        @touch_joystick = TouchJoystick.new(radius: 50)
        @touch_buttons = []
        @touch_buttons.push(
          TouchButton.new(
            label: "X", color: @game.red, width: 50, height: 50, for_key: "x"
            ),
          TouchButton.new(
            label: "Y", color: @game.yellow, width: 50, height: 50, for_key: "y"
          )
        )

        @fullscreen_button = TouchButton.new(label: "Fullscreen", color: @game.black, width: 100, height: 50)
        touch_handler_setup
        reposition_touch_controls
      },
    ],

    [
      "Loading done",
      proc {
        @game_loaded = true
      },
    ],
  ]

  return self
end

Public Instance Methods

build_spritesheet_and_sprites_list() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 266
def build_spritesheet_and_sprites_list
  spritesheet_data = @save_file.sprites
  width = spritesheet_data.columns
  height= spritesheet_data.rows
  size  = 16

  temp_canvas = `document.createElement('canvas')`
  temp_canvas_context = `#{temp_canvas}.getContext('2d')`
  `#{temp_canvas}.width  = #{spritesheet_data.columns}`
  `#{temp_canvas}.height = #{spritesheet_data.rows}`

  buffer = `new Uint8ClampedArray(#{spritesheet_data.to_blob})`
  image_data = `new ImageData(#{buffer}, #{width})`
  `#{temp_canvas_context}.putImageData(#{image_data}, 0, 0)`

  @spritesheet = `new Image()`
  `#{@spritesheet}.onload = function() { #{load_sprites} }`
  `#{@spritesheet}.src = #{temp_canvas}.toDataURL()`

end
draw() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 106
def draw
  @game.draw_background
  @game.draw
  return nil
end
draw_touch_controls() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 177
def draw_touch_controls
  @fullscreen_button.draw
  @touch_buttons.each(&:draw)
  @touch_joystick.draw
end
fullscreen_changed() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 310
def fullscreen_changed
  resize_canvas
end
load_sprites() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 287
def load_sprites
  spritesheet_data = @save_file.sprites
  width = spritesheet_data.columns
  height= spritesheet_data.rows
  size  = 16

  temp_canvas = `document.createElement('canvas')`
  temp_canvas_context = `#{temp_canvas}.getContext('2d')`
  `#{temp_canvas}.width  = #{size}`
  `#{temp_canvas}.height = #{size}`

  (height/size).times do |y|
    (width/size).times do |x|
      `#{temp_canvas_context}.clearRect(0,0, #{size}, #{size})`
      `#{temp_canvas_context}.drawImage(#{@spritesheet}, #{x * size}, #{y * size}, #{size}, #{size}, 0, 0, #{size}, #{size})`

      `createImageBitmap(#{@spritesheet}, #{x * size}, #{y * size}, #{size}, #{size}).then(sprite => { #{@sprites.push(`sprite`)} })`
    end
  end

  return nil
end
reposition_touch_controls() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 188
def reposition_touch_controls
  return nil unless @touch_joystick

  width  = `window.innerWidth`
  height = `window.innerHeight`
  game_width = 128 * @game.authorengine_scale
  game_height = 128 * @game.authorengine_scale

  # place controls under game
  if width < height
    area_width  = width
    area_height = height - game_height

    puts "space: width #{area_width} x height #{area_height}"

    @touch_joystick.x = @touch_joystick.radius + @touch_joystick.radius
    @touch_joystick.y = game_height + area_height / 2

    padding = 10
    last_x = 20
    @touch_buttons.reverse.each do |button|
      button.x = width - (last_x + button.width)
      button.y = (height - area_height) + area_height / 2 - button.height / 2

      last_x += button.width + padding
    end

    @fullscreen_button.x = width - (width / 2 + @fullscreen_button.width / 2)
    @fullscreen_button.y = height - @fullscreen_button.height

  # place controls beside game
  else
    area_width  = (`window.innerWidth` - game_width) / 2
    area_height = game_height

    puts "space: width #{area_width} x height #{area_height}"

    @touch_joystick.x = @touch_joystick.radius + @touch_joystick.radius
    @touch_joystick.y = game_height / 2

    padding = 10
    last_x = 50
    @touch_buttons.reverse.each do |button|
      button.x = width - (last_x + button.width)
      button.y = game_height / 2 - button.height / 2

      last_x += button.width + padding
    end

    @fullscreen_button.x = width - @fullscreen_button.width
    @fullscreen_button.y = 0
  end

  return nil
end
resize_canvas() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 244
def resize_canvas
  width  = `window.innerWidth`
  height = `window.innerHeight`

  if width < height
    @game.authorengine_scale = `#{width} / 128.0`
  else
    @game.authorengine_scale = `#{height} / 128.0`
  end

  `#{@game.authorengine_canvas}.width  = #{width}`
  `#{@game.authorengine_canvas}.height = #{height}`
  `#{@game.authorengine_canvas}.style.width  = #{width}`
  `#{@game.authorengine_canvas}.style.height = #{height}`

  `#{@game.authorengine_canvas_context}.imageSmoothingEnabled = false`

  reposition_touch_controls
  `#{@game.authorengine_canvas_context}.clearRect(0, 0, window.innerWidth, window.innerHeight)`
  return nil
end
run_game() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 117
def run_game
  `window.requestAnimationFrame(function() {#{run_game}})` # placed here to ensure next frame is called even if draw or update throw an error
  width  = `window.innerWidth`
  height = `window.innerHeight`
  game_width = 128 * @game.authorengine_scale
  game_height = 128 * @game.authorengine_scale

  area_width  = (`window.innerWidth` - game_width) / 2

  `#{@game.authorengine_canvas_context}.clearRect(#{area_width},0, #{game_width}, #{game_height})`

  @counted_frames+=1

  if @game.milliseconds - @frame_count_stated_at >= 1000.0
    @fps = @counted_frames
    @frame_count_stated_at = @game.milliseconds
    @counted_frames = 0
  end

  `#{@game.authorengine_canvas_context}.save()`
  `#{@game.authorengine_canvas_context}.translate(window.innerWidth/2 - #{game_height/2}, 0)`
  `#{@game.authorengine_canvas_context}.scale(#{@game.authorengine_scale}, #{@game.authorengine_scale})`
  `#{@game.authorengine_canvas_context}.save()`

  region = `new Path2D()`
  `#{region}.rect(0, 0, 128, 128)`
  `#{@game.authorengine_canvas_context}.clip(#{region})`
  `#{@game.authorengine_canvas_context}.save()`


  if @game_loaded or @loader_tasks.empty?
    draw
    `#{@game.authorengine_canvas_context}.restore()`
    `#{@game.authorengine_canvas_context}.restore()`
    `#{@game.authorengine_canvas_context}.restore()`

    update

    if @show_touch_controls
      draw_touch_controls
      update_touch_controls
    end

  else
    task = @loader_tasks.shift
    @game.rect(0, 0, @game.width, @game.height, @game.dark_purple)
    @game.text("AuthorEngine v#{AuthorEngine::VERSION}", 2, @game.height / 2 - 20, 10)
    @game.text("#{task[0]}...", 6, @game.height / 2 - 4, 8, 0, @game.light_gray)
    @game.text("Empowered by Opal v#{Opal::VERSION}, a Ruby interpeter.", 4, @game.height - 6, 4, 0, @game.indigo)

    `#{@game.authorengine_canvas_context}.restore()`
    `#{@game.authorengine_canvas_context}.restore()`
    `#{@game.authorengine_canvas_context}.restore()`

    task[1].call
  end

  return nil
end
show(update_interval = (1000.0 / 60)) click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 314
def show(update_interval = (1000.0 / 60))
  return unless RUBY_ENGINE == "opal"

  `window.addEventListener('resize', () => { #{resize_canvas} })`
  `document.addEventListener('keydown', (event) => { #{@show_touch_controls = false; AuthorEngine::Part::OpalInput::KEY_STATES[`event.key`] = true} })`
  `document.addEventListener('keyup',   (event) => { #{AuthorEngine::Part::OpalInput::KEY_STATES[`event.key`] = false} })`

  `#{@game.authorengine_canvas}.addEventListener('touchstart',  (event) => { #{@show_touch_controls = true; handle_touch_start(`event`)} })`
  `#{@game.authorengine_canvas}.addEventListener('touchmove',   (event) => { #{handle_touch_move(`event`)} })`
  `#{@game.authorengine_canvas}.addEventListener('touchcancel', (event) => { #{handle_touch_cancel(`event`)} })`
  `#{@game.authorengine_canvas}.addEventListener('touchend',    (event) => { #{handle_touch_end(`event`)} })`

  `#{@game.authorengine_canvas}.addEventListener('fullscreenchange',    () => { #{fullscreen_changed} })`

  `document.getElementById('loading').style.display = "none"`

  `window.requestAnimationFrame(function() {#{run_game}})`
  return nil
end
update() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 112
def update
  @game.update
  return nil
end
update_touch_controls() click to toggle source
# File lib/author_engine/game/opal/game_runner.rb, line 183
def update_touch_controls
  @touch_buttons.each { |button| button.trigger?(@current_touches) }
  @touch_joystick.update(@current_touches)
end