Is abstraction "good practice" in Lua?

Ed you nailed it.  

I created several Runtime events in my “player” module.

I passed the event.target (“player” module), and event.names to my “ui” module.

Anywhere there was a table property for “M.isMovingLeft = true” or “M.isJumping = true” has been replaced by a dispatchEvent and adding a unamed property to the event table.

It looks something like this in the UI module:

local function onButtonTap( event ) local taps = event.numTaps -- eventual use for double jump or some nifty mechanic local targetID = event.target.id if "jump" == targetID then Runtime:dispatchEvent({name = M.jumpEvent, target=uiTarget, true}) debugString = "Jump" end end

It looks something like this in the “player” module:

local function onUserInput(event) local name = event.name local val = event[1] if(name == "playerIsJumping") then player.isJumping = val elseif(name == "playerIsMovingRight") then player.isMovingRight = val elseif(name == "playerIsMovingLeft") then player.isMovingLeft = val elseif(name == "playerIsMovingUp") then player.isMovingUp = val elseif(name == "playerIsMovingDown") then player.isMovingDown = val end end

and the “GamePlayScene” moldule, where the onEnterFrameEvent is stored.  Which is nice because the dispatched events in the UI module effect the player module.  No middle-man.  The onEnterFrameEvent just gets to call appropriate methods without a sea of parameters as they are internal to the “player” module now instead of the “ui” module.

 player:setDeltaTime(dtk:getDeltaTime()) player:move() player:jump() map.updateView() -- Updates a map's camera system and culling. Should be called each frame and directly after you manually move the map.

Thumbs up or down Ed?

I’m going to say, thumb in the middle.

I give it a thumbs up because I do use a similar technique in my games (SSK2 has a whole set of modules [‘easy inputs’] that create inputs objects to throw standard events that the player, etc can listen for).

However, I’m going to give it a thumbs down because I see some places where it could be made a little cleaner.

a. Tap versus Touch - I’m a huge fan of the touch event and not a fan of tap events.

b. Shared listener - I can see your tap listener is not shared.  Sharing it would make it easier to maintain. (See below for example)

c. Listener is Runtime - I try to make all of my (touch, collision, etc) listeners ‘table listeners’.  (See example below).

d. String comparisons - In my event system I tend to make my custom listeners for one specific event with modifying a parameters. 

EX:  ‘onJoystick’ - Has these and other parameters, vx, vy, state, time, percent, …

Your code looks like it is checking for a number of different events in one listener: ‘playerIsJumping’, ‘playerIsMovingRight’, … 

I’m not saying this is necessarily wrong, but I don’t think I’d do it quite this way.

Shared Listener + Table Listener Example

This example module will create a basic button where each button throws a custom event when tapped (yes, using tap listener since you are):

-- May contain typos local sample = {} local function onTap( self, event ) local custom = {} custom.name = self.myEvent custom.taps = event.numTaps custom.time = event.time Runtime:dispatchEvent( custom ) end sample.create( group, x, y, w, y, text, eventName ) group = group or display.currentStage local button = display.newRect( group, x, y, w, h ) button:setFillColor( 0.25, 0.25, 0.25 ) button.label = display.newText( group, text, x, y ) button.myEvent = eventName button.tap = onTap button:addEventListener( "tap" ) return button end return sample

Now you can make buttons like this, each with their own custom named events, but all using the same listener event:

local sample = require "sample" -- Assumes I saved the module in sample.lua sample.create( nil, display.contentCenterX, display.contentCenterY - 200, 200, 30, "Call Bob", "onBob" ) sample.create( nil, display.contentCenterX, display.contentCenterY, 200, 30, "Call Bill", "onBill" ) sample.create( nil, display.contentCenterX, display.contentCenterY + 200, 200, 30, "Call Sue", "onSue" ) -- Now, you can create objects that listen for one or more of these events.

One more post coming…

I also wanted to post something about complex player control, but not in the last post or it would be too long.

I see you’re trying to put together a player control system.  Now, normally, when my player uses physics and has event a moderately complex movement style, I use this practice:

  1. Set fields/properties on the player to default values when I create it.  These properties are used by subsequent logic to decide how the player is moving.

  2. Add listener(s) to the player that will modify one or more of these fields as the result of an touch, swipe, button tap, etc.

  3. Add an enterFrame listener to the player that adjusts show the player is moving from frame to frame based on changes to the aforementioned fields.

-Ed

One more note.  Why Table listeners versus Functionlisteners?

Here is one of many reasons.  Table listeners are self cleaning.

You have to remove this on your own later:

local mRand = math.random local obj = display.newCircle( 10, 10, 10 ) local function onTap( event ) obj:setFillColor( mRand(),mRand(),mRand() ) end Runtime:addEventListener( "tap", onTap ) ... later display.remove( obj ) Runtime:removeEventListener( "tap", onTap )

This is automatically removed.

local mRand = math.random local obj = display.newCircle( 10, 10, 10 ) local function onTap( self, event ) self:setFillColor( mRand(),mRand(),mRand() ) end obj.tap = onTap obj:addEventListener( "tap" ) ... later display.remove( obj )

Note: Custom event listeners, as we’ve been setting them up above are only Runtime listeners. So you still have to clean them up.  

I wanted to quote several messages.  

  1.  I started with tap because it had a “numTaps” property I think.  I thought that might be fun to play with for double jumps or maybe some twitchy mechanic for dodging bullets.  it’s doesn’t have the other phases a touch event does.  So I’ll concede on your touch > tap.

  2.  I’m still learning “scope” in Lua.  So I’m still treating things in a C-ish Fashion.  Each “event” has its own listener.  I’ll show you my methodology.  I didn’t know if it was a “good idea”, but it did work.

The “player” module has these eventListerners:

Runtime:addEventListener("playerIsJumping", onUserInput) Runtime:addEventListener("playerIsMovingRight", onUserInput) Runtime:addEventListener("playerIsMovingLeft", onUserInput) Runtime:addEventListener("playerIsMovingUp", onUserInput) Runtime:addEventListener("playerIsMovingDown", onUserInput)

In the “game play scene” module I pass them to the “ui” module:

ui:setTargetForUI(player, "playerIsJumping", "playerIsMovingRight", "playerIsMovingLeft", "playerIsMovingUp", "playerIsMovingDown")

in the “ui” module that function looks like"

function M:setTargetForUI(t, jump, right, left, up, down) uiTarget = t jumpEvent = jump rightEvent = right leftEvent = left upEvent = up downEvent = down end

and those locals on the left of = are used in the various dispatchEvent functions:

 if movement\_X ~= 0 then -- If the player is moving along X - determine direction. if movement\_X \> 0 then Runtime:dispatchEvent({name = rightEvent, target=uiTarget, true}) else Runtime:dispatchEvent({name = leftEvent, target=uiTarget, true}) end end if movement\_Y ~= 0 then -- If the player is moving along Y - determine direction. if movement\_Y \> 0 then Runtime:dispatchEvent({name = downEvent, target=uiTarget, true}) else Runtime:dispatchEvent({name = upEvent, target=uiTarget, true}) end end if movement\_X == 0 then -- If the player is not moving along X - reset left and right. Runtime:dispatchEvent({name = leftEvent, target=uiTarget, false}) Runtime:dispatchEvent({name = rightEvent, target=uiTarget, false}) end if movement\_Y == 0 then -- If the player is not moving along Y - reset up and down. Runtime:dispatchEvent({name = upEvent, target=uiTarget, false}) Runtime:dispatchEvent({name = downEvent, target=uiTarget, false}) end

I don’t know if it’s good, but it did get rid of the arguments in the enterFrame event that someone somewhere in another thread I started said it should be cleaner.

I do like your table listeners example.  I’ll adapt my code to suit.  This while process has been extremely iterative, and each iteration is a little bit better than the last.  You have been a huge boon for my understanding of Corona and migrating to Lua.

Side note below!

My youngest daughter has Cerebral Palsy.  She’s only 2.  But somehow, somewhere, someway she’s picked up “I can’t” when she tries to do stuff.  It’s soul crushing for me as a parent because I know she can and I try to encourage her every day, all day.  I want to empower her.

So my motivation is to create a game for special needs kids.  Many may not be able to play “conventional” video games.  

Something that includes a way to add a slider in the settings on how much help the player may need.  

  •  Pausing before critical collisions or events.

  •  Slowing down “time” during battles when “bullets” get within a certain distance threshold

  •  Custom placement of the UI controls

  •  Whatever else I can think of to help.

So I decided that I simply must make a platform game called “Able Allison: ‘Ican’ Champion” or something similar.

Sorry to hear about your youngest, they can be bad enough at 2 at the best of times!  Sounds like great motivation to create a game to me.

Now onto Corona, I’m a great believer in KISS. Adding complexity when a project is small really doesn’t scale - it’s fine for hundreds of lines of code, maybe even thousands but it becomes an absolute nightmare at tens of thousands!  

Here is a variation that is much simpler. I assume you will have touch points on your UI to handle player input so we will point them to a single tap handler.  

local function onTap(event)   --which button was pressed?   if event.target.id == "leftButton" then     player:setDirection("left")   elseif event.target.id == "rightButton" then     player:setDirection("right")  end end

Let’s assume you also have a player class and that has a single public event (to set the desired direction) and an enter frame (to do the movement)

function player:setDirection(direction)   if direction == "left" then     self.moveX = -1   elseif direction == "right" then     self.moveX = 1   end end local function player:onEnterFrame(event)   --now move player   self.x = self.x + self.moveX end 

This assumes the player will continually move in a single direction based on the UI object tapped.  Would be simple to turn into touch and when the touch ended call player:setDirection(“none”) to stop movement  You could then extend this by setting a deltaX value and have that decay over time to provide a bit of momentum to make the movement more natural.

Full message queue implementations have their place - massively networked games like CoD for example.  But IMHO they are overkill for most Corona games and you can end up getting bogged down in the details and never shipping your project.

Adrian, thanks I appreciate it.  She’s a trooper.  She’s verbal and has total cognitive capacity.  Just a little slow in the mobility department.

I think I’m implementing something like you describe except I’m using 5 events to to do it.  The different events all call the same function, and the dispatched events all get called from related events in the UI (The touch events, key events).

So I should whittle those 5 events down to 1.  I think a bit of my “complexity” is to test the elements I’m picking up here and when it works I add more to test if it was a fluke.  

Thanks again Adrian.  I’m modifying code now.