osb 1.0.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dae3bb6b24e591176075dcfaeb67dc5e74c625c668e44dd91e8f35b660973222
4
- data.tar.gz: 30a7304fe4b0c47a81d9a55ea0cc91d5e60775ab06f01aa85e4d76c46b241c0c
3
+ metadata.gz: 9ac326d1ca7ffd249550ff8762b4b8f1f3a56aad69dd567a5f4146938ae9ca70
4
+ data.tar.gz: e832ae17332a76d259c2813da3d5f602945bf9017f00080539042d7225caa029
5
5
  SHA512:
6
- metadata.gz: 7fd1cc3edbf6bc0995eb04951ac640ca7d583130e36e46b8b9aa801cbbdbd040cbcd41f1b1ea2eff477361f9af39f7775487b80b65332737df0a7055830c877e
7
- data.tar.gz: 3d4a452f5fdc3002e544d5ffc32542fa850f792dc01e19da19a32ae506da120fccfdf49d68c2c6d25534320d9406bdf575a67d63c1b1904a98878f26be9c1424
6
+ metadata.gz: 1620c4004ef265061b2060969b115d02c28d848dd9f99cf1f3ef9045d3a5ce809025266a1637dbf19c72201d9c56dd10f618af81a8fede0cfe7caf8fca53962f
7
+ data.tar.gz: af61c900d6ced4c4f19007a1b569e96fcd089f64e9f2f2a8503095746ad195c5334a0abd847ef98d8bc75558afb16f7bb874f9ad063b1213370d40ddc2962d7c
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --no-private
2
+ -
3
+ README.md
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # osb
2
+ A simple osu! storyboard framework written in Ruby.
3
+
4
+ ## Installation
5
+
6
+ Install with Gem:
7
+ ```sh
8
+ gem install osb
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Create a storyboard container:
14
+ ```rb
15
+ require "osb"
16
+ include Osb
17
+ sb = Storyboard.new
18
+ ```
19
+
20
+ Create a static image:
21
+ ```rb
22
+ sp = Sprite.new(file_path: "test.png")
23
+ ```
24
+
25
+ Describe what it does:
26
+ ```rb
27
+ sp.fade(start_time: 1000, start_opacity: 1)
28
+ sp.move(start_time: 2000, start_position: [320, 640], end_position: [100, 100])
29
+ sp.scale(start_time: 100, end_time: 200, start_scale: 1, end_scale: 2)
30
+ ```
31
+
32
+ Add it to the container:
33
+ ```rb
34
+ sb << sp
35
+ ```
36
+
37
+ Generate your storyboard file:
38
+ ```rb
39
+ sb.generate("path/to/your_storyboard_file.osb")
40
+ ```
41
+
42
+ osb also supports DSL syntax.
43
+
44
+ ```rb
45
+ storyboard do
46
+ out_path "path/to/your_storyboard_file.osb"
47
+
48
+ sprite file_path: "test.png" do
49
+ fade start_time: 1000, start_opacity: 1
50
+
51
+ move start_time: 2000, start_position: [320, 640], end_position: [100, 100]
52
+ end
53
+ end
54
+ ```
55
+
56
+ Full documentation is available at https://rubydoc.info/gems/osb/index.
57
+
58
+ ## Contributing
59
+
60
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nanachi-code/osb-ruby.
61
+
62
+ ## License
63
+
64
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/osb/animation.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Osb
4
4
  # A moving image.
5
5
  class Animation
6
- # @api private
6
+ # @private
7
7
  attr_reader :commands, :layer
8
8
  include Commandable
9
9
 
data/lib/osb/assert.rb CHANGED
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osb
4
- # @api private
4
+ # @private
5
5
  class TypeError < StandardError
6
6
  end
7
7
 
8
- # @api private
8
+ # @private
9
9
  class InvalidValueError < StandardError
10
10
  end
11
11
 
12
- # @api private
12
+ # @private
13
13
  module Internal
14
- # @api private
14
+ # @private
15
15
  Boolean = [TrueClass, FalseClass]
16
16
 
17
- # @api private
17
+ # @private
18
18
  class TypedArray
19
19
  # @param [Class] type
20
20
  def initialize(type)
@@ -30,16 +30,21 @@ module Osb
30
30
  end
31
31
  end
32
32
 
33
- # @api private
33
+ # @private
34
34
  # @type [Hash{Class => Hash{Class => Object}}]
35
- T = { Array => { Numeric => TypedArray.new(Numeric) } }
35
+ T = {
36
+ Array => {
37
+ Numeric => TypedArray.new(Numeric),
38
+ Integer => TypedArray.new(Integer)
39
+ }
40
+ }
36
41
 
37
42
  # Check if supplied argument is correctly typed.
38
43
  # @param [Object] arg
39
44
  # @param [BasicObject, Array, TypedArray] possible_types
40
45
  # @param [String] param_name
41
46
  # @return [void]
42
- # @api private
47
+ # @private
43
48
  def self.assert_type!(arg, possible_types, param_name)
44
49
  if possible_types.is_a?(Array)
45
50
  valid =
@@ -75,7 +80,7 @@ module Osb
75
80
  # @param [BasicObject, Array, Range] possible_values
76
81
  # @param [String] param_name
77
82
  # @return [void]
78
- # @api private
83
+ # @private
79
84
  def self.assert_value!(arg, possible_values, param_name)
80
85
  val =
81
86
  if arg.is_a?(String) && arg.empty?
@@ -113,7 +118,7 @@ module Osb
113
118
  # Ensure the file name extenstion is correct.
114
119
  # @param [String] file_name
115
120
  # @param [String, Array<String>] exts
116
- # @api private
121
+ # @private
117
122
  def self.assert_file_name_ext!(file_name, exts)
118
123
  if exts.is_a?(Array)
119
124
  exts_ = exts.join("|")
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Osb
2
4
  # Beatmap's background.
3
5
  class Background
4
- # @api private
6
+ # @private
5
7
  attr_reader :command
6
8
 
7
9
  # @param [String] file_name location of the background image relative to the beatmap directory.
data/lib/osb/color.rb CHANGED
@@ -11,36 +11,52 @@ module Osb
11
11
  # @attribute [rw] b
12
12
  # @return Blue value.
13
13
 
14
- # @param [Integer] r red value
14
+ # @param [Integer, String, Array<Integer>] r red value, a hex +String+,
15
+ # or an +Array+ of 3 +{Integer}+s.
15
16
  # @param [Integer] g green value
16
17
  # @param [Integer] b blue value
17
- def initialize(r, g, b)
18
- Internal.assert_type!(r, Integer, "r")
19
- Internal.assert_value!(r, 0..255, "r")
18
+ def initialize(r, g = nil, b = nil)
19
+ Internal.assert_type!(
20
+ r,
21
+ [Integer, String, Internal::T[Array][Integer]],
22
+ "r"
23
+ )
24
+ if r.is_a?(Array)
25
+ if r.size != 3
26
+ raise InvalidValueError, "Must be an Array of 3 Integers."
27
+ end
28
+ @r = r[0]
29
+ @g = r[1]
30
+ @b = r[2]
31
+ elsif r.is_a?(String)
32
+ Color.from_hex(r)
33
+ else
34
+ Internal.assert_value!(r, 0..255, "r")
20
35
 
21
- Internal.assert_type!(g, Integer, "g")
22
- Internal.assert_value!(g, 0..255, "g")
36
+ Internal.assert_type!(g, Integer, "g")
37
+ Internal.assert_value!(g, 0..255, "g")
23
38
 
24
- Internal.assert_type!(b, Integer, "b")
25
- Internal.assert_value!(b, 0..255, "b")
39
+ Internal.assert_type!(b, Integer, "b")
40
+ Internal.assert_value!(b, 0..255, "b")
26
41
 
27
- @r = r
28
- @g = g
29
- @b = b
42
+ @r = r
43
+ @g = g
44
+ @b = b
45
+ end
30
46
  end
31
47
 
32
48
  # Returns whether 2 colors are not equal.
33
49
  # @param [Color] color
34
50
  def !=(color)
35
51
  Internal.assert_type!(color, Color, "color")
36
-
52
+
37
53
  color.r != self.r && color.g != self.g && color.b != self.b
38
54
  end
39
55
 
40
56
  # Converts an HSL color value to RGB.
41
- # @param [Integer] h
42
- # @param [Integer] s
43
- # @param [Integer] l
57
+ # @param [Integer] hue
58
+ # @param [Integer] saturation
59
+ # @param [Integer] lightness
44
60
  # @return [Color]
45
61
  def self.from_hsl(h, s, l)
46
62
  Internal.assert_type!(h, Integer, "h")
@@ -70,7 +86,7 @@ module Osb
70
86
  Color.new((r * 255).round, (g * 255).round, (b * 255).round)
71
87
  end
72
88
 
73
- # @api private
89
+ # @private
74
90
  # @param [Float] p
75
91
  # @param [Float] q
76
92
  # @param [Float] t_
@@ -84,7 +100,7 @@ module Osb
84
100
  return p
85
101
  end
86
102
 
87
- # Create a Color object from hex string.
103
+ # Create a +{Color}+ object from hex string.
88
104
  # @param [String] hex
89
105
  # @return [Color]
90
106
  def self.from_hex(hex)
@@ -95,4 +111,23 @@ module Osb
95
111
  components.collect { |component| component.to_i(16) }
96
112
  end
97
113
  end
114
+
115
+ # Create a new rgb +{Color}+.
116
+ # @param [Integer, String, Array<Integer>] r red value, a hex +String+,
117
+ # or an +Array+ of 3 +{Integer}+s.
118
+ # @param [Integer] g green value
119
+ # @param [Integer] b blue value
120
+ # @return [Color]
121
+ def rgb(r, g = nil, b = nil)
122
+ Color.new(r, g, b)
123
+ end
124
+
125
+ # Create a new hsl +{Color}+.
126
+ # @param [Integer] hue
127
+ # @param [Integer] saturation
128
+ # @param [Integer] lightness
129
+ # @return [Color]
130
+ def hsl(h, s, l)
131
+ Color.from_hsl(h, s, l)
132
+ end
98
133
  end
@@ -4,21 +4,21 @@ module Osb
4
4
  module Internal
5
5
  # @param [Integer] time
6
6
  # @return [void]
7
- # @api private
7
+ # @private
8
8
  def self.raise_if_invalid_start_time!(time)
9
9
  Internal.assert_type!(time, Integer, "start_time")
10
10
  end
11
11
 
12
12
  # @param [Integer] time
13
13
  # @return [void]
14
- # @api private
14
+ # @private
15
15
  def self.raise_if_invalid_end_time!(time)
16
16
  Internal.assert_type!(time, Integer, "end_time")
17
17
  end
18
18
 
19
19
  # @param [Integer] easing
20
20
  # @return [void]
21
- # @api private
21
+ # @private
22
22
  def self.raise_if_invalid_easing!(easing)
23
23
  Internal.assert_type!(easing, Integer, "easing")
24
24
  Internal.assert_value!(easing, Easing::ALL, "easing")
@@ -26,10 +26,12 @@ module Osb
26
26
  end
27
27
 
28
28
  module Commandable
29
+ # @private
29
30
  private def tab_level
30
31
  @is_in_trigger ? 2 : 1
31
32
  end
32
33
 
34
+ # @private
33
35
  private def raise_if_trigger_called!
34
36
  if @trigger_called
35
37
  raise RuntimeError, "Do not add commands after #trigger is called."
@@ -42,6 +44,7 @@ module Osb
42
44
  # @param [Integer] easing
43
45
  # @param [Numeric] start_opacity
44
46
  # @param [Numeric] end_opacity
47
+ # @return [void]
45
48
  def fade(
46
49
  start_time:,
47
50
  end_time: start_time,
@@ -71,6 +74,7 @@ module Osb
71
74
  # @param [Integer] easing
72
75
  # @param [Osb::Vector2, Array<Numeric>] start_position
73
76
  # @param [Osb::Vector2, Array<Numeric>] end_position
77
+ # @return [void]
74
78
  def move(
75
79
  start_time:,
76
80
  end_time: start_time,
@@ -84,12 +88,12 @@ module Osb
84
88
  Internal.raise_if_invalid_easing!(easing)
85
89
  Internal.assert_type!(
86
90
  start_position,
87
- [Osb::Vector2, T[Array][Numeric]],
91
+ [Osb::Vector2, Internal::T[Array][Numeric]],
88
92
  "start_position"
89
93
  )
90
94
  Internal.assert_type!(
91
95
  end_position,
92
- [Osb::Vector2, T[Array][Numeric]],
96
+ [Osb::Vector2, Internal::T[Array][Numeric]],
93
97
  "end_position"
94
98
  )
95
99
  if start_position.is_a?(Array)
@@ -100,8 +104,9 @@ module Osb
100
104
  tabs = " " * self.tab_level
101
105
  command =
102
106
  "#{tabs}M,#{start_time},#{end_time},#{start_position.x},#{start_position.y}"
103
- command += ",#{end_position.x},#{end_position.y}" if end_position !=
104
- start_position
107
+ if end_position != start_position
108
+ command += ",#{end_position.x},#{end_position.y}"
109
+ end
105
110
  @commands << command
106
111
  end
107
112
 
@@ -111,6 +116,7 @@ module Osb
111
116
  # @param [Integer] easing
112
117
  # @param [Numeric] start_x
113
118
  # @param [Numeric] end_x
119
+ # @return [void]
114
120
  def move_x(
115
121
  start_time:,
116
122
  end_time: start_time,
@@ -138,6 +144,7 @@ module Osb
138
144
  # @param [Integer] easing
139
145
  # @param [Numeric] start_y
140
146
  # @param [Numeric] end_y
147
+ # @return [void]
141
148
  def move_y(
142
149
  start_time:,
143
150
  end_time: start_time,
@@ -167,6 +174,7 @@ module Osb
167
174
  # @param [Integer] easing
168
175
  # @param [Numeric, Osb::Vector2, Array<Numeric>] start_scale
169
176
  # @param [Numeric, Osb::Vector2, Array<Numeric>] end_scale
177
+ # @return [void]
170
178
  def scale(
171
179
  start_time:,
172
180
  end_time: start_time,
@@ -180,12 +188,12 @@ module Osb
180
188
  Internal.raise_if_invalid_easing!(easing)
181
189
  Internal.assert_type!(
182
190
  start_scale,
183
- [Numeric, T[Array][Numeric], Osb::Vector2],
191
+ [Numeric, Internal::T[Array][Numeric], Osb::Vector2],
184
192
  "start_scale"
185
193
  )
186
194
  Internal.assert_type!(
187
195
  end_scale,
188
- [Numeric, T[Array][Numeric], Osb::Vector2],
196
+ [Numeric, Internal::T[Array][Numeric], Osb::Vector2],
189
197
  "end_scale"
190
198
  )
191
199
 
@@ -207,7 +215,6 @@ module Osb
207
215
  end
208
216
 
209
217
  start_scale = Osb::Vector2.new(start_scale) if start_scale.is_a?(Array)
210
-
211
218
  end_scale = Osb::Vector2.new(end_scale) if end_scale.is_a?(Array)
212
219
 
213
220
  command =
@@ -223,6 +230,7 @@ module Osb
223
230
  # @param [Integer] easing
224
231
  # @param [Float] start_angle start angle in radians.
225
232
  # @param [Float] end_angle end angle in radians.
233
+ # @return [void]
226
234
  def rotate(
227
235
  start_time:,
228
236
  end_time: start_time,
@@ -248,8 +256,9 @@ module Osb
248
256
  # @param [Integer] start_time
249
257
  # @param [Integer] end_time
250
258
  # @param [Integer] easing
251
- # @param [Osb::Color] start_color
252
- # @param [Osb::Color] end_color
259
+ # @param [Osb::Color, Array<Integer>] start_color
260
+ # @param [Osb::Color, Array<Integer>] end_color
261
+ # @return [void]
253
262
  def color(
254
263
  start_time:,
255
264
  end_time: start_time,
@@ -261,8 +270,19 @@ module Osb
261
270
  Internal.raise_if_invalid_start_time!(start_time)
262
271
  Internal.raise_if_invalid_end_time!(end_time)
263
272
  Internal.raise_if_invalid_easing!(easing)
264
- Internal.assert_type!(start_color, Osb::Color, "start_color")
265
- Internal.assert_type!(end_color, Osb::Color, "end_color")
273
+ Internal.assert_type!(
274
+ start_color,
275
+ [Osb::Color, Internal::T[Array][Integer]],
276
+ "start_color"
277
+ )
278
+ Internal.assert_type!(
279
+ end_color,
280
+ [Osb::Color, Internal::T[Array][Integer]],
281
+ "end_color"
282
+ )
283
+
284
+ start_color = Color.new(start_color) if start_color.is_a?(Array)
285
+ end_color = Color.new(end_color) if end_color.is_a?(Array)
266
286
 
267
287
  end_time = "" if start_time == end_time
268
288
  tabs = " " * self.tab_level
@@ -279,6 +299,7 @@ module Osb
279
299
  # @param [Integer] end_time
280
300
  # @param [Boolean] horizontally
281
301
  # @param [Boolean] vertically
302
+ # @return [void]
282
303
  def flip(start_time:, end_time:, horizontally: true, vertically: false)
283
304
  self.raise_if_trigger_called!
284
305
  Internal.raise_if_invalid_start_time!(start_time)
@@ -304,6 +325,7 @@ module Osb
304
325
  # Use additive-color blending instead of alpha-blending.
305
326
  # @param [Integer] start_time
306
327
  # @param [Integer] end_time
328
+ # @return [void]
307
329
  def additive_color_blending(start_time:, end_time:)
308
330
  self.raise_if_trigger_called!
309
331
  Internal.raise_if_invalid_start_time!(start_time)
@@ -338,7 +360,8 @@ module Osb
338
360
  # @param [String] on indicates the trigger condition. It can be "Failing" or "Passing".
339
361
  # @param [Integer] start_time the timestamp at which the trigger becomes valid.
340
362
  # @param [Integer] end_time the timestamp at which the trigger stops being valid.
341
- def trigger(on:, start_time:, end_time:)
363
+ # @return [void]
364
+ def trigger(on:, start_time:, end_time:, &blk)
342
365
  self.raise_if_trigger_called!
343
366
  Internal.raise_if_invalid_start_time!(start_time)
344
367
  Internal.raise_if_invalid_end_time!(end_time)
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osb
4
+ # @private
5
+ def current_object
6
+ return @sprite if @sprite
7
+ return @animation if @animation
8
+ return nil
9
+ end
10
+
11
+ # @private
12
+ def raise_unless_inside_object!
13
+ self.raise_unless_inside_storyboard!
14
+ unless self.current_object
15
+ raise RuntimeError,
16
+ "Do not call this method outside of a sprite/animation context."
17
+ end
18
+ end
19
+
20
+ # Change the opacity of the object (how transparent it is).
21
+ # @param [Integer] start_time
22
+ # @param [Integer] end_time
23
+ # @param [Integer] easing
24
+ # @param [Numeric] start_opacity
25
+ # @param [Numeric] end_opacity
26
+ # @return [void]
27
+ def fade(
28
+ start_time:,
29
+ end_time: start_time,
30
+ easing: Easing::Linear,
31
+ start_opacity:,
32
+ end_opacity: start_opacity
33
+ )
34
+ self.raise_unless_inside_object!
35
+ self.current_object.fade(
36
+ start_time: start_time,
37
+ end_time: end_time,
38
+ easing: easing,
39
+ start_opacity: start_opacity,
40
+ end_opacity: end_opacity
41
+ )
42
+ end
43
+
44
+ # Move the object to a new position in the play area.
45
+ # @param [Integer] start_time
46
+ # @param [Integer] end_time
47
+ # @param [Integer] easing
48
+ # @param [Osb::Vector2, Array<Numeric>] start_position
49
+ # @param [Osb::Vector2, Array<Numeric>] end_position
50
+ # @return [void]
51
+ def move(
52
+ start_time:,
53
+ end_time: start_time,
54
+ easing: Easing::Linear,
55
+ start_position:,
56
+ end_position: start_position
57
+ )
58
+ self.raise_unless_inside_object!
59
+ self.current_object.move(
60
+ start_time: start_time,
61
+ end_time: end_time,
62
+ easing: easing,
63
+ start_position: start_position,
64
+ end_position: end_position
65
+ )
66
+ end
67
+
68
+ # Move the object along the x axis.
69
+ # @param [Integer] start_time
70
+ # @param [Integer] end_time
71
+ # @param [Integer] easing
72
+ # @param [Numeric] start_x
73
+ # @param [Numeric] end_x
74
+ # @return [void]
75
+ def move_x(
76
+ start_time:,
77
+ end_time: start_time,
78
+ easing: Easing::Linear,
79
+ start_x:,
80
+ end_x: start_x
81
+ )
82
+ self.raise_unless_inside_object!
83
+ self.current_object.move_x(
84
+ start_time: start_time,
85
+ end_time: end_time,
86
+ easing: easing,
87
+ start_x: start_x,
88
+ end_x: end_x
89
+ )
90
+ end
91
+
92
+ # Move the object along the y axis.
93
+ # @param [Integer] start_time
94
+ # @param [Integer] end_time
95
+ # @param [Integer] easing
96
+ # @param [Numeric] start_y
97
+ # @param [Numeric] end_y
98
+ # @return [void]
99
+ def move_y(
100
+ start_time:,
101
+ end_time: start_time,
102
+ easing: Easing::Linear,
103
+ start_y:,
104
+ end_y: start_y
105
+ )
106
+ self.raise_unless_inside_object!
107
+ self.current_object.move_x(
108
+ start_time: start_time,
109
+ end_time: end_time,
110
+ easing: easing,
111
+ start_y: start_y,
112
+ end_y: end_y
113
+ )
114
+ end
115
+
116
+ # Change the size of the object relative to its original size. Will scale
117
+ # seperatedly if given +Osb::Vector2+s or +Array<Numeric>+s. The scaling is
118
+ # affected by the object's origin
119
+ # @param [Integer] start_time
120
+ # @param [Integer] end_time
121
+ # @param [Integer] easing
122
+ # @param [Numeric, Osb::Vector2, Array<Numeric>] start_scale
123
+ # @param [Numeric, Osb::Vector2, Array<Numeric>] end_scale
124
+ # @return [void]
125
+ def scale(
126
+ start_time:,
127
+ end_time: start_time,
128
+ easing: Easing::Linear,
129
+ start_scale:,
130
+ end_scale: start_scale
131
+ )
132
+ self.raise_unless_inside_object!
133
+ self.current_object.scale(
134
+ start_time: start_time,
135
+ end_time: end_time,
136
+ easing: easing,
137
+ start_scale: start_scale,
138
+ end_scale: end_scale
139
+ )
140
+ end
141
+
142
+ # Rotate the object around its origin.
143
+ # @param [Integer] start_time
144
+ # @param [Integer] end_time
145
+ # @param [Integer] easing
146
+ # @param [Float] start_angle start angle in radians.
147
+ # @param [Float] end_angle end angle in radians.
148
+ # @return [void]
149
+ def rotate(
150
+ start_time:,
151
+ end_time: start_time,
152
+ easing: Easing::Linear,
153
+ start_angle:,
154
+ end_angle: start_angle
155
+ )
156
+ self.raise_unless_inside_object!
157
+ self.current_object.rotate(
158
+ start_time: start_time,
159
+ end_time: end_time,
160
+ easing: easing,
161
+ start_angle: start_angle,
162
+ end_angle: end_angle
163
+ )
164
+ end
165
+
166
+ # The virtual light source colour on the object. The colours of the pixels on the object are determined subtractively.
167
+ # @param [Integer] start_time
168
+ # @param [Integer] end_time
169
+ # @param [Integer] easing
170
+ # @param [Osb::Color, Array<Integer>] start_color
171
+ # @param [Osb::Color, Array<Integer>] end_color
172
+ # @return [void]
173
+ def color(
174
+ start_time:,
175
+ end_time: start_time,
176
+ easing: Easing::Linear,
177
+ start_color:,
178
+ end_color: start_color
179
+ )
180
+ self.raise_unless_inside_object!
181
+ self.current_object.color(
182
+ start_time: start_time,
183
+ end_time: end_time,
184
+ easing: easing,
185
+ start_color: start_color,
186
+ end_color: end_color
187
+ )
188
+ end
189
+
190
+ # Flip the object horizontally or vertically.
191
+ # @param [Integer] start_time
192
+ # @param [Integer] end_time
193
+ # @param [Boolean] horizontally
194
+ # @param [Boolean] vertically
195
+ # @return [void]
196
+ def flip(start_time:, end_time:, horizontally: true, vertically: false)
197
+ self.raise_unless_inside_object!
198
+ self.current_object.flip(
199
+ start_time: start_time,
200
+ end_time: end_time,
201
+ horizontally: horizontally,
202
+ vertically: vertically
203
+ )
204
+ end
205
+
206
+ # Use additive-color blending instead of alpha-blending.
207
+ # @param [Integer] start_time
208
+ # @param [Integer] end_time
209
+ # @return [void]
210
+ def additive_color_blending(start_time:, end_time:)
211
+ self.raise_unless_inside_object!
212
+ self.current_object.additive_color_blending(
213
+ start_time: start_time,
214
+ end_time: end_time
215
+ )
216
+ end
217
+
218
+ # Add a group of commands on a specific condition.
219
+ # `#trigger` can only be called on an empty object declaration (no commands).
220
+ # Pass a block to this method call to specify which commands to run if
221
+ # the condition is met.
222
+ #
223
+ # @example
224
+ # img.trigger(on: "Passing", start_time: 0, end_time: 1000) do
225
+ # img.fade(start_time: 0, start_opacity: 0.5)
226
+ # end
227
+ #
228
+ # In addition to the "implicit" player feedback via the separate
229
+ # Pass/Fail layers, you can use one of several Trigger conditions
230
+ # to cause a series of events to happen whenever that condition is
231
+ # fulfilled within a certain time period.
232
+ # Note that `start_time` and `end_time` of any commands called inside
233
+ # the block become relative to the `start_time` and `end_time` of the
234
+ # `#trigger` command.
235
+ #
236
+ # While osu! supports trigger on hitsounds playing, we decided to not
237
+ # include it in because it is unreliable/unpredictable.
238
+ #
239
+ # @param [String] on indicates the trigger condition. It can be "Failing" or "Passing".
240
+ # @param [Integer] start_time the timestamp at which the trigger becomes valid.
241
+ # @param [Integer] end_time the timestamp at which the trigger stops being valid.
242
+ # @return [void]
243
+ def trigger(on:, start_time:, end_time:, &blk)
244
+ self.raise_unless_inside_object!
245
+ self.current_object.trigger(
246
+ on: on,
247
+ start_time: start_time,
248
+ end_time: end_time,
249
+ &blk
250
+ )
251
+ end
252
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osb
4
+ # @private
5
+ def raise_unless_inside_storyboard!
6
+ unless @storyboard
7
+ raise RuntimeError,
8
+ "Do not call this method outside of a storyboard context."
9
+ end
10
+ end
11
+
12
+ # Set the output directory of this storyboard.
13
+ # @param [String] path path to .osb or .osu file
14
+ def out_path(path)
15
+ self.raise_unless_inside_storyboard!
16
+ @out_path = path
17
+ end
18
+
19
+ # Create an osu! storyboard.
20
+ # @return [void]
21
+ def storyboard
22
+ if @storyboard
23
+ raise RuntimeError,
24
+ "Cannot create a new storyboard inside another storyboard."
25
+ end
26
+ @storyboard = Storyboard.new
27
+ yield
28
+ raise RuntimeError, "Output path is not set." unless @out_path
29
+ @storyboard.generate(@out_path)
30
+ @storyboard = nil
31
+ end
32
+
33
+ # Create a still image.
34
+ # @param [String] layer the layer the object appears on.
35
+ # @param [String] origin where on the image should osu! consider that image's origin (coordinate) to be.
36
+ # @param [String] file_path filename of the image.
37
+ # @param [Osb::Vector2, nil] initial_position where the object should be by default.
38
+ # @return [void]
39
+ def sprite(
40
+ layer: Layer::Background,
41
+ origin: Origin::Center,
42
+ file_path:,
43
+ initial_position: nil
44
+ )
45
+ self.raise_unless_inside_storyboard!
46
+
47
+ if @sprite || @animation
48
+ raise RuntimeError,
49
+ "Cannot create a new sprite inside another sprite/animation."
50
+ end
51
+ @sprite =
52
+ Sprite.new(
53
+ layer: layer,
54
+ origin: origin,
55
+ file_path: file_path,
56
+ initial_position: initial_position
57
+ )
58
+ @storyboard << @sprite
59
+ yield
60
+ @sprite = nil
61
+ end
62
+
63
+ # Group multiple objects for clarity.
64
+ # @return [void]
65
+ def group(name: nil)
66
+ end
67
+
68
+ # Create a moving image.
69
+ # @param [String] layer the layer the object appears on.
70
+ # @param [String] origin where on the image should osu! consider that image's origin (coordinate) to be.
71
+ # @param [String] file_path filename of the image.
72
+ # @param [Vector2, nil] initial_position where the object should be by default.
73
+ # @param [Integer] frame_count how many frames the animation has.
74
+ # @param [Integer] frame_delay how many milliseconds should be in between each frame.
75
+ # @param [Boolean] repeat if the animation should loop or not.
76
+ # @return [void]
77
+ def animation(
78
+ layer: Osb::Layer::Background,
79
+ origin: Osb::Origin::Center,
80
+ file_path:,
81
+ initial_position: nil,
82
+ frame_count:,
83
+ frame_delay:,
84
+ repeat: false
85
+ )
86
+ self.raise_unless_inside_storyboard!
87
+
88
+ if @sprite || @animation
89
+ raise RuntimeError,
90
+ "Cannot create a new animation inside another animation/animation."
91
+ end
92
+ @animation =
93
+ Animation.new(
94
+ layer: layer,
95
+ origin: origin,
96
+ file_path: file_path,
97
+ initial_position: initial_position,
98
+ frame_count: frame_count,
99
+ frame_delay: frame_delay,
100
+ repeat: repeat
101
+ )
102
+ @storyboard << @animation
103
+ yield
104
+ @animation = nil
105
+ end
106
+
107
+ # Set the background image for the beatmap.
108
+ # @param [String] file_name location of the background image relative to the beatmap directory.
109
+ # @return [void]
110
+ def background(file_path:)
111
+ self.raise_unless_inside_storyboard!
112
+
113
+ @storyboard << Background.new(file_path: file_path)
114
+ end
115
+
116
+ # Set the video for the beatmap.
117
+ # @param [String] file_name location of the video file relative to the beatmap directory.
118
+ # @param [Integer] start_time when the video starts.
119
+ # @return [void]
120
+ def video(file_path:, start_time:)
121
+ self.raise_unless_inside_storyboard!
122
+
123
+ @storyboard << Video.new(file_path: file_path, start_time: start_time)
124
+ end
125
+
126
+ # Add an audio sample to the storyboard.
127
+ # @param [Integer] time the timestamp that the sound should start playing.
128
+ # @param [String] layer the layer you want the sound to be on.
129
+ # @param [String] file_path filename of the audio.
130
+ # @param [Integer] volume indicate the relative loudness of the sound.
131
+ def sample(time:, layer:, file_path:, volume: 100)
132
+ self.raise_unless_inside_storyboard!
133
+
134
+ @storyboard << Sample.new(
135
+ time: time,
136
+ layer: layer,
137
+ file_path: file_path,
138
+ volume: volume
139
+ )
140
+ end
141
+ end
data/lib/osb/integer.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Integer
2
4
  # Does nothing. Just a way to tell people it's represented in milliseconds.
3
5
  # @return [Integer]
data/lib/osb/numeric.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Numeric
2
4
  # The degrees method is used to convert from degrees to radians.
3
5
  # @return [Float]
data/lib/osb/sample.rb CHANGED
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Osb
4
+ # Audio sample.
2
5
  class Sample
3
- # @api private
6
+ # @private
4
7
  attr_reader :command
5
8
 
6
9
  # @param [Integer] time the timestamp that the sound should start playing.
data/lib/osb/sprite.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Osb
4
4
  # A still image.
5
5
  class Sprite
6
- # @api private
6
+ # @private
7
7
  attr_reader :commands, :layer
8
8
  include Commandable
9
9
 
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osb
4
- # @api private
4
+ # @private
5
5
  module Internal
6
- # @api private
6
+ # @private
7
7
  class LayerManager
8
8
  attr_reader :background,
9
9
  :foreground,
@@ -71,7 +71,7 @@ module Osb
71
71
  # +Osb::Storyboard+ object, but we recommend you to split the project into multiple
72
72
  # +Osb::Group+ so it will be easier to manage.
73
73
  class Group
74
- # @api private
74
+ # @private
75
75
  attr_reader :layers
76
76
 
77
77
  def initialize
@@ -115,11 +115,11 @@ module Osb
115
115
  end
116
116
  end
117
117
 
118
- # Represent a osu! storyboard. Each sprite or animation can be added directly
118
+ # Represent an osu! storyboard. Each sprite or animation can be added directly
119
119
  # to the storyboard instance, or through an intermediate group. A group can
120
120
  # have multiple nested groups in itself.
121
121
  class Storyboard
122
- # @api private
122
+ # @private
123
123
  attr_reader :layers
124
124
 
125
125
  def initialize
data/lib/osb/vector2.rb CHANGED
@@ -10,14 +10,16 @@ module Osb
10
10
  # @return y coordinate of this vector
11
11
 
12
12
  # @param [Numeric, Array<Numeric>] x
13
- # x coordinate of this +Vector2+, or an +Array+ of 2 numbers.
14
- # @param [Numeric] y y coordinate of this +Vector2+
13
+ # x coordinate of this +{Vector2}+, or an +Array+ of 2 numbers.
14
+ # @param [Numeric] y y coordinate of this +{Vector2}+
15
15
  def initialize(x = 0, y = 0)
16
16
  Internal.assert_type!(x, [Numeric, Internal::T[Array][Numeric]], "x")
17
17
  Internal.assert_type!(y, Numeric, "y")
18
18
 
19
19
  if x.is_a?(Array)
20
- raise InvalidValueError, "Must be an Array of 2 Numeric values." if x.size != 2
20
+ if x.size != 2
21
+ raise InvalidValueError, "Must be an Array of 2 Numeric values."
22
+ end
21
23
  @x = x[0]
22
24
  @y = x[1]
23
25
  else
@@ -26,7 +28,7 @@ module Osb
26
28
  end
27
29
  end
28
30
 
29
- # Add another +Vector2+ to this one.
31
+ # Add another +{Vector2}+ to this one.
30
32
  # @param [Vector2] vector
31
33
  # @return [Vector2]
32
34
  def +(vector)
@@ -34,7 +36,7 @@ module Osb
34
36
  Vector2.new(self.x + vector.x, self.y + vector.y)
35
37
  end
36
38
 
37
- # Subtract another +Vector2+ from this one.
39
+ # Subtract another +{Vector2}+ from this one.
38
40
  # @param [Vector2] vector
39
41
  # @return [Vector2]
40
42
  def -(vector)
@@ -42,7 +44,7 @@ module Osb
42
44
  Vector2.new(self.x - vector.x, self.y - vector.y)
43
45
  end
44
46
 
45
- # Returns whether two +Vector2+ are equal within tolerance
47
+ # Returns whether two +{Vector2}+ are equal within tolerance
46
48
  # @param [Vector2] vector
47
49
  # @return [Boolean]
48
50
  def ==(vector)
@@ -50,14 +52,14 @@ module Osb
50
52
  Math.fuzzy_equal(self.x, vector.x) && Math.fuzzy_equal(self.y, vector.y)
51
53
  end
52
54
 
53
- # Returns whether two +Vector2+ are not equal within tolerance
55
+ # Returns whether two +{Vector2}+ are not equal within tolerance
54
56
  # @param [Vector2] vector
55
57
  # @return [Boolean]
56
58
  def !=(vector)
57
59
  !(self == vector)
58
60
  end
59
61
 
60
- # Makes a copy of this +Vector2+.
62
+ # Makes a copy of this +{Vector2}+.
61
63
  # @return [Vector2]
62
64
  def clone
63
65
  Vector2.new(self.x, self.y)
@@ -69,16 +71,24 @@ module Osb
69
71
  [self.x, self.y]
70
72
  end
71
73
 
72
- # Returns a string representation of this +Vector2+.
74
+ # Returns a string representation of this +{Vector2}+.
73
75
  # @return [String]
74
76
  def to_s
75
77
  self.to_a.to_s
76
78
  end
77
79
 
78
- # Returns the length of this +Vector2+.
80
+ # Returns the length of this +{Vector2}+.
79
81
  # @return [Float]
80
82
  def length
81
83
  Math.sqrt(self.x**2 + self.y**2)
82
84
  end
83
85
  end
86
+
87
+ # Create a +{Vector2}+.
88
+ # @param [Numeric, Array<Numeric>] x
89
+ # x coordinate of this +{Vector2}+, or an +Array+ of 2 numbers.
90
+ # @param [Numeric] y y coordinate of this +{Vector2}+
91
+ def vec2(x = 0, y = 0)
92
+ Vector2.new(x, y)
93
+ end
84
94
  end
data/lib/osb/video.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  module Osb
2
- # A video.
2
+ # Background video.
3
3
  class Video
4
- # @api private
4
+ # @private
5
5
  attr_reader :commands, :layer
6
6
 
7
- # @param [String] file_path location of the background image relative to the beatmap directory.
7
+ # @param [String] file_path location of the video file relative to the beatmap directory.
8
8
  # @param [Integer] start_time when the video starts.
9
9
  def initialize(file_path:, start_time:)
10
10
  Internal.assert_type!(file_path, String, "file_path")
11
- Internal.assert_file_name_ext!(file_path, %w[png jpg jpeg])
11
+ Internal.assert_file_name_ext!(file_path, "mp4")
12
12
  Internal.assert_type!(start_time, Integer, "start_time")
13
13
 
14
14
  @command = "1,#{start_time},\"#{file_path}\""
data/lib/osb.rb CHANGED
@@ -16,7 +16,12 @@ require_relative "osb/sample"
16
16
  require_relative "osb/video"
17
17
  require_relative "osb/background"
18
18
  require_relative "osb/storyboard"
19
+ require_relative "osb/dsl/object"
20
+ require_relative "osb/dsl/commands"
19
21
 
20
22
  module Osb
21
- VERSION = "1.0.3"
23
+ VERSION = "1.1.0"
22
24
  end
25
+
26
+ # Extend the main object with the DSL commands.
27
+ extend Osb
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dinh Vu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-19 00:00:00.000000000 Z
11
+ date: 2023-08-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple framework for building osu! storyboard.
14
14
  email: dinhvu2509@gmail.com
@@ -16,12 +16,16 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - ".yardopts"
20
+ - README.md
19
21
  - lib/osb.rb
20
22
  - lib/osb/animation.rb
21
23
  - lib/osb/assert.rb
22
24
  - lib/osb/background.rb
23
25
  - lib/osb/color.rb
24
26
  - lib/osb/commandable.rb
27
+ - lib/osb/dsl/commands.rb
28
+ - lib/osb/dsl/object.rb
25
29
  - lib/osb/enums/easing.rb
26
30
  - lib/osb/enums/layer.rb
27
31
  - lib/osb/enums/origin.rb
@@ -52,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
56
  - !ruby/object:Gem::Version
53
57
  version: '0'
54
58
  requirements: []
55
- rubygems_version: 3.1.6
59
+ rubygems_version: 3.4.18
56
60
  signing_key:
57
61
  specification_version: 4
58
62
  summary: osu! storyboard framework