wattageTileEngineDocs

The physics engine in Corona SDK supports three types of physics bodies:
Static, Dynamic, and Kinematic. This example will show how to integrate
static and dynamic types. It will not cover integrating kinematic types,
however, they would be handled in a way similar to how dynamic types
are handled.

Typically, using Corona SDK, one simply turns DisplayObjects into
physical entities directly. So, one may expect to do the same within
the Wattage Tile Engine. While this approach will work for DisplayObjects
in EntityLayers, it will not work for DisplayObjects in TileLayers.
This is because DisplayObjects are created and destroyed dynamically
by the tile engine in order to efficiently paint the tile layers. So
the physical model associated with the TileLayers in your application
will need to be created as their own model. This is not as cumbersome
as it may sound at first, as this example will illustrate.

On a side note, this may lead one to ask why the tile engine doesn’t
manage static physical objects in a similar way that it manages the
DisplayObjects for tiles. In other words, why doesn’t it dynamically
create and destroy them on demand depending on what is visible? There
are a number of reasons for this:

There has not yet been a need for us to do this. While managing
tile DisplayObjects this way has resulted in dramatic performance
improvements, it has been our experience that the same cannot be said
about static physical objects.

This would prevent physics simulation from taking place outside
of the viewable area, which is likely not desirable in your application.

The physics model needed for the application may not directly match
the layout of the tiles.

Setup

This example builds on the Wattage Tile Engine Template which
can be found here.
The completed example can be found
here.

Overview

This example will start with the plus-sign-shaped room provided by the
template, then make the following changes:

A physics model will be created to turn the walls into static physics
objects.

An EntityLayer will be added and a player token will be added to this
layer.

The sprite for the player token will be retrieved from the engine and
made into a physics object.

An analog control stick will be added and its input will be used to
apply force to the player token in the direction specified by the
user’s touch. The force will be adjusted according to how far away from
the center of the control the touch falls. This is meant to simulate an
analog control stick.

The camera will be configured to follow the player token.

Walkthrough

Starting with the template, the following changes need to be made.

The first change is to define the analog control stick. A new file
was introduced called analogControlStick.lua which contains the new
code. The code is copied below. It is heavily commented, so further
explanation will not be given here.

localTileEngine=require"plugin.wattageTileEngine"localUtils=TileEngine.Utils-- Localize external function calls.localsqrt=math.sqrtlocalAnalogControlStick={}AnalogControlStick.new=function(params)Utils.requireParams({"parentGroup","centerX","centerY","centerDotRadius","outerCircleRadius"},params)localself={}localtouchId-- Used to set touch focus on the control sticklocalcenterDot-- The center of the control sticklocalouterCircle-- The outer circle of the control sticklocalouterCircleRadius=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.localcurrentRawDirectionVectorXlocalcurrentRawDirectionVectorY-- Same as currentRawDirectionVector except it is capped at 1 (or 100%).localcurrentDirectionVectorXlocalcurrentDirectionVectorYlocalfunctioncalculateDirectionVectors(x,y)-- Create vector for the control center pointlocalcenterPointVectorX=centerDot.xlocalcenterPointVectorY=centerDot.y-- Create vector for the touch pointlocaltouchPointVectorX=xlocaltouchPointVectorY=y-- Subtract the center vector from the touch point vector to get the control direction vector.localvectorToTouchPointX=touchPointVectorX-centerPointVectorXlocalvectorToTouchPointY=touchPointVectorY-centerPointVectorY-- Determine the magnitude of the vectorlocalmagnitude=math.sqrt(vectorToTouchPointX*vectorToTouchPointX+vectorToTouchPointY*vectorToTouchPointY)-- Determine the magnitude's percent of the outer circle radiuslocalpercent=magnitude/outerCircleRadius-- Store the capped percent. This will result in any value greater than 1 being set to 1 instead.localcappedPercent=math.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*percentcurrentRawDirectionVectorY=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*cappedPercentcurrentDirectionVectorY=vectorToTouchPointY/magnitude*cappedPercentend-- Handle for touch eventslocalfunctiontouchHandler(event)-- If the control is already focused on a touch and this touch-- is not the current touch, exit early.iftouchId~=nilandtouchId~=event.idthenreturnfalseendifevent.phase=="began"then-- Touch has began-- Store the ID of the touchtouchId=event.id-- Set the focus of the current touch to this control exclusively.display.getCurrentStage():setFocus(outerCircle,touchId)-- calculate the direction vectorscalculateDirectionVectors(event.x,event.y)elseifevent.phase=="moved"then-- Touch has moved-- calculate the direction vectorscalculateDirectionVectors(event.x,event.y)elseifevent.phase=="ended"orevent.phase=="cancelled"then-- Touch has ended or was cancelled-- Remove the focusdisplay.getCurrentStage():setFocus(outerCircle,nil)-- Clear the touchIDtouchId=nil-- Set direction vectors to nilcurrentRawDirectionVectorX=nilcurrentRawDirectionVectorY=nilcurrentDirectionVectorX=nilcurrentDirectionVectorY=nilend-- Indicate that the touch was handled by returning truereturntrueend-- Return both the raw and capped vector valuesfunctionself.getCurrentValues()return{cappedDirectionVector={x=currentDirectionVectorX,y=currentDirectionVectorY},rawDirectionVector={x=currentRawDirectionVectorX,y=currentRawDirectionVectorY}}end-- Perform cleanup of resources allocated by this classfunctionself.destroy()outerCircle:removeEventListener("touch",touchHandler)display.getCurrentStage():setFocus(outerCircle,nil)outerCircle:removeSelf()outerCircle=nilcenterDot:removeSelf()centerDot=nilend-- Initiallizes the managed resourceslocalfunctioninitialize()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=1centerDot.alpha=0.25outerCircle=display.newCircle(params.parentGroup,params.centerX,params.centerY,params.outerCircleRadius)outerCircle:setFillColor(1,1,1,1)outerCircle.alpha=0.25outerCircle:addEventListener("touch",touchHandler)endinitialize()returnselfendreturnAnalogControlStick

In this example, the player token will have a maximum possible force to
apply when the analog control stick is at full throttle. It will also
make use of linear damping to prevent the feeling of moving on ice.
These values are stored in the following constants.

localTILE_SIZE=32-- Constant for the tile sizelocalMAX_FORCE=10-- The maximum force that will be applied to the player entitylocalLINEAR_DAMPING=1-- Provides a little resistance to linear motion.

When the physics objects are added, a filter needs to be specified to
indicate what objects can collide with others. These categories are
defined below.

localplayerCategory={categoryBits=1,maskBits=2}-- Category for the player physics object. Will collide with walls.localwallCategory={categoryBits=2,maskBits=1}-- Category for the wall physics objects. Will collide with players.

Add variables to store references to the control stick and to the player
sprite.

localcontrolStick-- Reference to the control sticklocalplayerSprite-- Reference to the player sprite

Create a helper function to create the physics model for the static
walls.

-- ------------------------------------------------------------------------------------- A helper function to set up the physical environment. This will add a static-- box physics object for each wall tile.-- -----------------------------------------------------------------------------------localfunctionaddPhysicsObjectsForWalls(displayGroup,module)forrow=1,ROW_COUNTdoforcol=1,COLUMN_COUNTdolocalvalue=ENVIRONMENT[row][col]ifvalue==1thenlocaldisplayObject=display.newRect(displayGroup,(col-1)*TILE_SIZE+TILE_SIZE/2,(row-1)*TILE_SIZE+TILE_SIZE/2,TILE_SIZE,TILE_SIZE)displayObject.isVisible=falsePhysics.addBody(displayObject,"static",{density=0.1,friction=0.1,filter=wallCategory})module.addPhysicsBody(displayObject)endendendend

In the section of onFrame() called after the first frame, gather the
input from the control stick and apply the appropriate force to the
player token.

-- Get the direction vectors from the control sticklocalcappedPercentVector=controlStick.getCurrentValues().cappedDirectionVector-- If the control stick is currently being pressed, then apply the appropriate forceifcappedPercentVector.x~=nilandcappedPercentVector.y~=nilthen-- 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.localforceVectorX=cappedPercentVector.x*MAX_FORCElocalforceVectorY=cappedPercentVector.y*MAX_FORCE-- Apply the force to the center of the player entity.playerSprite:applyForce(forceVectorX,forceVectorY,playerSprite.x,playerSprite.y)end

In the same section of onFrame() update the camera to point at the player
token.

-- Have the camera follow the playerlocaltileXCoord=playerSprite.x/TILE_SIZElocaltileYCoord=playerSprite.y/TILE_SIZEcamera.setLocation(tileXCoord,tileYCoord)

In the scene:create() function, start up the physics engine.

-- Start physicsPhysics.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)

After setting up the floor TileLayer, create the physical model for the
walls by calling the helper function defined earlier.

Create the entity layer and add the player token to it. Retrieve the
DisplayObject for the sprite (playerSprite) from the engine and make
it into a physics object. Finally, insert the layer into the module
at index 2 above the floor layer.

-- Add an entity layer for the playerlocalentityLayer=TileEngine.EntityLayer.new({tileSize=TILE_SIZE,spriteResolver=spriteResolver})-- Add the player entity to the entity layerlocalentityId,spriteInfo=entityLayer.addEntity("tiles_2")-- Move the player entity to the center of the environment.entityLayer.centerEntityOnTile(entityId,8,8)-- Store a reference to the player entity sprite. It will be-- used to apply forces to and to align the camera with.playerSprite=spriteInfo.imageRect-- Make the player sprite a physical entityPhysics.addBody(playerSprite,"dynamic",{density=1,friction=0.5,bounce=0.2,radius=12,filter=playerCategory})-- 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-- 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,2,0)