Class: MovieMaker::Movie

Inherits:
Object
  • Object
show all
Defined in:
lib/movie_maker.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Movie

Takes an options-hash as argument: :screen => the screen to draw on :target_framerate => what framerate should it aim for, defaults to 60 :background => Color or Imageinstance :framework => :rubygame (default) or :gosu



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/movie_maker.rb', line 44

def initialize(options = {})
	@screen = options[:screen] || nil
	@framework = options[:framework] || :rubygame	# this can also be :gosu
	@target_framerate = options[:target_framerate] || 100
	@background = options[:background] || nil
	@draw_mode = options[:draw_mode] || nil
	@loop = options[:loop] || false
	
	if options[:background].kind_of? ::Rubygame::Color::ColorRGB
		@background = Surface.new(@screen.size)
		@background.draw_box_s([0,0],[@screen.width,@screen.height], options[:background])
	end
		
	@actions = []
	@onetime_actions = []
	@update_actions = []
	@tick = @start_at = @stop_at = 0
	@restart_at = nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Makes Object-initializing out of incomming missing actions ie. @movie.rotate(*arg) => Rotate.new(*arg) This makes for standalone extendable actions, one class per action

Ex. movie.between(0,2000).move(:ball, :from => [100,100], :to => [500,100])



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/movie_maker.rb', line 262

def method_missing(method, *args)
	
	## ".move" (the string) ==> Move.new (the class)
	klass = MovieMaker::Action.const_get(camelize(method))
	
	options = { :start_at => @start_at, 
							:stop_at => @stop_at,
							:screen => @screen,
							:background => @background,
							:object => @resource,
							:framework => @framework || :rubygame
						}
										
	standard_args = []
	args.each do |arg|
		if arg.is_a? Hash
			options.merge!(arg)
		else
			standard_args << arg
		end
	end
	
	
	action = klass.new(options, *standard_args)
	#puts "ACTION: #{action.class}(#{standard_args}) - between(#{@start_at}, #{@stop_at})"
	
	@actions << action
	#
	# Separate actions that needs 1-time trigger
	#
	if @resource.kind_of? Sound or @stop_at == @start_at
		@onetime_actions << action
	#
	# And thoose who needs constant update/drawing
	#
	else
		@update_actions << action
	end
	 
	self
end

Instance Attribute Details

#actionsObject

Returns the value of attribute actions.



33
34
35
# File 'lib/movie_maker.rb', line 33

def actions
  @actions
end

#backgroundObject (readonly)

Returns the value of attribute background.



34
35
36
# File 'lib/movie_maker.rb', line 34

def background
  @background
end

#clockObject (readonly)

Returns the value of attribute clock.



34
35
36
# File 'lib/movie_maker.rb', line 34

def clock
  @clock
end

#screenObject (readonly)

Returns the value of attribute screen.



34
35
36
# File 'lib/movie_maker.rb', line 34

def screen
  @screen
end

#stop_atObject (readonly)

Calculates the length of the movie by checking stop_at-times of all @actions that the movie consists of.

TODO:

  • current the method at() doesn’t provide at stop_at, which makes total_playtime fail



70
71
72
# File 'lib/movie_maker.rb', line 70

def stop_at
  @stop_at
end

#updated_countObject (readonly)

Returns the value of attribute updated_count.



34
35
36
# File 'lib/movie_maker.rb', line 34

def updated_count
  @updated_count
end

Instance Method Details

#at(start_at) ⇒ Object

Starts action at a start_time millisecs into the movie, no specific stop time

Example: @movie.at(2000).play_sound(@moo_sound)



325
326
327
328
329
# File 'lib/movie_maker.rb', line 325

def at(start_at)
	@start_at = start_at
	@stop_at = start_at
	self
end

#between(start_at, stop_at) ⇒ Object

Start following action start_at millisecs into the movie .. and stop it stop_at millisecs into the movie.



313
314
315
316
317
# File 'lib/movie_maker.rb', line 313

def between(start_at, stop_at)
	@start_at = start_at
	@stop_at = stop_at
	self
end

#camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) ⇒ Object



247
248
249
250
251
252
253
# File 'lib/movie_maker.rb', line 247

def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
	if first_letter_in_uppercase
		lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
	else
		lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
	end
end

#classify(table_name) ⇒ Object

Ripped from rails Inflector Used to convert move()-calls to a new instance of class Move and play_sound()-calls to a new instance of PlaySound .. etc.



244
245
246
# File 'lib/movie_maker.rb', line 244

def classify(table_name)
	camelize(singularize(table_name.to_s.sub(/.*\./, '')))
end

#delay(time) ⇒ Object

Start following action after the last one finishes + a millisecs delay argument

Example:

@movie.at(1).play_sound(@drip).delay(0.1).play_sound(@drip, => 0.5).delay(0.1).play_sound(@drip, => 0.2)



363
364
365
366
# File 'lib/movie_maker.rb', line 363

def delay(time)
	@start_at = @stop_at||@start_at + time
	self
end

#during(length) ⇒ Object

Start following action right away and specify how long is should run



334
335
336
337
338
339
340
341
342
343
# File 'lib/movie_maker.rb', line 334

def during(length)
	if @stop_at==0
		@start_at = :now
	else
		@start_at = @stop_at
	end
	
	@stop_at = @stop_at + length
	self
end

#gosu_draw(current_time) ⇒ Object

gosu_update - GOSU specific updateloop



205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/movie_maker.rb', line 205

def gosu_draw(current_time)
	@background.draw(0, 0, 0)	if	@background
	@update_actions.select { |action| action.started?(current_time) }.each do |action|
		action.sprite.image.draw_rot(	action.sprite.x, 
																	action.sprite.y, 
																	1, 
																	action.sprite.angle, 0.5, 0.5, 
																	action.sprite.width_scaling, 
																	action.sprite.height_scaling, 
																	action.sprite.color,
																	(@draw_mode || action.sprite.draw_mode))
	end
end

#gosu_update(current_time) ⇒ Object

gosu_update - GOSU specific updateloop



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/movie_maker.rb', line 183

def gosu_update(current_time)
	@updated_count = 0
	
	# Restart movie cleanly?
	#if current_time > @restart_at
	#end
	
	@onetime_actions.select { |action| action.started?(current_time) and !action.finalized? }.each do |action|
		#puts "onetime action: #{action.start_at} - #{action.stop_at}" + action.class.to_s
		action.finalize
	end
		
	@update_actions.select { |action| action.playing?(current_time) }.each do |action|
		#puts "update(#{current_time}): #{action.start_at} - #{action.stop_at}" + action.class.to_s
		action.update(current_time - action.start_at)
		@updated_count += 1
	end
		
end

#pauseObject

Pauses the movie, which should resume with a call to play() - NOT YET IMPLEMENTED



234
235
236
# File 'lib/movie_maker.rb', line 234

def pause
	self
end

#play(options = {}) ⇒ Object

Loops through the movie’s full timeline and updates all the @actions This method blocks until the movie ends

To play the movie Within your own gameloop, use update()



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/movie_maker.rb', line 87

def play(options = {})
	@framework ||= options[:framework] || :rubygame    							# this can also be :gosu
	@movie_stop_at ||= options[:stop_at] ? options[:stop_at] * 1000.0 : stop_at
	
	# Convert all start_at's filled with :now to current timestamp (meaning they'll start .. "now")
	@actions.each do |action|
		action.start_at = @clock.lifetime	if action.start_at.is_a? Symbol and action.start_at == :now
	end
	
	setup
		
	while @clock.lifetime < @movie_stop_at
		@tick = @clock.tick()
		
		title = "[framerate: #{@clock.framerate.to_i}] [Spriteupdates last tick: #{@updated_count}]"
		
		if @framework == :rubygame
			@screen.title = title 
			rubygame_update(@clock.lifetime)	
		else
			@screen.caption = title
			gosu_update(@clock.lifetime)
			gosu_draw(@clock.lifetime)
		end
			
		yield	 if block_given?
	end
end

#playing?(current_time) ⇒ Boolean

Is movie still playing?

Returns:

  • (Boolean)


77
78
79
# File 'lib/movie_maker.rb', line 77

def playing?(current_time)
	current_time <= stop_at 
end

#resource(resource) ⇒ Object Also known as: sprite, sound

Sprite/Sound/Resource selection



371
372
373
374
375
376
# File 'lib/movie_maker.rb', line 371

def resource(resource)
	@resource = resource
	@start_at = 0
	@stop_at = 0
	self
end

#restart_at(time) ⇒ Object



220
221
222
223
# File 'lib/movie_maker.rb', line 220

def restart_at(time)
	@restart_at = time
	self
end

#rubygame_update(current_time) ⇒ Object

rubygame_update() - Rubygame specific update Rubygame specific include: blit, sprite rects, dirty_rects and update_rects

  • goes through all @actions and calls undraw/update/draw.

  • goes through all @onetime_actions and calls play on them once.

Several optimizations are possible here:

  • sort @actions after start_at so update doesn’t have to loop through all events each time only until start_at isn’t lower then current_time anymore

  • remove “played-out” actions from @actions

  • remove “played-out” actions from @onetime_actions



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/movie_maker.rb', line 143

def rubygame_update(current_time)
	@updated_count = 0

	#@onetime_actions.select { |action| !action.playing?(current_time) and action.started?(current_time) }.each do |action|
	@onetime_actions.select { |action| action.started?(current_time) and !action.finalized?}.each do |action|
		action.finalize
	end
	
	dirty_rects = []
	# Only undraw/update actions that are active on the timeline
	@update_actions.select { |action| action.playing?(current_time) }.each do |action|
		dirty_rects << @background.blit(@screen, action.sprite.rect, action.sprite.rect) 
		action.update(current_time - action.start_at)
		#puts "update(#{current_time}): #{action.start_at} - #{action.stop_at}" + action.class.to_s
		@updated_count += 1
		
		#
		# Rotozoom lies here because of 2 reasons:
		# - Action can't do the actual imagemanipulation since Gosu does it drawtime
		# - By moving it out of the action, we can have more then 1 action manipulting rotozoom parameeters
		#
		if action.sprite.angle != 0 || action.sprite.width_scaling != 1 || action.sprite.height_scaling != 1
			
			action.sprite.image = action.image.rotozoom(	action.sprite.angle, 
																										[action.sprite.width_scaling, action.sprite.height_scaling],
																										true)
			action.sprite.realign_center
		end
	end
		
	@update_actions.select { |action| action.started?(current_time) }.each do |action|
		dirty_rects << action.sprite.image.blit(@screen, action.sprite.rect)
	end
		
	@screen.update_rects(dirty_rects)
end

#setupObject

Starts the clock which time will be sent to all events update()‘s Also paint a background, if any.



120
121
122
123
124
125
126
127
# File 'lib/movie_maker.rb', line 120

def setup
	@clock = ::Gosu::Clock.new
	@clock.target_framerate = @target_framerate
	if @framework == :rubygame
		@background.blit(@screen, [0, 0])	if @background
		@screen.update
	end
end

#stopObject

Stops/resets the movie- NOT YET IMPLEMENTED



227
228
229
# File 'lib/movie_maker.rb', line 227

def stop
	self
end

#thenObject

Start following action after the last one finishes

Example: @movie.between(0,1).move(@stone, :from => [0,0], :to => [0,400]).then.play_sound(@crash)



351
352
353
354
# File 'lib/movie_maker.rb', line 351

def then
	@start_at = @stop_at
	self
end