nominally working hopping demo
This commit is contained in:
84
arena.lua
Normal file
84
arena.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
|
||||
local sideHeight = 35
|
||||
local width = 55
|
||||
local topHeight = 20
|
||||
local polys = {
|
||||
{
|
||||
0, 0, width/2, topHeight/2,
|
||||
0, topHeight, -width/2, topHeight/2,
|
||||
},
|
||||
{
|
||||
0, topHeight, width/2, topHeight/2,
|
||||
width/2, topHeight/2 + sideHeight, 0, topHeight + sideHeight,
|
||||
},
|
||||
{
|
||||
0, topHeight, -width/2, topHeight/2,
|
||||
-width/2, topHeight/2 + sideHeight, 0, topHeight + sideHeight,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return function(size, discs, topCol, rightCol, leftCol)
|
||||
assert(size > 0)
|
||||
for disc in pairs(discs) do
|
||||
assert(disc >= -size and disc <= size)
|
||||
end
|
||||
|
||||
local colours = {topCol, rightCol, leftCol}
|
||||
local function drawCube(x, y)
|
||||
love.graphics.push()
|
||||
love.graphics.translate(x, y)
|
||||
for ix, poly in ipairs(polys) do
|
||||
love.graphics.setColor(colours[ix])
|
||||
love.graphics.polygon("fill", poly)
|
||||
end
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
local function getHeight()
|
||||
return size*(topHeight/2 + sideHeight) + topHeight/2
|
||||
end
|
||||
|
||||
local x = love.graphics.getWidth()/2
|
||||
local y = love.graphics.getHeight()/2 - getHeight()/2;
|
||||
|
||||
local function qpos2Coords(se, sw)
|
||||
return x + se * width/2 - sw * width/2,
|
||||
y + (se + sw)*(topHeight/2+sideHeight)
|
||||
end
|
||||
|
||||
return {
|
||||
getWidth = function()
|
||||
return size*width*2
|
||||
end,
|
||||
|
||||
getHeight = getHeight,
|
||||
|
||||
getSize = function() return size; end,
|
||||
getCubeSideHeight = function() return sideHeight; end,
|
||||
getCubeTopHeight = function() return topHeight; end,
|
||||
getCubeHeight = function() return sideHeight+topHeight; end,
|
||||
getCubeWidth = function() return width; end,
|
||||
getRowHeight = function() return sideHeight+topHeight/2; end,
|
||||
getColWidth = function() return width/2; end,
|
||||
qpos2Coords = qpos2Coords,
|
||||
|
||||
draw = function()
|
||||
for se = 0, size-1 do
|
||||
for sw = 0, size-1 do
|
||||
if (se + sw < size) then
|
||||
local x, y = qpos2Coords(se, sw)
|
||||
drawCube(x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
update = function(dt)
|
||||
end,
|
||||
}
|
||||
end
|
||||
50
main.lua
Normal file
50
main.lua
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
local newArena = require "arena"
|
||||
local newQbert = require "qbert"
|
||||
|
||||
local arena = newArena(
|
||||
7,
|
||||
{3, -3},
|
||||
{255, 0, 0}, {0, 255, 0}, {0, 0, 255}
|
||||
)
|
||||
local time = 0
|
||||
local qbert = newQbert(
|
||||
arena
|
||||
)
|
||||
|
||||
function love.load()
|
||||
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
arena.draw()
|
||||
qbert.draw()
|
||||
end
|
||||
|
||||
local dirs = {"se", "sw", "ne", "nw"}
|
||||
local ix = 1
|
||||
function love.update(dt)
|
||||
--[[ time = time + dt
|
||||
if (time > 2) then
|
||||
time = 0
|
||||
local dir = dirs[ix]
|
||||
ix = (ix+1)%4 + 1
|
||||
qbert.jump(dir)
|
||||
end
|
||||
]]--
|
||||
if love.keyboard.isDown('i') then
|
||||
qbert.jump('ne')
|
||||
end
|
||||
if love.keyboard.isDown('u') then
|
||||
qbert.jump('nw')
|
||||
end
|
||||
if love.keyboard.isDown('k') then
|
||||
qbert.jump('se')
|
||||
end
|
||||
if love.keyboard.isDown('j') then
|
||||
qbert.jump('sw')
|
||||
end
|
||||
|
||||
arena.update(dt);
|
||||
qbert.update(dt);
|
||||
end
|
||||
211
qbert.lua
Normal file
211
qbert.lua
Normal file
@@ -0,0 +1,211 @@
|
||||
|
||||
|
||||
|
||||
local height = 10
|
||||
local width = height
|
||||
local jumpHeight = 10
|
||||
local jumpDist = 20
|
||||
local jumpDirs = {
|
||||
-- { se-axis, sw-axis, path x-multiplier, path reverse }
|
||||
ne = {0, -1, -1, true},
|
||||
nw = {-1, 0, 1, true},
|
||||
se = {1, 0, 1, false},
|
||||
sw = {0, 1, -1, false},
|
||||
}
|
||||
|
||||
--[[
|
||||
|
||||
Jump equation, assuming y is up the screen and gravity down, chosen so
|
||||
that vy and ay are +ve:
|
||||
|
||||
y = y0 - vy*t + ay*t*t (A)
|
||||
x = x0 + vx*t (B)
|
||||
|
||||
Jump starts at t0, y0 and x0.
|
||||
Jump ends at tend, yend and xend.
|
||||
Jump peak is at ypeak.
|
||||
These are all known.
|
||||
|
||||
We want to infer vx, ay and vy.
|
||||
|
||||
We can get vx thus:
|
||||
|
||||
xend = x0 + vx*tend
|
||||
vx = (xend - x0) / tend (C)
|
||||
|
||||
Next to get ay and vy. For simplicity, let:
|
||||
|
||||
yend2 = yend - y0
|
||||
ypeak2 = ypeak - y0
|
||||
|
||||
|
||||
Using (A) to get vy:
|
||||
|
||||
y = y0 - vy*t + ay*t*t
|
||||
vy*t = y0 - y + ay*t*t
|
||||
vy*t = ay*t*t - yend2
|
||||
vy = (ay*t*t - yend2)/t
|
||||
vy = ay*t - yend2/t (D)
|
||||
|
||||
Using (D) at time tend:
|
||||
|
||||
vy = ay*tend - yend2/tend (E)
|
||||
|
||||
Using (D) at time tpeak:
|
||||
|
||||
vy = ay*tpeak - ypeak2/tpeak (F)
|
||||
|
||||
Unfortunately we still don't know tpeak, but we can eliminate it.
|
||||
|
||||
Knowing that dy/dt = 0 means:
|
||||
|
||||
dy/dt = -vy + 2*ay*t = 0
|
||||
|
||||
By definition the time at this point is tpeak, so:
|
||||
|
||||
vy = 2*ay*tpeak
|
||||
tpeak = 0.5*vy/ay
|
||||
|
||||
So eliminating tpeak in (F):
|
||||
|
||||
vy = ay*tpeak - ypeak2/tpeak = ay*(0.5*vy/ay) - ypeak2/(0.5*vy/ay)
|
||||
vy = 0.5*vy - ypeak2*ay/(0.5*vy)
|
||||
vy = 0.5*vy - 2*ypeak2*ay/vy
|
||||
2*vy = vy - 4*ypeak2*ay/vy
|
||||
4*ypeak2*ay/vy = vy - 2*vy
|
||||
4*ypeak2*ay/vy = -vy
|
||||
4*ypeak2*ay = -vy^2
|
||||
ay = -0.25*vy^2/ypeak2 (G)
|
||||
|
||||
vy = sqrt(-4*ypeak2*ay)
|
||||
vy = 2*sqrt(-ypeak2*ay) (H)
|
||||
|
||||
Eliminating vy using (H) and (E):
|
||||
|
||||
vy = 2*sqrt(-ypeak2*ay) = ay*tend - yend2/tend
|
||||
-4*ypeak2*ay = (ay*tend - yend2/tend)^2
|
||||
-4*ypeak2*ay = ay^2*tend^2 + yend2^2/tend^2 - 2*ay*tend*yend2/tend
|
||||
0 = ay^2*tend^2 + yend2^2/tend^2 - 2*ay*yend2 + 4*ypeak2*ay
|
||||
0 = ay^2*tend^2 + 4*ypeak2*ay - 2*ay*yend2 + yend2^2/tend^2
|
||||
0 = ay^2*tend^2 + ay*(4*ypeak2 - 2*yend2) + yend2^2/tend^2
|
||||
|
||||
This is a quadratic of the form:
|
||||
|
||||
0 = a*ay^2 + b*ay + c
|
||||
|
||||
Using the quadratic solution equation:
|
||||
|
||||
ay = (-b +/- sqrt(b^2 - 4*a*c)/(2*a)
|
||||
|
||||
With
|
||||
|
||||
a = tend^2
|
||||
b = 4*ypeak2 - 2*yend2
|
||||
c = yend2^2/tend^2
|
||||
|
||||
Having calculated ay, we can get vy from
|
||||
|
||||
vy = ay*tend - yend2/tend (E)
|
||||
|
||||
]]--
|
||||
|
||||
local function newJumpPath(jumpDist, jumpHeight)
|
||||
local steps = 5
|
||||
local vx = jumpDist/steps
|
||||
|
||||
local x0 = 0
|
||||
local y0 = 0
|
||||
local ypeak = -jumpHeight/2
|
||||
local yend = jumpHeight
|
||||
local ypeak2 = ypeak - y0
|
||||
local yend2 = yend - y0
|
||||
local tend = 5
|
||||
local a = tend^2
|
||||
local b = 4*ypeak2 - 2*yend2
|
||||
local c = yend2^2/tend^2
|
||||
local ay = 0.5*(-b + math.sqrt(b^2 - 4*a*c))/a
|
||||
local vy = ay*tend - yend2/tend
|
||||
local path = {}
|
||||
-- print(vy, ay) # DEBUG
|
||||
for t = 0,tend do
|
||||
table.insert(path, {x0 + vx*t,
|
||||
y0 - vy*t + ay*t*t})
|
||||
end
|
||||
--[[ DEBUG
|
||||
for i in ipairs(path) do
|
||||
print (i, path[i][1], path[i][2])
|
||||
end
|
||||
]]--
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
local frameDuration = 0.1 -- millis
|
||||
local function time2frame(time)
|
||||
return 1 + math.floor(time / frameDuration)
|
||||
end
|
||||
|
||||
local function jumpDelta(frame, dir, path)
|
||||
local _, _, mx, isReverse = unpack(jumpDirs[dir])
|
||||
local ox, oy = unpack(path[1]) -- path offset
|
||||
if isReverse then -- reverse the path sequence
|
||||
frame = 1 + #path - frame
|
||||
ox, oy = unpack(path[#path])
|
||||
end
|
||||
local dx, dy = unpack(path[frame])
|
||||
return dx*mx-ox*mx, dy-oy
|
||||
end
|
||||
|
||||
-- constructor
|
||||
return function(arena)
|
||||
assert(arena ~= nil, "arena must not be nil")
|
||||
local jumpTime = nil
|
||||
local jumpDir = "se"
|
||||
local jumpFrame = 1
|
||||
local se, sw = 0, 0
|
||||
local jumpPath = newJumpPath(arena.getColWidth(), arena.getRowHeight())
|
||||
return {
|
||||
getWidth = function()
|
||||
return width
|
||||
end,
|
||||
|
||||
getHeight = function()
|
||||
return height
|
||||
end,
|
||||
|
||||
jump = function(dir)
|
||||
if jumpTime ~= nil then
|
||||
return -- already jumping
|
||||
end
|
||||
-- print("jump",dir) -- DEBUG
|
||||
if jumpDirs[dir] then -- it's a valid direction
|
||||
jumpTime = 0
|
||||
jumpDir = dir
|
||||
end
|
||||
end,
|
||||
|
||||
draw = function()
|
||||
local cx, cy = arena.qpos2Coords(se, sw)
|
||||
local dx, dy = jumpDelta(jumpFrame, jumpDir, jumpPath)
|
||||
love.graphics.setColor(128,128,0);
|
||||
love.graphics.circle("fill", cx+dx, cy+dy, height)
|
||||
end,
|
||||
|
||||
update = function(dt)
|
||||
if jumpTime ~= nil then
|
||||
jumpTime = jumpTime + dt
|
||||
jumpFrame = time2frame(jumpTime)
|
||||
if jumpFrame > #jumpPath then
|
||||
jumpTime = nil -- finished
|
||||
jumpFrame = 1
|
||||
|
||||
-- update qpos (se, sw)
|
||||
local dse, dsw = unpack(jumpDirs[jumpDir])
|
||||
-- print("update ", se, sw, dse, dsw) -- DEBUG
|
||||
se = se + dse
|
||||
sw = sw + dsw
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
Reference in New Issue
Block a user