# ============================================================================= # ACTOR (FRAMEWORK FILE) VERSION 2026-07-10 # ============================================================================= # An actor displays an animated sprite clip, with customizable thinking. # Actors are managed by the engine. If you know how to make your own engine, # you could delete this file. type actor_factory is func(): actor # ----------------------------------------------------------------------------- class actor extends class_id_base var clip: clip var x: int, y: int # If screen_space=true, then the camera does not affect this sprite var screen_space: bool var visible: bool var flip_x: bool, flip_y: bool, rotate: bool var theme: byte # Assigned to io_sprite.layer, which can be used to move the sprite behind # a tilemap var layer: byte # 0..3; default is 1 # Within a layer, sprites with a higher depth appear behind other sprites var _depth: byte # 0..3; default is 2 # render() uses a double-linked list with ability to move nodes around var _renderlist_prev_link: actor var _renderlist_next_link: actor # think() uses a single-linked list that does not actually remove items # until after the iterator has finished var _thinklist_next_link: actor var _last_clip: clip var _frame_index: byte var _duration: byte # set to 0 when assigning a new index # --------------------------------------------------------------------------- constructor() base() .layer <- 1 ._depth <- 2 .visible <- true end constructor # --------------------------------------------------------------------------- # Initialize this actor using data from an "s_actor_spot" symbol. new hook apply_spot(spot: s_actor_spot) .x <- spot.x .y <- spot.y .clip <- spot.starting_clip .start_clip_frame(spot.starting_frame_index) .theme <- spot.theme .flip_x <- spot.flip_x .flip_y <- spot.flip_y .rotate <- spot.rotate end hook # --------------------------------------------------------------------------- # This hook gets called by the engine after the actor is added to the # thinklist. It is mainly for actors that get removed and then re-added. # (Naming convention: The "on_" prefix indicates a function that is called # by some other system to notify that an event has occurred.) new hook on_added() end hook # --------------------------------------------------------------------------- # This hook gets called by engine::think() once every rendering frame. # Use it to move the actor around on the screen or perform other behaviors. new hook think() end hook # --------------------------------------------------------------------------- # This hook gets called by the engine after the actor is removed from # the thinklist. Use it to clean up any related objects. new hook on_removed() end hook # --------------------------------------------------------------------------- func is_added(): bool var result: bool result <- ._renderlist_prev_link <> null return result end func # --------------------------------------------------------------------------- func set_depth(depth: byte) if depth >= 4 then kernel::fail_code(20) # function was called with an invalid argument end if if ._depth <> depth then ._depth <- depth if ._renderlist_prev_link <> null then # set_depth() does not trigger on_added() or on_removed() events engine::_remove_actor(self) # thinklist is preserved because _prune_thinklist() is not called here engine::_add_actor(self, false) end if end if end func # --------------------------------------------------------------------------- func start_clip_frame(frame_index: byte) ._last_clip <- .clip ._frame_index <- frame_index # 0 indicates that we're loading a new frame ._duration <- 0 end func # --------------------------------------------------------------------------- func freeze_clip_frame(frame_index: byte) ._last_clip <- .clip ._frame_index <- frame_index # 255 indicates means animation is disabled ._duration <- 255 end func # --------------------------------------------------------------------------- func render(sprite_index: int, frame_delta: pair) var io_sprite: io_sprite io_sprite <- io::sprites[sprite_index] if .clip = null then # Hide the sprite io_sprite.pixels_address <- 0 else # Note that animation continues normally when .visible = false if .clip <> ._last_clip then # If the clip has changed, reset the animation .start_clip_frame(0) end if var frames: clip_frame[] frames <- .clip.frames # If the current frame was set, make sure it is valid if ._frame_index >= frames.size do ._frame_index <- 0 var p: pair if frame_delta >= 30 then # The video system renders at 30 frame/sec; if more than one second # has elapsed, then truncate frame_delta to avoid too much looping. p <- 30 else p <- frame_delta end if # TODO: optimize this loop by iterating ._frame_index instead of t loop if p <= 0 do drop # finished rendering if ._duration = 255 do drop # animation is disabled if ._duration > 1 then # count down ._duration <- to_byte(._duration - 1) else # (if duration=0 then we are loading the first frame) if ._duration <> 0 then # counted down to 1, time for the next frame ._frame_index <- to_byte(._frame_index + 1) if ._frame_index >= frames.size do ._frame_index <- 0 end if ._duration <- frames[._frame_index].duration if ._duration = 0 then # 0 is our special code for loading a new frame, # so if the clip specified that, make it a synonym for 255 ._duration <- 255 end if end if p <- to_pair(p - 1) end loop var x: int, y: int var x_pair: pair, y_pair: pair x <- .x y <- .y if not .screen_space then x <- x - engine::camera_x y <- y - engine::camera_y end if x_pair <- to_pair(x) y_pair <- to_pair(y) # Also hide the actor if to_pair() overflows if .visible and x_pair = x and y_pair = y then # Display the current frame (+3 skips array size) io_sprite.pixels_address <- to_address(frames[._frame_index].pixels) + 3 io_sprite.x <- x_pair io_sprite.y <- y_pair io_sprite.width <- .clip.width io_sprite.height <- .clip.height if .layer >= 4 do kernel::fail_code(20) # function was called with an invalid argument var flags: byte flags <- .layer if .flip_x do flags <- to_byte(flags + 4) if .flip_y do flags <- to_byte(flags + 8) if .rotate do flags <- to_byte(flags + 16) io_sprite.flags <- flags io_sprite.theme <- .theme else io_sprite.pixels_address <- 0 end if end if end func end class