├── LICENSE ├── README.md ├── example.p8 ├── starter.p8 └── tilephysics.p8 /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Anton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pico-Kit :triangular_ruler: 2 | 3 | A collection of helpers to make your life easier. Comes with four libraries: 4 | * oop 5 | * `oop.class(properties?, parents?, constructor?, metatable?) -> table` 6 | 7 | A multi-purpose class creator that returns a table that can be called with a table of properties to generate class instances. The class's constructor is automatically generated and does the following for every parent and then current instance: 8 | * checks that all required properties are satisfied 9 | * copies over first the default properties and then the properties passed to the constructor to the instance table 10 | * calls the provided `constructor` argument (if it exists) 11 | 12 | Detailed explanation of arguments: 13 | * `properties`: A table of property keys/values that will be copied down to every instances. Properties with a value of `"req"` are considered unset and are required to be provided on an instance creation. 14 | * `parents`: A list of parents. The class's `__index` is set to these parents. Their constructors are also automatically called upon an instance creation. 15 | * `constructor`: A function called at the end of instance creation. 16 | * `metatable`: A metatable passed to every instance. 17 | 18 | * tools 19 | * `tools.assign(table, initial={}) -> table` 20 | 21 | Similar to javascript's `Object.assign`: copies over a table to an empty table, or, if provided, an existing table. 22 | 23 | * `tools.assigndeep(table, initial={}) -> table` 24 | 25 | Similar to `tools.assign` but it iterates through tables, effectively performing a deep copy. 26 | 27 | * debug 28 | * `debug.tstr(table, indent?) -> str` 29 | 30 | Converts a table to a string, optionally indenting each line with `indent` number of spaces. 31 | 32 | * `debug.print(...)` 33 | 34 | Prints values to the console with `printh`. If it encounters nil it prints `nil`, and if it encounters a table it uses `debug.tstr` to print out the entire table. Very useful for debugging because not only can you print multiple values at once, but `tstr` allows you to see the entire contents of even a nested table. 35 | 36 | * physics 37 | * `physics.collided(body1, body2) -> bool` 38 | 39 | Returns whether two bodies intersect. 40 | 41 | * `physics.world(parameters) -> world` 42 | 43 | 44 | * properties 45 | 46 | * `bodies={}` 47 | 48 | * `gravity={0, .5}` a vector that is applied with `body.shove` to every body during `world.update`, taking into consideration each bodies' `weight` and `mass` 49 | 50 | * `physics.world:update()` 51 | 52 | For each non-static body: applies gravity, friction, checks for collisions and then updates the position. Call this every frame. 53 | 54 | * `physics.world:addbody(body) -> body` 55 | 56 | Adds a body to the world and returns it. 57 | 58 | * `physics.body(parameters) -> body` 59 | 60 | A single physics body. 61 | 62 | * properties 63 | * `pos="req"` 64 | * `size="req"` 65 | * `vel={0, 0}` 66 | * `mass={1, 1}` proportionally affects all `body.shove` and `body.slow` calls 67 | * `weight={1, 1}` proportionally affects all gravity applied to the body 68 | * `friction={.1, 0}` applied with `body.slow` every `world.update` call, proportionally affects velocity 69 | * `collisions={}` a list of object collisions, reset during `world.update` call 70 | * `static=false` a static object that is ignored during `world.update` 71 | * `layer=0x1` a binary field that represents collision layers. Every `world.update` calls a `band` operation between two bodies' `layer` properties determines whether they will be checked for a collision. 72 | 73 | * `physics.body:update()` 74 | 75 | Adds the current velocity to the position. 76 | 77 | * `physics.body:shove(vector)` 78 | 79 | For x and y, adds the vector multiplied by `body.mass` to the current velocity. 80 | 81 | * `physics.body:slow(vector)` 82 | 83 | For x and y, subtracts the vector multiplied by `body.mass` from the current velocity. If the result of this operation crosses 0, the velocity on the corresponding axis is set to 0. 84 | 85 | * `physics.body:checkcollided(body)` 86 | 87 | For x and y, checks if the current body and provided body would have collided with that axis of the body's velocity, and if so: 88 | 89 | * Adds the provided body to `body.collisions`. 90 | * Adjusts the body's position on that axis to the edge of the provided object. 91 | * Sets that axis of the body's velocity to 0. 92 | 93 | ## Setting up 94 | To use this library, just download the `starter.p8` file and open it in Pico. Check out `example.p8` for a little example game. 95 | 96 | ## Contributing 97 | Suggested edits are welcome! Just create a pull request and make sure to edit both the starter and example files. 98 | -------------------------------------------------------------------------------- /example.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 16 3 | __lua__ 4 | function _init() 5 | print = debug.print 6 | 7 | world = physics.world{} 8 | body = oop.class({}, {physics.body}, function(self) 9 | world:addbody(self) 10 | end) 11 | mob = oop.class({ 12 | sprite="req", 13 | }, {body}) 14 | function mob:draw() 15 | spr(self.sprite, self.pos[1], self.pos[2]) 16 | end 17 | rec = oop.class({ 18 | color="req", 19 | static=true, 20 | }, {body}) 21 | function rec:draw() 22 | rectfill( 23 | self.pos[1], self.pos[2], 24 | self.pos[1] + self.size[1] - 1, 25 | self.pos[2] + self.size[2] - 1, 26 | self.color 27 | ) 28 | end 29 | p = mob{ 30 | pos={13*8, 11*8}, 31 | size={8, 8}, 32 | sprite=1, 33 | } 34 | 35 | bounds = {} 36 | for x=0,15 do 37 | for y=0,15 do 38 | if fget(mget(x, y)) == 1 then 39 | add(bounds, rec{ 40 | pos={x*8, y*8}, 41 | size={8, 8}, 42 | color={3}, 43 | }) 44 | end 45 | end 46 | end 47 | end 48 | -->8 49 | function _update60() 50 | if btnp(1) then 51 | p:shove{3, 0} 52 | elseif btnp(0) then 53 | p:shove{-3, 0} 54 | end 55 | 56 | if btnp(2) then 57 | p:shove{0, -6} 58 | end 59 | 60 | world:update() 61 | end 62 | -->8 63 | function _draw() 64 | cls(7) 65 | foreach(bounds, rec.draw) 66 | p:draw() 67 | end 68 | -->8 69 | --pico-tools 70 | oop = {} 71 | local function make(vars, parents, new) 72 | local required = {} 73 | for k,v in pairs(vars) do 74 | if v == "req" then 75 | add(required, k) 76 | vars[k] = nil 77 | end 78 | end 79 | return function(self, input) 80 | for v in all(required) do 81 | assert( 82 | input[v] ~= nil, 83 | "missing constructor argument " .. v 84 | ) 85 | end 86 | for parent in all(parents) do 87 | parent.new(self, input) 88 | end 89 | tools.deepassign(vars, self) 90 | tools.assign(input, self) 91 | if new then new(self) end 92 | end 93 | end 94 | local function search(k, list) 95 | for v in all(list) do 96 | if v[k] then return v[k] end 97 | end 98 | end 99 | local function parent_function(parents) 100 | local parent 101 | if #parents == 1 then 102 | parent = parents[1] 103 | else 104 | parent = function(t, k) 105 | return search(k, parents) 106 | end 107 | end 108 | return parent 109 | end 110 | 111 | function oop.class(properties, parents, new, metatable) 112 | local class = setmetatable({}, metatable or {}) 113 | if parents and #parents ~= 0 then 114 | getmetatable(class).__index = parent_function(parents) 115 | end 116 | local instance_mt = { 117 | __index = class 118 | } 119 | 120 | class.new = make(properties, parents, new) 121 | function new(self, input) 122 | local instance = setmetatable({}, instance_mt) 123 | class.new(instance, input) 124 | return instance 125 | end 126 | getmetatable(class).__call = new 127 | return class 128 | end 129 | 130 | tools = {} 131 | function tools.assign(t, initial) 132 | initial = initial or {} 133 | for k, v in pairs(t) do 134 | initial[k] = v 135 | end 136 | return initial 137 | end 138 | function tools.deepassign(t, initial) 139 | initial = initial or {} 140 | for k, v in pairs(t) do 141 | if type(v) == "table" then 142 | initial[k] = tools.deepassign(v) 143 | else 144 | initial[k] = v 145 | end 146 | end 147 | return initial 148 | end 149 | 150 | debug = {} 151 | function debug.tstr(t, indent) 152 | indent = indent or 0 153 | local indentstr = "" 154 | for i=0,indent do 155 | indentstr = indentstr .. " " 156 | end 157 | local str = "" 158 | for k, v in pairs(t) do 159 | if type(v) == "table" then 160 | str = str .. indentstr .. k .. "\n" .. debug.tstr(v, indent + 1) .. "\n" 161 | else 162 | str = str .. indentstr .. tostr(k) .. ": " .. tostr(v) .. "\n" 163 | end 164 | end 165 | str = sub(str, 1, -2) 166 | return str 167 | end 168 | function debug.print(...) 169 | printh("\n") 170 | for v in all{...} do 171 | if type(v) == "table" then 172 | printh(debug.tstr(v)) 173 | elseif type(v) == "nil" then 174 | printh("nil") 175 | else 176 | printh(v) 177 | end 178 | end 179 | end 180 | 181 | physics = {} 182 | function physics.collided(body1, body2) 183 | local result = true 184 | for i=1,2 do 185 | result = result and 186 | body1.pos[i] < body2.pos[i] + body2.size[i] and 187 | body2.pos[i] < body1.pos[i] + body1.size[i] 188 | end 189 | return result 190 | end 191 | physics.world = oop.class{ 192 | bodies = {}, 193 | gravity = {0, .5}, 194 | } 195 | function physics.world:update() 196 | for body in all(self.bodies) do 197 | if not body.static then 198 | if body.mass[1] or body.mass[2] then 199 | body:shove{ 200 | self.gravity[1] * body.weight[1], self.gravity[2] * body.weight[2] 201 | } 202 | if body.friction[1] ~= 0 or body.friction[2] ~= 0 then 203 | body:slow(body.friction) 204 | end 205 | end 206 | 207 | body.collisions = {} 208 | for body2 in all(self.bodies) do 209 | if band(body.layer, body2.layer) ~= 0 and body ~= body2 then 210 | body:checkcollided(body2) 211 | end 212 | end 213 | body:update() 214 | end 215 | end 216 | end 217 | function physics.world:addbody(body) 218 | add(self.bodies, body) 219 | return body 220 | end 221 | physics.body = oop.class{ 222 | pos="req", 223 | size="req", 224 | vel={0, 0}, 225 | mass={1, 1}, 226 | weight={1, 1}, 227 | friction={.1, 0}, 228 | collisions={}, 229 | static=false, 230 | layer=0x1, 231 | } 232 | function physics.body:shove(vel) 233 | for i=1,2 do 234 | self.vel[i] += vel[i] * self.mass[i] 235 | end 236 | end 237 | function physics.body:update() 238 | for i=1,2 do 239 | self.pos[i] += self.vel[i] 240 | end 241 | end 242 | function physics.body:slow(vel) 243 | for i=1,2 do 244 | if self.vel[i] > 0 then 245 | self.vel[i] -= vel[i] * self.mass[i] 246 | if self.vel[i] < 0 then 247 | self.vel[i] = 0 248 | end 249 | elseif self.vel[i] < 0 then 250 | self.vel[i] += vel[i] * self.mass[i] 251 | if self.vel[i] > 0 then 252 | self.vel[i] = 0 253 | end 254 | end 255 | end 256 | end 257 | function physics.body:checkcollided(body) 258 | local oldpos = tools.assign(self.pos) 259 | for i=1,2 do 260 | local pos = tools.assign(oldpos) 261 | pos[i] += self.vel[i] 262 | if physics.collided({pos=pos, size=self.size}, body) then 263 | add(self.collisions, body) 264 | if self.vel[i] >= 0 then 265 | self.pos[i] = body.pos[i] - self.size[i] 266 | else 267 | self.pos[i] = body.pos[i] + body.size[i] 268 | end 269 | self.vel[i] = 0 270 | end 271 | end 272 | end 273 | __gfx__ 274 | 00000000888888882222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 275 | 00000000800000082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 276 | 00700700808888082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 277 | 00077000808888082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 278 | 00077000808888082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 279 | 00700700808888082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 280 | 00000000800000082222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 281 | 00000000888888882222222200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 282 | __gff__ 283 | 0000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 284 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 285 | __map__ 286 | 0202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 287 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 288 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 289 | 0200000000000002020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 290 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 291 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 292 | 0202020202020200000000020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 293 | 0200000000000000000000020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 294 | 0200000000000000000000020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 295 | 0200000000000000000202020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 296 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 297 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 298 | 0200000000000202020200000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 299 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 300 | 0200000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 301 | 0202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 302 | -------------------------------------------------------------------------------- /starter.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 16 3 | __lua__ 4 | function _init() 5 | print = debug.print 6 | end 7 | -->8 8 | function _update60() 9 | end 10 | -->8 11 | function _draw() 12 | cls() 13 | end 14 | -->8 15 | --pico-tools 16 | oop = {} 17 | local function make(vars, parents, new) 18 | local required = {} 19 | for k,v in pairs(vars) do 20 | if v == 'req' then 21 | add(required, k) 22 | vars[k] = nil 23 | end 24 | end 25 | return function(self, input) 26 | for v in all(required) do 27 | assert( 28 | input[v] ~= nil, 29 | 'missing constructor argument ' .. v 30 | ) 31 | end 32 | for parent in all(parents) do 33 | parent.new(self, input) 34 | end 35 | tools.deepassign(vars, self) 36 | tools.assign(input, self) 37 | if new then new(self) end 38 | end 39 | end 40 | local function search(k, list) 41 | for v in all(list) do 42 | if v[k] then return v[k] end 43 | end 44 | end 45 | local function parent_function(parents) 46 | local parent 47 | if #parents == 1 then 48 | parent = parents[1] 49 | else 50 | parent = function(t, k) 51 | return search(k, parents) 52 | end 53 | end 54 | return parent 55 | end 56 | function oop.class(properties, parents, new, metatable) 57 | local class = setmetatable({}, metatable or {}) 58 | if parents and #parents ~= 0 then 59 | getmetatable(class).__index = parent_function(parents) 60 | end 61 | local instance_mt = { 62 | __index = class 63 | } 64 | class.new = make(properties, parents, new) 65 | function new(self, input) 66 | local instance = setmetatable({}, instance_mt) 67 | class.new(instance, input) 68 | return instance 69 | end 70 | getmetatable(class).__call = new 71 | return class 72 | end 73 | 74 | tools = {} 75 | function tools.assign(t, initial) 76 | initial = initial or {} 77 | for k, v in pairs(t) do 78 | initial[k] = v 79 | end 80 | return initial 81 | end 82 | function tools.deepassign(t, initial) 83 | initial = initial or {} 84 | for k, v in pairs(t) do 85 | if type(v) == "table" then 86 | initial[k] = tools.deepassign(v) 87 | else 88 | initial[k] = v 89 | end 90 | end 91 | return initial 92 | end 93 | 94 | debug = {} 95 | function debug.tstr(t, indent) 96 | indent = indent or 0 97 | local indentstr = '' 98 | for i=0,indent do 99 | indentstr = indentstr .. ' ' 100 | end 101 | local str = '' 102 | for k, v in pairs(t) do 103 | if type(v) == 'table' then 104 | str = str .. indentstr .. k .. '\n' .. debug.tstr(v, indent + 1) .. '\n' 105 | else 106 | str = str .. indentstr .. tostr(k) .. ': ' .. tostr(v) .. '\n' 107 | end 108 | end 109 | str = sub(str, 1, -2) 110 | return str 111 | end 112 | function debug.print(...) 113 | printh("\n") 114 | for v in all{...} do 115 | if type(v) == "table" then 116 | printh(debug.tstr(v)) 117 | elseif type(v) == "nil" then 118 | printh("nil") 119 | else 120 | printh(v) 121 | end 122 | end 123 | end 124 | 125 | physics = {} 126 | function physics.collided(body1, body2) 127 | local result = true 128 | for i=1,2 do 129 | result = result and 130 | body1.pos[i] < body2.pos[i] + body2.size[i] and 131 | body2.pos[i] < body1.pos[i] + body1.size[i] 132 | end 133 | return result 134 | end 135 | physics.world = oop.class{ 136 | bodies = {}, 137 | gravity = {0, .5}, 138 | } 139 | function physics.world:update() 140 | for body in all(self.bodies) do 141 | if not body.static then 142 | if body.mass[1] or body.mass[2] then 143 | body:shove{ 144 | self.gravity[1] * body.weight[1], self.gravity[2] * body.weight[2] 145 | } 146 | if body.friction[1] ~= 0 or body.friction[2] ~= 0 then 147 | body:slow(body.friction) 148 | end 149 | end 150 | 151 | body.collisions = {} 152 | for body2 in all(self.bodies) do 153 | if band(body.layer, body2.layer) ~= 0 and body ~= body2 then 154 | body:checkcollided(body2) 155 | end 156 | end 157 | body:update() 158 | end 159 | end 160 | end 161 | function physics.world:addbody(body) 162 | add(self.bodies, body) 163 | return body 164 | end 165 | physics.body = oop.class{ 166 | pos="req", 167 | size="req", 168 | vel={0, 0}, 169 | mass={1, 1}, 170 | weight={1, 1}, 171 | friction={.1, 0}, 172 | collisions={}, 173 | static=false, 174 | layer=0x1, 175 | } 176 | function physics.body:shove(vel) 177 | for i=1,2 do 178 | self.vel[i] += vel[i] * self.mass[i] 179 | end 180 | end 181 | function physics.body:update() 182 | for i=1,2 do 183 | self.pos[i] += self.vel[i] 184 | end 185 | end 186 | function physics.body:slow(vel) 187 | for i=1,2 do 188 | if self.vel[i] > 0 then 189 | self.vel[i] -= vel[i] * self.mass[i] 190 | if self.vel[i] < 0 then 191 | self.vel[i] = 0 192 | end 193 | elseif self.vel[i] < 0 then 194 | self.vel[i] += vel[i] * self.mass[i] 195 | if self.vel[i] > 0 then 196 | self.vel[i] = 0 197 | end 198 | end 199 | end 200 | end 201 | function physics.body:checkcollided(body) 202 | local oldpos = tools.assign(self.pos) 203 | for i=1,2 do 204 | local pos = tools.assign(oldpos) 205 | pos[i] += self.vel[i] 206 | if physics.collided({pos=pos, size=self.size}, body) then 207 | add(self.collisions, body) 208 | if self.vel[i] >= 0 then 209 | self.pos[i] = body.pos[i] - self.size[i] 210 | else 211 | self.pos[i] = body.pos[i] + body.size[i] 212 | end 213 | self.vel[i] = 0 214 | end 215 | end 216 | end 217 | -------------------------------------------------------------------------------- /tilephysics.p8: -------------------------------------------------------------------------------- 1 | physics = {} 2 | function physics.collided(body1, body2) 3 | local result = true 4 | for i=1,2 do 5 | result = result and 6 | body1.pos[i] < body2.pos[i] + body2.size[i] and 7 | body2.pos[i] < body1.pos[i] + body1.size[i] 8 | end 9 | return result 10 | end 11 | function physics.togrid(pos) 12 | --return tools.map(pos, function(num) 13 | --return flr(num / 8) 14 | --end) 15 | return {flr(pos[1]/8), flr(pos[2]/8)} 16 | end 17 | function physics.fromgrid(pos) 18 | return {pos[1]*8, pos[2]*8} 19 | end 20 | physics.world = oop.class{ 21 | bodies={}, 22 | gravity={0, .5}, 23 | grid="req", 24 | } 25 | function physics.world:update() 26 | for body in all(self.bodies) do 27 | if not body.static then 28 | if body.mass[1] or body.mass[2] then 29 | body:shove{ 30 | self.gravity[1] * body.weight[1], self.gravity[2] * body.weight[2] 31 | } 32 | if body.friction[1] ~= 0 or body.friction[2] ~= 0 then 33 | body:slow(body.friction) 34 | end 35 | end 36 | if body.check then 37 | body.collisions = {} 38 | for body2 in all(self.bodies) do 39 | if body ~= body2 then 40 | body:checkcollided( 41 | body2, 42 | band(body.layer, body2.layer) != 0 43 | ) 44 | end 45 | end 46 | body.tcollisions = {} 47 | body:gridcollided(self) 48 | end 49 | body:update() 50 | end 51 | end 52 | end 53 | function physics.world:addbody(body) 54 | add(self.bodies, body) 55 | return body 56 | end 57 | function physics.world:gettile(pos) 58 | local x = self.grid[pos[1]] 59 | if x then 60 | return x[pos[2]] 61 | end 62 | end 63 | physics.body = oop.class({ 64 | pos="req", 65 | size="req", 66 | vel={0, 0}, 67 | mass={1, 1}, 68 | weight={1, 1}, 69 | friction={.1, 0}, 70 | collisions={}, 71 | tcollisions={}, 72 | static=false, 73 | layer=0x1, 74 | check=false, 75 | }, {}, function(self) 76 | self.tsize = physics.togrid( 77 | self.size 78 | ) 79 | end) 80 | local body = physics.body 81 | function body:shove(vel) 82 | for i=1,2 do 83 | self.vel[i] += vel[i] * self.mass[i] 84 | end 85 | end 86 | function body:update() 87 | for i=1,2 do 88 | self.pos[i] += self.vel[i] 89 | end 90 | end 91 | function body:slow(vel) 92 | for i=1,2 do 93 | if self.vel[i] > 0 then 94 | self.vel[i] -= vel[i] * self.mass[i] 95 | if self.vel[i] < 0 then 96 | self.vel[i] = 0 97 | end 98 | elseif self.vel[i] < 0 then 99 | self.vel[i] += vel[i] * self.mass[i] 100 | if self.vel[i] > 0 then 101 | self.vel[i] = 0 102 | end 103 | end 104 | end 105 | end 106 | function body:align(i, body) 107 | if self.vel[i] >= 0 then 108 | self.pos[i] = body.pos[i] - self.size[i] 109 | else 110 | self.pos[i] = body.pos[i] + body.size[i] 111 | end 112 | self.vel[i] = 0 113 | end 114 | function body:checkcollided(body, move) 115 | local oldpos = tools.assign(self.pos) 116 | local checks = {} 117 | for i=1,2 do 118 | local pos = tools.assign(oldpos) 119 | pos[i] += self.vel[i] 120 | --if self:collided(body, pos) then 121 | if physics.collided({ 122 | pos=pos, size=self.size 123 | }, body) then 124 | checks[i] = true 125 | add(self.collisions, body) 126 | if move then 127 | self:align(i, body) 128 | end 129 | end 130 | end 131 | if 132 | not checks[1] and 133 | not checks[2] 134 | then 135 | local pos = { 136 | oldpos[1] + self.vel[1], 137 | oldpos[2] + self.vel[2], 138 | } 139 | --if self:collided(body, pos) then 140 | if physics.collided({ 141 | pos=pos, size=self.size 142 | }, body) then 143 | add(self.collisions, body) 144 | if move then 145 | for i=1,2 do 146 | self:align(i, body) 147 | end 148 | end 149 | end 150 | end 151 | end 152 | function body:gridcollided(world) 153 | local oldpos = tools.assign(self.pos) 154 | for i=1,2 do 155 | local pos = tools.assign(oldpos) 156 | pos[i] += self.vel[i] 157 | 158 | local tpos = self:findtpos(pos) 159 | for pos in all(tpos) do 160 | tile = world:gettile(pos) 161 | if tile then 162 | if band(self.layer, tile) != 0 then 163 | self:align(i, { 164 | pos=physics.fromgrid(pos), 165 | size={8, 8} 166 | }) 167 | end 168 | add(self.tcollisions, pos) 169 | break 170 | end 171 | end 172 | end 173 | if #self.tcollisions == 0 then 174 | local tpos = self:findtpos({ 175 | oldpos[1] + self.vel[1], 176 | oldpos[2] + self.vel[2], 177 | }) 178 | for pos in all(tpos) do 179 | tile = world:gettile(pos) 180 | if tile then 181 | if band(self.layer, tile) != 0 then 182 | for i=1,2 do 183 | self:align(i, { 184 | pos=physics.fromgrid(pos), 185 | size={8, 8} 186 | }) 187 | end 188 | end 189 | add(self.tcollisions, pos) 190 | break 191 | end 192 | end 193 | end 194 | end 195 | function body:findtpos(pos) 196 | local tpos = physics.togrid(pos) 197 | local poss = {tpos} 198 | 199 | for size in all{ 200 | {1, 1}, 201 | {1, 0}, 202 | {0, 1}, 203 | } do 204 | local tpos2 = physics.togrid{ 205 | pos[1]+(self.size[1]-1)*size[1], 206 | pos[2]+(self.size[2]-1)*size[2], 207 | } 208 | if tpos2[1] != tpos[1] or 209 | tpos2[2] != tpos[2] then 210 | add(poss, tpos2) 211 | end 212 | end 213 | return poss 214 | end 215 | --[[ 216 | function physics.collided(body, pos) 217 | return physics.collided({ 218 | pos=pos, size=self.size 219 | }, body) 220 | end 221 | --------------------------------------------------------------------------------