Class: Zif::Layers::Camera

Inherits:
Object
  • Object
show all
Includes:
Actions::Actionable
Defined in:
lib/zif/layers/camera.rb

Overview

Designed to be used with LayerGroup.

The Camera is given a set of sprites, typically the containing sprites for a set of Layerables via LayerGroup#layer_containing_sprites.

It is responsible for directing the layers to reposition based on camera movements. Specifically, it alters each layer’s source_x and source_y values for panning.

This class includes Actions::Actionable, so you can pan the camera using a Actions::Action.

It has the capability of issuing camera movements based on following a particular sprite on a layer (like a player character).

It also has the capability of zooming in and out, by controlling each layer’s source_w and source_h. It can be registered as a scrollable with Services::InputSerive.

Constant Summary collapse

DEFAULT_SCREEN_WIDTH =
1280
DEFAULT_SCREEN_HEIGHT =
720

Instance Attribute Summary collapse

Attributes included from Actions::Actionable

#actions, #dirty

Instance Method Summary collapse

Methods included from Actions::Actionable

#bounce_forever_around, #delayed_action, #fade_in, #fade_out, #fade_out_and_in_forever, #new_action, #perform_actions, #run_action, #running_actions?, #stop_action

Constructor Details

#initialize(layer_sprites:, starting_width: DEFAULT_SCREEN_WIDTH, starting_height: DEFAULT_SCREEN_HEIGHT, initial_x: 4000, initial_y: 2000) ⇒ Camera

Setup vars, min/max camera position, arbitrary initial x/y Width/height should be an integer multiple of 16:9 ratio! This class expects to register itself with the Services::ActionService during initialize, at $game.services.named(:action_service)

Parameters:

  • layer_sprites (Array<Zif::Sprite>)

    #layers The width and height of these will be assigned to the screen parameters

  • starting_width (Integer) (defaults to: DEFAULT_SCREEN_WIDTH)

    Sets #cur_w

  • starting_height (Integer) (defaults to: DEFAULT_SCREEN_HEIGHT)

    Sets #cur_h

  • initial_x (Integer) (defaults to: 4000)

    The initial X position of the camera #pos_x, set via #move

  • initial_y (Integer) (defaults to: 2000)

    The initial Y position of the camera #pos_y, set via #move



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

def initialize(layer_sprites:,
               starting_width: DEFAULT_SCREEN_WIDTH,
               starting_height: DEFAULT_SCREEN_HEIGHT,
               initial_x: 4000,
               initial_y: 2000)

  @native_screen_width  = DEFAULT_SCREEN_WIDTH
  @native_screen_height = DEFAULT_SCREEN_HEIGHT

  # Camera views a game-window-sized chunk of target at zoom level 1.0
  # These mirror the source_w/h attrs on the sprite layers
  @cur_w = starting_width
  @cur_h = starting_height
  @max_w = layer_sprites.map(&:w).max
  @max_h = layer_sprites.map(&:h).max

  @max_x = @max_w - @cur_w
  @max_y = @max_h - @cur_h

  @layers = layer_sprites.map do |layer|
    layer.assign(
      w: @native_screen_width,
      h: @native_screen_height
    )
  end

  # These values are the allowable extremes for zooming, described as a multiple of the native screen width/height
  @max_zoom_in  = 0.5
  @max_zoom_out = 2.0

  # Each scroll action will increase or decrease zoom factor by this amount:
  @zoom_steps = [0.5, 0.8, 1.0, 1.28, 1.6, 2.0]
  # Index of above
  @zoom_step = 2

  self.cur_w = starting_width
  self.cur_h = starting_height

  @min_x = 0
  @min_y = 0
  @pos_x = 0
  @pos_y = 0

  # Default follow params
  @follow_margins  = [300, 600, 300, 600] # These values will be multiplied by the zoom factor
  @follow_duration = 0.5.seconds
  @follow_easing   = :smooth_stop

  # Following will not work unless registered with an ActionService
  $game&.services&.named(:action_service)&.register_actionable(self)

  move(initial_x, initial_y)
end

Instance Attribute Details

#cur_hFloat

Returns The current height of the camera (maps to source_h on layers).

Returns:

  • (Float)

    The current height of the camera (maps to source_h on layers)



55
56
57
# File 'lib/zif/layers/camera.rb', line 55

def cur_h
  @cur_h
end

#cur_wFloat

Returns The current width of the camera (maps to source_w on layers).

Returns:

  • (Float)

    The current width of the camera (maps to source_w on layers)



53
54
55
# File 'lib/zif/layers/camera.rb', line 53

def cur_w
  @cur_w
end

#follow_durationInteger

Returns How many ticks should camera following take? Defaults to 0.5 seconds.

Returns:

  • (Integer)

    How many ticks should camera following take? Defaults to 0.5 seconds.



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

def follow_duration
  @follow_duration
end

#follow_easingSymbol

Returns The easing function (Actions::Action::EASING_FUNCS) to use for camera following.

Returns:



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

def follow_easing
  @follow_easing
end

#follow_marginsArray<Integer>

Returns The margins on the screen which will stop camera panning. [top, right, bottom, left] These values are multiplied by the zoom factor when used. Defaults to [300, 600, 300, 600].

Returns:

  • (Array<Integer>)

    The margins on the screen which will stop camera panning. [top, right, bottom, left] These values are multiplied by the zoom factor when used. Defaults to [300, 600, 300, 600]



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

def follow_margins
  @follow_margins
end

#last_camera_movementZif::Actions::Action

Returns The most recent camera movement Actions::Action.

Returns:



63
64
65
# File 'lib/zif/layers/camera.rb', line 63

def last_camera_movement
  @last_camera_movement
end

#last_followArray<Float>

Returns The center point [x, y] of the last followed sprite. Used to center zooming.

Returns:

  • (Array<Float>)

    The center point [x, y] of the last followed sprite. Used to center zooming.



70
71
72
# File 'lib/zif/layers/camera.rb', line 70

def last_follow
  @last_follow
end

#layersArray<Zif::Sprite>

Returns The layer sprites this camera will move in unison.

Returns:

  • (Array<Zif::Sprite>)

    The layer sprites this camera will move in unison.



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

def layers
  @layers
end

#max_hInteger

Returns Of the given #layers, the largest height value.

Returns:

  • (Integer)

    Of the given #layers, the largest height value



31
32
33
# File 'lib/zif/layers/camera.rb', line 31

def max_h
  @max_h
end

#max_wInteger

Returns Of the given #layers, the largest width value.

Returns:

  • (Integer)

    Of the given #layers, the largest width value



29
30
31
# File 'lib/zif/layers/camera.rb', line 29

def max_w
  @max_w
end

#max_xInteger

Returns This is #max_w subtracted by #cur_w: The maximum X value the layers source_x can have.

Returns:

  • (Integer)

    This is #max_w subtracted by #cur_w: The maximum X value the layers source_x can have



58
59
60
# File 'lib/zif/layers/camera.rb', line 58

def max_x
  @max_x
end

#max_yInteger

Returns This is #max_h subtracted by #cur_h: The maximum Y value the layers source_y can have.

Returns:

  • (Integer)

    This is #max_h subtracted by #cur_h: The maximum Y value the layers source_y can have



60
61
62
# File 'lib/zif/layers/camera.rb', line 60

def max_y
  @max_y
end

#max_zoom_inFloat

Returns The maximum zoom in level. Defaults to 0.5. A value of 1.0 is no zoom at all.

Returns:

  • (Float)

    The maximum zoom in level. Defaults to 0.5. A value of 1.0 is no zoom at all.



34
35
36
# File 'lib/zif/layers/camera.rb', line 34

def max_zoom_in
  @max_zoom_in
end

#max_zoom_outFloat

Returns The maximum zoom out level. Defaults to 2.0. A value of 1.0 is no zoom at all.

Returns:

  • (Float)

    The maximum zoom out level. Defaults to 2.0. A value of 1.0 is no zoom at all.



36
37
38
# File 'lib/zif/layers/camera.rb', line 36

def max_zoom_out
  @max_zoom_out
end

#min_xInteger

Returns The minimum X value the #layers source_x can have, typically 0.

Returns:

  • (Integer)

    The minimum X value the #layers source_x can have, typically 0



25
26
27
# File 'lib/zif/layers/camera.rb', line 25

def min_x
  @min_x
end

#min_yInteger

Returns The minimum Y value the #layers source_y can have, typically 0.

Returns:

  • (Integer)

    The minimum Y value the #layers source_y can have, typically 0



27
28
29
# File 'lib/zif/layers/camera.rb', line 27

def min_y
  @min_y
end

#native_screen_heightInteger (readonly)

Returns The native height of the screen, defaults to 720.

Returns:

  • (Integer)

    The native height of the screen, defaults to 720



46
47
48
# File 'lib/zif/layers/camera.rb', line 46

def native_screen_height
  @native_screen_height
end

#native_screen_widthInteger (readonly)

TODO:

Right now, the native screen size can’t be changed.

Returns The native width of the screen, defaults to 1280.

Returns:

  • (Integer)

    The native width of the screen, defaults to 1280



44
45
46
# File 'lib/zif/layers/camera.rb', line 44

def native_screen_width
  @native_screen_width
end

#pos_xFloat

Returns The current X position of the camera (lower left, maps to source_x on layers).

Returns:

  • (Float)

    The current X position of the camera (lower left, maps to source_x on layers)



49
50
51
# File 'lib/zif/layers/camera.rb', line 49

def pos_x
  @pos_x
end

#pos_yFloat

Returns The current Y position of the camera (lower left, maps to source_y on layers).

Returns:

  • (Float)

    The current Y position of the camera (lower left, maps to source_y on layers)



51
52
53
# File 'lib/zif/layers/camera.rb', line 51

def pos_y
  @pos_y
end

#target_xFloat

Returns The relative change in X the camera is moving towards with #last_camera_movement.

Returns:



65
66
67
# File 'lib/zif/layers/camera.rb', line 65

def target_x
  @target_x
end

#target_yFloat

Returns The relative change in Y the camera is moving towards with #last_camera_movement.

Returns:



67
68
69
# File 'lib/zif/layers/camera.rb', line 67

def target_y
  @target_y
end

#zoom_stepInteger

Returns The index of #zoom_steps pointing to the current zoom level.

Returns:

  • (Integer)

    The index of #zoom_steps pointing to the current zoom level.



38
39
40
# File 'lib/zif/layers/camera.rb', line 38

def zoom_step
  @zoom_step
end

#zoom_stepsArray<Float>

Returns An ordered array of discrete zoom levels. Defaults to [0.5, 0.8, 1.0, 1.28, 1.6, 2.0].

Returns:

  • (Array<Float>)

    An ordered array of discrete zoom levels. Defaults to [0.5, 0.8, 1.0, 1.28, 1.6, 2.0]



40
41
42
# File 'lib/zif/layers/camera.rb', line 40

def zoom_steps
  @zoom_steps
end

Instance Method Details

#center(from = pos) ⇒ Array<Float>

Calculates the difference between [{cur_w}/2, {cur_h}/2] and from

Parameters:

  • from (Array<Float>) (defaults to: pos)

    [x, y], defaults to #pos

Returns:

  • (Array<Float>)

    The difference between [{cur_w}/2, {cur_h}/2] and from



301
302
303
304
305
306
307
308
309
310
# File 'lib/zif/layers/camera.rb', line 301

def center(from=pos)
  Zif.add_positions(
    from,
    Zif.position_math(
      :idiv,
      [@cur_w, @cur_h],
      [2, 2]
    )
  )
end

#center_screen(around = center) ⇒ Object

Parameters:

  • around (Array<Float>) (defaults to: center)

    [x, y] positional array to center the screen around, using #move Defaults to #center



280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/zif/layers/camera.rb', line 280

def center_screen(around=center)
  center_to = Zif.sub_positions(
    around,
    Zif.position_math(
      :idiv,
      [@cur_w, @cur_h],
      [2, 2]
    )
  )

  move(*center_to)
end

#each_layer(&block) ⇒ Enumerator

Returns Iterate over each element of #layers.

Parameters:

  • block (Block)

    The block to execute on each element of #layers

Returns:

  • (Enumerator)

    Iterate over each element of #layers



164
165
166
# File 'lib/zif/layers/camera.rb', line 164

def each_layer(&block)
  @layers.each(&block)
end

#follow_xFloat

To support moving the default zoom point while following something

Returns:



187
188
189
# File 'lib/zif/layers/camera.rb', line 187

def follow_x
  @last_follow[0]
end

#follow_x=(x) ⇒ Object

Parameters:



192
193
194
# File 'lib/zif/layers/camera.rb', line 192

def follow_x=(x)
  @last_follow[0] = x
end

#follow_yFloat

Returns The Y value of #last_follow.

Returns:



197
198
199
# File 'lib/zif/layers/camera.rb', line 197

def follow_y
  @last_follow[1]
end

#follow_y=(y) ⇒ Object

Parameters:



202
203
204
# File 'lib/zif/layers/camera.rb', line 202

def follow_y=(y)
  @last_follow[1] = y
end

#full_view_rectArray<Integer>

Returns [{cur_w}, {cur_h}].

Returns:

  • (Array<Integer>)

    [{cur_w}, {cur_h}]



158
159
160
# File 'lib/zif/layers/camera.rb', line 158

def full_view_rect
  [@cur_w, @cur_h]
end

#max_view_rectArray<Integer>

Returns [w, h] The maximum width and height of the camera viewport, assuming fully zoomed out.

Returns:

  • (Array<Integer>)

    [w, h] The maximum width and height of the camera viewport, assuming fully zoomed out



153
154
155
# File 'lib/zif/layers/camera.rb', line 153

def max_view_rect
  [(@native_screen_width * @max_zoom_out).to_i, (@native_screen_height * @max_zoom_out).to_i]
end

#min_view_rectArray<Integer>

Returns [w, h] The minimum width and height of the camera viewport, assuming fully zoomed in.

Returns:

  • (Array<Integer>)

    [w, h] The minimum width and height of the camera viewport, assuming fully zoomed in



148
149
150
# File 'lib/zif/layers/camera.rb', line 148

def min_view_rect
  [(@native_screen_width * @max_zoom_in).to_i, (@native_screen_height * @max_zoom_in).to_i]
end

#move(x, y) ⇒ Array<Float>

Adjust pos_x and pos_y together, return difference

Parameters:

Returns:

  • (Array<Float>)

    [x, y] array, the difference between the given params and the original position



210
211
212
213
214
215
216
217
218
# File 'lib/zif/layers/camera.rb', line 210

def move(x, y)
  orig_pos_x = @pos_x
  orig_pos_y = @pos_y

  self.pos_x = x
  self.pos_y = y

  [@pos_x - orig_pos_x, @pos_y - orig_pos_y]
end

#move_rel(x = nil, y = nil) ⇒ Object

Relative positional change where [1, 1] means add 1 to x/y instead of (1,1) Accepts nil to support behavior of directional_vector

Parameters:

  • x (Float) (defaults to: nil)

    Set #pos_x to the current value plus this

  • y (Float) (defaults to: nil)

    Set #pos_y to the current value plus this



224
225
226
227
228
229
# File 'lib/zif/layers/camera.rb', line 224

def move_rel(x=nil, y=nil)
  return unless x && y

  self.pos_x = @pos_x + x.round
  self.pos_y = @pos_y + y.round
end

#posArray<Float>

Returns +[#pos_x, #pos_y].

Returns:



294
295
296
# File 'lib/zif/layers/camera.rb', line 294

def pos
  [@pos_x, @pos_y]
end

#scrolled?(_point, direction) ⇒ Boolean

TODO:

This could be used to zoom in and out based on where the mouse is pointed - currently ignored. Feel free to override if you need this behavior, or submit a PR to split the Camera for this feature.

This method is used as a hook for Services::InputService, when given this obj with Services::InputService#register_scrollable. By default, it will attempt to zoom to either the last followed object, or center of the screen.

Parameters:

  • _point (Array<Float>)

    The point the mouse was at when scrolled, currently ignored

  • direction (Symbol)

    The direction of scroll, either :up or :down

Returns:

  • (Boolean)


345
346
347
348
349
350
351
352
353
# File 'lib/zif/layers/camera.rb', line 345

def scrolled?(_point, direction)
  # translated_point = translate_pos(point)
  # puts "Camera scrolled: #{point}->#{translated_point} #{direction}"
  if direction == :down
    zoom_out # (translated_point)
  else
    zoom_in # (translated_point)
  end
end

#start_following(sprite) ⇒ Object

Cause the Camera to start an Actions::Action on itself to pan #pos_x and #pos_y towards the center point of the given sprite. Obeys #follow_margins. Stops any existing #last_camera_movement. Panning will take #follow_duration and use #follow_easing.

Parameters:



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/zif/layers/camera.rb', line 235

def start_following(sprite)
  # We want to start following the leading sprite if it reaches the margins:
  top_margin, right_margin, down_margin, left_margin = @follow_margins

  top_margin, right_margin = Zif.position_math(:mult, [top_margin, right_margin], zoom_factor)
  down_margin, left_margin = Zif.position_math(:mult, [down_margin, left_margin], zoom_factor)

  relative_x = (sprite.x - @pos_x).to_i
  relative_y = (sprite.y - @pos_y).to_i

  @target_x = if relative_x < left_margin
                -(left_margin - relative_x)
              elsif relative_x > (@cur_w - right_margin)
                relative_x - (@cur_w - right_margin)
              else
                0
              end

  @target_y = if relative_y < down_margin
                -(down_margin - relative_y)
              elsif relative_y > (@cur_h - top_margin)
                relative_y - (@cur_h - top_margin)
              else
                0
              end

  return if @target_x.zero? && @target_y.zero?

  stop_action(@last_camera_movement) if @last_camera_movement

  # puts "Camera#start_following: Running action #{{pos_x: @pos_x + @target_x, pos_y: @pos_y + @target_y}}"
  @last_camera_movement = new_action(
    {
      pos_x: @pos_x + @target_x,
      pos_y: @pos_y + @target_y
    },
    duration: @follow_duration,
    easing:   @follow_easing
  ) { @last_follow = sprite.center }

  run_action(@last_camera_movement)
end

#translate_pos(given) ⇒ Array<Float>

Translate a 2 element point from the screen’s perspective to the map’s perspective

Parameters:

  • given (Array<Float>)

    [x, y] positional array from the screen’s perspective

Returns:

  • (Array<Float>)

    [x, y] positional array from the map’s perspective, taking into account camera position and zoom level.



394
395
396
# File 'lib/zif/layers/camera.rb', line 394

def translate_pos(given)
  Zif.add_positions(Zif.position_math(:mult, given, zoom_factor), pos)
end

#zoom_factorFloat

The current zoom factor, this value is compared against #max_zoom_out and #max_zoom_in when zooming.

Returns:

  • (Float)

    The current zoom factor. A value of 1.0 means we are zoomed to the native screen resolution Lower values mean we are zoomed in, higher values are zoomed out.



386
387
388
# File 'lib/zif/layers/camera.rb', line 386

def zoom_factor
  Zif.position_math(:fdiv, [@cur_w, @cur_h], zoom_unit)
end

#zoom_in(point = (@last_follow || center)) ⇒ Object

Zooms in by a single value in #zoom_steps, around the given point

Parameters:

  • point (Array<Float>) (defaults to: (@last_follow || center))

    [x, y] The point to center the zoom in around.



364
365
366
367
# File 'lib/zif/layers/camera.rb', line 364

def zoom_in(point=(@last_follow || center))
  @zoom_step = [@zoom_step - 1, 0].max
  zoom_to(@zoom_steps[@zoom_step], point)
end

#zoom_out(point = (@last_follow || center)) ⇒ Object

Zooms out by a single value in #zoom_steps, around the given point

Parameters:

  • point (Array<Float>) (defaults to: (@last_follow || center))

    [x, y] The point to center the zoom out around.



357
358
359
360
# File 'lib/zif/layers/camera.rb', line 357

def zoom_out(point=(@last_follow || center))
  @zoom_step = [@zoom_step + 1, @zoom_steps.length - 1].min
  zoom_to(@zoom_steps[@zoom_step], point)
end

#zoom_to(factor = 1.0, point = (@last_follow || center)) ⇒ Object

Zooms to a specific zoom factor, around the given point. You probably should prefer using #zoom_out and #zoom_in to do stepwise zooms instead of this method.

Parameters:

  • factor (Float) (defaults to: 1.0)

    The zoom factor to zoom to. Ideally this would be a value in #zoom_steps.

  • point (Array<Float>) (defaults to: (@last_follow || center))

    [x, y] The point to center the zoom in around.



373
374
375
376
377
378
379
380
381
# File 'lib/zif/layers/camera.rb', line 373

def zoom_to(factor=1.0, point=(@last_follow || center))
  base_mult = (factor.round(2) * 80)
  self.cur_w = (base_mult * 16)
  self.cur_h = (base_mult * 9)

  # puts "#zoom_to: Zoomed to #{zoom_factor}"

  center_screen(point)
end

#zoom_unitArray<Integer>

A zoom factor of 1.0 corresponds to this resolution

Returns:

  • (Array<Integer>)

    [{native_screen_width}, {native_screen_height}]



334
335
336
# File 'lib/zif/layers/camera.rb', line 334

def zoom_unit
  [@native_screen_width, @native_screen_height]
end