From de94226e1b302cee2a006f78f0153aa5fa081f47 Mon Sep 17 00:00:00 2001 From: cursed22bc Date: Sun, 1 Mar 2026 12:30:05 +0200 Subject: player and animation --- world.lua | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 15 deletions(-) (limited to 'world.lua') diff --git a/world.lua b/world.lua index 6890e60..b9ba01f 100644 --- a/world.lua +++ b/world.lua @@ -1,5 +1,6 @@ local Tilemap = require("tilemap") local Entity = require("entity") +local Player = require("player") local Camera = require("camera") local World = {} @@ -18,6 +19,10 @@ function World:new() self.enemies = {} self.entities = {} self.camera = nil + self.groundContacts = {} + self.contactCounts = {} + self.contactEntity = {} + self.contactKind = {} return self end @@ -26,7 +31,17 @@ function World:load(mapPath, tilesetPath) self.groundEntities = {} end self.physicsWorld = love.physics.newWorld(0, GRAVITY) - self.physicsWorld:setCallbacks(nil, nil, nil, nil) + self.physicsWorld:setCallbacks( + function(fa, fb, contact) + local udA, udB = fa:getUserData(), fb:getUserData() + local nx, ny = contact:getNormal() + self:_onBeginContact(udA, udB, nx, ny, contact) + end, + function(fa, fb, contact) + local udA, udB = fa:getUserData(), fb:getUserData() + self:_onEndContact(udA, udB, contact) + end + ) self.tilemap = Tilemap:new(mapPath, tilesetPath) self.mapData = self.tilemap:getMapData() @@ -39,18 +54,38 @@ function World:load(mapPath, tilesetPath) local groundLayer = self.tilemap:getGroundLayer() if groundLayer and groundLayer.data then - local w = self.mapData.width or 0 + local mapW = self.mapData.width or 0 + local mapH = self.mapData.height or 0 local data = groundLayer.data - for i, gid in ipairs(data) do - if gid and gid ~= 0 then - local idx = i - 1 - local tx = idx % w - local ty = math.floor(idx / w) - local x = tx * tileWidth - local y = ty * tileHeight - local tileEntity = Entity:new(x, y, tileWidth, tileHeight) - tileEntity:enablePhysics(self.physicsWorld, "static") - table.insert(self.groundEntities, tileEntity) + + local solid = {} + for row = 0, mapH - 1 do + solid[row] = {} + for col = 0, mapW - 1 do + local idx = row * mapW + col + 1 + local gid = data[idx] + solid[row][col] = (gid and gid ~= 0) + end + end + + for row = 0, mapH - 1 do + local col = 0 + while col < mapW do + if solid[row][col] then + local startCol = col + while col < mapW and solid[row][col] do + col = col + 1 + end + local stripWidth = (col - startCol) * tileWidth + local x = startCol * tileWidth + local y = row * tileHeight + local strip = Entity:new(x, y, stripWidth, tileHeight) + strip:enablePhysics(self.physicsWorld, "static") + strip.fixture:setUserData("ground") + table.insert(self.groundEntities, strip) + else + col = col + 1 + end end end end @@ -62,8 +97,7 @@ function World:load(mapPath, tilesetPath) for _, spawn in ipairs(spawns) do local entityType = spawn:get("entity", "") if entityType == "Player" and not self.player then - self.player = Entity:new(spawn.x, spawn.y, spawn.width, spawn.height) - self.player:enablePhysics(self.physicsWorld, "dynamic") + self.player = Player.new(self.physicsWorld, spawn.x, spawn.y) self.player.isPlayer = true table.insert(self.entities, self.player) else @@ -78,6 +112,82 @@ function World:load(mapPath, tilesetPath) end +function World:_addContact(entity, kind) + self.contactCounts[entity] = self.contactCounts[entity] or { floor = 0, wall = 0, ceiling = 0 } + self.contactCounts[entity][kind] = (self.contactCounts[entity][kind] or 0) + 1 + entity.contact = entity.contact or { floor = 0, wall = 0, ceiling = 0 } + entity.contact[kind] = 1 +end + +function World:_removeContact(entity, kind) + self.contactCounts[entity] = self.contactCounts[entity] or { floor = 0, wall = 0, ceiling = 0 } + self.contactCounts[entity][kind] = math.max(0, (self.contactCounts[entity][kind] or 0) - 1) + entity.contact = entity.contact or { floor = 0, wall = 0, ceiling = 0 } + entity.contact[kind] = (self.contactCounts[entity][kind] > 0) and 1 or 0 +end + +function World:_normalToContactType(nx, ny) + local ax, ay = math.abs(nx), math.abs(ny) + if ay >= ax then + if ny < -0.3 then return "floor" end + if ny > 0.3 then return "ceiling" end + else + if ax > 0.3 then return "wall" end + end + return nil +end + +function World:_isTrackedEntity(ud) + return type(ud) == "table" and (ud.body ~= nil or ud.x ~= nil) +end + +function World:_isPlayerLike(ud) + return type(ud) == "table" and ud.jump ~= nil +end + +function World:_onBeginContact(udA, udB, nx, ny, contact) + if udA == "ground" and self:_isTrackedEntity(udB) then + local kind = self:_normalToContactType(nx, ny) + if kind then + self:_addContact(udB, kind) + self.contactEntity[contact] = udB + self.contactKind[contact] = kind + end + self.groundContacts[udB] = (self.groundContacts[udB] or 0) + 1 + if self:_isPlayerLike(udB) then udB.grounded = true end + elseif udB == "ground" and self:_isTrackedEntity(udA) then + local kind = self:_normalToContactType(-nx, -ny) + if kind then + self:_addContact(udA, kind) + self.contactEntity[contact] = udA + self.contactKind[contact] = kind + end + self.groundContacts[udA] = (self.groundContacts[udA] or 0) + 1 + if self:_isPlayerLike(udA) then udA.grounded = true end + end +end + +function World:_onEndContact(udA, udB, contact) + local ent = self.contactEntity[contact] + local kind = ent and self.contactKind[contact] + if ent and kind then + self:_removeContact(ent, kind) + self.contactEntity[contact] = nil + self.contactKind[contact] = nil + end + if udA == "ground" and self:_isTrackedEntity(udB) then + self.groundContacts[udB] = math.max(0, (self.groundContacts[udB] or 0) - 1) + if self:_isPlayerLike(udB) then + udB.grounded = (self.groundContacts[udB] or 0) > 0 + end + elseif udB == "ground" and self:_isTrackedEntity(udA) then + self.groundContacts[udA] = math.max(0, (self.groundContacts[udA] or 0) - 1) + if self:_isPlayerLike(udA) then + udA.grounded = (self.groundContacts[udA] or 0) > 0 + end + end +end + function World:setCamera(camera) self.camera = camera self.camera:setLimits(self.tilemap:getCameraLimits()) @@ -134,10 +244,29 @@ function World:draw() if e.draw then e:draw() else World.drawEntityDefault(e) end end + if DEBUG then + World.drawPhysicsBodyOutlines(self.entities) + World.drawPhysicsBodyOutlines(self.groundEntities) + end + drawTileLayer(self.tilemap:getForegroundLayer(), tw, th, tilesetImage, tileQuads) end --- TODO remove. draw method handled by each entity +function World.drawPhysicsBodyOutlines(entityList) + if not entityList then return end + love.graphics.setColor(0, 1, 0, 1) + for _, e in ipairs(entityList) do + local body = e.body + if body and e.physicsWidth and e.physicsHeight then + local cx, cy = body:getX(), body:getY() + local x = cx - e.physicsWidth / 2 + local y = cy - e.physicsHeight / 2 + love.graphics.rectangle("line", x, y, e.physicsWidth, e.physicsHeight) + end + end + love.graphics.setColor(1, 1, 1, 1) +end + function World.drawEntityDefault(entity) love.graphics.setColor(0.2, 0.6, 1, 1) if entity.isPlayer then -- cgit v1.2.3