Example Using the RegionManager to Handle Very Large Worlds
The RegionManager provides a mechanism which enables the Wattage Tile Engine to handle very large environments. Environments which would be too large to load into RAM. It does this by only rendering smaller “regions” as they become visible to the camera. Your application supplies these regions by implementing a listener which the RegionManager can query for regions when they are needed. Please refer to the RegionManager usage guide for more details and definitions of terms used here.
Setup
This example builds on the Wattage Tile Engine Template which can be found here. The completed example can be found here.
Overview
In this example, the getRegion() listener function has been implemented to supply horizontal hallways on even numbered region rows, vertical hallways on even number region columns, and intersections where the hallways intersect. This results in a repeating pattern of crossing hallways. Physics bodies are also added and a collection of references to them is attached to the region data returned by the listener function. When the region needs to be overwritten with new data, the RegionManager will call the regionReleased() listener function and pass the same region data back to that function. Having the physics bodies collection attached to the region data enables the listener to cleanup the physics bodies when they are no longer needed. Please note that an actual implementation of these listeners would likely be more complex resulting in a non-patterned environment. This simple implementation is only meant as an example.
This example also includes zooming functionality. By zooming out very far, one is able to see how the RegionManager repaints sub-regions within the buffer layer as the player token moves.
Code
A control stick class is used by this example. It provides analog directional movement. Here is the code.
local TileEngine = require "plugin.wattageTileEngine"
local Utils = TileEngine.Utils
-- Localize external function calls.
local sqrt = math.sqrt
local min = math.min
local AnalogControlStick = {}
AnalogControlStick.new = function(params)
Utils.requireParams({
"parentGroup",
"centerX",
"centerY",
"centerDotRadius",
"outerCircleRadius"
},params)
local self = {}
local touchId -- Used to set touch focus on the control stick
local centerDot -- The center of the control stick
local outerCircle -- The outer circle of the control stick
local outerCircleRadius = params.outerCircleRadius -- private variable to store the outer circle radius
-- Vector whose magnitude is a percentage of the radius from the center dot. For example, if the outer
-- ring of the control is touched, this vector would have a magnitude of 1, representing 100% of the radius.
-- If the control was touched half way between the center dot and the outer ring, this vector would have a
-- magnitude of 0.5 representing 50%. If the touch point moves outside the radius, the value will be
-- greater than 1 representing a value greater than 100%. This can be used like a throttle control by
-- setting an entity's velocity to a percentage of the max velocity.
local currentRawDirectionVectorX
local currentRawDirectionVectorY
-- Same as currentRawDirectionVector except it is capped at 1 (or 100%).
local currentDirectionVectorX
local currentDirectionVectorY
local function calculateDirectionVectors(x, y)
-- Create vector for the control center point
local centerPointVectorX = centerDot.x
local centerPointVectorY = centerDot.y
-- Create vector for the touch point
local touchPointVectorX = x
local touchPointVectorY = y
-- Subtract the center vector from the touch point vector to get the control direction vector.
local vectorToTouchPointX = touchPointVectorX - centerPointVectorX
local vectorToTouchPointY = touchPointVectorY - centerPointVectorY
-- Determine the magnitude of the vector
local magnitude = sqrt(
vectorToTouchPointX * vectorToTouchPointX + vectorToTouchPointY * vectorToTouchPointY)
-- Determine the magnitude's percent of the outer circle radius
local percent = magnitude / outerCircleRadius
-- Store the capped percent. This will result in any value greater than 1 being set to 1 instead.
local cappedPercent = min(percent, 1)
-- Calculates the vector where magnitude is the distance from the center dot represented as a
-- percentage of the outer ring radius as described in the variable's declaration.
currentRawDirectionVectorX = vectorToTouchPointX / magnitude * percent
currentRawDirectionVectorY = vectorToTouchPointY / magnitude * percent
-- Calculates the vector where magnitude is the distance from the center dot represented as a
-- percentage of the outer ring radius and capped at 1 (100%) as described in the variable's declaration.
currentDirectionVectorX = vectorToTouchPointX / magnitude * cappedPercent
currentDirectionVectorY = vectorToTouchPointY / magnitude * cappedPercent
end
-- Handle for touch events
local function touchHandler(event)
-- If the control is already focused on a touch and this touch
-- is not the current touch, exit early.
if touchId ~= nil and touchId ~= event.id then
return false
end
if event.phase == "began" then
-- Touch has began
-- Store the ID of the touch
touchId = event.id
-- Set the focus of the current touch to this control exclusively.
display.getCurrentStage():setFocus(outerCircle, touchId)
-- calculate the direction vectors
calculateDirectionVectors(event.x, event.y)
elseif event.phase == "moved" then
-- Touch has moved
-- calculate the direction vectors
calculateDirectionVectors(event.x, event.y)
elseif event.phase == "ended" or event.phase == "cancelled" then
-- Touch has ended or was cancelled
-- Remove the focus
display.getCurrentStage():setFocus(outerCircle, nil)
-- Clear the touchID
touchId = nil
-- Set direction vectors to nil
currentRawDirectionVectorX = nil
currentRawDirectionVectorY = nil
currentDirectionVectorX = nil
currentDirectionVectorY = nil
end
-- Indicate that the touch was handled by returning true
return true
end
-- Return both the raw and capped vector values
function self.getCurrentValues()
return {
cappedDirectionVector = {x = currentDirectionVectorX, y = currentDirectionVectorY},
rawDirectionVector = {x = currentRawDirectionVectorX, y = currentRawDirectionVectorY}
}
end
-- Perform cleanup of resources allocated by this class
function self.destroy()
outerCircle:removeEventListener("touch", touchHandler)
display.getCurrentStage():setFocus(outerCircle, nil)
outerCircle:removeSelf()
outerCircle = nil
centerDot:removeSelf()
centerDot = nil
end
-- Initiallizes the managed resources
local function initialize()
centerDot = display.newCircle(
params.parentGroup,
params.centerX,
params.centerY,
params.centerDotRadius
)
centerDot:setFillColor(1,1,1,1)
centerDot:setStrokeColor(1,1,1,1)
centerDot.strokeWidth = 1
centerDot.alpha = 0.25
outerCircle = display.newCircle(
params.parentGroup,
params.centerX,
params.centerY,
params.outerCircleRadius
)
outerCircle:setFillColor(1,1,1,1)
outerCircle.alpha = 0.25
outerCircle:addEventListener("touch", touchHandler)
end
initialize()
return self
end
return AnalogControlStick
The example scene is listed here. The comments within should be adequate. Please also reference the usage guide for more explanation.
local AnalogControlStick = require "analogControlStick"
local Composer = require( "composer" )
local Physics = require "physics"
local TileEngine = require "plugin.wattageTileEngine"
local RegionManager = TileEngine.RegionManager
local scene = Composer.newScene()
-- Import file generated by PhysicsEditor which contains physics data for the tiles.
local scaleFactor = 1.0
local physicsData = (require "tilePhysicsBodies").physicsData(scaleFactor)
-- -----------------------------------------------------------------------------------
-- This table translates values used in the region templates to lookup names
-- used in the sprite sheet.
-- -----------------------------------------------------------------------------------
local REGION_VALUE_TO_TILE_NAME_MAP = {
[0] = "tiles_00",
[1] = "tiles_01",
[2] = "tiles_02",
[3] = "tiles_03",
[4] = "tiles_04",
[5] = "tiles_05",
[6] = "tiles_06",
[7] = "tiles_07",
[8] = "tiles_08",
[9] = "tiles_09",
[10] = "tiles_10",
[11] = "tiles_11",
[12] = "tiles_12",
[13] = "tiles_13",
[14] = "tiles_14"
}
local VERTICAL_HALL_REGION = {
{1,4,3},
{1,4,3},
{1,4,3}
}
local HORIZONTAL_HALL_REGION = {
{2,2,2},
{4,4,4},
{0,0,0}
}
local CROSS_REGION = {
{ 5, 4, 12},
{ 4, 4, 4},
{10, 4, 11}
}
local REGION_WIDTH_IN_TILES = 3 -- The width in tiles for each sub-region
local REGION_HEIGHT_IN_TILES = 3 -- The height in tiles for each sub-region
local BUFFER_TILE_LAYER_INDEX = 1 -- The layer index for the buffer tile layer
local ENTITY_LAYER_INDEX = 2 -- The layer index for the entity layer
local MIN_ZOOM = 0.1 -- The minimum zoom level of the camera
local MAX_ZOOM = 2 -- The maximum zoom level of the camera
local TILE_SIZE = 128 -- Constant for the tile size
local BUFFER_LAYER_ROW_COUNT = 180 -- Row count of the buffer layer
local BUFFER_LAYER_COLUMN_COUNT = 180 -- Column count of the buffer layer
local MAX_FORCE = 500 -- The maximum force that will be applied to the player entity
local LINEAR_DAMPING = 1 -- Provides a little resistance to linear motion.
local tileEngine -- Reference to the tile engine
local lightingModel -- Reference to the lighting model
local tileEngineViewControl -- Reference to the UI view control
local regionManager -- Reference to the region manager
local controlStick -- Reference to the control stick
local playerEntityId -- ID used to interact with the player entity
local playerSprite -- Sprite for the player
local lastTime -- Used to track how much time passes between frames
local zoomInButton -- Reference to the zoom in button
local zoomOutButton -- Reference to the zoom out button
local curZoom -- The current camera zoom level
local isZoomingIn -- Flag set when zooming in
local isZoomingOut -- Flag set when zooming out
local touchId -- Used to track touches on zoom buttons
-- -----------------------------------------------------------------------------------
-- This will load in the example sprite sheet.
-- -----------------------------------------------------------------------------------
local spriteSheetInfo = require "tiles"
local spriteSheet = graphics.newImageSheet("tiles.png", spriteSheetInfo:getSheet())
-- -----------------------------------------------------------------------------------
-- A sprite resolver is required by the engine. Its function is to create a
-- SpriteInfo object for the supplied key. This function will utilize the
-- example sprite sheet.
-- -----------------------------------------------------------------------------------
local spriteResolver = {}
spriteResolver.resolveForKey = function(key)
-- Get the sprite sheet lookup name for the key value
local name = REGION_VALUE_TO_TILE_NAME_MAP[key]
-- Get the frame index for the lookup name
local frameIndex = spriteSheetInfo:getFrameIndex(name)
-- Retrieve the frame from the sprite sheet
local frame = spriteSheetInfo.sheet.frames[frameIndex]
-- Create the DisplayObject
local displayObject = display.newImageRect(spriteSheet, frameIndex, frame.width, frame.height)
-- Return a table containing the DisplayObject and its dimensions.
return {
imageRect = displayObject,
width = frame.width,
height = frame.height
}
end
-- -----------------------------------------------------------------------------------
-- Handler for the zoom in button
-- -----------------------------------------------------------------------------------
local function zoomInHandler(event)
-- If the control is already focused on a touch and this touch
-- is not the current touch, exit early.
if touchId ~= nil and touchId ~= event.id then
return false
end
if event.phase == "began" then
-- Touch has began
-- Store the ID of the touch
touchId = event.id
-- Set the focus of the current touch to this control exclusively.
display.getCurrentStage():setFocus(zoomInButton, touchId)
-- Set zoom flag to true
isZoomingIn = true
elseif event.phase == "moved" then
-- Touch has moved
elseif event.phase == "ended" or event.phase == "cancelled" then
-- Touch has ended or was cancelled
-- Remove the focus
display.getCurrentStage():setFocus(zoomInButton, nil)
-- Clear the touchID
touchId = nil
-- Set zoom flag to false
isZoomingIn = false
end
-- Indicate that the touch was handled by returning true
return true
end
-- -----------------------------------------------------------------------------------
-- Handler for the zoom out button
-- -----------------------------------------------------------------------------------
local function zoomOutHandler(event)
-- If the control is already focused on a touch and this touch
-- is not the current touch, exit early.
if touchId ~= nil and touchId ~= event.id then
return false
end
if event.phase == "began" then
-- Touch has began
-- Store the ID of the touch
touchId = event.id
-- Set the focus of the current touch to this control exclusively.
display.getCurrentStage():setFocus(zoomOutButton, touchId)
-- Set zoom flag to true
isZoomingOut = true
elseif event.phase == "moved" then
-- Touch has moved
elseif event.phase == "ended" or event.phase == "cancelled" then
-- Touch has ended or was cancelled
-- Remove the focus
display.getCurrentStage():setFocus(zoomOutButton, nil)
-- Clear the touchID
touchId = nil
-- Set zoom flag to false
isZoomingOut = false
end
-- Indicate that the touch was handled by returning true
return true
end
-- -----------------------------------------------------------------------------------
-- This will be called every frame. It is responsible for setting the camera
-- position, updating the lighting model, rendering the tiles, and reseting
-- the dirty tiles on the lighting model.
-- -----------------------------------------------------------------------------------
local function onFrame(event)
local camera = tileEngineViewControl.getCamera()
local lightingModel = tileEngine.getActiveModule().lightingModel
if lastTime ~= 0 then
-- Determine the amount of time that has passed since the last frame and
-- record the current time in the lastTime variable to be used in the next
-- frame.
local curTime = event.time
local deltaTime = curTime - lastTime
lastTime = curTime
-- Get the direction vectors from the control stick
local cappedPercentVector = controlStick.getCurrentValues().cappedDirectionVector
-- If the control stick is currently being pressed, then apply the appropriate force
if cappedPercentVector.x ~= nil and cappedPercentVector.y ~= nil then
-- Determine the percent of max force to apply. The magnitude of the vector from the
-- conrol stick indicates the percentate of the max force to apply.
local forceVectorX = cappedPercentVector.x * MAX_FORCE
local forceVectorY = cappedPercentVector.y * MAX_FORCE
-- Apply the force to the center of the player entity.
playerSprite:applyForce(forceVectorX, forceVectorY, playerSprite.x, playerSprite.y)
end
-- Have the camera follow the player
-- Use the RegionManager to get the location of the player entity
-- Do NOT get the position directly from the x,y values on the sprite
-- as they will not correctly reflect world coordinates. Please reference
-- the RegionManager usage guide for more info.
local playerX, playerY = regionManager.getEntityLocation(2, playerEntityId)
local tileXCoord = playerX / TILE_SIZE
local tileYCoord = playerY / TILE_SIZE
-- Set the camera location using the RegionManager. Again, setting the
-- location directly on the camera will not account for the world coordinate
-- to local coordinate conversion. Please reference
-- the RegionManager usage guide for more info.
regionManager.setCameraLocation(tileXCoord, tileYCoord)
-- Determine curZoom
if isZoomingIn then
curZoom = curZoom * 1.05
if curZoom > MAX_ZOOM then
curZoom = MAX_ZOOM
end
end
if isZoomingOut then
curZoom = curZoom * 0.95
if curZoom < MIN_ZOOM then
curZoom = MIN_ZOOM
end
end
-- It is ok to set the zoom level directly on the camera when using the RegionManager
camera.setZoom(curZoom)
-- Update the lighting model passing the amount of time that has passed since
-- the last frame.
lightingModel.update(deltaTime)
else
-- This is the first call to onFrame, so lastTime needs to be initialized.
lastTime = event.time
-- The initial camera zoom level is 1
curZoom = 1
-- This is the initial position of the camera set using the RegionManager.
-- Its initial value is centered on the player entity location.
regionManager.setCameraLocation(1.5,1.5)
-- Since a time delta cannot be calculated on the first frame, 1 is passed
-- in here as a placeholder.
lightingModel.update(1)
end
-- Render the tiles visible to the passed in camera.
tileEngine.render(camera)
-- The lighting model tracks changes, then acts on all accumulated changes in
-- the lightingModel.update() function. This call resets the change tracking
-- and must be called after lightingModel.update().
lightingModel.resetDirtyFlags()
end
-- -----------------------------------------------------------------------------------
-- This will create the physics objects for each region.
-- -----------------------------------------------------------------------------------
local function createPhysicsObjects(regionTemplate, topRowOffset, leftColumnOffset)
local physicsObjects = {}
-- Physics objects will be added to the tile engine's master
-- group, but will not be visible.
local physicsGroup = tileEngine.getMasterGroup()
-- Iterate through the rows and columns of the region template.
-- The template dimensions must match the dimensions specified
-- to the RegionManager.
for regionRow=1,REGION_HEIGHT_IN_TILES do
for regionCol=1,REGION_WIDTH_IN_TILES do
-- Obtain the value of the region tile
local value = regionTemplate[regionRow][regionCol]
-- Check to see if any physics bodies are associated with this tile value
local firstPhysicsBody = physicsData:get(REGION_VALUE_TO_TILE_NAME_MAP[value])
if firstPhysicsBody ~= nil then
-- This tile value has physics bodies
-- Calculate the center x, y coordinate where the physics
-- body will be placed. Use the offset values to properly
-- place the body. NOTE: regionCol and regionRow are reduced
-- by 0.5 so that x and y reflect the center of the tile.
local x = (leftColumnOffset + regionCol - 0.5) * TILE_SIZE
local y = (topRowOffset + regionRow - 0.5) * TILE_SIZE
-- Create an invisible displayObject to attach physics data to
local physicsObject = display.newRect(
physicsGroup,
x,
y,
TILE_SIZE,
TILE_SIZE)
physicsObject.isVisible = false
-- Add the physics body information
Physics.addBody(
physicsObject,
"static",
physicsData:get(REGION_VALUE_TO_TILE_NAME_MAP[value]))
-- Add the physics objects to the collection
table.insert(physicsObjects, physicsObject)
end
end
end
-- Return the collection of physics objects created here
return physicsObjects
end
-- -----------------------------------------------------------------------------------
-- EndlessWorldRegionManager listener functions
--
-- This listener is responsible for supplying the region data to the RegionManager.
-- The absolute region row and column (which are the region coordinates relative to the world)
-- are passed to the function. These coordinates should be used to determine what
-- data should be returned for the region.
--
-- The row and column offsets for the region are also passed to the function. These
-- represent the offset in local coordinates in tile units to the top left corner of the region.
-- These coordinate offsets should be added to the local coordinates of any
-- additional objects added to this region (such as physics bodies).
--
-- This example function will place horizontal passages on even numbered rows,
-- vertical passages on even numbered columns, and crosses at the intersections.
-- However, this function could be much more complex to create more interesting
-- environments.
--
-- Physics objects created here are added to the returned table so they may be cleaned up
-- when the regionReleased() listener function is called.
-- -----------------------------------------------------------------------------------
function scene.getRegion(params)
local absoluteRegionRow = params.absoluteRegionRow
local absoluteRegionCol = params.absoluteRegionCol
local topRowOffset = params.topRowOffset
local leftColumnOffset = params.leftColumnOffset
if absoluteRegionRow % 2 == 0 and absoluteRegionCol % 2 == 0 then
local regionTemplate = CROSS_REGION
local physicsObjects = createPhysicsObjects(regionTemplate, topRowOffset, leftColumnOffset)
return {
physicsObjects = physicsObjects,
tilesByLayerIndex = {
regionTemplate
}
}
end
if absoluteRegionRow % 2 == 0 then
local regionTemplate = HORIZONTAL_HALL_REGION
local physicsObjects = createPhysicsObjects(regionTemplate, topRowOffset, leftColumnOffset)
return {
physicsObjects = physicsObjects,
tilesByLayerIndex = {
regionTemplate
}
}
end
if absoluteRegionCol % 2 == 0 then
local regionTemplate = VERTICAL_HALL_REGION
local physicsObjects = createPhysicsObjects(regionTemplate, topRowOffset, leftColumnOffset)
return {
physicsObjects = physicsObjects,
tilesByLayerIndex = {
regionTemplate
}
}
end
return {}
end
-- -----------------------------------------------------------------------------------
-- This listener function is called when a region is no longer valid and
-- has been released. Anything that was allocated and attached to the
-- regionData in the getRegion() function must be released here. In this
-- example, the physics objects created in getRegion() need to be cleaned
-- up.
-- -----------------------------------------------------------------------------------
function scene.regionReleased(regionData)
if regionData ~= nil and regionData.physicsObjects ~= nil then
local physicsObjects = regionData.physicsObjects
-- Release the physics objects, they are no longer valid.
for i=1,#physicsObjects do
physicsObjects[i]:removeSelf()
end
regionData.physicsObjects = nil
end
end
-- -----------------------------------------------------------------------------------
-- Scene event functions
-- -----------------------------------------------------------------------------------
-- create()
function scene:create( event )
local sceneGroup = self.view
-- Start physics
Physics.start()
-- This example does not want any gravity, set it to 0.
Physics.setGravity(0,0)
-- Set scale (determined by trial and error for what feels right)
Physics.setScale(32)
-- Create a group to act as the parent group for all tile engine DisplayObjects.
local tileEngineLayer = display.newGroup()
sceneGroup:insert(tileEngineLayer)
-- Create an instance of TileEngine.
tileEngine = TileEngine.Engine.new({
parentGroup=tileEngineLayer,
tileSize=TILE_SIZE,
spriteResolver=spriteResolver,
compensateLightingForViewingPosition=false,
hideOutOfSightElements=false
})
-- The tile engine needs at least one Module. It can support more than
-- one, but this template sets up only one which should meet most use cases.
-- A module is composed of a LightingModel and a number of Layers
-- (TileLayer or EntityLayer). An instance of the lighting model is created
-- first since it is needed to instantiate the Module.
lightingModel = TileEngine.LightingModel.new({
isTransparent = function() return true end, -- All tiles are transparent
isTileAffectedByAmbient = function() return true end, -- All tiles affected by ambient
useTransitioners = false,
compensateLightingForViewingPosition = false
})
-- Instantiate the module.
local module = TileEngine.Module.new({
name="moduleMain",
rows= BUFFER_LAYER_ROW_COUNT,
columns= BUFFER_LAYER_COLUMN_COUNT,
lightingModel=lightingModel,
losModel=TileEngine.LineOfSightModel.ALL_VISIBLE -- All tiles are in line of sight
})
-- Next, create the buffer layer
local bufferLayer = TileEngine.TileLayer.new({
rows = BUFFER_LAYER_ROW_COUNT,
columns = BUFFER_LAYER_COLUMN_COUNT
})
bufferLayer.setLightingMode(TileEngine.LayerConstants.LIGHTING_MODE_NONE) -- Lighting data not used.
bufferLayer.resetDirtyTileCollection() -- No tiles are added directly to this layer, so go ahead and reset dirty tiles.
-- Add the layer to the module at index 1 (indexes start at 1, not 0). Set
-- the scaling delta to zero (Scaling delta must be zero when using the RegionManager)
module.insertLayerAtIndex(bufferLayer, BUFFER_TILE_LAYER_INDEX, 0)
-- Next create the entity layer for the player token
local entityLayer = TileEngine.EntityLayer.new({
tileSize = TILE_SIZE,
spriteResolver = spriteResolver
})
entityLayer.setLightingMode(TileEngine.LayerConstants.LIGHTING_MODE_NONE) -- Lighting data not used.
-- Add the entity layer to the module at index 2 (indexes start at 1, not 0). Set
-- the scaling delta to zero.
module.insertLayerAtIndex(entityLayer, ENTITY_LAYER_INDEX, 0)
-- Add the module to the engine.
tileEngine.addModule({module = module})
-- Set the module as the active module.
tileEngine.setActiveModule({
moduleName = "moduleMain"
})
-- To render the tiles to the screen, create a ViewControl. This example
-- creates a ViewControl to fill the entire screen, but one may be created
-- to fill only a portion of the screen if needed.
tileEngineViewControl = TileEngine.ViewControl.new({
parentGroup = sceneGroup,
centerX = display.contentCenterX,
centerY = display.contentCenterY,
pixelWidth = display.actualContentWidth,
pixelHeight = display.actualContentHeight,
tileEngineInstance = tileEngine
})
-- Now create the endless world region manager. Notice that
-- no tiles have been added to the buffer layer. This manager
-- will utilize the listener passed in to query for regions of
-- tiles when they are needed and will also inform the listener
-- when regions have been released.
regionManager = RegionManager.new({
regionWidthInTiles = REGION_WIDTH_IN_TILES,
regionHeightInTiles = REGION_HEIGHT_IN_TILES,
renderRegionWidth = 5, -- 5 tiles wide is enough to fill an iPhone 6 screen
renderRegionHeight = 3, -- 3 tiles high is enough to fill an iPhone 6 screen
tileSize = 128, -- Larger tiles will yeild higher performance as fewer of them are required to fill the screen
tileLayersByIndex = { [BUFFER_TILE_LAYER_INDEX] = bufferLayer }, -- Table will all tile layers keyed by their index
entityLayersByIndex = { [ENTITY_LAYER_INDEX] = entityLayer }, -- Table will all entity layers keyed by their index
camera = tileEngineViewControl.getCamera(), -- RegionManager will manage this camera by correctly transforming world and local coordinates.
listener = scene -- The table containing the listener functions
})
-- Add the player entity to the entity layer
local spriteInfo
playerEntityId, spriteInfo = entityLayer.addEntity(14)
-- Store a reference to the player entity sprite. It will be
-- used to apply forces to.
playerSprite = spriteInfo.imageRect
-- Make the player sprite a physical entity
Physics.addBody( playerSprite, "dynamic", physicsData:get(REGION_VALUE_TO_TILE_NAME_MAP[14]) )
-- Handle the player sprite as a bullet to prevent passing through walls
-- when moving very quickly.
playerSprite.isBullet = true
-- This will prevent the player from "sliding" too much.
playerSprite.linearDamping = LINEAR_DAMPING
-- Move the player entity to the center of the middle cross.
regionManager.centerEntityOnTile(2, playerEntityId, 1, 1)
local radius = 150
controlStick = AnalogControlStick.new({
parentGroup = sceneGroup,
centerX = display.screenOriginX + radius,
centerY = display.screenOriginY + display.actualContentHeight - radius,
centerDotRadius = 0.1 * radius,
outerCircleRadius = radius
})
-- Create the zoom buttons
zoomInButton = display.newImageRect(sceneGroup, "img/plus.png", 200, 200)
zoomInButton.xScale = 0.5
zoomInButton.yScale = 0.5
zoomInButton.x = display.screenOriginX + display.actualContentWidth - 200
zoomInButton.y = display.screenOriginY + display.actualContentHeight - 75
zoomOutButton = display.newImageRect(sceneGroup, "img/minus.png", 200, 200)
zoomOutButton.xScale = 0.5
zoomOutButton.yScale = 0.5
zoomOutButton.x = display.screenOriginX + display.actualContentWidth - 75
zoomOutButton.y = display.screenOriginY + display.actualContentHeight - 75
isZoomingIn = false
isZoomingOut = false
end
-- show()
function scene:show( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- Code here runs when the scene is still off screen (but is about to come on screen)
-- Set the lastTime variable to 0. This will indicate to the onFrame event handler
-- that it is the first frame.
lastTime = 0
-- Register the onFrame event handler to be called before each frame.
Runtime:addEventListener( "enterFrame", onFrame )
zoomInButton:addEventListener("touch", zoomInHandler)
zoomOutButton:addEventListener("touch", zoomOutHandler)
elseif ( phase == "did" ) then
-- Code here runs when the scene is entirely on screen
end
end
-- hide()
function scene:hide( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- Code here runs when the scene is on screen (but is about to go off screen)
-- Remove the onFrame event handler.
Runtime:removeEventListener( "enterFrame", onFrame )
-- Remove zoom listeners
zoomInButton:removeEventListener("touch", zoomInHandler)
display.getCurrentStage():setFocus(zoomInButton, nil)
isZoomingIn = false
zoomOutButton:removeEventListener("touch", zoomOutHandler)
display.getCurrentStage():setFocus(zoomOutButton, nil)
isZoomingOut = false
touchId = nil
elseif ( phase == "did" ) then
-- Code here runs immediately after the scene goes entirely off screen
end
end
-- destroy()
function scene:destroy( event )
local sceneGroup = self.view
-- Code here runs prior to the removal of scene's view
-- Release the zoom buttons
zoomInButton:removeSelf()
zoomInButton = nil
zoomOutButton:removeSelf()
zoomOutButton = nil
-- Destroy regionManager (NOTE: This may result in calls to regionReleased() listener function)
regionManager.destroy()
regionManager = nil
-- Destroy the tile engine instance to release all of the resources it is using
tileEngine.destroy()
tileEngine = nil
-- Destroy the ViewControl to release all of the resources it is using
tileEngineViewControl.destroy()
tileEngineViewControl = nil
-- Destroy the control stick
controlStick.destroy()
controlStick = nil
-- Set the reference to the lighting model to nil.
lightingModel = nil
end
-- -----------------------------------------------------------------------------------
-- Scene event function listeners
-- -----------------------------------------------------------------------------------
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )
-- -----------------------------------------------------------------------------------
return scene
When run, the user is able to slide their finger along the control stick and see the player token move in response. The camera will follow the player token. The player token will not be able to move through walls. The user may use the “+” and “-“ buttons to zoom in and out. By zooming out very far, the user will be able to see how the RegionManager only repaints smaller sub-regions of the buffer layer. NOTE: Since this example uses a repeating pattern, it may not look like previously visited areas are being repainted when visited again. However, they are being repainted.
The code for this example can be found here.
A video of the results can be found here.