DEMI-PROJECT-PACKAGE Ñ{"files":[{"hash":"sha256:c0cf0f29a7aa7380f3f0d2a1271d775a04699d3662045384ea55aadf895cbf87","path":"README.md","size":182},{"hash":"sha256:9ce8f4ff670745d67d4522acca5e2b09e1917b391b9c7d40327ea6dafeddcb2c","path":"scripts/demi/gameplay/character_controller_2d.lua","size":1568},{"hash":"sha256:1e24e4d15f2ba477e874df41c566f5fa29cf29c053521021ee4128b81a91fdd7","path":"scripts/demi/gameplay/controllers.lua","size":1121},{"hash":"sha256:698a3294efe8fa30bb5ae31f1cd701391e51f7efbe1dcbd53bf6fd36f873de7a","path":"tests/controllers_test.lua","size":830}],"format_version":1,"manifest":{"dependencies":{},"engine":"^0.1.0","exported_events":[],"files":["README.md","scripts/demi/gameplay/controllers.lua","scripts/demi/gameplay/character_controller_2d.lua","tests/controllers_test.lua"],"format_version":1,"name":"demi.gameplay.controllers","public_modules":["demi.gameplay.controllers","demi.gameplay.character_controller_2d"],"version":"1.0.0"},"package_type":"DemiProjectPackage"}# demi.gameplay.controllers Small input-to-intent adapters for platform, top-down, click-to-move, and isometric games. Public engine physics/navigation services execute the intent. local CharacterController2D = {} CharacterController2D.__index = CharacterController2D function CharacterController2D.new(options) options = options or {} return setmetatable({ move_action = options.move_action or "move", jump_action = options.jump_action or "jump", ground_layer = options.ground_layer or "platform", move_speed = options.move_speed or 5.0, jump_speed = options.jump_speed or 9.0, ground_distance = options.ground_distance or 0.56, flip_sprite = options.flip_sprite ~= false, idle_animation = options.idle_animation or "idle", run_animation = options.run_animation or "run", }, CharacterController2D) end function CharacterController2D:is_grounded(entity_id) local x, y = Transform.get_position(entity_id) if x == nil or y == nil then return false end return Physics2D.raycast(x, y, 0.0, -1.0, self.ground_distance, self.ground_layer, entity_id) ~= nil end function CharacterController2D:update_horizontal(entity_id) local axis = Input.action_value(self.move_action) Rigidbody2D.set_velocity_x(entity_id, axis * self.move_speed) if self.flip_sprite and axis ~= 0.0 then Sprite2D.set_flip(entity_id, axis < 0.0, false) end Sprite2D.play_animation(entity_id, axis == 0.0 and self.idle_animation or self.run_animation) return axis end function CharacterController2D:try_jump(entity_id, grounded) if grounded and Input.action_pressed(self.jump_action) then Rigidbody2D.set_velocity_y(entity_id, self.jump_speed) return true end return false end return CharacterController2D local Controllers = {} function Controllers.platform(input, state, config) local intent = { x = input.x or 0, jump = false } local grace = state.grounded and (config.coyote_time or 0) or math.max(0, (state.coyote or 0) - input.dt) if state.grounded then grace = config.coyote_time or 0 end local buffer = input.jump and (config.jump_buffer or 0) or math.max(0, (state.jump_buffer or 0) - input.dt) if buffer > 0 and grace > 0 then intent.jump, buffer, grace = true, 0, 0 end return intent, { coyote = grace, jump_buffer = buffer, grounded = state.grounded } end function Controllers.top_down(input) local x, y = input.x or 0, input.y or 0; local length = math.sqrt(x * x + y * y) if length > 1 then x, y = x / length, y / length end return { x = x, y = y, aim_x = input.aim_x, aim_y = input.aim_y } end function Controllers.click_to_move(input) return input.clicked and { target_x = input.x, target_y = input.y } or nil end function Controllers.isometric(input) return { grid_x = (input.right or 0) - (input.left or 0), grid_y = (input.down or 0) - (input.up or 0) } end return Controllers local Controllers = require("demi.gameplay.controllers") Test.case("platform coyote and buffered jump are package policy", function() local intent = Controllers.platform({ x = 1, jump = true, dt = 0.01 }, { grounded = false, coyote = 0.05, jump_buffer = 0 }, { coyote_time = 0.1, jump_buffer = 0.1 }) Test.equal(intent.jump, true) end) Test.case("top down diagonal is normalized", function() local intent = Controllers.top_down({ x = 1, y = 1 }) Test.near(math.sqrt(intent.x * intent.x + intent.y * intent.y), 1) end) Test.case("character controller module is independently loadable", function() local Controller = require("demi.gameplay.character_controller_2d") local controller = Controller.new({ move_speed = 7 }) Test.equal(controller.move_speed, 7); Test.equal(controller.ground_layer, "platform") end)