You know what I hate? Boxes made of wood. Luckily, Crate Whacker (free in the Myo Market, and optionally playable with the Oculus Rift) has me covered with unlimited crate smashing fun. Wailing on things with the mace is pretty great, but what really interests me are the grenades. The throw grenade action is based on an interesting zoning concept that I think will be useful in a lot of applications. Join me (@thalmicdev) to discuss it in this week's #MyoCraft:

using UnityEngine;  
using System.Collections;

public class MyoGenadeThrower : MonoBehaviour  
{
    public GameObject myo;
    private ThalmicMyo _myo = null;

    public GameObject jointBehaviour;
    private JointBehaviour _jointBehaviour;

    public GameObject GrenadePrefab = null;

    public int rollAMin;
    public int rollAMax;
    public int rollBMin;
    public int rollBMax;
    public int yawAMin;
    public int yawAMax;
    public int yawBMin;
    public int yawBMax;
    public int pitchAMin;
    public int pitchAMax;
    public int pitchBMin;
    public int pitchBMax;

    private bool _inPosition = false;
    private float _inPositionTime;
    private bool _startedThrow = false;
    private float _startThrowTime;

    public float minThrowStrength;
    public float maxThrowStrength;
    public float baseThrowStrength;
    public float normalThrowTime;
    public float maxThrowTime = 1f;

    void Start()
    {
        _myo = myo.GetComponent<ThalmicMyo>();
        _jointBehaviour = (JointBehaviour)jointBehaviour.GetComponent(typeof(JointBehaviour));
    }

    void Update()
    {
        int armYawAMin = yawAMin;
        int armYawAMax = yawAMax;
        int armYawBMin = yawBMin;
        int armYawBMax = yawBMax;

        // Adjust yaw A and B zones for left arm.
        if (_myo.arm == Thalmic.Myo.Arm.Left) {
            armYawAMin = 360 - yawAMax;
            armYawAMax = 360 - yawAMin;
            armYawBMin = 360 - yawBMax;
            armYawBMax = 360 - yawBMin;
        }

        bool zoneA = inZone(rollAMin, rollAMax, armYawAMin, armYawAMax, pitchAMin, pitchAMax);

        if (!_inPosition && zoneA) {
            _inPosition = true;
            _startedThrow = false;
            _inPositionTime = Time.time;

            Debug.Log(_jointBehaviour.transform.localRotation.eulerAngles);
            Debug.Log("_inPosition=" + _inPosition);
        }

        if (_inPosition && !_startedThrow) {
            _startedThrow = !zoneA;

            if (_startedThrow) {
                Debug.Log(_jointBehaviour.transform.localRotation.eulerAngles);
                Debug.Log("_startedThrow=" + _startedThrow);

                _inPosition = false;
                _startThrowTime = Time.time;
            }
        } else if (_startedThrow && (Time.time - _startThrowTime) > maxThrowTime) {
            // Didn't move to zoneB in time so reset throw state.
            _inPosition = false;
            _startedThrow = false;
            Debug.Log("throw timed out");
        } else if (_startedThrow) {
            bool zoneB = inZone(rollBMin, rollBMax, armYawBMin, armYawBMax, pitchBMin, pitchBMax);

            if (zoneB) {
                Debug.Log(_jointBehaviour.transform.localRotation.eulerAngles);
                Debug.Log("zoneB=" + zoneB);
                _startedThrow = false;

                throwGrenade();
            }
        }
    }

    private bool inZone(int rollAMin, int rollAMax, int yawAMin, int yawAMax, int pitchAMin, int pitchAMax)
    {
        Vector3 rotation = _jointBehaviour.transform.localRotation.eulerAngles;
        float roll = rotation.z;
        float pitch = rotation.x;
        float yaw = rotation.y;

        return (rollAMin <= roll && rollAMax >= roll) &&
            (yawAMin <= yaw && yawAMax >= yaw) &&
            (pitchAMin <= pitch && pitchAMax >= pitch);
    }

    private void throwGrenade()
    {
        GameObject grenade = (GameObject)Object.Instantiate(GrenadePrefab);
        grenade.transform.position = transform.position;
        grenade.transform.rotation = transform.rotation;

        Vector3 direction = transform.root.forward;
        direction.y += 1;

        float timeDiff = Time.time - _startThrowTime;
        float multiplier = normalThrowTime <= 0 ? 1f : (normalThrowTime / timeDiff);
        float throwStrength = baseThrowStrength * multiplier;

        Debug.Log("timeDiff= " + timeDiff + " throwStrength=" + throwStrength);
        throwStrength = Mathf.Clamp(throwStrength, minThrowStrength, maxThrowStrength);

        grenade.GetComponent<Rigidbody>().AddForce(direction * throwStrength, ForceMode.Impulse);
        grenade.GetComponent<Rigidbody>().AddTorque(Random.insideUnitSphere, ForceMode.Impulse);
    }
}

This script is done in C# in Unity. If you weren't aware, the Myo armband comes with a Unity package that you can use to easily build sweet Myo-enabled games. There is an included JointOrientation script that is a good place to start with showing your arm in 3D space, and now you have this MyoGranadeThrower script as a basis for zones and motion gestures.

Basically what's happening here is we have a start zone, defined by a min/max pitch, roll, and yaw, and an ending zone done the same way. The idea is, if your arm starts in Zone A (in this case, on the lower left side of your body, assuming your armband is on the right), and moves into Zone B (the right side of your body) quickly enough, you throw a grenade.

grenade

Determining if you are in a zone or not is pretty easy:

    private bool inZone(int rollAMin, int rollAMax, int yawAMin, int yawAMax, int pitchAMin, int pitchAMax)
    {
        Vector3 rotation = _jointBehaviour.transform.localRotation.eulerAngles;
        float roll = rotation.z;
        float pitch = rotation.x;
        float yaw = rotation.y;

        return (rollAMin <= roll && rollAMax >= roll) &&
            (yawAMin <= yaw && yawAMax >= yaw) &&
            (pitchAMin <= pitch && pitchAMax >= pitch);
    }

We just check if your arm is between the min and max for each axis in the zone.

This results in a pretty natural and fluid way to toss grenades without using any hand poses. If you had more to do in your application or game you could very easily structure things so certain poses did different actions in different zones.

And that's it! Pretty straight forward, but a very powerful concept. If you've got something you want to share on #MyoCraft, let me know on Twitter (@thalmicdev) or at MyoCraft@thalmic.com

Otherwise, see you next week!

Newsletter

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