Entities = { Player = 0, Sword = 1, Enemy = 2 } States = { Walk = "walk", Idle = "idle" } _id = 0 function _next_id() i = _id _id += 1 return i end Entity = {} Entity.__index = Entity function Entity:b_add_state(name, state) if self.states == nil then self.states = {} end self.states[name] = state if self.state == nil then self.state = name end return self end function Entity:b_state(name) assert(self.states[name] != nil) self.state = name return self end function Entity:b_render_order(ord) self.render_order = ord return self end function Entity:b_position(vec) self.position = vec2(vec) return self end function Entity:b_sprite_position(vec) self.sprite_position = vec2(vec) return self end function Entity:b_type(entity_type) self.entity_type = type return self end function Entity:b_equipped(equipped) self.equipped = equipped return self end function Entity:b_collidable() self.collision = true return self end function Entity:transition_to(state_name) if self:transition(self.state, state_name) then self.state = state_name self.state_stopwatch = 0 end return self end function Entity:transition(from, to) if from == States.Walk and to == States.Idle or to == States.Idle and from == States.Walk then return true end return false end function Entity:update(dt) if self.state_stopwatch == nil then self.state_stopwatch = 0 end self.state_stopwatch += dt self:integrate(dt) self:update_sprite_position() self:update_line_of_sight() if (self.velocity.x == 0 and self.velocity.x == self.velocity.y) then self:transition_to(States.Idle) else self:transition_to(States.Walk) end if (self.equipped != nil) then parent = self foreach(self.equipped, function (e) e:equipped_from(parent) end) end end function Entity:update_line_of_sight() nv = self.velocity:apply(normalize_scalar) if nv.y != 0 then self.line_of_sight = vec2(0, nv.y) elseif nv.x != 0 then self.line_of_sight = vec2(nv.x, 0) else self.line_of_sight = vec2(0, 0) end end -- prevent cobblestoning during non-manhattan movement by "lagging" the sprite -- behind the actual physical position function Entity:update_sprite_position() if self.sprite_position == nil then return end -- step in only x or y. trivial if self.velocity == nil or self.velocity.y == 0 or self.velocity.x == 0 then self.sprite_position.x = self.position.x self.sprite_position.y = self.position.y return end dpos = self.position - self.sprite_position dx, dy = dpos.x, dpos.y adx, ady = abs(dx), abs(dy) yslow = abs(self.velocity.y) < abs(self.velocity.x) xslow = abs(self.velocity.x) <= abs(self.velocity.y) pushx, pushy = false, false jerkdelta = 1 if adx >= jerkdelta and xslow and ady >= 1 then pushx, pushy = true, true elseif ady >= jerkdelta and yslow and adx >= 1 then pushx, pushy = true, true elseif xslow and ady >= 1 then pushy = true elseif yslow and adx >= 1 then pushx = true end if pushx then func = flr if dx < 0 then func = ceil end self.sprite_position.x += func(dx) end if pushy then func = flr if dy < 0 then func = ceil end self.sprite_position.y += func(dy) end return self end function Entity:integrate(dt) if (self.velocity != nil) then self.position = self.position + (self.velocity * dt) end end _equip_distance = 6 function Entity:equipped_from(parent) self.line_of_sight = vec2(parent.line_of_sight) offset = (parent.line_of_sight * _equip_distance) self.position = parent.position + offset self.sprite_position = parent.sprite_position + offset end function _get_frame(anim, t) len = #anim.seq frames_passed = (t / anim.dt) idx = flr(1 + (frames_passed % len)) return anim.seq[idx] end _animation_keys = {"neg", "", "pos"} function _get_animation_key(line_of_sight) n_line_of_sight = line_of_sight:apply(normalize_scalar) if n_line_of_sight.y ~= 0 then return _animation_keys[n_line_of_sight.y + 2] .. "_y" end return _animation_keys[n_line_of_sight.x + 2] .. "_x" end function Entity:render() pos = self.sprite_position or self.position animation = self.states[self.state].animation if (animation == nil) then return end key = _get_animation_key(self.line_of_sight) animation = animation[key] or animation assert(animation[key]) frame = _get_frame(animation.sequence, self.state_t) reflection = animation.reflection or vec2(false, false) spr( frame, entity.sprite_position.x, entity.sprite_position.y, 1, 1, reflection.x, reflection.y ) end function entity() id = _next_id() World.add(setmetatable( { id = id, position = vec2(0,0), velocity = vec2(0,0), collision = false }, Entity )) return World[id] end