Tutorial: Reading input sent from the smartphone to your application

Note

This tutorial is for Unity, but Muvslide works in any software that can include a DLL library.

Objective

Follow this tutorial to learn how to read the different types of input sent from the smartphone. This includes orientation, touch gestures, and acceleration. This tutorial uses the Observer design pattern however there are other approaches. For example the Basic Connection tutorial has a basic example of reading orientation without using that design pattern.

Setup your environment

Go to the downloads page. Intall Muvslide App in your Android and download the API to your computer. The API is a .DLL library you need to include in your project.

Create a new Unity project

This tutorial starts from an empty Unity project.

  1. Create a Plugins folder and pull the Muvslide API files.

  2. Add a cube. We will control this cube based on the smartphone input.

  3. Save the scene in a new Scenes folder and create an empty Scripts folder that we will use later.

Screenshot

Setup the connection

Let's create some of the basic code we will use to handle the connection between your application and your smartphone. If you want to learn more about how to setup the connection, check the Basic Connection tutorial. Note that there are slight differences to the code produced in that tutorial because here we are using the Observer design pattern approach:

1) Create a new script called AutoConnection in your Scripts folder, and then add it to a new empty object in your scene. I called the object ConnectionManager.

Screenshot

2) Put the following code in the AutoConnection class. This will make your game connect automatically to your phone if you have the Muvslide App open. The highlighted lines are the ones related to the Observer design pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using UnityEngine;
using ForestIndieGames.Muvslide;

public class AutoConnection : MonoBehaviour, IMuvslideObserver {

    private static bool created = false;

    private MuvslideConnection muvslideConn;
    private float lastMessageTime;
    private float waitTimeToReconnect = 2; //seconds
    private bool messageReceived;

    public void MuvslideNotify(string message)
    {   
        messageReceived = true;
    }

    private void Awake()
    {
        if (!created)
        {
            DontDestroyOnLoad(this.gameObject);
            created = true;
            muvslideConn = new MuvslideConnection(this);
        }
    }

    private void OnApplicationQuit()
    {
        if (muvslideConn != null)
            muvslideConn.Close();
    }

    private void Update()
    {
        if (messageReceived)
        {
            lastMessageTime = Time.time;
            messageReceived = false;
        }

        /*If no messages received in the specified time,
          clean the connection and try to start a new one*/
        bool timeoutWaitingMessages = Time.time - lastMessageTime > waitTimeToReconnect;
        if (timeoutWaitingMessages)
        {
            muvslideConn.StartConnection();
            lastMessageTime = Time.time; //To avoid the program to retry during the connection process
        }
    }

}

Tip

Did you notice that we are not reading the last message's time in the MuvslideNotify method, but in the Update method? This is because the Time class is Unity specific, and the MuvslideNotify method runs in a thread different to the main one. By the time this tutorial was created, Unity does not allow to read the time in any thread different to the main one.

Base code

Let's create some code that we will use for all the types of input we are going to read.

1) In the AutoConnection class, create a read only property to get the MuvslideConnection object. We will need other objects to observe the MuvslideConnection object for new messages and use the input received.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using UnityEngine;
using ForestIndieGames.Muvslide;

public class AutoConnection : MonoBehaviour, IMuvslideObserver {

    private static bool created = false;

    private MuvslideConnection muvslideConn;
    private float lastMessageTime;
    private float waitTimeToReconnect = 2; //seconds
    private bool messageReceived;

    public void MuvslideNotify(string message)
    {   
        messageReceived = true;
    }

    private void Awake()
    {
        if (!created)
        {
            DontDestroyOnLoad(this.gameObject);
            created = true;
            muvslideConn = new MuvslideConnection(this);
        }
    }

    private void OnApplicationQuit()
    {
        if (muvslideConn != null)
            muvslideConn.Close();
    }

    private void Update()
    {
        if (messageReceived)
        {
            lastMessageTime = Time.time;
            messageReceived = false;
        }

        /*If no messages received in the specified time,
          clean the connection and try to start a new one*/
        bool timeoutWaitingMessages = Time.time - lastMessageTime > waitTimeToReconnect;
        if (timeoutWaitingMessages)
        {
            muvslideConn.StartConnection();
            lastMessageTime = Time.time; //To avoid the program to retry during the connection process
        }
    }

    public MuvslideConnection Connection
    {
        get { return muvslideConn; }
    }

}

Tip

You could decide to make the AutoConnection class the only object that knows the connection and then centralize any interaction with the MuvslideAPI there. In that way you could read the messages from the API only once, or read the properties directly as in the Basic Connection tutorial. In this case, I consider the simplicity of using and maintain the Observer pattern is greater than the performance gain I could have from doing it in the other way.

2) Create a new script called PhoneInputHandler and add it to your cube.

Screenshot

3) Add the code to find the ConnectionManager object on Start:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour
{
    private AutoConnection conn;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
    }

    // Update is called once per frame
    void Update()
    {
    }
}

3) In the PhoneInputHandler class include the ForestIndieGames.Muvslide using. Then make the class to implement the IMuvslideObserver interface, which includes adding the method MuvslideNotify. The Muvslide API will call this method every time it receives input from your phone. Then you can decide what to do about it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    private AutoConnection conn;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
    }
}

4) Add the code to subscribe your object for the MuvslideConnection notifications and unsubscribe it on destroy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    private AutoConnection conn;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
    }

    private void OnDestroy()
    {
        if (conn.Connection.ContainsObserver(this)) //checking just in case...
            conn.Connection.RemoveObserver(this);
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
    }
}

Getting the smartphone orientation

The MuvslideNotify method can receive multiple strings, including debugging messages. Those messages are documented here. There are 5 messages we care about in this case, those are TapDown, TapUp, Scroll, Fling, and Motion.

1) In the PhoneInputHandler class, in the MuvslideNotify method, check if the String received is "Motion". If it is, mark a Boolean variable and then check it in the Update method to know if you need to obtain the angles from the phone and transmit them to the current object's transform:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    private AutoConnection conn;
    private bool isMotionAvailable;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
    }

    private void OnDestroy()
    {
        if (conn.Connection.ContainsObserver(this)) //checking just in case...
            conn.Connection.RemoveObserver(this);
    }

    // Update is called once per frame
    void Update()
    {
        if (isMotionAvailable)
        {
            //Rotate the object here!
            isMotionAvailable = false;
        }
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
        }
    }
}

Tip

Note that we don't transform the object directly in the MuvslideNotify method. This is because MuvslideNotify runs in a separate thread, Unity won't allow you to modify the object's transform from a different thread. Furthermore, why would you do that? The MuvslideNotify method could be called multiple times per frame, and you only want to modify the object's transform before it is rendered.

2) In the Update method, replace the comment previously set with a call to a new method called Rotate. In the new method, read the phone's orientation using GetOrientationDegrees from the MuvslideInput object in the connection. Note that the angles are received as an array of float, where X is in the position 0, Y in the position 1, and Z in the position 2.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            isMotionAvailable = false;
        }
    }

    private void Rotate()
    {
        float[] angles = conn.Connection.GetInputManager().GetOrientationDegrees();
        Vector3 newOrientation = new Vector3(angles[0], angles[1], angles[2]);
        transform.localEulerAngles = newOrientation;
    } 

3) Run the project. Open the Muvslide App in your phone and wait for it to connect. It should happen almost immediately. The cube should rotate when you rotate your phone.

Cube moving

Tip

If the cube is not rotating, make sure your firewall is not blocking Unity and that your phone and your computer are in the same network. If you are in a public or corporate network, there are chances that the broadcast messages are blocked. In that case, you need to create a separate local network between your phone and your computer.

Getting the smartphone acceleration

We already know when the API sends a "Motion" message to our object. The same message means that your phone also sent acceleration input. In this example, we will use the acceleration input to move the cube's position.

1) Create two new private variables:

  • float accelerationSpeed. Used to control how much the object moves based on the acceleration input.

  • Vector3 originalPosition. Assigned on start. Used to return our object to its original position.

2) Create a new method called Accelerate. Read the acceleration data using GetAcceleration(), multiply it by the acceleration speed variable, and subtract it from the original position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    private AutoConnection conn;
    private bool isMotionAvailable;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
        originalPosition = transform.position;
    }

    private void OnDestroy()
    {
        if (conn.Connection.ContainsObserver(this)) //checking just in case...
            conn.Connection.RemoveObserver(this);
    }

    // Update is called once per frame
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
    }

    private void Rotate()
    {
        float[] angles = conn.Connection.GetInputManager().GetOrientationDegrees();
        Vector3 newOrientation = new Vector3(angles[0], angles[1], angles[2]);
        transform.localEulerAngles = newOrientation;
    }

    private void Accelerate()
    {
        float[] accel = conn.Connection.GetInputManager().GetAcceleration();
        transform.localPosition = new Vector3(
            originalPosition.x - accel[0] * accelerationSpeed,
            originalPosition.y - accel[1] * accelerationSpeed,
            originalPosition.z - accel[2] * accelerationSpeed);
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
        }
    }
}

Tip

The accelerometer data is more complicated than what you may expect. Because of how accelerometers work, the motion you get is not exactly mimicking you phone motion, but in this case, it provides a good-enough approximation. This is also discussed in the VR Controller tutorial. Note that Muvslide API is sending the Android's linear acceleration and applying some noise filtering on top of it. You could search other approaches about how to use accelerometer data to mimic movement and apply that instead.

Using TapUp and TapDown

Let's change our cube's material when the user clicks in the Muvslide App screen.

1) Create a new material. In this case, I created a yellow material called HighlightedCube.

Screenshot

2) In the PhoneInputHandler class, create a public Material variable to store the highlighted material (you can also use a private serializable variable as per Object Oriented design best practices). Create also a private Material variable to store the original cube's material and assign it on start.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    public Material highlightMaterial;
    private Material originalMaterial;

    private AutoConnection conn;
    private bool isMotionAvailable;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
        originalPosition = transform.position;
        originalMaterial = GetComponent<Material>();
    }
...

3) Add the new material to the PhoneInputHandler script in the cube:

Screenshot

4) Add two new Boolean variables to mark when the phone sent tap down and tap up gestures. Add the corresponding conditions in the MuvslideNotify method. Note that we also check for the Fling gesture because we also want to consider a Fling gesture as a tap up.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    public Material highlightMaterial;
    private Material originalMaterial;

    private AutoConnection conn;
    private bool isMotionAvailable;
    private bool isTapDownAvailable;
    private bool isTapUpAvailable;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
        originalPosition = transform.position;
        originalMaterial = GetComponent<Material>();
    }

    private void OnDestroy()
    {
        if (conn.Connection.ContainsObserver(this)) //checking just in case...
            conn.Connection.RemoveObserver(this);
    }

    // Update is called once per frame
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
    }

    private void Rotate()
    {
        float[] angles = conn.Connection.GetInputManager().GetOrientationDegrees();
        Vector3 newOrientation = new Vector3(angles[0], angles[1], angles[2]);
        transform.localEulerAngles = newOrientation;
    }

    private void Accelerate()
    {
        float[] accel = conn.Connection.GetInputManager().GetAcceleration();
        transform.localPosition = new Vector3(
            originalPosition.x - accel[0] * accelerationSpeed,
            originalPosition.y - accel[1] * accelerationSpeed,
            originalPosition.z - accel[2] * accelerationSpeed);
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
            case "TapDown":
                isTapDownAvailable = true;
                break;
            case "TapUp":
                isTapUpAvailable = true;
                break;
            case "Fling":
                isTapUpAvailable = true; //if the TapUp gesture happens when moving the finger, it could produce a Fling signal instead
                break;
        }
    }
}

5) In the Update method, change the material based on the new flags.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
        if (isTapDownAvailable)
        {
            GetComponent<Renderer>().material = highlightMaterial;
            isTapDownAvailable = false;
        }
        if (isTapUpAvailable)
        {
            GetComponent<Renderer>().material = originalMaterial;
            isTapUpAvailable = false;
        }
    }

6) Test. At this point your cube should move and react to your taps:

Cube moving and changing color

Using Scroll

Let's move our cube in XZ when the user uses the scroll gesture, i.e. holding down the finger while moving through the screen.

1) In the class variables section, add a new Boolean for scroll signals, and a speed variable. Since the scroll function returns distance in pixels, we need to reduce the amount multiplying by a very small amount, so 100 pixels become one Unity meter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    public Material highlightMaterial;
    private Material originalMaterial;

    private AutoConnection conn;
    private bool isMotionAvailable;
    private bool isTapDownAvailable;
    private bool isTapUpAvailable;
    private bool isScrollAvailable;
    private float scrollSpeed = 0.01f;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;

2) In the MuvslideNotify method, include the case for detecting scroll gestures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
            case "TapDown":
                isTapDownAvailable = true;
                break;
            case "TapUp":
                isTapUpAvailable = true;
                break;
            case "Fling":
                isTapUpAvailable = true; //if the TapUp gesture happens when moving the finger, it could produce a Fling signal instead
                break;
            case "Scroll":
                isScrollAvailable = true;
                break;
        }
    }

3) Modify the Update method to use the new Boolean. Then use methods GetLastScrollDistanceX and GetLastScrollDistanceY from the MuvslideInput object to obtain the distance in pixels the user moved the finger. Using this data, we modify originalPosition variable that we are using already in the Accelerate method in order to relocate the cube.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
        if (isTapDownAvailable)
        {
            GetComponent<Renderer>().material = highlightMaterial;
            isTapDownAvailable = false;
        }
        if (isTapUpAvailable)
        {
            GetComponent<Renderer>().material = originalMaterial;
            isTapUpAvailable = false;
        }
        if (isScrollAvailable)
        {
            float changeX = - conn.Connection.GetInputManager().GetLastScrollDistanceX() * scrollSpeed;
            float changeY = conn.Connection.GetInputManager().GetLastScrollDistanceY() * scrollSpeed;
            originalPosition = new Vector3(originalPosition.x + changeX, originalPosition.y, originalPosition.z + changeY);
            isScrollAvailable = false;
        }
    }

Using Fling

Finally, let's do something interesting. Every time the user flings, we are going to clone the cube and push the clone. Every clone will remain connected to the smartphone, so you will control multiple cubes simultaneously.

1) Start adding the new required variables.

  • bool isFlingAvailable as in the other cases, is the variable used to know when the smartphone sent a fling gesture.
  • Vector2 flingInput will store the speed in X and Y received from the fling gesture and use them to accelerate the clone in that direction. We will reduce these values gradually to produce a deceleration effect.
  • float flingSpeedLowLimit this limit is the minimum distance we still want to consider for moving the clones. Once the flingInput values go below this, we will not process that input anymore.
  • float flingDeceleration this should be a value between 0 and 1. We will multiply flingInput by it in order to reduce its values.
  • static int clonesCount will count the amount of clones, this is only for us to show the amount of clones. Also because cloning cubes feels so good that you may end up cloning too many and causing Unity to really slow down.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    public Material highlightMaterial;
    private Material originalMaterial;

    private AutoConnection conn;
    private bool isMotionAvailable;
    private bool isTapDownAvailable;
    private bool isTapUpAvailable;
    private bool isScrollAvailable;
    private bool isFlingAvailable;
    private Vector2 flingInput;
    private float flingSpeedLowLimit = 0.001f;
    private float flingDeceleration = 0.9f; //reduce 10% every fixed update
    private static int clonesCount = 1;
    private float scrollSpeed = 0.01f;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;
...

2) In the MuvslideNotify method, update the case for detecting fling gestures adding the new Boolean variable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
            case "TapDown":
                isTapDownAvailable = true;
                break;
            case "TapUp":
                isTapUpAvailable = true;
                break;
            case "Fling":
                isFlingAvailable = true;
                isTapUpAvailable = true; //if the TapUp gesture happens when moving the finger, it could produce a Fling signal instead
                break;
            case "Scroll":
                isScrollAvailable = true;
                break;
        }
    }

3) Create a method to receive the fling information. We need this because we are applying the acceleration to a cloned object, so in this case the original object will receive the data and then pass it to the cloned object. I hope you don't mind this example is a little more complicated, it's just to keep things interesting ;).

1
2
3
4
    public void SetAdditionalAcceleration(float forceX, float forceY)
    {
        flingInput = new Vector2(forceX, forceY);
    }

4) Add the new section for handling fling gestures in the Update method. Note that we first clone the cube, and then read the fling data from the MuvslideInput object using GetFlingSpeedX and GetFlingSpeedY. Finally we send the obtained data to the cloned object using our new method SetAdditionalAcceleration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
        if (isTapDownAvailable)
        {
            GetComponent<Renderer>().material = highlightMaterial;
            isTapDownAvailable = false;
        }
        if (isTapUpAvailable)
        {
            GetComponent<Renderer>().material = originalMaterial;
            isTapUpAvailable = false;
        }
        if (isScrollAvailable)
        {
            float changeX = - conn.Connection.GetInputManager().GetLastScrollDistanceX() * scrollSpeed;
            float changeY = conn.Connection.GetInputManager().GetLastScrollDistanceY() * scrollSpeed;
            originalPosition = new Vector3(originalPosition.x + changeX, originalPosition.y, originalPosition.z + changeY);
            isScrollAvailable = false;
        }
        if (isFlingAvailable)
        {
            GameObject clone = Instantiate(gameObject);
            clonesCount++;
            Debug.Log(clonesCount + " dancing cubes!");
            float forceX = conn.Connection.GetInputManager().GetFlingSpeedX();
            float forceY = -conn.Connection.GetInputManager().GetFlingSpeedY();
            clone.GetComponent<PhoneInputHandler>().SetAdditionalAcceleration(forceX, forceY);
            isFlingAvailable = false;
        }
    }

5) Finally, we need to use the received fling data. Since we are going to deaccelerate the object every time the method is executed, instead of using the Update method, we will use FixedUpdate that is independent from the frame rate.

1
2
3
4
5
6
7
8
    private void FixedUpdate()
    {
        if (Mathf.Abs(flingInput.x) > flingSpeedLowLimit || Mathf.Abs(flingInput.y) > flingSpeedLowLimit)
        {
            originalPosition = new Vector3(originalPosition.x + flingInput.x, originalPosition.y, originalPosition.z + flingInput.y);
            flingInput = flingInput * flingDecceleration;
        }
    }

6) Test it, now you can clone the cubes using a fling gesture. Every cube will keep listening to the MuvslideConnection so all of them keep reacting to the phone, and the cubes get cloned exponentially.

Tip

Make sure to not clone too many or Unity can get really slow! I know it feels great, but please, be strong.

Dancing cubes

For your reference, the final version of the code is:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using ForestIndieGames.Muvslide;
using UnityEngine;

public class PhoneInputHandler : MonoBehaviour, IMuvslideObserver
{
    public Material highlightMaterial;
    private Material originalMaterial;

    private AutoConnection conn;
    private bool isMotionAvailable;
    private bool isTapDownAvailable;
    private bool isTapUpAvailable;
    private bool isScrollAvailable;
    private bool isFlingAvailable;
    private Vector2 flingInput;
    private float flingSpeedLowLimit = 0.001f;
    private float flingDecceleration = 0.9f; //reduce 1% every fixed update
    private static int clonesCount = 1;
    private float scrollSpeed = 0.01f;
    private float accelerationSpeed = 0.5f;
    private Vector3 originalPosition;

    // Use this for initialization
    void Start()
    {
        conn = GameObject.Find("ConnectionManager").GetComponent<AutoConnection>();
        conn.Connection.AddObserver(this);
        originalPosition = transform.position;
        originalMaterial = GetComponent<Renderer>().material;
    }

    private void OnDestroy()
    {
        if (conn.Connection.ContainsObserver(this)) //checking just in case...
            conn.Connection.RemoveObserver(this);
    }

    // Update is called once per frame
    void Update()
    {
        if (isMotionAvailable)
        {
            Rotate();
            Accelerate();
            isMotionAvailable = false;
        }
        if (isTapDownAvailable)
        {
            GetComponent<Renderer>().material = highlightMaterial;
            isTapDownAvailable = false;
        }
        if (isTapUpAvailable)
        {
            GetComponent<Renderer>().material = originalMaterial;
            isTapUpAvailable = false;
        }
        if (isScrollAvailable)
        {
            float changeX = - conn.Connection.GetInputManager().GetLastScrollDistanceX() * scrollSpeed;
            float changeY = conn.Connection.GetInputManager().GetLastScrollDistanceY() * scrollSpeed;
            originalPosition = new Vector3(originalPosition.x + changeX, originalPosition.y, originalPosition.z + changeY);
            isScrollAvailable = false;
        }
        if (isFlingAvailable)
        {
            GameObject clone = Instantiate(gameObject);
            clonesCount++;
            Debug.Log(clonesCount + " dancing cubes!");
            float forceX = conn.Connection.GetInputManager().GetFlingSpeedX();
            float forceY = -conn.Connection.GetInputManager().GetFlingSpeedY();
            clone.GetComponent<PhoneInputHandler>().SetAdditionalAcceleration(forceX, forceY);
            isFlingAvailable = false;
        }
    }

    private void FixedUpdate()
    {
        if (Mathf.Abs(flingInput.x) > flingSpeedLowLimit || Mathf.Abs(flingInput.y) > flingSpeedLowLimit)
        {
            originalPosition = new Vector3(originalPosition.x + flingInput.x, originalPosition.y, originalPosition.z + flingInput.y);
            flingInput = flingInput * flingDecceleration;
        }
    }

    public void SetAdditionalAcceleration(float forceX, float forceY)
    {
        flingInput = new Vector2(forceX, forceY);
    }

    private void Rotate()
    {
        float[] angles = conn.Connection.GetInputManager().GetOrientationDegrees();
        Vector3 newOrientation = new Vector3(angles[0], angles[1], angles[2]);
        transform.localEulerAngles = newOrientation;
    }

    private void Accelerate()
    {
        float[] accel = conn.Connection.GetInputManager().GetAcceleration();
        transform.localPosition = new Vector3(
            originalPosition.x - accel[0] * accelerationSpeed,
            originalPosition.y - accel[1] * accelerationSpeed,
            originalPosition.z - accel[2] * accelerationSpeed);
    }

    public void MuvslideNotify(string message)
    {
        /* Here we can receive different strings, we care about:
         * TapDown, TapUp, Scroll, Fling, Motion
         * This is documented in http://muvslide.com/ForestIndieGames.Muvslide.IMuvslideObserver/ */
        switch (message)
        {
            case "Motion":
                isMotionAvailable = true;
                break;
            case "TapDown":
                isTapDownAvailable = true;
                break;
            case "TapUp":
                isTapUpAvailable = true;
                break;
            case "Fling":
                isFlingAvailable = true;
                isTapUpAvailable = true; //if the TapUp gesture happens when moving the finger, it could produce a Fling signal instead
                break;
            case "Scroll":
                isScrollAvailable = true;
                break;
        }
    }
}