Note: We discuss a more advanced version of this approach here.

Hey folks! We’re back with this week’s edition of #MyoCraft. If you need a refresher on what we’re doing, check out this post here. Today, we’re featuring our connector for Saint’s Row IV. It demonstrates a very useful technique you will see in a lot of Myo Scripts: Function binding.

Here’s the script:

  


scriptId = 'com.thalmic.saintsrow4'  
    minMyoConnectVersion = '0.7.0'  
    scriptDetailsUrl = 'https://market.myo.com/app/5474d22be4b081c4011c77ba'  
    scriptTitle = 'Saints Row IV Connector'

    description = [[  
    A third-person shooter that consistently chooses fun over physics and laughter over common decency.  
    Play through the intro until you get to the Steelport simulation, then enable cheats and try dem  
    superpowers. Telekin dat car into dem people, or vice versa. And be sure to serve plenty o fireballs  
    to those hungry customers.

    Suggestions/notes:  
    - adjust the mouse sensitivity
    - cheats can be enable from the in-game menu (hit tab) under extras > cheats
    ]]

    link = [[http://store.steampowered.com/app/314580/]]

    controls = [[  
    This script assumes default control mappings.

    Use your arm to aim and make a Fist to start shooting, release it to stop.  
    Use FingersSpread to activate your current super power.

    Holding waveOut allows you to move your arm without moving the mouse,  
    which can become necessary if you arm ends up in an uncomfortable position.  
    Release to resume control of the mouse.

    Standard keyboard controls are used for everything else.  
    - movement (wasd), reload (r), melee (f), etc.
    - Number keys for selecting/changing guns
    - F1,F2,F3,F4 to change your current superpower, or change element of current power
    ]]

    knownIssues = [[  
    - Aiming is sensitive to the Myo armband's position on your arm. To workaround this
      position the armband such that the main module does not tilt when making/releasing poses.
    - OS's mouse is still active while playing, if it clicks the Dock you will jump out of the game.
    ]]

    ----------------------------
    -- Useful for disabling debug output (by commenting the implementation)
    function debug(msg)  
       -- myo.debug(msg)
    end

    ----------------------------
    -- Shooting
    function startShooting()  
        debug("start shooting")
        myo.mouse("left","down")
    end  
    function stopShooting()  
        debug("stop shooting")
        myo.mouse("left","up")
    end

    ----------------------------
    -- Superpowers
    function activateSuperpower()  
        debug("activate superpower")
        myo.mouse("center","click")
    end

    ----------------------------
    -- Mouse control
    function mouseOn()  
        debug("mouse on")
        myo.controlMouse(true)
    end  
    function mouseOff()  
        debug("mouse off")
        myo.controlMouse(false)
    end

    ----------------------------
    -- Map poses to functions
    local BINDINGS = {  
        fist_on          = startShooting,
        fist_off         = stopShooting,

        fingersSpread_on = activateSuperpower,

        waveOut_on       = mouseOff,
        waveOut_off      = mouseOn,
    }

    -- Set how the Myo Armband handles locking
    myo.setLockingPolicy("none")

    function onPoseEdge(pose, edge)  
        fn = BINDINGS[pose .. "_" .. edge]
        if fn then
            fn()
        end
    end

    ----------------------------
    function activeAppName()  
        return "Saints Row IV"
    end

    function onActiveChange(isActive)  
        if isActive then
            myo.centerMousePosition()
            myo.controlMouse(true)
        end
    end

    function onForegroundWindowChange(app, title)  
        return platform == "Windows" and string.match(app, "^SaintsRowIV.exe$")
    end

    function onActiveChange(isActive)  
        -- Start with the mouse in a known position and enabled.
        if isActive then
            myo.centerMousePosition()
            mouseOn()
        end
    end  

There’s a lot of good stuff in there, but the thing I want to talk about today is the onPoseEdge implementation. In other scripts, especially when you’re just starting you may use a series of if statements (one for each pose). That’s OK, but it gets a bit big and messy, especially when you want to make something easy to modify or take pieces from later.

saints-row-cover

Luckily that’s not the only way to do it. The approach we use in the Saint’s Row IV connector relies on the fact that in Lua a function is a first class value. That means you can assign any variable to point to a function, and in fact this:

  


function myCoolFunction()  
        -- Some code
    end

is just some nice syntactic sugar for this:

  


myCoolFunction = function ()  
        -- Some code
    end

This means that you can do things like this:

  


function onPoseEdge(pose, edge)  
        fn = BINDINGS[pose .. "_" .. edge]
        if fn then
            fn()
        end
    end

Where bindings looks like this:

  


local BINDINGS = {  
        fist_on          = startShooting,
        fist_off         = stopShooting,

        fingersSpread_on = activateSuperpower,

        waveOut_on       = mouseOff,
        waveOut_off      = mouseOn,
    }

So, when you start the fist pose, onPoseEdge is called with pose == “fist” and edge == “on”. That means that the first line of onPoseEdge turns into

  


fn = BINDINGS["fist" .. "_" .. "on"]`  

which is, of course:

  


fn = BINDINGS["fist_on"]

which, when you look it up in the BINDINGS table really points to startShooting. So fn() is really calling startShooting(), and from there away you go.

The net effect is that you can have the EXACT same, very tight onPoseEdge implementation for every single one of your scripts, and just replace the definitions with names of the functions of your choosing in the BINDINGS declaration.

You can also use this to swap in different functions on the fly (to have multiple states in your script or to cover multiple similar applications, for example), and it cleanly handles gestures you don’t use.

It’s a neat trick, and you’ll probably see it in a lot of scripts, so it’s good to know how it works. If you have a neat trick of your own, or some other cool example or demo you want to show off in a future #MyoCraft, let me know on Twitter at @thalmicdev, or with an email to MyoCraft@thalmic.com.

Otherwise, see you next Saturday!

Newsletter

Enter your email address and get all latest content delivered to your inbox every now and then.