ippa-movie_maker 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. data/History.txt +27 -0
  2. data/README.rdoc +154 -0
  3. data/Rakefile +19 -0
  4. data/examples/axe.rb +24 -0
  5. data/examples/balls.rb +20 -0
  6. data/examples/echoes.rb +30 -0
  7. data/examples/gosu_base.rb +40 -0
  8. data/examples/gosu_rain.rb +34 -0
  9. data/examples/gosu_river_of_stars.rb +35 -0
  10. data/examples/gosu_triangles.rb +34 -0
  11. data/examples/gosu_wish_upon_a_star.rb +48 -0
  12. data/examples/ippagaming_intro.rb +23 -0
  13. data/examples/media/23700__hazure__chop.wav +0 -0
  14. data/examples/media/Establo.ttf +0 -0
  15. data/examples/media/FeaturedItem.ttf +0 -0
  16. data/examples/media/FreeSans.ttf +0 -0
  17. data/examples/media/Hursheys.ttf +0 -0
  18. data/examples/media/axe.png +0 -0
  19. data/examples/media/axe.svg +136 -0
  20. data/examples/media/ball.png +0 -0
  21. data/examples/media/ball.svg +182 -0
  22. data/examples/media/black.bmp +0 -0
  23. data/examples/media/blue_triangle.png +0 -0
  24. data/examples/media/blue_triangle.svg +81 -0
  25. data/examples/media/chop.wav +0 -0
  26. data/examples/media/cloth_background.png +0 -0
  27. data/examples/media/drawing.svg +86 -0
  28. data/examples/media/drip.wav +0 -0
  29. data/examples/media/green_triangle.png +0 -0
  30. data/examples/media/green_triangle.svg +104 -0
  31. data/examples/media/hit.wav +0 -0
  32. data/examples/media/ippa_gaming.png +0 -0
  33. data/examples/media/ippa_gaming.svg +463 -0
  34. data/examples/media/oil_drip.wav +0 -0
  35. data/examples/media/outdoor_scene.bmp +0 -0
  36. data/examples/media/outdoor_scene.png +0 -0
  37. data/examples/media/outdoor_scene.svg +316 -0
  38. data/examples/media/rain-bak1.wav +0 -0
  39. data/examples/media/rain.wav +0 -0
  40. data/examples/media/rain2.wav +0 -0
  41. data/examples/media/raindrop.png +0 -0
  42. data/examples/media/raindrop.svg +87 -0
  43. data/examples/media/raindrop_small.bmp +0 -0
  44. data/examples/media/raindrop_small.png +0 -0
  45. data/examples/media/red_triangle.png +0 -0
  46. data/examples/media/red_triangle.svg +81 -0
  47. data/examples/media/spaceship_noalpha.png +0 -0
  48. data/examples/media/star_5.png +0 -0
  49. data/examples/media/star_5.svg +83 -0
  50. data/examples/media/star_6.png +0 -0
  51. data/examples/media/star_6.svg +86 -0
  52. data/examples/rain.rb +26 -0
  53. data/examples/rain_advanced.rb +26 -0
  54. data/examples/rubygame_river_of_stars.rb +28 -0
  55. data/examples/zoom.rb +27 -0
  56. data/lib/movie_maker.rb +411 -0
  57. data/lib/movie_maker/action.rb +389 -0
  58. data/lib/movie_maker/core_extensions.rb +17 -0
  59. data/lib/movie_maker/gosu_autoload.rb +38 -0
  60. data/lib/movie_maker/gosu_clock.rb +139 -0
  61. data/lib/movie_maker/named_resource.rb +254 -0
  62. data/lib/movie_maker/sprite.rb +157 -0
  63. metadata +126 -0
Binary file
@@ -0,0 +1,83 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg
4
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
5
+ xmlns:cc="http://creativecommons.org/ns#"
6
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7
+ xmlns:svg="http://www.w3.org/2000/svg"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ width="744.09448819"
12
+ height="1052.3622047"
13
+ id="svg2"
14
+ sodipodi:version="0.32"
15
+ inkscape:version="0.46"
16
+ sodipodi:docname="star_5.svg"
17
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
18
+ <defs
19
+ id="defs4">
20
+ <inkscape:perspective
21
+ sodipodi:type="inkscape:persp3d"
22
+ inkscape:vp_x="0 : 526.18109 : 1"
23
+ inkscape:vp_y="0 : 1000 : 0"
24
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
25
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
26
+ id="perspective10" />
27
+ </defs>
28
+ <sodipodi:namedview
29
+ id="base"
30
+ pagecolor="#ffffff"
31
+ bordercolor="#666666"
32
+ borderopacity="1.0"
33
+ gridtolerance="10000"
34
+ guidetolerance="10"
35
+ objecttolerance="10"
36
+ inkscape:pageopacity="0.0"
37
+ inkscape:pageshadow="2"
38
+ inkscape:zoom="0.35"
39
+ inkscape:cx="375"
40
+ inkscape:cy="520"
41
+ inkscape:document-units="px"
42
+ inkscape:current-layer="layer1"
43
+ showgrid="false"
44
+ inkscape:window-width="870"
45
+ inkscape:window-height="756"
46
+ inkscape:window-x="1343"
47
+ inkscape:window-y="141" />
48
+ <metadata
49
+ id="metadata7">
50
+ <rdf:RDF>
51
+ <cc:Work
52
+ rdf:about="">
53
+ <dc:format>image/svg+xml</dc:format>
54
+ <dc:type
55
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
56
+ </cc:Work>
57
+ </rdf:RDF>
58
+ </metadata>
59
+ <g
60
+ inkscape:label="Layer 1"
61
+ inkscape:groupmode="layer"
62
+ id="layer1">
63
+ <path
64
+ sodipodi:type="star"
65
+ style="opacity:0.512;fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:18.17716598999999900;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
66
+ id="path2389"
67
+ sodipodi:sides="5"
68
+ sodipodi:cx="154.28571"
69
+ sodipodi:cy="912.36218"
70
+ sodipodi:r1="133.98331"
71
+ sodipodi:r2="343.54694"
72
+ sodipodi:arg1="0.80892323"
73
+ sodipodi:arg2="1.4372418"
74
+ inkscape:flatsided="false"
75
+ inkscape:rounded="0"
76
+ inkscape:randomized="0"
77
+ d="M 246.77142,1009.305 L 200.03168,1252.8498 L 90.667225,1030.2783 L -155.40096,1061.0856 L 22.481603,888.2955 L -82.85715,663.79074 L 136.44477,779.57202 L 317.41003,610.01312 L 275.0635,854.36003 L 492.24494,974.07163 L 246.77142,1009.305 z"
78
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,-245.83589,-213.84628)"
79
+ inkscape:export-filename="C:\Dev\projects\rubygame_movie_maker\samples\media\star_5.png"
80
+ inkscape:export-xdpi="27.559999"
81
+ inkscape:export-ydpi="27.559999" />
82
+ </g>
83
+ </svg>
Binary file
@@ -0,0 +1,86 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg
4
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
5
+ xmlns:cc="http://creativecommons.org/ns#"
6
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7
+ xmlns:svg="http://www.w3.org/2000/svg"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ width="744.09448819"
12
+ height="1052.3622047"
13
+ id="svg2"
14
+ sodipodi:version="0.32"
15
+ inkscape:version="0.46"
16
+ sodipodi:docname="star_6.svg"
17
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
18
+ inkscape:export-filename="C:\dev\projects\rubygame_movie_maker\samples\media\star_6.png"
19
+ inkscape:export-xdpi="29.873182"
20
+ inkscape:export-ydpi="29.873182">
21
+ <defs
22
+ id="defs4">
23
+ <inkscape:perspective
24
+ sodipodi:type="inkscape:persp3d"
25
+ inkscape:vp_x="0 : 526.18109 : 1"
26
+ inkscape:vp_y="0 : 1000 : 0"
27
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
28
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
29
+ id="perspective10" />
30
+ </defs>
31
+ <sodipodi:namedview
32
+ id="base"
33
+ pagecolor="#ffffff"
34
+ bordercolor="#666666"
35
+ borderopacity="1.0"
36
+ gridtolerance="10000"
37
+ guidetolerance="10"
38
+ objecttolerance="10"
39
+ inkscape:pageopacity="0.0"
40
+ inkscape:pageshadow="2"
41
+ inkscape:zoom="0.35"
42
+ inkscape:cx="375"
43
+ inkscape:cy="520"
44
+ inkscape:document-units="px"
45
+ inkscape:current-layer="layer1"
46
+ showgrid="false"
47
+ inkscape:window-width="870"
48
+ inkscape:window-height="756"
49
+ inkscape:window-x="1343"
50
+ inkscape:window-y="141" />
51
+ <metadata
52
+ id="metadata7">
53
+ <rdf:RDF>
54
+ <cc:Work
55
+ rdf:about="">
56
+ <dc:format>image/svg+xml</dc:format>
57
+ <dc:type
58
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
59
+ </cc:Work>
60
+ </rdf:RDF>
61
+ </metadata>
62
+ <g
63
+ inkscape:label="Layer 1"
64
+ inkscape:groupmode="layer"
65
+ id="layer1">
66
+ <path
67
+ sodipodi:type="star"
68
+ style="opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:18.17716598999999900;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
69
+ id="path2389"
70
+ sodipodi:sides="6"
71
+ sodipodi:cx="154.28571"
72
+ sodipodi:cy="912.36218"
73
+ sodipodi:r1="133.98331"
74
+ sodipodi:r2="343.54694"
75
+ sodipodi:arg1="0.80892323"
76
+ sodipodi:arg2="1.332522"
77
+ inkscape:flatsided="false"
78
+ inkscape:rounded="0"
79
+ inkscape:randomized="0"
80
+ d="M 246.77142,1009.305 L 235.37174,1246.2028 L 116.57358,1040.9286 L -94.285728,1149.505 L 24.087866,943.98573 L -175.37176,815.66444 L 61.799987,815.41932 L 73.199675,578.52157 L 191.99783,783.79577 L 402.85714,675.21932 L 284.48355,880.73863 L 483.94317,1009.0599 L 246.77142,1009.305 z"
81
+ transform="matrix(0.9659258,0.258819,-0.258819,0.9659258,444.25101,-428.84409)"
82
+ inkscape:export-filename="C:\dev\projects\rubygame_movie_maker\samples\media\star_6.png"
83
+ inkscape:export-xdpi="34"
84
+ inkscape:export-ydpi="34" />
85
+ </g>
86
+ </svg>
data/examples/rain.rb ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/ruby
2
+ require File.join("..", "lib", "movie_maker")
3
+
4
+ include Rubygame
5
+ include MovieMaker
6
+ include MovieMaker::Rubygame
7
+
8
+ Rubygame.init()
9
+ Surface.autoload_dirs = [ "media" ]
10
+
11
+ @screen = Screen.set_mode([800, 600], 0)
12
+ @movie = Movie.new(:screen => @screen, :background => Color[:black], :target_framerate => 200)
13
+
14
+ #
15
+ # generate 5 seconds of rain, each raindrop moving from top to bottom in 1 second (1000ms), 50ms between each drop.
16
+ #
17
+ (1..100).each do |nr|
18
+ x = rand(800)
19
+ start_at = nr / 20.0
20
+ stop_at = nr / 20.0 + 1
21
+
22
+ @raindrop = Sprite.new("raindrop.png")
23
+ @movie.resource(@raindrop).move([x,0]).between(start_at, stop_at).move([x,650])
24
+ end
25
+
26
+ @movie.play
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/ruby
2
+ require File.join("..", "lib", "movie_maker")
3
+
4
+ include Rubygame
5
+ include MovieMaker
6
+ include MovieMaker::Rubygame
7
+
8
+ Rubygame.init()
9
+ Surface.autoload_dirs = [ "media" ]
10
+ Sound.autoload_dirs = [ "media" ]
11
+
12
+ @screen = Screen.set_mode([800, 600], 0)
13
+ @movie = Movie.new(:screen => @screen, :background => Color[:black], :target_framerate => 200)
14
+
15
+ start_at = stop_at = 0
16
+ (1..500).each do |nr|
17
+ x = rand(800)
18
+ start_at = nr / 50.0
19
+ stop_at = nr / 50.0 + 1.0
20
+
21
+ @raindrop = Sprite.new("raindrop_small.png")
22
+ # OLD: movie.between(start_at, stop_at).move_facing_direction(@raindrop, :from => [x,0], :to => [x+100+(nr/5)+rand(50),650])
23
+ @movie.resource(@raindrop).move([x,0]).between(start_at, stop_at).move_facing_direction([x+100+(nr/5)+rand(50),650])
24
+ end
25
+ @movie.at(0).play_sound(Sound["rain2.wav"])
26
+ @movie.play
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/ruby
2
+ require File.join("..", "lib", "movie_maker")
3
+
4
+ include Rubygame
5
+ include MovieMaker
6
+ include MovieMaker::Rubygame
7
+
8
+ Rubygame.init()
9
+ Surface.autoload_dirs = [ "media" ]
10
+
11
+ @screen = Screen.set_mode([800, 600], 0)
12
+ @background = Surface.autoload("cloth_background.png")
13
+
14
+ @movie = Movie.new(:screen => @screen, :background => Color[:black], :framework => :rubygame)
15
+ 0.upto(20) do |start|
16
+ @star = Sprite.new("star_5.png") # x,y = 0,0 is default
17
+ @movie.resource(@star) # all following actions are applied to this resource
18
+ #@movie.color( Color.new(100 + rand(155),rand(255),rand(255),rand(255)) )
19
+ @movie.zoom(0.2)
20
+ @movie.between(start/10.0, start/10.0 + 2)
21
+ @movie.velocity([10,0])
22
+ @movie.acceleration([0, 0.05 + rand(0.1)])
23
+ @movie.rotate(angle = 90 * rand(10))
24
+ @movie.zoom(1.0 + rand(2.0))
25
+ @movie.then.color(0x00FFFFFF)
26
+ end
27
+
28
+ @movie.play
data/examples/zoom.rb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/ruby
2
+ require File.join("..", "lib", "movie_maker")
3
+
4
+ include Rubygame
5
+ include MovieMaker
6
+ include MovieMaker::Rubygame
7
+
8
+ TTF.setup()
9
+ Rubygame.init()
10
+ Surface.autoload_dirs = [ "media" ]
11
+ Sound.autoload_dirs = [ "media" ]
12
+
13
+ @screen = Screen.set_mode([800, 600], 0, [HWSURFACE])
14
+ @font = TTF.new(File.join("media", "FeaturedItem.ttf"), 200)
15
+
16
+ @ttf_sprites = []
17
+ %w{I P P A}.each_with_index do |letter, index|
18
+ @ttf_sprites << TTFSprite.new(letter, :font => @font, :color => Color[:white], :position => [200+(index*100),100])
19
+ end
20
+
21
+ movie = Movie.new(:screen => @screen, :background => Color[:black])
22
+
23
+ @ttf_sprites.each_with_index do |sprite, index|
24
+ # OLD: movie.between(index, index+1).zoom(sprite, :scale_from => 5, :scale_to => 0.1).rotate(sprite, :angle => 360).then.play_sound(Sound["hit.wav"])
25
+ movie.resource(sprite).at(index).zoom(5).between(index, index+1).zoom(0.6).rotate(360).then.play_sound(Sound["hit.wav"])
26
+ end
27
+ movie.play(:stop_at => 6)
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), 'lib')
3
+ ['rubygems','gosu','rubygame'].each do |gem|
4
+ require gem
5
+ end
6
+
7
+ ['action','sprite','core_extensions', 'gosu_clock'].each do |lib|
8
+ require File.join(File.dirname(__FILE__), 'movie_maker', lib)
9
+ end
10
+
11
+ module MovieMaker
12
+ VERSION = "0.3.0"
13
+ end
14
+
15
+ #
16
+ # MovieMaker
17
+ # A class to make simple rubygamemovies (moving around sprites in a timed fashion, playing sounds etc)
18
+ # Rubygame solves all the heavy lifting for us when it comes to drawing on the screen.
19
+ # MovieMaker adds makes timing and setup of a scene esay. Use it for intros, ingame animations and simple "demos".
20
+ # Namingconventions tries to follow Rubygame.
21
+ #
22
+ # This class is responsible for:
23
+ #
24
+ # - Keeping track of actions (move, hide, show, play a sound etc)
25
+ # - Playing them according to userspecified timing (2 actions can start at the same time)
26
+ # - Keeping track of what sprite/surface to paint to, framerate etc
27
+ #
28
+ module MovieMaker
29
+
30
+ class Movie
31
+
32
+
33
+ attr_accessor :actions
34
+ attr_reader :screen, :background, :clock, :updated_count, :stop_at
35
+
36
+ #
37
+ # Takes an options-hash as argument:
38
+ # :screen => the screen to draw on
39
+ # :target_framerate => what framerate should it aim for, defaults to 60
40
+ # :background => Color or Imageinstance
41
+ # :framework => :rubygame (default) or :gosu
42
+ #
43
+
44
+ def initialize(options = {})
45
+ @screen = options[:screen] || nil
46
+ @framework = options[:framework] || :rubygame # this can also be :gosu
47
+ @target_framerate = options[:target_framerate] || 100
48
+ @background = options[:background] || nil
49
+ @draw_mode = options[:draw_mode] || nil
50
+ @loop = options[:loop] || false
51
+
52
+ if options[:background].kind_of? ::Rubygame::Color::ColorRGB
53
+ @background = Surface.new(@screen.size)
54
+ @background.draw_box_s([0,0],[@screen.width,@screen.height], options[:background])
55
+ end
56
+
57
+ @actions = []
58
+ @onetime_actions = []
59
+ @update_actions = []
60
+ @tick = @start_at = @stop_at = 0
61
+ @restart_at = nil
62
+ end
63
+
64
+ #
65
+ # Calculates the length of the movie by checking stop_at-times of all @actions that the movie consists of.
66
+ #
67
+ # TODO:
68
+ # - current the method at() doesn't provide at stop_at, which makes total_playtime fail
69
+ #
70
+ def stop_at
71
+ @movie_stop_at ||= @actions.inject(0) { |time, action| action.stop_at > time ? action.stop_at : time }
72
+ end
73
+
74
+ #
75
+ # Is movie still playing?
76
+ #
77
+ def playing?(current_time)
78
+ current_time <= stop_at
79
+ end
80
+
81
+ #
82
+ # Loops through the movie's full timeline and updates all the @actions
83
+ # This method blocks until the movie ends
84
+ #
85
+ # To play the movie Within your own gameloop, use update()
86
+ #
87
+ def play(options = {})
88
+ @framework ||= options[:framework] || :rubygame # this can also be :gosu
89
+ @movie_stop_at ||= options[:stop_at] ? options[:stop_at] * 1000.0 : stop_at
90
+
91
+ # Convert all start_at's filled with :now to current timestamp (meaning they'll start .. "now")
92
+ @actions.each do |action|
93
+ action.start_at = @clock.lifetime if action.start_at.is_a? Symbol and action.start_at == :now
94
+ end
95
+
96
+ setup
97
+
98
+ while @clock.lifetime < @movie_stop_at
99
+ @tick = @clock.tick()
100
+
101
+ title = "[framerate: #{@clock.framerate.to_i}] [Spriteupdates last tick: #{@updated_count}]"
102
+
103
+ if @framework == :rubygame
104
+ @screen.title = title
105
+ rubygame_update(@clock.lifetime)
106
+ else
107
+ @screen.caption = title
108
+ gosu_update(@clock.lifetime)
109
+ gosu_draw(@clock.lifetime)
110
+ end
111
+
112
+ yield if block_given?
113
+ end
114
+ end
115
+
116
+ #
117
+ # Starts the clock which time will be sent to all events update()'s
118
+ # Also paint a background, if any.
119
+ #
120
+ def setup
121
+ @clock = ::Gosu::Clock.new
122
+ @clock.target_framerate = @target_framerate
123
+ if @framework == :rubygame
124
+ @background.blit(@screen, [0, 0]) if @background
125
+ @screen.update
126
+ end
127
+ end
128
+
129
+ #
130
+ # rubygame_update() - Rubygame specific update
131
+ # Rubygame specific include: blit, sprite rects, dirty_rects and update_rects
132
+ #
133
+ # - goes through all @actions and calls undraw/update/draw.
134
+ # - goes through all @onetime_actions and calls play on them once.
135
+ #
136
+ # Several optimizations are possible here:
137
+ # - sort @actions after start_at so update doesn't have to loop through all events each time
138
+ # only until start_at isn't lower then current_time anymore
139
+ # - remove "played-out" actions from @actions
140
+ # - remove "played-out" actions from @onetime_actions
141
+ #
142
+ #
143
+ def rubygame_update(current_time)
144
+ @updated_count = 0
145
+
146
+ #@onetime_actions.select { |action| !action.playing?(current_time) and action.started?(current_time) }.each do |action|
147
+ @onetime_actions.select { |action| action.started?(current_time) and !action.finalized?}.each do |action|
148
+ action.finalize
149
+ end
150
+
151
+ dirty_rects = []
152
+ # Only undraw/update actions that are active on the timeline
153
+ @update_actions.select { |action| action.playing?(current_time) }.each do |action|
154
+ dirty_rects << @background.blit(@screen, action.sprite.rect, action.sprite.rect)
155
+ action.update(current_time - action.start_at)
156
+ #puts "update(#{current_time}): #{action.start_at} - #{action.stop_at}" + action.class.to_s
157
+ @updated_count += 1
158
+
159
+ #
160
+ # Rotozoom lies here because of 2 reasons:
161
+ # - Action can't do the actual imagemanipulation since Gosu does it drawtime
162
+ # - By moving it out of the action, we can have more then 1 action manipulting rotozoom parameeters
163
+ #
164
+ if action.sprite.angle != 0 || action.sprite.width_scaling != 1 || action.sprite.height_scaling != 1
165
+
166
+ action.sprite.image = action.image.rotozoom( action.sprite.angle,
167
+ [action.sprite.width_scaling, action.sprite.height_scaling],
168
+ true)
169
+ action.sprite.realign_center
170
+ end
171
+ end
172
+
173
+ @update_actions.select { |action| action.started?(current_time) }.each do |action|
174
+ dirty_rects << action.sprite.image.blit(@screen, action.sprite.rect)
175
+ end
176
+
177
+ @screen.update_rects(dirty_rects)
178
+ end
179
+
180
+ #
181
+ # gosu_update - GOSU specific updateloop
182
+ #
183
+ def gosu_update(current_time)
184
+ @updated_count = 0
185
+
186
+ # Restart movie cleanly?
187
+ #if current_time > @restart_at
188
+ #end
189
+
190
+ @onetime_actions.select { |action| action.started?(current_time) and !action.finalized? }.each do |action|
191
+ #puts "onetime action: #{action.start_at} - #{action.stop_at}" + action.class.to_s
192
+ action.finalize
193
+ end
194
+
195
+ @update_actions.select { |action| action.playing?(current_time) }.each do |action|
196
+ #puts "update(#{current_time}): #{action.start_at} - #{action.stop_at}" + action.class.to_s
197
+ action.update(current_time - action.start_at)
198
+ @updated_count += 1
199
+ end
200
+
201
+ end
202
+ #
203
+ # gosu_update - GOSU specific updateloop
204
+ #
205
+ def gosu_draw(current_time)
206
+ @background.draw(0, 0, 0) if @background
207
+ @update_actions.select { |action| action.started?(current_time) }.each do |action|
208
+ action.sprite.image.draw_rot( action.sprite.x,
209
+ action.sprite.y,
210
+ 1,
211
+ action.sprite.angle, 0.5, 0.5,
212
+ action.sprite.width_scaling,
213
+ action.sprite.height_scaling,
214
+ action.sprite.color,
215
+ (@draw_mode || action.sprite.draw_mode))
216
+ end
217
+ end
218
+
219
+
220
+ def restart_at(time)
221
+ @restart_at = time
222
+ self
223
+ end
224
+ #
225
+ # Stops/resets the movie- NOT YET IMPLEMENTED
226
+ #
227
+ def stop
228
+ self
229
+ end
230
+
231
+ #
232
+ # Pauses the movie, which should resume with a call to play() - NOT YET IMPLEMENTED
233
+ #
234
+ def pause
235
+ self
236
+ end
237
+
238
+
239
+ #
240
+ # Ripped from rails Inflector
241
+ # Used to convert move()-calls to a new instance of class Move
242
+ # and play_sound()-calls to a new instance of PlaySound .. etc.
243
+ #
244
+ def classify(table_name)
245
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
246
+ end
247
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
248
+ if first_letter_in_uppercase
249
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
250
+ else
251
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
252
+ end
253
+ end
254
+
255
+ #
256
+ # Makes Object-initializing out of incomming missing actions
257
+ # ie. @movie.rotate(*arg) => Rotate.new(*arg)
258
+ # This makes for standalone extendable actions, one class per action
259
+ #
260
+ # Ex. movie.between(0,2000).move(:ball, :from => [100,100], :to => [500,100])
261
+ #
262
+ def method_missing(method, *args)
263
+
264
+ ## ".move" (the string) ==> Move.new (the class)
265
+ klass = MovieMaker::Action.const_get(camelize(method))
266
+
267
+ options = { :start_at => @start_at,
268
+ :stop_at => @stop_at,
269
+ :screen => @screen,
270
+ :background => @background,
271
+ :object => @resource,
272
+ :framework => @framework || :rubygame
273
+ }
274
+
275
+ standard_args = []
276
+ args.each do |arg|
277
+ if arg.is_a? Hash
278
+ options.merge!(arg)
279
+ else
280
+ standard_args << arg
281
+ end
282
+ end
283
+
284
+
285
+ action = klass.new(options, *standard_args)
286
+ #puts "ACTION: #{action.class}(#{standard_args}) - between(#{@start_at}, #{@stop_at})"
287
+
288
+ @actions << action
289
+ #
290
+ # Separate actions that needs 1-time trigger
291
+ #
292
+ if @resource.kind_of? Sound or @stop_at == @start_at
293
+ @onetime_actions << action
294
+ #
295
+ # And thoose who needs constant update/drawing
296
+ #
297
+ else
298
+ @update_actions << action
299
+ end
300
+
301
+ self
302
+ end
303
+
304
+ #
305
+ # Bellow are methods to time actions and make them chainable
306
+ #
307
+
308
+
309
+ #
310
+ # Start following action start_at millisecs into the movie
311
+ # .. and stop it stop_at millisecs into the movie.
312
+ #
313
+ def between(start_at, stop_at)
314
+ @start_at = start_at
315
+ @stop_at = stop_at
316
+ self
317
+ end
318
+
319
+ #
320
+ # Starts action at a start_time millisecs into the movie, no specific stop time
321
+ #
322
+ # Example:
323
+ # @movie.at(2000).play_sound(@moo_sound)
324
+ #
325
+ def at(start_at)
326
+ @start_at = start_at
327
+ @stop_at = start_at
328
+ self
329
+ end
330
+
331
+ #
332
+ # Start following action right away and specify how long is should run
333
+ #
334
+ def during(length)
335
+ if @stop_at==0
336
+ @start_at = :now
337
+ else
338
+ @start_at = @stop_at
339
+ end
340
+
341
+ @stop_at = @stop_at + length
342
+ self
343
+ end
344
+
345
+ #
346
+ # Start following action after the last one finishes
347
+ #
348
+ # Example:
349
+ # @movie.between(0,1).move(@stone, :from => [0,0], :to => [0,400]).then.play_sound(@crash)
350
+ #
351
+ def then
352
+ @start_at = @stop_at
353
+ self
354
+ end
355
+
356
+ #
357
+ # Start following action after the last one finishes + a millisecs delay argument
358
+ #
359
+ # Example:
360
+ #
361
+ # @movie.at(1).play_sound(@drip).delay(0.1).play_sound(@drip, {:volume => 0.5}).delay(0.1).play_sound(@drip, {:volume => 0.2})
362
+ #
363
+ def delay(time)
364
+ @start_at = @stop_at||@start_at + time
365
+ self
366
+ end
367
+
368
+ #
369
+ # Sprite/Sound/Resource selection
370
+ #
371
+ def resource(resource)
372
+ @resource = resource
373
+ @start_at = 0
374
+ @stop_at = 0
375
+ self
376
+ end
377
+ alias :sprite :resource
378
+ alias :sound :resource
379
+
380
+ end
381
+ end
382
+
383
+ #
384
+ # Test the MovieMaker class, a first crude spec
385
+ # This code will change alot as I use it as a quick way of testing various new stuff out.
386
+ #
387
+ if $0 == __FILE__
388
+ include MovieMaker # A must for actions to work
389
+ include Rubygame # for easy access to rubygame stuff
390
+ include MovieMaker::Rubygame # for easy access to moviemakers special rubygamesprites
391
+
392
+ Surface.autoload_dirs = [ File.join("samples", "media"), "media" ]
393
+ Sound.autoload_dirs = [ File.join("samples", "media"), "media" ]
394
+
395
+ @screen = Screen.set_mode([800, 600], 0)
396
+ @background = Surface.autoload("outdoor_scene.png")
397
+
398
+ movie = Movie.new(:framework => :rubygame,
399
+ :screen => @screen,
400
+ :background => @background,
401
+ :target_framerate => 200)
402
+
403
+ (0..2).each do |nr|
404
+ @spaceship = Sprite.new("spaceship_noalpha.png")
405
+
406
+ ## New style!
407
+ movie.resource(@spaceship).between(0,4).move([400+rand(300),rand(350)]).rotate(360).then.play_sound(Sound["hit.wav"])
408
+
409
+ end
410
+ movie.play(:stop_at => 5)
411
+ end