summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--enemy.lua1
-rw-r--r--hud.lua146
-rw-r--r--main.lua51
-rw-r--r--player.lua48
-rw-r--r--spike.lua1
-rw-r--r--world.lua22
6 files changed, 265 insertions, 4 deletions
diff --git a/enemy.lua b/enemy.lua
index 9732e98..152cadf 100644
--- a/enemy.lua
+++ b/enemy.lua
@@ -5,6 +5,7 @@ setmetatable(Enemy, { __index = Entity })
function Enemy.new(spawnX, spawnY, width, height, physicsWidth, physicsHeight)
local self = setmetatable(Entity:new(spawnX or 0, spawnY or 0, width or 16, height or 16, physicsWidth or width or 8, physicsHeight or height or 8), Enemy)
+ self.isEnemy = true
self.direction = math.random(1, 2) == 1 and 1 or -1
self.speed = 20
self.turnDelay = 0.35
diff --git a/hud.lua b/hud.lua
new file mode 100644
index 0000000..ce877bb
--- /dev/null
+++ b/hud.lua
@@ -0,0 +1,146 @@
+local Textbox = require("textbox")
+
+local HUD = {}
+HUD.__index = HUD
+
+HUD.slots = {
+ { enabled = false },
+ { enabled = false },
+ { enabled = false },
+ { enabled = false },
+}
+
+local SLOT_SIZE = 16
+local SLOT_SPACING = 8
+local LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
+
+local function createMissingTexture()
+ local size = SLOT_SIZE
+ local half = size / 2
+ local imgData = love.image.newImageData(size, size)
+ for y = 0, size - 1 do
+ for x = 0, size - 1 do
+ local inBlack = (x < half and y < half) or (x >= half and y >= half)
+ if inBlack then
+ imgData:setPixel(x, y, 0, 0, 0, 1)
+ else
+ imgData:setPixel(x, y, 1, 0, 1, 1)
+ end
+ end
+ end
+ local img = love.graphics.newImage(imgData)
+ img:setFilter("nearest", "nearest")
+ return img
+end
+
+function HUD:new()
+ local self = setmetatable({}, HUD)
+ self.missingTexture = createMissingTexture()
+ self.textbox = Textbox:new()
+ self.hoveredSlot = nil
+ self.slotRects = {}
+ self.screenW = 0
+ self.screenH = 0
+ self.drawScale = 1
+ return self
+end
+
+function HUD:computeSlotRects(screenW, screenH, drawScale)
+ local slotW = SLOT_SIZE * drawScale
+ local slotH = SLOT_SIZE * drawScale
+ local spacing = SLOT_SPACING * drawScale
+ local totalW = 4 * slotW + 3 * spacing
+ local startX = math.floor((screenW - totalW) / 2)
+ local startY = math.floor(screenH / 2 - slotH / 2)
+ local rects = {}
+ for i = 1, 4 do
+ rects[i] = {
+ x = startX + (i - 1) * (slotW + spacing),
+ y = startY,
+ w = slotW,
+ h = slotH,
+ }
+ end
+ return rects
+end
+
+function HUD:update(dt, screenW, screenH, drawScale)
+ self.screenW = screenW
+ self.screenH = screenH
+ self.drawScale = drawScale
+ self.slotRects = self:computeSlotRects(screenW, screenH, drawScale)
+
+ local mx, my = love.mouse.getPosition()
+ local prevHovered = self.hoveredSlot
+ self.hoveredSlot = nil
+ for i, rect in ipairs(self.slotRects) do
+ if mx >= rect.x and mx < rect.x + rect.w
+ and my >= rect.y and my < rect.y + rect.h then
+ self.hoveredSlot = i
+ break
+ end
+ end
+
+ if self.hoveredSlot and self.hoveredSlot ~= prevHovered then
+ local rect = self.slotRects[self.hoveredSlot]
+ local centerX = screenW / 2
+ local topY = rect.y + rect.h + 8 * drawScale
+ self.textbox:show(LOREM, { centerX, topY, "center" }, "show", {
+ fontSize = 16,
+ centeredText = true,
+ wrapToFit = true,
+ maxChars = 40,
+ })
+ self.textbox.y = topY + self.textbox.height / 2
+ elseif not self.hoveredSlot and prevHovered then
+ self.textbox:hide()
+ end
+
+ self.textbox:update(dt)
+end
+
+function HUD:mousepressed(mx, my, button)
+ if button ~= 1 then return end
+ for i, rect in ipairs(self.slotRects) do
+ if mx >= rect.x and mx < rect.x + rect.w
+ and my >= rect.y and my < rect.y + rect.h then
+ HUD.slots[i].enabled = not HUD.slots[i].enabled
+ break
+ end
+ end
+end
+
+function HUD:draw()
+ local screenW = self.screenW
+ local screenH = self.screenH
+ local drawScale = self.drawScale
+
+ love.graphics.setColor(0, 0, 0, 0.4)
+ love.graphics.rectangle("fill", 0, 0, screenW, screenH)
+
+ for i, rect in ipairs(self.slotRects) do
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.draw(self.missingTexture, rect.x, rect.y, 0, drawScale, drawScale)
+
+ if HUD.slots[i].enabled then
+ love.graphics.setColor(0, 1, 0, 0.3)
+ love.graphics.rectangle("fill", rect.x, rect.y, rect.w, rect.h)
+ end
+
+ if self.hoveredSlot == i then
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.setLineWidth(2)
+ else
+ love.graphics.setColor(1, 1, 1, 0.5)
+ love.graphics.setLineWidth(1)
+ end
+ love.graphics.rectangle("line", rect.x, rect.y, rect.w, rect.h)
+ end
+
+ love.graphics.setLineWidth(1)
+ love.graphics.setColor(1, 1, 1, 1)
+
+ self.textbox:draw()
+end
+
+return HUD
diff --git a/main.lua b/main.lua
index 066ff00..c3ecedf 100644
--- a/main.lua
+++ b/main.lua
@@ -1,7 +1,7 @@
local VIRTUAL_WIDTH, VIRTUAL_HEIGHT = 16*10*5, 9*10*5
local CANVAS_PADDING = 6
-DEBUG = true
+DEBUG = false
local CANVAS_WIDTH = VIRTUAL_WIDTH + CANVAS_PADDING
local CANVAS_HEIGHT = VIRTUAL_HEIGHT + CANVAS_PADDING
@@ -18,6 +18,9 @@ local shaderEnabled = true
local cameraModule = require("camera")
local camera = nil
local fonts = require("fonts")
+local HUD = require("hud")
+local hud = nil
+local previousState = nil
local currentState = "game"
local currentMapPath = "assets/maps/tilemap.lua"
@@ -30,7 +33,8 @@ local world = nil
local states = {
game = {},
menu = {},
- settings = {}
+ settings = {},
+ hud = {},
}
local function recalcScale(w, h)
@@ -48,6 +52,7 @@ function states.game.load()
local target = world:getPlayer() or { x = 0, y = 0, width = 16, height = 16 }
camera = cameraModule:new(target, VIRTUAL_WIDTH/2, VIRTUAL_HEIGHT/2, true, WORLD_TO_CANVAS)
world:setCamera(camera)
+ hud = HUD:new()
end
function states.game.update(dt)
@@ -82,6 +87,19 @@ function states.settings.draw()
love.graphics.print("Settings", 10, 10)
end
+function states.hud.update(dt)
+ if hud then
+ local w, h = love.graphics.getWidth(), love.graphics.getHeight()
+ hud:update(dt, w, h, finalScale)
+ end
+end
+
+function states.hud.draw()
+ if camera then camera:set() end
+ if world then world:draw() end
+ if camera then camera:unset() end
+end
+
function love.load()
love.graphics.setDefaultFilter("nearest", "nearest")
love.window.setTitle("Openformer")
@@ -100,6 +118,8 @@ function love.load()
local w, h = love.graphics.getWidth(), love.graphics.getHeight()
recalcScale(w, h)
+ love.mouse.setVisible(false)
+
local state = states[currentState]
if state and state.load then state.load() end
end
@@ -123,9 +143,28 @@ function love.keypressed(key, scancode, isrepeat)
if key == "f1" and DEBUG then
shaderEnabled = not shaderEnabled
end
+ if key == "tab" and not isrepeat then
+ if currentState == "game" then
+ previousState = currentState
+ currentState = "hud"
+ love.mouse.setVisible(true)
+ elseif currentState == "hud" then
+ currentState = previousState or "game"
+ previousState = nil
+ love.mouse.setVisible(false)
+ end
+ end
if (key == "space" or key == "up" or key == "w") and not isrepeat then
- local player = world and world:getPlayer()
- if player then player:jump() end
+ if currentState == "game" then
+ local player = world and world:getPlayer()
+ if player then player:jump() end
+ end
+ end
+end
+
+function love.mousepressed(x, y, button)
+ if currentState == "hud" and hud then
+ hud:mousepressed(x, y, button)
end
end
@@ -162,6 +201,10 @@ function love.draw()
love.graphics.setShader()
end
+ if currentState == "hud" and hud then
+ hud:draw()
+ end
+
if DEBUG then
local fps = love.timer.getFPS()
local shaderLoaded = smoothCameraShader ~= nil
diff --git a/player.lua b/player.lua
index 14f0885..a09e887 100644
--- a/player.lua
+++ b/player.lua
@@ -15,6 +15,9 @@ function Player.new(world, spawnX, spawnY)
local pw, ph = 16, 16 -- physics body size
local self = setmetatable(Entity:new(spawnX or 0, spawnY or 0, w, h, pw, ph), Player)
+ self.spawnX = spawnX or 0
+ self.spawnY = spawnY or 0
+
self.directionState = { 'side', 'up', 'down', 'side_up', 'side_down' }
self.directionIndex = 1
self.direction = self.directionState[self.directionIndex]
@@ -65,6 +68,9 @@ function Player.new(world, spawnX, spawnY)
self.isInLiquid = false
self.wasInLiquid = false
self.waterSurfaceContact = false
+
+ self.isDead = false
+ self.respawnTimer = 0
return self
end
@@ -98,6 +104,17 @@ end
function Player:update(dt)
self:syncFromPhysicsBody()
+
+ if self.isDead then
+ self.respawnTimer = self.respawnTimer - dt
+ if self.respawnTimer <= 0 then
+ self:respawn()
+ end
+ self.currentAnim = self.animations.dead or self.animations.idle
+ self.currentAnim:update(dt)
+ return
+ end
+
self:trackDirectionByKeyPressed()
local vx, vy = self.body:getLinearVelocity()
@@ -196,12 +213,43 @@ function Player:update(dt)
swimming = self.animations.swimming,
stop_running = self.animations.stop_running,
ground_hit = self.animations.ground_hit,
+ dead = self.animations.dead or self.animations.idle,
}
self.currentAnim = animMap[self.state] or self.animations.idle
self.currentAnim:update(dt)
end
+function Player:die(nx, ny)
+ if self.isDead then return end
+ self.isDead = true
+ self.state = "dead"
+ self.respawnTimer = 4
+
+ local len = math.sqrt(nx * nx + ny * ny)
+ if len < 0.01 then
+ nx, ny = 0, -1
+ else
+ nx, ny = nx / len, ny / len
+ end
+
+ local BOUNCE_SPEED = 200
+ self.body:setLinearVelocity(nx * BOUNCE_SPEED, ny * BOUNCE_SPEED)
+end
+
+function Player:respawn()
+ self.isDead = false
+ self.respawnTimer = 0
+ self.state = "idle"
+ self.jumpsUsed = 0
+ local cx = self.spawnX + self.physicsWidth / 2
+ local cy = self.spawnY + self.height - self.physicsHeight / 2
+ self.body:setPosition(cx, cy)
+ self.body:setLinearVelocity(0, 0)
+ self:syncFromPhysicsBody()
+end
+
function Player:jump()
+ if self.isDead then return false end
local c = self.contact or { floor = 0, wall = 0, ceiling = 0 }
local onFloor = c.floor == 1
local onWall = c.wall == 1
diff --git a/spike.lua b/spike.lua
index f6e72b5..c6a2831 100644
--- a/spike.lua
+++ b/spike.lua
@@ -5,6 +5,7 @@ setmetatable(Spike, { __index = Entity })
function Spike.new(entity)
local self = setmetatable(Entity:new(entity.x, entity.y, entity.width, entity.height), Spike)
+ self.isSpike = true
return self
end
diff --git a/world.lua b/world.lua
index 2aff628..861a7da 100644
--- a/world.lua
+++ b/world.lua
@@ -33,6 +33,7 @@ function World:new()
self.contactCounts = {}
self.contactEntity = {}
self.contactKind = {}
+ self.pendingPlayerDeath = nil
self.playerTextbox = Textbox:new()
return self
end
@@ -193,6 +194,10 @@ function World:_isPlayerLike(ud)
return type(ud) == "table" and ud.jump ~= nil
end
+function World:_isHazard(ud)
+ return type(ud) == "table" and (ud.isSpike or ud.isEnemy)
+end
+
function World:_onBeginContact(udA, udB, nx, ny, contact)
if udA == "ground" and self:_isTrackedEntity(udB) then
local kind = self:_normalToContactType(nx, ny)
@@ -239,6 +244,18 @@ function World:_onBeginContact(udA, udB, nx, ny, contact)
self.waterContacts[udA] = (self.waterContacts[udA] or 0) + 1
doWaterSplash(udB, udA)
end
+
+ local playerEntity, hazardNx, hazardNy
+ if self:_isHazard(udA) and self:_isPlayerLike(udB) then
+ playerEntity = udB
+ hazardNx, hazardNy = nx, ny
+ elseif self:_isHazard(udB) and self:_isPlayerLike(udA) then
+ playerEntity = udA
+ hazardNx, hazardNy = -nx, -ny
+ end
+ if playerEntity and not playerEntity.isDead then
+ self.pendingPlayerDeath = { nx = hazardNx, ny = hazardNy }
+ end
end
function World:_onEndContact(udA, udB, contact)
@@ -523,6 +540,11 @@ function World:update(dt)
if not self.physicsWorld then return end
self.physicsWorld:update(PHYSICS_DT)
+ if self.pendingPlayerDeath and self.player then
+ self.player:die(self.pendingPlayerDeath.nx, self.pendingPlayerDeath.ny)
+ self.pendingPlayerDeath = nil
+ end
+
for _, e in ipairs(self.entities) do
if e.body then
e:syncFromPhysicsBody()