Last active
August 29, 2015 14:17
-
-
Save nadar71/1083f3b230a0cb49f21e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Perspective v2.0.2 | |
A library for easily and smoothly integrating a virtual camera into your game. | |
Based on modified version of the Dusk camera system. | |
v2.0.2 adds a more stable tracking system and re-implements scrollX and scrollY | |
--]] | |
local lib_perspective = {} | |
-------------------------------------------------------------------------------- | |
-- Localize | |
-------------------------------------------------------------------------------- | |
local display_newGroup = display.newGroup | |
local display_remove = display.remove | |
local type = type | |
local table_insert = table.insert | |
local math_huge = math.huge | |
local math_nhuge = -math.huge | |
local clamp = function(v, l, h) return (v < l and l) or (v > h and h) or v end | |
-------------------------------------------------------------------------------- | |
-- Create View | |
-------------------------------------------------------------------------------- | |
lib_perspective.createView = function(layerCount) | |
------------------------------------------------------------------------------ | |
-- Create view, internal object, and layers | |
------------------------------------------------------------------------------ | |
local view = display_newGroup() | |
view.damping = 1 | |
view.snapWhenFocused = true -- Do we instantly snap to the object when :setFocus() is called? | |
local isTracking | |
local internal -- So we can access it from inside the declaration | |
internal = { | |
trackingLevel = 1, | |
damping = 1, | |
scaleBoundsToScreen = true, | |
xScale = 1, | |
yScale = 1, | |
addX = display.contentCenterX, | |
addY = display.contentCenterY, | |
bounds = { | |
xMin = math_nhuge, | |
xMax = math_huge, | |
yMin = math_nhuge, | |
yMax = math_huge | |
}, | |
scaledBounds = { | |
xMin = math_nhuge, | |
xMax = math_huge, | |
yMin = math_nhuge, | |
yMax = math_huge | |
}, | |
trackFocus = true, | |
focus = nil, | |
viewX = 0, | |
viewY = 0, | |
getViewXY = function() if internal.focus then return internal.focus.x, internal.focus.y else return internal.viewX, internal.viewY end end, | |
layer = {}, | |
updateAddXY = function() internal.addX = display.contentCenterX / view.xScale internal.addY = display.contentCenterY / view.yScale end | |
} | |
local layers = {} | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
-- Internal Methods | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
-- Scale Bounds | |
------------------------------------------------------------------------------ | |
internal.scaleBounds = function(doX, doY) | |
if internal.scaleBoundsToScreen then | |
local xMin = internal.bounds.xMin | |
local xMax = internal.bounds.xMax | |
local yMin = internal.bounds.yMin | |
local yMax = internal.bounds.yMax | |
local doX = doX and not ((xMin == math_nhuge) or (xMax == math_huge)) | |
local doY = doY and not ((yMin == math_nhuge) or (yMax == math_huge)) | |
if doX then | |
local scaled_xMin = xMin / view.xScale | |
local scaled_xMax = xMax - (scaled_xMin - xMin) | |
if scaled_xMax < scaled_xMin then | |
local hopDist = scaled_xMin - scaled_xMax | |
local halfDist = hopDist * 0.5 | |
scaled_xMax = scaled_xMax + halfDist | |
scaled_xMin = scaled_xMin - halfDist | |
end | |
internal.scaledBounds.xMin = scaled_xMin | |
internal.scaledBounds.xMax = scaled_xMax | |
end | |
if doY then | |
local scaled_yMin = yMin / view.yScale | |
local scaled_yMax = yMax - (scaled_yMin - yMin) | |
if scaled_yMax < scaled_yMin then | |
local hopDist = scaled_yMin - scaled_yMax | |
local halfDist = hopDist * 0.5 | |
scaled_yMax = scaled_yMax + halfDist | |
scaled_yMin = scaled_yMin - halfDist | |
end | |
internal.scaledBounds.yMin = scaled_yMin | |
internal.scaledBounds.yMax = scaled_yMax | |
end | |
else | |
camera.scaledBounds.xMin, camera.scaledBounds.xMax, camera.scaledBounds.yMin, camera.scaledBounds.yMax = camera.bounds.xMin, camera.bounds.xMax, camera.bounds.yMin, camera.bounds.yMax | |
end | |
end | |
------------------------------------------------------------------------------ | |
-- Process Viewpoint | |
------------------------------------------------------------------------------ | |
internal.processViewpoint = function() | |
if internal.damping ~= view.damping then internal.trackingLevel = 1 / view.damping internal.damping = view.damping end | |
if internal.trackFocus then | |
local x, y = internal.getViewXY() | |
if view.xScale ~= internal.xScale or view.yScale ~= internal.yScale then internal.updateAddXY() end | |
if view.xScale ~= internal.xScale then internal.xScale = view.xScale internal.scaleBounds(true, false) end | |
if view.yScale ~= internal.yScale then internal.yScale = view.yScale internal.scaleBounds(false, true) end | |
x = clamp(x, internal.scaledBounds.xMin, internal.scaledBounds.xMax) | |
y = clamp(y, internal.scaledBounds.yMin, internal.scaledBounds.yMax) | |
internal.viewX, internal.viewY = x, y | |
end | |
end | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
-- Public Methods | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
------------------------------------------------------------------------------ | |
-- Append Layer | |
------------------------------------------------------------------------------ | |
view.appendLayer = function() | |
local layer = display_newGroup() | |
layer.xParallax, layer.yParallax = 1, 1 | |
view:insert(layer) | |
layer:toBack() | |
table_insert(layers, layer) | |
layer._perspectiveIndex = #layers | |
internal.layer[#layers] = { | |
x = 0, | |
y = 0, | |
xOffset = 0, | |
yOffset = 0 | |
} | |
function layer:setCameraOffset(x, y) internal.layer[layer._perspectiveIndex].xOffset, internal.layer[layer._perspectiveIndex].yOffset = x, y end | |
end | |
------------------------------------------------------------------------------ | |
-- Add an Object to the Camera | |
------------------------------------------------------------------------------ | |
function view:add(obj, l, isFocus) | |
local l = l or 4 | |
layers[l]:insert(obj) | |
obj._perspectiveLayer = l | |
if isFocus then view:setFocus(obj) end | |
-- Move an object to a layer | |
function obj:toLayer(newLayer) if layer[newLayer] then layer[newLayer]:insert(obj) obj._perspectiveLayer = newLayer end end | |
--Move an object back a layer | |
function obj:back() if layer[obj._perspectiveLayer + 1] then layer[obj._perspectiveLayer + 1]:insert(obj) obj._perspectiveLayer = obj.layer + 1 end end | |
--Moves an object forwards a layer | |
function obj:forward() if layer[obj._perspectiveLayer - 1] then layer[obj._perspectiveLayer - 1]:insert(obj) obj._perspectiveLayer = obj.layer - 1 end end | |
--Moves an object to the very front of the camera | |
function obj:toCameraFront() layer[1]:insert(obj) obj._perspectiveLayer = 1 obj:toFront() end | |
--Moves an object to the very back of the camera | |
function obj:toCameraBack() layer[#layers]:insert(obj) obj._perspectiveLayer = #layers obj:toBack() end | |
end | |
------------------------------------------------------------------------------ | |
-- Main Tracking Function | |
------------------------------------------------------------------------------ | |
function view:trackFocus() | |
internal.processViewpoint() | |
local viewX, viewY = internal.viewX, internal.viewY | |
layers[1].xParallax, layers[1].yParallax = 1, 1 | |
for i = 1, #layers do | |
local addX, addY = internal.addX, internal.addY | |
local layerX, layerY = internal.layer[i].x, internal.layer[i].y | |
local diffX = (-viewX - layerX) | |
local diffY = (-viewY - layerY) | |
local incrX = diffX | |
local incrY = diffY | |
internal.layer[i].x = layerX + incrX | |
internal.layer[i].y = layerY + incrY | |
layers[i].x = (layers[i].x - (layers[i].x - (internal.layer[i].x + addX) * layers[i].xParallax) * internal.trackingLevel) | |
layers[i].y = (layers[i].y - (layers[i].y - (internal.layer[i].y + addY) * layers[i].yParallax) * internal.trackingLevel) | |
end | |
view.scrollX, view.scrollY = layers[1].x, layers[1].y | |
end | |
------------------------------------------------------------------------------ | |
-- Set the Camera Bounds | |
------------------------------------------------------------------------------ | |
function view:setBounds(x1, x2, y1, y2) | |
local xMin, xMax, yMin, yMax | |
if x1 ~= nil then if not x1 then xMin = math_nhuge else xMin = x1 end end | |
if x2 ~= nil then if not x2 then xMax = math_huge else xMax = x2 end end | |
if y1 ~= nil then if not y1 then yMin = math_nhuge else yMin = y1 end end | |
if y2 ~= nil then if not y2 then yMax = math_huge else yMax = y2 end end | |
internal.bounds.xMin = xMin | |
internal.bounds.xMax = xMax | |
internal.bounds.yMin = yMin | |
internal.bounds.yMax = yMax | |
internal.scaleBounds(true, true) | |
end | |
------------------------------------------------------------------------------ | |
-- Miscellaneous Functions | |
------------------------------------------------------------------------------ | |
-- Begin auto-tracking | |
function view:track() if not isTracking then Runtime:addEventListener("enterFrame", view.trackFocus) isTracking = true end end | |
-- Stop auto-tracking | |
function view:cancel() if isTracking then Runtime:removeEventListener("enterFrame", view.trackFocus) isTracking = false end end | |
-- Remove an object from the view | |
function view:remove(obj) if obj and obj._perspectiveLayer then layers[obj._perspectiveLayer]:remove(obj) end end | |
-- Set the view's focus | |
function view:setFocus(obj) if obj then internal.focus = obj end if view.snapWhenFocused then view.snap() end end | |
-- Snap the view to the focus point | |
function view:snap() local t = internal.trackingLevel local d = internal.damping internal.trackingLevel = 1 internal.damping = view.damping view:trackFocus() internal.trackingLevel = t internal.damping = d end | |
-- Move the view to a point | |
function view:toPoint(x, y) view:cancel() local newFocus = {x = x, y = y} view:setFocus(newFocus) view:track() return newFocus end | |
-- Get a layer of the view | |
function view:layer(n) return layers[n] end | |
-- Destroy the view | |
function view:destroy() view:cancel() for i = 1, #layers do for o = 1, layers[i].numChildren do layers[i]:remove(layers[i][o]) end end display_remove(view) view = nil return true end | |
-- Set layer parallax | |
function view:setParallax(...) for i = 1, #arg do if type(arg[i]) == "table" then layers[i].xParallax, layers[i].yParallax = arg[i][1], arg[i][2] else layers[i].xParallax, layers[i].yParallax = arg[i], arg[i] end end end | |
-- Get number of layers | |
function view:layerCount() return #layers end | |
------------------------------------------------------------------------------ | |
-- Build Layers | |
------------------------------------------------------------------------------ | |
for i = layerCount or 8, 1, -1 do view.appendLayer() end | |
return view | |
end | |
return lib_perspective |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local perspective = require("perspective") -- Include the library | |
local camera = perspective.createView([numLayers]) -- Optional parameter is the number of layers | |
camera.appendLayer() -- Add a new layer to the back of the camera | |
camera:add(obj, layer, [isFocus]) -- obj is any display object; layer is the layer to add the object to (lower numbers = front of camera); isFocus is a convenience value for whether to initially set the focus to this object | |
camera:trackFocus() -- "Tick" the camera once | |
camera:setBounds(x1, x2, y1, y2) -- Set the bounding box that tracking is confined to. Any values that evaluate to Boolean negative are interpreted as infinite; infinite values apply to an entire axis (if x2 is infinite and x1 is not, X-axis constraint will be disabled) | |
camera:track() -- Begin auto-tracking. This eliminates the need to update the camera every frame | |
camera:cancel() -- Stop auto-tracking | |
camera:remove(obj) -- Remove an object from the camera | |
camera:setFocus(obj) -- Set the camera focus to an object | |
camera:snap() -- Snap the camera to the focus point (ignores damping) | |
camera:toPoint(x, y) -- Change the camera's focus to an X,Y point | |
camera:layer(n) -- Get a layer object of the camera | |
camera:destroy() -- Destroy the camera and clear memory | |
camera:setParallax(...) -- Set parallax quickly for multiple layers. Each value provided will apply to the correspondingly indexed layer. Provide a table with {x, y} values for an argument to set X and Y parallax independently | |
camera:layerCount() -- Get the number of layers in the camera | |
---------------------- | |
camera.damping = n -- "Fluidity" the camera implements with tracking. Higher values will make the camera move more slowly; values approaching 1 will make the camera move more rigidly | |
layer.xParallax = n -- X-parallax ratio of a layer; expressed as fraction of "normal" movement | |
layer.yParallax = n -- Y-parallax ratio of a layer; expressed as fraction of "normal" movement |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------------------------------------------------- | |
-- Perspective Demo | |
-------------------------------------------------------------------------------- | |
display.setStatusBar(display.HiddenStatusBar) | |
-------------------------------------------------------------------------------- | |
-- Localize | |
-------------------------------------------------------------------------------- | |
local require = require | |
local perspective = require("perspective") | |
local function forcesByAngle(totalForce, angle) local forces = {} local radians = -math.rad(angle) forces.x = math.cos(radians) * totalForce forces.y = math.sin(radians) * totalForce return forces end | |
-------------------------------------------------------------------------------- | |
-- Build Camera | |
-------------------------------------------------------------------------------- | |
local camera = perspective.createView() | |
-------------------------------------------------------------------------------- | |
-- Build Player | |
-------------------------------------------------------------------------------- | |
local player = display.newPolygon(0, 0, {-50,-30, -50,30, 50,0}) | |
player.strokeWidth = 6 | |
player:setFillColor(0, 0, 0, 0) | |
player.anchorX = 0.2 -- Slightly more "realistic" than center-point rotating | |
-- Some various movement parameters | |
player.angularVelocity = 0 -- Speed at which player rotates | |
player.angularAcceleration = 1.05 -- Angular acceleration rate | |
player.angularDamping = 0.9 -- Angular damping rate | |
player.angularMax = 10 -- Max angular velocity | |
player.moveSpeed = 0 -- Current movement speed | |
player.linearDamping = 0 -- Linear damping rate | |
player.linearAcceleration = 1.05 -- Linear acceleration rate | |
player.linearMax = 10 -- Max linear velocity | |
camera:add(player, 1) -- Add player to layer 1 of the camera | |
-------------------------------------------------------------------------------- | |
-- "Scenery" | |
-------------------------------------------------------------------------------- | |
local scene = {} | |
for i = 1, 100 do | |
scene[i] = display.newCircle(0, 0, 10) | |
scene[i].x = math.random(display.screenOriginX, display.contentWidth * 3) | |
scene[i].y = math.random(display.screenOriginY, display.contentHeight) | |
scene[i]:setFillColor(math.random(100) * 0.01, math.random(100) * 0.01, math.random(100) * 0.01) | |
camera:add(scene[i], math.random(2, camera:layerCount())) | |
end | |
camera:setParallax(1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3) -- Here we set parallax for each layer in descending order | |
-------------------------------------------------------------------------------- | |
-- Movement Buttons | |
-------------------------------------------------------------------------------- | |
local m = {} | |
m.result = "none" | |
m.rotate = {} | |
m.rotate.left = display.newRect(0, 0, 60, 60) | |
m.rotate.left.x = display.screenOriginX + m.rotate.left.contentWidth + 10 | |
m.rotate.left.y = display.contentHeight - m.rotate.left.contentHeight - 10 | |
m.rotate.left.result = "rotate:left" | |
m.rotate.right = display.newRect(0, 0, 60, 60) | |
m.rotate.right.x = display.contentWidth - display.screenOriginX - m.rotate.right.contentWidth - 10 | |
m.rotate.right.y = display.contentHeight - m.rotate.right.contentHeight - 10 | |
m.rotate.right.result = "rotate:right" | |
m.forward = display.newRect(0, 0, display.contentWidth * 0.75, 60) | |
m.forward.x = display.contentCenterX | |
m.forward.y = display.contentHeight - m.forward.contentHeight - 10 | |
m.forward.result = "move" | |
-------------------------------------------------------------------------------- | |
-- Touch Movement Buttons | |
-------------------------------------------------------------------------------- | |
function m.touch(event) | |
local t = event.target | |
if "began" == event.phase then | |
display.getCurrentStage():setFocus(t) | |
t.isFocus = true | |
m.result = t.result | |
if t.result == "rotate:left" then | |
player.angularVelocity = -2 | |
elseif t.result == "rotate:right" then | |
player.angularVelocity = 2 | |
elseif t.result == "move" then | |
player.moveSpeed = 2 | |
end | |
elseif t.isFocus then | |
if "moved" == event.phase then | |
elseif "ended" == event.phase then | |
display.getCurrentStage():setFocus(nil) | |
t.isFocus = false | |
m.result = "none" | |
end | |
end | |
end | |
m.rotate.left:addEventListener("touch", m.touch) | |
m.rotate.right:addEventListener("touch", m.touch) | |
m.forward:addEventListener("touch", m.touch) | |
-------------------------------------------------------------------------------- | |
-- Runtime Loop | |
-------------------------------------------------------------------------------- | |
local function enterFrame(event) | |
if m.result == "rotate:left" then | |
player.angularVelocity = player.angularVelocity * player.angularAcceleration | |
player.angularVelocity = math.max(player.angularVelocity, -player.angularMax) | |
player.moveSpeed = player.moveSpeed * player.linearDamping | |
elseif m.result == "rotate:right" then | |
player.angularVelocity = player.angularVelocity * player.angularAcceleration | |
player.angularVelocity = math.min(player.angularVelocity, player.angularMax) | |
player.moveSpeed = player.moveSpeed * player.linearDamping | |
elseif m.result == "move" then | |
player.moveSpeed = player.moveSpeed * player.linearAcceleration | |
player.moveSpeed = math.min(player.moveSpeed, player.linearMax) | |
player.angularVelocity = player.angularVelocity * player.angularDamping | |
elseif m.result == "none" then | |
player.angularVelocity = player.angularVelocity * player.angularDamping | |
player.moveSpeed = player.moveSpeed * player.linearDamping | |
end | |
local forces = forcesByAngle(player.moveSpeed, 360 - player.rotation) | |
player:translate(forces.x, forces.y) | |
player:rotate(player.angularVelocity) | |
end | |
-------------------------------------------------------------------------------- | |
-- Add Listeners | |
-------------------------------------------------------------------------------- | |
Runtime:addEventListener("enterFrame", enterFrame) | |
camera.damping = 10 -- A bit more fluid tracking | |
camera:setFocus(player) -- Set the focus to the player | |
camera:track() -- Begin auto-tracking |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment