summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTom Black <[email protected]>2018-01-30 00:23:34 -0800
committerTom Black <[email protected]>2018-05-06 15:41:06 -0700
commit7d3cff5e5d6d53d7f4def94a9dc831e099e03e67 (patch)
treec3fc81f0ee676208dee1530ff5935f1b88692677
parent2e07255723428d09eab38ad1a5fde11fc9a287d8 (diff)
downloadruby2d-7d3cff5e5d6d53d7f4def94a9dc831e099e03e67.tar.gz
ruby2d-7d3cff5e5d6d53d7f4def94a9dc831e099e03e67.zip
Sprite class redesign
See #33 for discussion
-rw-r--r--ext/ruby2d/ruby2d.c14
-rw-r--r--lib/ruby2d/sprite.rb171
m---------test/media0
-rw-r--r--test/sprite.rb99
-rw-r--r--test/sprite_spec.rb6
5 files changed, 222 insertions, 68 deletions
diff --git a/ext/ruby2d/ruby2d.c b/ext/ruby2d/ruby2d.c
index 0b9e338..2daa7ed 100644
--- a/ext/ruby2d/ruby2d.c
+++ b/ext/ruby2d/ruby2d.c
@@ -379,6 +379,14 @@ static R_VAL ruby2d_sprite_ext_init(R_VAL self, R_VAL path) {
#endif
S2D_Log(S2D_INFO, "Init sprite: %s", RSTRING_PTR(path));
S2D_Sprite *spr = S2D_CreateSprite(RSTRING_PTR(path));
+
+ // Get width and height from Ruby class. If set, use it, else choose the
+ // native dimensions of the sprite image.
+ R_VAL w = r_iv_get(self, "@width");
+ R_VAL h = r_iv_get(self, "@height");
+ r_iv_set(self, "@width" , r_test(w) ? w : INT2NUM(spr->width));
+ r_iv_set(self, "@height", r_test(h) ? h : INT2NUM(spr->height));
+
r_iv_set(self, "@data", r_data_wrap_struct(sprite, spr));
return R_NIL;
}
@@ -392,6 +400,8 @@ static R_VAL ruby2d_sprite_ext_render(mrb_state* mrb, R_VAL self) {
#else
static R_VAL ruby2d_sprite_ext_render(R_VAL self) {
#endif
+ r_funcall(self, "update", 0);
+
S2D_Sprite *spr;
r_data_get_struct(self, "@data", &sprite_data_type, S2D_Sprite, spr);
@@ -402,8 +412,8 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) {
spr,
NUM2INT(r_iv_get(self, "@clip_x")),
NUM2INT(r_iv_get(self, "@clip_y")),
- NUM2INT(r_iv_get(self, "@clip_w")),
- NUM2INT(r_iv_get(self, "@clip_h"))
+ NUM2INT(r_iv_get(self, "@clip_width")),
+ NUM2INT(r_iv_get(self, "@clip_height"))
);
S2D_DrawSprite(spr);
diff --git a/lib/ruby2d/sprite.rb b/lib/ruby2d/sprite.rb
index 7c66f97..a9d9e98 100644
--- a/lib/ruby2d/sprite.rb
+++ b/lib/ruby2d/sprite.rb
@@ -2,11 +2,12 @@
module Ruby2D
class Sprite
+ include Renderable
- attr_accessor :x, :y, :clip_x, :clip_y, :clip_w, :clip_h, :data
- attr_reader :z
+ attr_accessor :x, :y, :width, :height, :loop,
+ :clip_x, :clip_y, :clip_width, :clip_height, :data
- def initialize(x, y, path, z=0)
+ def initialize(path, opts = {})
unless RUBY_ENGINE == 'opal'
unless File.exists? path
@@ -14,88 +15,130 @@ module Ruby2D
end
end
- @x, @y, @path = x, y, path
- @clip_x, @clip_y, @clip_w, @clip_h = 0, 0, 0, 0
- @default = nil
- @animations = {}
- @current_animation = nil
- @current_frame = 0
- @current_frame_time = 0
- @z = z
-
- ext_init(path)
- if Module.const_defined? :DSL
- Application.add(self)
- end
+ @path = path
+ @x = opts[:x] || 0
+ @y = opts[:y] || 0
+ @z = opts[:z] || 0
+ @width = opts[:width] || nil
+ @height = opts[:height] || nil
+ @start_time = 0.0
+ @loop = opts[:loop] || false
+ @frame_time = opts[:time] || 300
+ @animations = opts[:animations] || {}
+ @playing = false
+ @current_frame = opts[:default] || 0
+ @last_frame = 0
+
+ ext_init(@path)
+ @clip_x = opts[:clip_x] || 0
+ @clip_y = opts[:clip_y] || 0
+ @clip_width = opts[:clip_width] || @width
+ @clip_height = opts[:clip_height] || @height
+ @animations[:default] = 0..(@width / @clip_width) - 1 # set default animation
+
+ @defaults = {
+ animation: @animations.first[0],
+ frame: @current_frame,
+ frame_time: @frame_time,
+ clip_x: @clip_x,
+ clip_y: @clip_y,
+ clip_width: @clip_width,
+ clip_height: @clip_height,
+ loop: @loop
+ }
+
+ add
end
- def start(x, y, w, h)
- @default = [x, y, w, h]
- clip(x, y, w, h)
- end
+ def play(animation = nil, loop = nil)
+ if !@playing || (animation != @playing_animation && animation != nil)
+
+ @playing = true
+ @playing_animation = animation || :default
+ frames = @animations[@playing_animation]
+
+ case frames
+ # When animation is a range, play through frames horizontally
+ when Range
+ @first_frame = frames.first || @defaults[:frame]
+ @current_frame = frames.first || @defaults[:frame]
+ @last_frame = frames.last
+ # When array...
+ when Array
+ @first_frame = 0
+ @current_frame = 0
+ @last_frame = frames.length - 1
+ end
- def add(animations)
- @animations.merge!(animations)
- end
+ # Set looping
+ @loop = loop == :loop || @defaults[:loop] ? true : false
- def animate(animation)
- if @current_animation != animation
- @current_frame = 0
- @current_frame_time = 0
- @current_animation = animation
+ set_frame
+ restart_time
end
- animate_frames(@animations[animation])
end
- def reset
- clip(@default[0], @default[1], @default[2], @default[3])
- @current_animation = nil
+ # Stop the current animation and set to the default frame
+ def stop
+ @playing = false
+ @playing_animation = @defaults[:animation]
+ @current_frame = @defaults[:frame]
+ set_frame
end
- # TODO: Sprite already has an `add` method, have to reconsile
- # def add
- # if Module.const_defined? :DSL
- # Application.add(self)
- # end
- # end
+ # Reset frame to defaults
+ def reset_clipping_rect
+ @clip_x = @defaults[:clip_x]
+ @clip_y = @defaults[:clip_y]
+ @clip_width = @defaults[:clip_width]
+ @clip_height = @defaults[:clip_height]
+ end
- def remove
- if Module.const_defined? :DSL
- Application.remove(self)
+ # Set the position of the clipping retangle based on the current frame
+ def set_frame
+ frames = @animations[@playing_animation]
+ case frames
+ when Range
+ reset_clipping_rect
+ @clip_x = @current_frame * @clip_width
+ when Array
+ f = frames[@current_frame]
+ @clip_x = f[:x] || @defaults[:clip_x]
+ @clip_y = f[:y] || @defaults[:clip_y]
+ @clip_width = f[:width] || @defaults[:clip_width]
+ @clip_height = f[:height] || @defaults[:clip_height]
+ @frame_time = f[:time] || @defaults[:frame_time]
end
end
- def width
- @current_animation ? @animations[@current_animation][@current_frame][2] : @default[2]
+ # Calculate the time in ms
+ def elapsed_time
+ (Time.now.to_f - @start_time) * 1000
end
- def height
- @current_animation ? @animations[@current_animation][@current_frame][3] : @default[3]
+ # Restart the timer
+ def restart_time
+ @start_time = Time.now.to_f
end
- private
+ # Update the sprite animation, called by `Sprite#ext_render`
+ def update
+ if @playing
- def clip(x, y, w, h)
- @clip_x, @clip_y, @clip_w, @clip_h = x, y, w, h
- end
+ # Advance the frame
+ unless elapsed_time <= (@frame_time || @defaults[:frame_time])
+ @current_frame += 1
+ restart_time
+ end
- def animate_frames(frames)
- if @current_frame_time < frames[@current_frame][4]
- clip_with_current_frame(frames)
- @current_frame_time += 1
- else
- @current_frame += 1
- if @current_frame == frames.length
- @current_frame = 0
+ # Reset to the starting frame if all frames played
+ if @current_frame > @last_frame
+ @current_frame = @first_frame
+ unless @loop then stop end
end
- clip_with_current_frame(frames)
- @current_frame_time = 0
- end
- end
- def clip_with_current_frame(frames)
- clip(frames[@current_frame][0], frames[@current_frame][1],
- frames[@current_frame][2], frames[@current_frame][3])
+ set_frame
+ end
end
end
diff --git a/test/media b/test/media
-Subproject 0d54e768f8da2217203649cb270f4d0add82328
+Subproject 44ae8c4a971e3ba6520ad9953fb4dd12eb36d2c
diff --git a/test/sprite.rb b/test/sprite.rb
new file mode 100644
index 0000000..713375a
--- /dev/null
+++ b/test/sprite.rb
@@ -0,0 +1,99 @@
+require 'ruby2d'
+
+if RUBY_ENGINE == 'opal'
+ media = "../test/media"
+else
+ media = "media"
+end
+
+set title: "Ruby 2D — Sprite", width: 350, height: 150
+
+
+coin = Sprite.new(
+ "#{media}/coin.png",
+ clip_width: 84,
+ time: 300,
+ loop: true
+)
+
+coin.play
+
+boom = Sprite.new(
+ "#{media}/boom.png",
+ x: 109,
+ clip_width: 127,
+ time: 75
+)
+
+hero = Sprite.new(
+ "#{media}/hero.png",
+ x: 261,
+ clip_width: 78,
+ time: 250,
+ animations: {
+ walk: 1..2,
+ climb: 3..4,
+ cheer: 5..6
+ }
+)
+
+atlas = Sprite.new(
+ "#{media}/texture_atlas.png",
+ x: 10, y: 100,
+ animations: {
+ count: [
+ {
+ x: 0, y: 0,
+ width: 35, height: 41,
+ time: 300
+ },
+ {
+ x: 26, y: 46,
+ width: 35, height: 38,
+ time: 400
+ },
+ {
+ x: 65, y: 10,
+ width: 32, height: 41,
+ time: 500
+ },
+ {
+ x: 10, y: 99,
+ width: 32, height: 38,
+ time: 600
+ },
+ {
+ x: 74, y: 80,
+ width: 32, height: 38,
+ time: 700
+ }
+ ]
+ }
+)
+
+atlas.play :count, :loop
+
+
+on :key_down do |e|
+ close if e.key == 'escape'
+
+ case e.key
+ when 'p'
+ coin.play
+ boom.play
+ atlas.play :count
+ when 's'
+ coin.stop
+ hero.stop
+ atlas.stop
+ when 'right'
+ hero.play :walk, :loop
+ when 'up'
+ hero.play :climb, :loop
+ when 'down'
+ hero.play :cheer
+ end
+end
+
+
+show
diff --git a/test/sprite_spec.rb b/test/sprite_spec.rb
index e44ea62..a9cbf31 100644
--- a/test/sprite_spec.rb
+++ b/test/sprite_spec.rb
@@ -3,13 +3,15 @@ require 'ruby2d'
RSpec.describe Ruby2D::Sprite do
describe '#new' do
+
it "raises exception if file doesn't exist" do
- expect { Sprite.new(0, 0, "bad_sprite_sheet.png") }.to raise_error(Ruby2D::Error)
+ expect { Sprite.new("bad_sprite_sheet.png") }.to raise_error(Ruby2D::Error)
end
it 'creates a new sprite' do
- Sprite.new(0, 0, "test/media/sprite_sheet.png")
+ Sprite.new("test/media/coin.png")
end
+
end
end