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, draw, update) 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 { 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) draw(cx+dx, cy+dy, se, sw) 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 update(dt, se, sw, jumpTime, jumpFrame, jumpDir) end, } end