Skip to main content

Tangible Level Editor


The Tangible Level Editor is a peripheral for a platformer-style game where the configuration of specially-designed blocks in the real world are mapped in the virtual world allowing players to create levels by arranging said blocks. It is a device designed and built by me, Elliot Brodzki and Richard Pianka during our undergraduate studies at Worcester Polytechnic Institute.

Motivation

It was designed to be a tangible substitute to level creation using standard controllers. Typically a virtual world is created by an artist with a keyboard and mouse. Such peripheral devices do not resonate with very young users hence the interface itself is occluding a child's access to the creative potential of building virtual environments. Most children are acquainted with toy blocks at an early age so it makes sense that the interfaces should be design with the familiarity of toy blocks in mind. The Tangible Level Editor is this interface.

Implementation

The Tangible Level Editor is a proof of concept input device that uses an Arduino microcontroller, a resistor network, a multiplexer, and auxiliary devices such as the speaker and joystick.

Top-Level Diagram

The diagram below shows a top-level view of the system. Note that the speaker and joystick, neither of which are shown, are actually connected to the microcontroller.

Module A - Blocks & Workbench

Notice how the blocks and the workbench form a resistor network. See how each column of blocks form a series resistance circuit. All the resistors have the same value and if we provide a source voltage of a known magnitude, we can determine the voltage across the last resistor, the resistor embedded in the workbench, corresponding to the number of blocks in that column by using Ohm's Law. In other words, there is a one-to-one mapping of the number of blocks in a column and the voltage value in the embedded resistor at that column.

Module B - Multiplexor

This means that each column's embedded resistor needs to be constantly measured for its voltage value. Since there are more resistors to measure than there are pins on the microcontroller to measure them, the solution is to multiplex the many resistors with one pin. We used an Analog Devices ADG line multiplexor to multiplex the eight resistors to one Analog-to-Digital pin in the Arduino.

Module C - Microcontroller

The Arduino sends out three bit signals to the multiplexor to tell which eight resistor it needs to connect to in sequential order as you can see from the code snippet below.

 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
using UnityEngine;
using System.Collections;

public class LevelCreationScript: MonoBehaviour {
  private Controllers controllerScript;
  private int[] levelState;
  private bool levelChanged = false;
  public Transform[] platforms;

  void Start() {
    controllerScript = (Controllers) gameObject.GetComponent("Controllers");

    levelState = new int[8];

    for (int i = 0; i < 8; i++)
      levelState[i] = 0;
  }

  void FixedUpdate() {
    for (int i = 0; i < 8; i++) {
      if (controllerScript.pos[i] != levelState[i])
        levelChanged = true;
    }

    if (levelChanged) {
      for (int i = 0; i < 8; i++) {
        if (controllerScript.pos[i] != levelState[i]) {
          GameObject col = GameObject.FindWithTag("col" + i);

          if (col != null) {
            if (controllerScript.pos[i] != 0) {
              GameObject plat = GameObject.Instantiate(platforms[controllerScript.pos[i] - 1].gameObject,
                  col.transform.position, Quaternion.identity) as GameObject;

              plat.transform.parent = col.transform;

              plat.transform.Translate(0, (controllerScript.pos[i] - 1) * 10 + 10, 0);
            } else
              col.transform.DetachChildren();
          }
        }
      }
      levelChanged = false;

      for (int k = 0; k < 8; k++)
        levelState[k] = controllerScript.pos[k];
    }
  }
}

Also, the voltage source of the series circuit in each column will come from the Arduino's 5V power pin.

Module D - Virtual Environment

We used Unity to create the virtual environment. Below shows the script that takes the data  transmitted from the Arduino to the PC and converts it into elevated platforms rendered in the game engine.

 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
using UnityEngine;
using System.Collections;

public class LevelCreationScript: MonoBehaviour {
  private Controllers controllerScript;
  private int[] levelState;
  private bool levelChanged = false;
  public Transform[] platforms;

  void Start() {
    controllerScript = (Controllers) gameObject.GetComponent("Controllers");

    levelState = new int[8];

    for (int i = 0; i < 8; i++)
      levelState[i] = 0;
  }

  void FixedUpdate() {
    for (int i = 0; i < 8; i++) {
      if (controllerScript.pos[i] != levelState[i])
        levelChanged = true;
    }

    if (levelChanged) {
      for (int i = 0; i < 8; i++) {
        if (controllerScript.pos[i] != levelState[i]) {
          GameObject col = GameObject.FindWithTag("col" + i);

          if (col != null) {
            if (controllerScript.pos[i] != 0) {
              GameObject plat = GameObject.Instantiate(platforms[controllerScript.pos[i] - 1].gameObject,
                  col.transform.position, Quaternion.identity) as GameObject;

              plat.transform.parent = col.transform;

              plat.transform.Translate(0, (controllerScript.pos[i] - 1) * 10 + 10, 0);
            } else
              col.transform.DetachChildren();
          }
        }
      }
      levelChanged = false;

      for (int k = 0; k < 8; k++)
        levelState[k] = controllerScript.pos[k];
    }
  }
}

An example of a created level is shown in the screen capture below.


The rendering script is shown below.

 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
using UnityEngine;
using System.Collections;
using System.IO.Ports;

public class WinScript : MonoBehaviour 
{
  public int forceRange = 1;
  public int numObjects = 100;
  public Transform spawn;
  private Controllers controllerScript;
  // Use this for initialization
  void Start () 
  {
    controllerScript = spawn.GetComponent<Controllers>();
  }

  void OnTriggerEnter(Collider other)
  {
    if(other.tag == "Player")
    {
      Vector3 pos = other.transform.position;
      controllerScript.sp.WriteLine("0");
      Destroy(other.gameObject);
      
      for(int i = 0; i < numObjects; i++)
      {
        GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Cube);
        sphere.transform.localScale = new Vector3(5, 5, 5);
        sphere.transform.position = pos;
        sphere.AddComponent("Rigidbody");
        int force1 = Random.Range(-forceRange, forceRange);
        int force2 = Random.Range(-forceRange, forceRange);
        int force3 = Random.Range(-forceRange, forceRange);
        sphere.transform.rigidbody.AddForce(force1, force2, force3);
      }
    }
  }
}

The complete script including those used to animate and control the virtual avatar can be found in the appendix.

Module E - Speaker & Joystick

We wanted to showcase the power of the Arduino as well so we had speakers and a joystick wired to it. After the level has been created, when the virtual avatar reach the other side of the level, a melody gets played through the speaker. The Arduino code snippet below shows how we did this.

 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
// from example code

void playTone(int tone, int duration) 
{
  for (long i = 0; i < duration * 1000L; i += tone * 2) 
  {
    digitalWrite(SPEAKER, HIGH);
    delayMicroseconds(tone);
    digitalWrite(SPEAKER, LOW);
    delayMicroseconds(tone);
  }
}

// from example code

void playNote(char note, int duration) 
{
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) 
  {
    if (names[i] == note) 
    {
      playTone(tones[i], duration);
    }
  }
}

if (Serial.available() > 0) 
{
  byte input = Serial.read();

  // hit a field
  if (input == '0') 
  {
    playNote('g', 30); delay(30);
    playNote('b', 30); delay(30);
    playNote('a', 30); delay(30);
    playNote('a', 30); delay(30);
    playNote('C', 30);
  } 
  else if (input == '1') 
  {
    playNote('c', 50); delay(50);
    playNote('c', 50);
  } 
  else if (input == '2') 
  {
    playNote('b', 30); 
  }
}

The joystick is a simple mechanical stick that adjusts the resistance of its internal potentiometer allowing shifts in the stick to produce different voltage levels that can be read by the Arduino. Below is a photo of the joystick we made and the Arduino code to use it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int val = analogRead(1);

if (val < 350) 
{
  Serial.print('+');
} 
else if (val > 750) 
{
  Serial.print('-');
} 
else 
{
  Serial.print('0'); 
}

Module Amalgamation

Connecting all the modules together produces the completed Tangible Level Editor. Pictures of our finished prototype are shown below.

You can see the complete wiring (minus the speakers and joystick) in the diagram below. Observe how the arrangement of the blocks below correspond to the screen capture of the virtual environment shown earlier.

Video Demonstration

Here we demonstrate the final prototype and provide only the pithy details on how it all works.


Conclusion

After using the Tangible Level Editor, building levels with a tangible interface did very much feel more intuitive than a traditional mouse and keyboard. As adults we were able to understand how the blocks were a metaphor for the virtual environment; however, we would need to have children to play it for themselves to see if they too are able to comprehend that same idea.

This article was first published in 2009.

Comments

Post a Comment

Popular posts from this blog

The Ontology of Virtual Reality

In 2012 the Oculus Rift, a virtual reality (VR) headset display, made headlines in the technology world when it raised US$2.5 million through crowdfunding. In 2014 it was acquired by Facebook for a whopping US$2 billion. Ever since then leaders in technology have been pouring money in VR research & development and there is no shortage of creative studios producing VR content. Although VR have not reached universal adoption, the data looks to be auspicious. In 2010, just 2 years prior to VR's supposed harbinger, I wrote a chapter in my Sufficiency in the Philosophy of Technology under Prof. John Sanbonmatsu titled 'The Ontology of Virtual Reality'. Rereading it, I think it is still relevant today. Here it is: There are two aspects of virtual reality that pertains to human use: information and communication (Valentine and Holloway n.d.). Virtual reality is a space filled with information provided by its architects. Perhaps the easiest example to grasp is the internet...

Don Taylor's Creon in 'Antigone'

Who is the protagonist in Sophocles’ Antigone ? Is it Antigone or Creon? Scholars in literature often debate that question. But in Don Taylor’s 1984 rendition of Antigone , there is no doubt that Creon (played by John Shrapnel) is the protagonist. Don Taylor’s direction to have a Creon-focused Antigone was a precarious decision, but a highly successful one. You have to see it to believe it. The story of Antigone (played by Juliet Stevenson) is about the recalcitrant title character who buried her dead brother thereby violating a decree set forth by her uncle, the new king Creon. She did it to uphold a religious right she believed to be ubiquitous, but Creon viewed it as an act against his power and therefore refused to grant her impunity from death. Even the fact that she was his niece and future daughter-in-law could not have saved her nor did she want to be saved, at least not through family ties. After all, Antigone's iconoclastic action, her brother’s burial, was also ...

M6 Software-Defined Radio

Here describes a design of a digital radio receiver for a multi-user transmitter with an error prone oscillator that functions in non-ideal transmission channels. The implementation is software-defined (i.e. no hardware other than an analog-to-digital converter) with Matlab simulations yielding deciphered messages that are fully comprehensible. It was designed by me and advised by Prof. Andrew Klein of Worcester Polytechnic Institute. What to expect of the signal clarity by the end of this article. Introduction The M6 radio receiver was designed to work in tandem with a transmitter with the specifications listed in the table below:  symbol source alphabet +1, +3   assigned intermediate frequency  210 MHz  nominal symbol period 121.6 nanoseconds  SRRC pulse shape rolloff f  β ∈ [0.1,0.3]  FDM user slot allotment 10.7 MHz   truncated width of SRRC pulse shape 8 transmitter clock periods  preamble sequen...