DEMI-PROJECT-PACKAGE ½{"files":[{"hash":"sha256:eb59758465e7941b7a03398cfc5eaafa28794edfdd37d30689f48c3fce86c185","path":"README.md","size":171},{"hash":"sha256:e436b33ef36966f91c560992409e7c57217bcc1ade30e2be19f89d86b0abf3a9","path":"scripts/demi/gameplay/camera.lua","size":1891},{"hash":"sha256:d73afda5b4b83e48c842083ceed07e27fdaa0b5fcf06a1beac577bfdbf48923f","path":"tests/camera_test.lua","size":732}],"format_version":1,"manifest":{"dependencies":{},"engine":"^0.1.0","exported_events":[],"files":["README.md","scripts/demi/gameplay/camera.lua","tests/camera_test.lua"],"format_version":1,"name":"demi.gameplay.camera","public_modules":["demi.gameplay.camera"],"version":"1.0.0"},"package_type":"DemiProjectPackage"}# demi.gameplay.camera Composable follow, look-ahead, bounds, zone priority, and deterministic shake policy. Apply the returned pose through the existing camera service. local Camera = {} Camera.__index = Camera function Camera.new(values) values = values or {} return setmetatable({ x = values.x or 0, y = values.y or 0, smoothing = values.smoothing or 10, look_ahead = values.look_ahead or 0, zones = {}, shakes = {} }, Camera) end function Camera:set_zone(id, values) values.id = id; self.zones[id] = values end function Camera:remove_zone(id) self.zones[id] = nil end function Camera:shake(amplitude, duration) table.insert(self.shakes, { amplitude = amplitude, left = duration }) end function Camera:update(target, velocity, dt, random) local desired_x = target.x + (velocity.x or 0) * self.look_ahead local desired_y = target.y + (velocity.y or 0) * self.look_ahead local selected for _, zone in pairs(self.zones) do if target.x >= zone.left and target.x <= zone.right and target.y >= zone.top and target.y <= zone.bottom and (not selected or (zone.priority or 0) > (selected.priority or 0) or (zone.priority or 0) == (selected.priority or 0) and zone.id < selected.id) then selected = zone end end if selected then desired_x = math.max(selected.left, math.min(selected.right, desired_x)) desired_y = math.max(selected.top, math.min(selected.bottom, desired_y)) end local blend = math.min(1, self.smoothing * dt) self.x, self.y = self.x + (desired_x - self.x) * blend, self.y + (desired_y - self.y) * blend local shake_x, shake_y = 0, 0 for index = #self.shakes, 1, -1 do local shake = self.shakes[index]; shake.left = shake.left - dt if shake.left <= 0 then table.remove(self.shakes, index) else shake_x = shake_x + ((random and random() or 0.5) * 2 - 1) * shake.amplitude shake_y = shake_y + ((random and random() or 0.5) * 2 - 1) * shake.amplitude end end return { x = self.x + shake_x, y = self.y + shake_y, zone = selected and selected.id } end return Camera local Camera = require("demi.gameplay.camera") Test.case("overlapping zones use priority then stable id", function() local camera = Camera.new({ smoothing = 100 }) camera:set_zone("b", { left = 0, right = 10, top = 0, bottom = 10, priority = 2 }) camera:set_zone("a", { left = 0, right = 10, top = 0, bottom = 10, priority = 2 }) Test.equal(camera:update({ x = 5, y = 5 }, {}, 1).zone, "a") camera:remove_zone("a"); Test.equal(camera:update({ x = 5, y = 5 }, {}, 1).zone, "b") end) Test.case("shake expires without retaining state", function() local camera = Camera.new({ smoothing = 100 }); camera:shake(5, 0.1) camera:update({ x = 0, y = 0 }, {}, 0.2, function() return 1 end) Test.equal(#camera.shakes, 0) end)