Module: Zif::Layers::Tileable

Included in:
ActiveBitmaskedTiledLayer, ActiveTiledLayer, BitmaskedTiledLayer, TiledLayer
Defined in:
lib/zif/layers/tileable.rb

Overview

Functionality shared between TiledLayer & BitmaskedTiledLayer

Modifies the layer to be based around the concept of a “logical” position. This is used to differentiate between the “natural” x, y position on the screen. The “logical” position is instead the natural position divided by the tile size, and is used to index the sprites array.

For performance reasons, the sprites array is still implemented as one-dimensional array, but the idea is that it could have been implemented as a two-dimensional array of rows (logical_x) and columns (logical_y).

Add sprites to the layer using #add_positioned_sprite which sets the logical position on the sprite.

Instance Method Summary collapse

Instance Method Details

#add_positioned_sprite(sprite:, logical_x:, logical_y:) ⇒ Object

Overrides SimpleLayer#add_positioned_sprite or ActiveLayer#add_positioned_sprite Adds sprite to the @sprites array at the position indicated by logical_x and logical_y. Ensures the logical position is saved on the sprite.

Parameters:

  • sprite (Zif::Sprite)

    The sprite to add to @sprites

  • logical_x (Integer)

    The logical x position of sprite

  • logical_y (Integer)

    The logical y position of sprite



40
41
42
43
44
45
46
47
48
# File 'lib/zif/layers/tileable.rb', line 40

def add_positioned_sprite(sprite:, logical_x:, logical_y:)
  # puts "#{@layer_name}: Tileable#add_positioned_sprite #{logical_x} #{logical_y}"
  # puts "#{@sprites.class.to_s}"
  @sprites[tile_pos_to_sprite_index(logical_x, logical_y)] = position_sprite(
    sprite:    sprite,
    logical_x: logical_x,
    logical_y: logical_y
  )
end

#exclude_from_serializeObject



133
134
135
# File 'lib/zif/layers/tileable.rb', line 133

def exclude_from_serialize
  %w[sprites primitives]
end

#intersecting_sprites(left:, bottom:, right:, top:) ⇒ Object

Overrides Layerable#intersecting_sprites A convenience to calling #visible_sprites with boundary values instead of a [x,y,w,h] rect.

Parameters:

  • left (Integer)

    Look for sprites to the right of this value

  • bottom (Integer)

    Look for sprites above this value

  • right (Integer)

    Look for sprites to the left of this value

  • top (Integer)

    Look for sprites below this value



129
130
131
# File 'lib/zif/layers/tileable.rb', line 129

def intersecting_sprites(left:, bottom:, right:, top:)
  visible_sprites([left, bottom, right - left, top - bottom])
end

#reinitialize_spritesObject

Overrides SimpleLayer#reinitialize_sprites or ActiveLayer#reinitialize_sprites Clears the @sprites array, based on the logical_height and logical_width of the LayerGroup



28
29
30
31
32
# File 'lib/zif/layers/tileable.rb', line 28

def reinitialize_sprites
  super
  # puts "#{@layer_name}: Tileable#reinitialize_sprites #{@map.logical_height} #{@map.logical_width}"
  @sprites = Array.new(@map.logical_height * @map.logical_width)
end

#remove_positioned_sprite(sprite) ⇒ Object

Overrides Layerable#remove_positioned_sprite Remove a sprite which has already been added. Finds the sprite by calculating its position in the @sprites array using #tile_pos_to_sprite_index and the sprite‘s logical position.

Parameters:

  • sprite (Zif::Sprite)

    The sprite to remove from @sprites



54
55
56
# File 'lib/zif/layers/tileable.rb', line 54

def remove_positioned_sprite(sprite)
  @sprites[tile_pos_to_sprite_index(sprite.logical_x, sprite.logical_y)] = nil
end

#remove_tile(logical_x, logical_y) ⇒ Object

Clears the element in the @sprites array indicated by the logical position.

Parameters:

  • logical_x (Integer)

    The logical x position to clear.

  • logical_y (Integer)

    The logical y position to clear.



61
62
63
# File 'lib/zif/layers/tileable.rb', line 61

def remove_tile(logical_x, logical_y)
  @sprites[tile_pos_to_sprite_index(logical_x, logical_y)] = nil
end

#tile(logical_x, logical_y) ⇒ Zif::Sprite

Uses #tile_pos_to_sprite_index and returns the sprite at that position in @sprites

Returns:

  • (Zif::Sprite)

    The sprite in @sprites at the position indicated by logical_x & logical_y



22
23
24
# File 'lib/zif/layers/tileable.rb', line 22

def tile(logical_x, logical_y)
  @sprites[tile_pos_to_sprite_index(logical_x, logical_y)]
end

#tile_pos_to_sprite_index(logical_x, logical_y) ⇒ Integer

Convert logical_x and logical_y to the index of the sprite in the @sprites array.

Returns:

  • (Integer)

    Index of the sprite in the @sprites array.



16
17
18
# File 'lib/zif/layers/tileable.rb', line 16

def tile_pos_to_sprite_index(logical_x, logical_y)
  (logical_y * @map.logical_width) + logical_x
end

#visible_sprites(given_rect = nil) ⇒ Enumerator

Overrides Layerable#visible_sprites This returns an enumerator which can be used to iterate over only the tiles which are visible. Only for layers which have allocated_tiles!

Parameters:

  • given_rect (Array<Integer>) (defaults to: nil)

    [x, y, w, h] array to check for sprites in @sprites

Returns:

  • (Enumerator)

    An Enumerator to use to iterate over the Sprites selected by given_rect



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
# File 'lib/zif/layers/tileable.rb', line 70

def visible_sprites(given_rect=nil)
  # puts "Tileable#visible_sprites: #{@layer_name} '#{given_rect}'"
  if given_rect.nil?
    containing_sprite.view_actual_size! unless containing_sprite.source_is_set?
    left   = containing_sprite.source_x
    bottom = containing_sprite.source_y
    w      = containing_sprite.source_w
    h      = containing_sprite.source_h
  else
    left   = given_rect.x
    bottom = given_rect.y
    w      = given_rect.w
    h      = given_rect.h
  end

  logical_x = left.idiv(@map.tile_width)
  logical_y = bottom.idiv(@map.tile_height)
  x_range   = w.fdiv(@map.tile_width)
  y_range   = h.fdiv(@map.tile_height)

  max_y = [logical_y + y_range.ceil + 1, @map.logical_height].min
  max_x = [logical_x + x_range.ceil + 1, @map.logical_width].min

  # This enumerator is basically the equivalent of:
  #
  # @floor_tiles[logical_y..max_y].map do |x_tiles|
  #   x_tiles[logical_x..max_x]
  # end.flatten.to_enum
  #
  # The benefit of doing this instead is that we avoid some extraneous iteration and allocation if we call
  # #visible_tiles more than once per tick.  This definitely feels faster, but I haven't benchmarked.

  starting_a = [logical_x, 0].max
  starting_b = [logical_y, 0].max
  a = starting_a
  b = starting_b

  # puts " -> #{logical_x} #{logical_y} #{x_range} #{y_range} #{max_y} #{max_x} #{starting_a} #{starting_b} "

  Enumerator.new do |yielder|
    loop do
      next_tile = tile(a, b)
      yielder << next_tile if next_tile
      r, a = (a + 1).divmod(max_x)
      if r.positive?
        a = starting_a
        b += r
      end
      break if b > max_y
    end
  end
end