Class: Zif::Layers::LayerGroup

Inherits:
Object
  • Object
show all
Includes:
Traceable
Defined in:
lib/zif/layers/layer_group.rb

Overview

Designed to be used with Camera.

Creates a set of overlapping play area layers based on SimpleLayer (RenderTarget) or ActiveLayer (CompoundSprite) and handles redrawing them.

Has a concept of logical position as a multiple of tile width/height, applicable to any Tileable layers. For example, if your tiles are 16px wide, the 5th tile is at @logical_x==4 but at @x==64 on the layer.

Of all things in the Zif library, this is probably the most opinionated - It’s for a 2d game made of rectangular, non-overlapping tiles where the play area is larger than the screen. As always YMMV

As an example, you can have a “tiles” layer which gets redrawn only at the start of the game, an “interactive objects” layer which gets redrawn whenever objects appear or disappear, and then an “avatar” layer which gets redrawn constantly. The advantage of using RenderTargets and CompoundSprites here is to keep the positioning consistent across all of the layers. You can just pass all of the containing sprites to Camera and it will pan them all in unison.

You setup and configure these layers via #new_simple_layer, #new_tiled_layer, etc.

Performance notes:

  • The memory requirements for anything based on SimpleLayer is the number of layers times the area of each layer. Consider using ActiveLayer if you have a lot of layers with few sprites in them.

  • It is expensive to redraw a RenderTarget (SimpleLayer) with thousands of sprites. Consider: 1280x720 (screen size) / 16x16 (a small tile) -> 80*45 tiles = 3600 tiles. Of course it’s more expensive to draw every tile every tick (using ActiveLayer), but you will see noticable hiccups if you redraw the render target often. Try not to redraw RenderTargets with lots of sprites while action is happening.

  • The takeaway is this: try to organize your layers between things that change often, and things that can be prerendered or need few updates duing play. Then choose between SimpleLayer and ActiveLayer as appropriate.

Examples:

Setting up a group of 2 layers

tile_width_and_height         = 64  # Each tile is 64x64 pixels
map_width_and_height_in_tiles = 100 # 64 * 100 = 6400x6400 pixels, 10000 tiles total

@map = Zif::Layers::LayerGroup.new(
  tile_width:     tile_width_and_height,
  tile_height:    tile_width_and_height,
  logical_width:  map_width_and_height_in_tiles,
  logical_height: map_width_and_height_in_tiles
)

# This example is only using Zif::Layers::ActiveLayer because they are easier to set up,
# and it is a good place to start in terms of performance.
@map.new_active_tiled_layer(:tiles)
@map.new_active_layer(:avatar)

@map.layers[:avatar].source_sprites = [@dragon]

# Add a bunch of tiles
a_new_tile = Zif::Sprite.new....
@map.layers[:tiles].add_positioned_sprite(sprite: a_new_tile, logical_x: x, logical_y: y)

# Set up a camera
@camera = Zif::Camera.new(
  layer_sprites: @map.layer_containing_sprites,
  initial_x: 1800,
  initial_y: 1200
)

$gtk.args.outputs.static_sprites << @camera.layers

# All set!  You can move your sprites (like @dragon) around.  You can control the Camera using actions.
# Most or all of the above code could be placed in a Zif::Scene#prepare_scene method.

Instance Attribute Summary collapse

Attributes included from Traceable

#tracer_service_name

Instance Method Summary collapse

Methods included from Traceable

#mark, #mark_and_print, #mark_prefix, #tracer

Constructor Details

#initialize(name: 'map', tile_width: 64, tile_height: 64, logical_width: 100, logical_height: 100) ⇒ LayerGroup

Returns a new instance of LayerGroup.

Parameters:

  • name (Symbol, String) (defaults to: 'map')

    #name The name of the layer group

  • tile_width (Integer) (defaults to: 64)

    #tile_width Pixel width of each tile

  • tile_height (Integer) (defaults to: 64)

    #tile_height Pixel height of each tile

  • logical_width (Integer) (defaults to: 100)

    #logical_width Integer multiple of tiles.

  • logical_height (Integer) (defaults to: 100)

    #logical_height Integer multiple of tiles.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/zif/layers/layer_group.rb', line 91

def initialize(name: 'map',
               tile_width: 64,
               tile_height: 64,
               logical_width: 100,
               logical_height: 100)
  @name           = name
  @tile_width     = tile_width
  @tile_height    = tile_height
  @logical_width  = logical_width
  @logical_height = logical_height
  @z_index        = 0

  @layers = {}

  @tracer_service_name = :tracer
end

Instance Attribute Details

#layersHash<(Symbol, String), Zif::Layers::Layerable>

Returns All of the layers in this group, indexed by their name.

Returns:



84
85
86
# File 'lib/zif/layers/layer_group.rb', line 84

def layers
  @layers
end

#logical_heightInteger

Returns Integer multiple of tiles. How many tiles high is the whole map?.

Returns:

  • (Integer)

    Integer multiple of tiles. How many tiles high is the whole map?



80
81
82
# File 'lib/zif/layers/layer_group.rb', line 80

def logical_height
  @logical_height
end

#logical_widthInteger

Returns Integer multiple of tiles. How many tiles wide is the whole map?.

Returns:

  • (Integer)

    Integer multiple of tiles. How many tiles wide is the whole map?



78
79
80
# File 'lib/zif/layers/layer_group.rb', line 78

def logical_width
  @logical_width
end

#nameSymbol, String

Returns The name of the layer group.

Returns:

  • (Symbol, String)

    The name of the layer group



72
73
74
# File 'lib/zif/layers/layer_group.rb', line 72

def name
  @name
end

#tile_heightInteger

Returns Pixel height of each tile.

Returns:

  • (Integer)

    Pixel height of each tile



76
77
78
# File 'lib/zif/layers/layer_group.rb', line 76

def tile_height
  @tile_height
end

#tile_widthInteger

Returns Pixel width of each tile.

Returns:

  • (Integer)

    Pixel width of each tile



74
75
76
# File 'lib/zif/layers/layer_group.rb', line 74

def tile_width
  @tile_width
end

#z_indexInteger (readonly)

Returns The current stacking layer index. Starts at 0 and is incremented for every new layer.

Returns:

  • (Integer)

    The current stacking layer index. Starts at 0 and is incremented for every new layer



82
83
84
# File 'lib/zif/layers/layer_group.rb', line 82

def z_index
  @z_index
end

Instance Method Details

#add_layer(name, layer) ⇒ Zif::Layers::Layerable

Adds a layer to the layer group. Registers it in #layers and increments #z_index.



110
111
112
113
114
# File 'lib/zif/layers/layer_group.rb', line 110

def add_layer(name, layer)
  @layers[name] = layer
  @z_index += 1
  return @layers[name]
end

#force_refreshObject

Iterate through each in #layers, set should_render to true, rerender, and set it back to what it was Useful for first-time setup of render targets, during init



219
220
221
222
223
224
225
226
# File 'lib/zif/layers/layer_group.rb', line 219

def force_refresh
  @layers.each do |_name, layer|
    render_setting = layer.should_render
    layer.should_render = true
    layer.rerender
    layer.should_render = render_setting
  end
end

#inspectObject



263
264
265
# File 'lib/zif/layers/layer_group.rb', line 263

def inspect
  serialize.to_s
end

#layer_containing_spritesArray<Zif::Sprite>

Returns The containing sprite for each layer. For active layers this returns the layer itself For simple layers it returns the render target containing_sprite.

Returns:

  • (Array<Zif::Sprite>)

    The containing sprite for each layer. For active layers this returns the layer itself For simple layers it returns the render target containing_sprite



236
237
238
# File 'lib/zif/layers/layer_group.rb', line 236

def layer_containing_sprites
  @layers.values.map(&:containing_sprite)
end

#layer_namesArray<Symbol, String>

Returns The names of each registered layer.

Returns:

  • (Array<Symbol, String>)

    The names of each registered layer



229
230
231
# File 'lib/zif/layers/layer_group.rb', line 229

def layer_names
  @layers.keys
end

#logical_pos(x, y) ⇒ Array<Integer>

Converts pixel x/y to logical x/y (rounded down)

Examples:

With 16x16 tiles

logical_pos(20, 20) # => [1, 1]

Parameters:

  • x (Integer)

    The x position to convert to logical

  • y (Integer)

    The y position to convert to logical

Returns:

  • (Array<Integer>)

    The resulting logical point [logical_x, logical_y]



210
211
212
213
214
215
# File 'lib/zif/layers/layer_group.rb', line 210

def logical_pos(x, y)
  [
    (x / @tile_width).floor,
    (y / @tile_height).floor
  ]
end

#max_heightInteger

Returns #tile_height times #logical_height.

Returns:



183
184
185
# File 'lib/zif/layers/layer_group.rb', line 183

def max_height
  @tile_height * @logical_height
end

#max_widthInteger

Returns #tile_width times #logical_width.

Returns:



178
179
180
# File 'lib/zif/layers/layer_group.rb', line 178

def max_width
  @tile_width * @logical_width
end

#natural_rect(logical_x, logical_y, x_range, y_range) ⇒ Array<Integer>

Converts a logical rect to a natural one

Examples:

With 16x16 tiles

natural_rect(10, 10, 1, 1) # => [160, 160, 16, 16]

Parameters:

  • logical_x (Integer)

    The logical x position to start the rect at

  • logical_y (Integer)

    The logical y position to start the rect at

  • x_range (Integer)

    The number of tiles wide the rect contains

  • y_range (Integer)

    The number of tiles high the rect contains

Returns:

  • (Array<Integer>)

    The resulting natural rect [x, y, w, h]



195
196
197
198
199
200
201
202
# File 'lib/zif/layers/layer_group.rb', line 195

def natural_rect(logical_x, logical_y, x_range, y_range)
  [
    logical_x * @tile_width,
    logical_y * @tile_width,
    x_range * @tile_width,
    y_range * @tile_width
  ]
end

#new_active_bitmasked_tiled_layer(name) ⇒ Zif::Layers::ActiveBitmaskedTiledLayer

Creates a new ActiveBitmaskedTiledLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the new layer

Returns:



173
174
175
# File 'lib/zif/layers/layer_group.rb', line 173

def new_active_bitmasked_tiled_layer(name)
  add_layer(name, Zif::Layers::ActiveBitmaskedTiledLayer.new(self, name, z_index: @z_index))
end

#new_active_layer(name) ⇒ Zif::Layers::ActiveLayer

Creates a new ActiveLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the new layer

Returns:



119
120
121
# File 'lib/zif/layers/layer_group.rb', line 119

def new_active_layer(name)
  add_layer(name, Zif::Layers::ActiveLayer.new(self, name, z_index: @z_index))
end

#new_active_tiled_layer(name) ⇒ Zif::Layers::ActiveTiledLayer

Creates a new ActiveTiledLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the new layer

Returns:



155
156
157
# File 'lib/zif/layers/layer_group.rb', line 155

def new_active_tiled_layer(name)
  add_layer(name, Zif::Layers::ActiveTiledLayer.new(self, name, z_index: @z_index))
end

#new_bitmasked_tiled_layer(name, render_only_visible: false) ⇒ Zif::Layers::BitmaskedTiledLayer

Creates a new BitmaskedTiledLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the new layer

  • render_only_visible (Boolean) (defaults to: false)

    When rerendering, should this layer use #visible_sprites?

Returns:



163
164
165
166
167
168
# File 'lib/zif/layers/layer_group.rb', line 163

def new_bitmasked_tiled_layer(name, render_only_visible: false)
  add_layer(
    name,
    Zif::Layers::BitmaskedTiledLayer.new(self, name, z_index: @z_index, render_only_visible: render_only_visible)
  )
end

#new_simple_layer(name, render_only_visible: false, clear_sprites_after_draw: false) ⇒ Zif::Layers::SimpleLayer

Creates a new SimpleLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the layer

  • render_only_visible (Boolean) (defaults to: false)

    When rerendering, should this layer use #visible_sprites?

  • clear_sprites_after_draw (Boolean) (defaults to: false)

    When rerendering, should this layer clear the @sprites array?

Returns:



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/zif/layers/layer_group.rb', line 128

def new_simple_layer(name, render_only_visible: false, clear_sprites_after_draw: false)
  add_layer(
    name,
    Zif::Layers::SimpleLayer.new(
      self,
      name,
      z_index:                  @z_index,
      render_only_visible:      render_only_visible,
      clear_sprites_after_draw: clear_sprites_after_draw
    )
  )
end

#new_tiled_layer(name, render_only_visible: false) ⇒ Zif::Layers::TiledLayer

Creates a new TiledLayer and sends it through #add_layer

Parameters:

  • name (Symbol, String)

    The name of the new layer

  • render_only_visible (Boolean) (defaults to: false)

    When rerendering, should this layer use #visible_sprites?

Returns:



145
146
147
148
149
150
# File 'lib/zif/layers/layer_group.rb', line 145

def new_tiled_layer(name, render_only_visible: false)
  add_layer(
    name,
    Zif::Layers::TiledLayer.new(self, name, z_index: @z_index, render_only_visible: render_only_visible)
  )
end

#refreshObject

Calls #rerender on each in #layers



241
242
243
244
245
246
247
248
249
# File 'lib/zif/layers/layer_group.rb', line 241

def refresh
  @layers.each do |layer_name, layer|
    mark("#refresh: Rerendering #{layer_name}")
    layer.rerender
    mark("#refresh: Rerendered #{layer_name}")
  end

  mark('#refresh: Rerendered all layers')
end

#serializeObject



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/zif/layers/layer_group.rb', line 251

def serialize
  {
    name:           name,
    tile_width:     tile_width,
    tile_height:    tile_height,
    logical_width:  logical_width,
    logical_height: logical_height,
    z_index:        z_index,
    layers:         layers.keys
  }
end

#to_sObject



267
268
269
# File 'lib/zif/layers/layer_group.rb', line 267

def to_s
  serialize.to_s
end