indexing description: "[ A sprite is a list of drawables with seperate framerates for each image. Sprites may be generated with an *.anim file using EM_ANIMATION to parse the file. ]" date: "$Date$" revision: "$Revision$" class EM_SPRITE inherit EM_TIME_SINGLETON export {NONE} all end EM_SHARED_BITMAP_FACTORY export {NONE} all undefine default_create end EM_DRAWABLE redefine draw_part, set_x_y end create make, make_from_drawable, make_from_animation feature -- creation make (img:STRING; duration, an_x, a_y: INTEGER) is -- creates an animation with 1 element -- looping is set to false by default -- duration is the time in milliseconds this image will be displayed do bitmap_factory.create_bitmap_from_image (img) if bitmap_factory.last_bitmap = Void then make_from_drawable (void, duration, an_x, a_y) else make_from_drawable (bitmap_factory.last_bitmap, duration, an_x, a_y) -- just set the width and hight of the first image width := bitmap_factory.last_bitmap.width height := bitmap_factory.last_bitmap.height end process_change set_visible (True) ensure is_visible: is_visible end make_from_drawable (drawable: EM_DRAWABLE; delay, an_x, a_y: INTEGER) is -- same as make, but takes a bitmap instead of a path to the image -- void can be given to display nothing for a period of time do do_loop := false playing := false time_paused := -1 x := an_x y := a_y if drawable /= void then drawable.set_x_y (an_x,a_y) end create animation.make animation.force_last (create {EM_PAIR [EM_DRAWABLE, INTEGER]}.make (drawable, delay)) process_change set_visible (True) ensure is_visible: is_visible end make_from_animation (an_animation: EM_ANIMATION; an_x, a_y: INTEGER) is -- Creates a sprite from an animation. -- takes an EM_ANIMATION and the delay of each image. require animation_not_void: an_animation /= Void local i: INTEGER do do_loop := false playing := false time_paused := -1 x := an_x y := a_y from i := an_animation.lower until i > an_animation.upper loop if an_animation.item (i).first /= Void then an_animation.item (i).first.set_x_y (x, y) end extend_drawable (an_animation.item (i).first, an_animation.item (i).second) i := i + 1 end process_change set_visible (True) ensure is_visible: is_visible end feature -- Access do_loop: BOOLEAN -- Does the animation loop? playing: BOOLEAN -- Is the animation playing? height: INTEGER -- Height of the animation width: INTEGER -- Width of the animation time_paused: INTEGER -- Saves, when the animation was paused (-1 if not paused) feature -- Element Change start is -- starts the animation local img: EM_DRAWABLE do if time_paused /= -1 then playing := True -- paused at the moment => resume time_started := time.ticks - time_paused + time_started time_paused := -1 else animation.start time_started := time.ticks end playing := True -- set image position img := image end pause is -- pauses the animation do time_paused := time.ticks end stop is -- stops and sets back the animation do animation.start time_paused := -1 playing := False end set_x_y (an_x, a_y: INTEGER) is -- sets `x' and `y' local cursor: DS_LINKED_LIST_CURSOR [EM_PAIR [EM_DRAWABLE, INTEGER]] do x := an_x y := a_y create cursor.make (animation) from cursor.start until cursor.off loop if cursor.item.first /= Void then cursor.item.first.set_x_y (x,y) end cursor.forth end end feature -- modify extend (img: STRING; duration: INTEGER) is -- extends the list by an image and an offset do bitmap_factory.create_bitmap_from_image (img) if bitmap_factory.last_bitmap /= Void then bitmap_factory.last_bitmap.set_x_y (x, y) end extend_drawable (bitmap_factory.last_bitmap, duration) end extend_drawable (drawable: EM_DRAWABLE; duration: INTEGER) is -- same as extend, but takes a bitmap instead of a path to the image -- void can be given to display nothing for a period of time local delay: INTEGER do if animation /= Void then delay := duration + animation.last.second else create animation.make delay := duration end if drawable /= Void then drawable.set_x_y (x, y) end animation.force_last (create {EM_PAIR [EM_DRAWABLE,INTEGER]}.make (drawable, delay)) process_change end set_frame_rate (fps: INTEGER) is -- Resets all frames to a constant frame rate of `fps' require fps_valid: fps > 0 local ms_per_frame, t: DOUBLE cursor: DS_LINKED_LIST_CURSOR [EM_PAIR [EM_DRAWABLE, INTEGER]] do t := 0 ms_per_frame := 1000.0 / fps create cursor.make (animation) from cursor.start until cursor.off loop t := t + ms_per_frame cursor.item.put_second (t.rounded) cursor.forth end playtime := t.rounded end set_do_loop (b:BOOLEAN) is -- sets do_loop (if we want to loop the animation) do do_loop := b ensure loop_set: b = do_loop end set_width (v: INTEGER) is -- set the width for EM_DRAWABLE. require width_valid: v > 0 do width := v end set_height (v: INTEGER) is -- set the height for EM_DRAWABLE. require height_valid: v > 0 do height := v end feature -- draw draw (screen: EM_SURFACE) is -- draws the current bitmap on the screen (if it is playing) local img: EM_DRAWABLE do if is_visible then img := image if img /= Void then img.draw (screen) end end end draw_part (rect: EM_BLITTING_RECTANGLE; a_surface: EM_SURFACE) is -- Draws rectangular part of `current' defined by `rect' to `a_surface' local frame: EM_DRAWABLE do if is_visible then -- Get current frame. frame := image if frame /= Void then frame.set_x (x) frame.set_y (y) frame.draw_part (rect, a_surface) end end end feature {NONE} -- implementation animation: DS_LINKED_LIST [EM_PAIR [EM_DRAWABLE, INTEGER]] -- Internal datastructure which saves the animation. a list of pairs of images and durations animation_cursor: DS_LINKED_LIST_CURSOR [EM_PAIR [EM_DRAWABLE, INTEGER]] -- cursor on the current position of animation time_started: INTEGER -- Saves, when the animation was started playtime: INTEGER -- total play time of the animation current_frame_started: INTEGER -- the time at which the current frame started go_to_next_image is -- goes one step forth on `animation_cursor' and sets `current_frame_started' accordingly require animation_cursor_valid: animation_cursor /= Void and animation_cursor.item /= Void do if animation_cursor.is_last then -- the animation_cursor was at the last position current_frame_started := 0 animation_cursor.start else -- move the cursor forth current_frame_started := animation_cursor.item.second animation_cursor.forth end end image: EM_DRAWABLE is -- returns the current bitmap. if there is none, returns void local curtime, i: INTEGER do if playing then curtime := (time.ticks - time_started) \\ playtime if time_paused /= -1 then -- time is pause, return current image Result := animation_cursor.item.first else -- not paused, playing -- check if already passed the first round if not do_loop and time.ticks >= time_started + playtime then stop else -- loop through all animations until the correct one was found from i := 0 variant animation.count - i + 1 until curtime >= current_frame_started and curtime < animation_cursor.item.second loop go_to_next_image i := i + 1 end if animation_cursor.item.first /= Void then animation_cursor.item.first.set_x_y (x, y) end Result := animation_cursor.item.first end end end end feature {EM_SPRITE_COMPOSITION} -- Implementation process_change is -- recomputes the maximum width and height. -- recreates the cursor local cursor: DS_LINKED_LIST_CURSOR [EM_PAIR [EM_DRAWABLE, INTEGER]] do width := 0 height := 0 create cursor.make (animation) from cursor.start until cursor.off loop if cursor.item.first /= Void then if cursor.item.first.width > width then width := cursor.item.first.width end if cursor.item.first.height > height then height := cursor.item.first.height end end cursor.forth end cursor.finish playtime := cursor.item.second create animation_cursor.make (animation) animation_cursor.start ensure height_valid: height >= 0 width_valid: width >= 0 end end