Punch vs Turn

Controller prototypes to test different mechanics in Unity
Prototyping
Unity
C#
Arduino
C+
Machine Learning
Blender
Animation
2022
Illustration of leprechauns punching and opening the door with "St.Patty's Happy Dog" printed on top bold. Illustration used for user testing volunteer posters.
Overview
Built two game controller prototypes for a Unity game, St.Patty's Happy Dog, to test which controller elicit more joy in players. Conducted A/B test between the two prototypes where: A) punches the door open and B) turns the door knob open.
Timeline
2 months (Feb - Apr 2022)
Team
Solo Project
Tools
Unity, Arduino, Blender, Mixamo

Gameplay

A simple “treasure hunt” where a leprechaun looks for happy dog behind doors.

Main Features

Player

Leprechaun

Challenge

Open the door

Goal

Find happy dog

Unknown behind the doors.

Depends on what sits behind the door, the accumulated points and the remaining time in timer can go up with happy dog or down with angry dog.

Problem

How will the player open the door?

User Testing Playtesting

In the game, the player (a leprechuan, St.Patty) must navigate through numerous  doors to find the goal, the happy dog, and collect points before the timer expires. The joystick controls for running around the game scene are effective, but the design of the door-opening trigger is crucial for engaging gameplay. The challenge is to design this trigger in a way that encourages players to continue interacting with the game enthusiastically.

Questions

Which door-opening mechanism was most popular among players?

Did player preferences change as the number of doors increased?

How effective was this mechanism in the context of St. Patty's Happy Dog game?

A/B Test
Door-Opening Triggers

Two game controller prototypes were designed to evaluate different door-opening mechanisms. Each prototype features a joystick for navigating the leprechaun and a distinct control dedicated to opening doors.

A

Punch the door

Leprechaun is the focus
POV: 3rd person camera
Machine learning to train the motion data
Leprechaun punching animation

B

Turn door knob

Door is the focus
POV: 1st person camera
Rotary encoder and push button
Door opening and door knob turing animation

Starter

$279/year

All limited links
Own analytics platform
Chat support
Optimize hashtags
Unlimited users
Cancel Anytime

Enterprise

$449/year

All limited links
Own analytics platform
Chat support
Optimize hashtags
Unlimited users
Cancel Anytime

punch controller

Prototype A

This prototype focuses on controlling the character, St. Patty, using two interfaces. The user moves St. Patty with a joystick held in one hand, and in the other hand, holds an Arduino board.

Punching with the hand holding the Arduino board acts as a game controller input.

Punch Controller Development

1. Model training

Training motion data: "punch" and "flex".

2. gesture classifction

Pre-trained data is classified and logged as "punch" or "flex" with respective accuracy levels.

3. emoji keybord - output

Pre-trained data is classified with respective label which outputs with respective emoji.

4. next steps...

Integrate the gesture classification with the leprechaun character in Unity and trigger it to open the door animation.

turn controller

Prototype B

Sketch of the Turn Controller prototype showing door handle in the back and joystick in the front.
This prototype centers on the door knob.

The user controls St. Patty's movement with a joystick located at the front of the controller and reaches for the door knob to trigger the door opening when needed.
Turn Prototype overview

User Testing Results

Positive

Negative

Punch

  • Enjoyable gameplay
  • Fun to play it in front of friends
  • Takes a little bit of onboarding
  • No physical reception of punching
  • After a few doors smashing, it's a workout

Turn

  • Very intuitive, no onboarding needed
  • Focus on the game itself more
  • "Wish gameplay was more flushed out"

Out of 13 playtesters, 62% preferred the Punch prototype to the Turn prototype, mainly because of its unique interaction style. The Turn prototype highlighted some gaps in gameplay. Both prototypes revealed important areas for further development.

Leprechaun Development

Below character development process is documented. The leprechaun controller was scripted in Unity to work with mouse, keyboard, and physical controllers, including a pull-up button and joystick, via Arduino.

Leprechaun Animation

Utilized open-source 3D asset and Blender and Mixamo for character development. Uploaded T-pose rigged asset onto Mixamo to skin pre-made animations onto the character.

Mixamo
Skinned pre-made animations on the rigged character. For Leprechaun, downloaded the following animations: idle, walking, running and punching.
Blender
Cleaned up the free 3D assets before and after using Mixamo.

St.Patty Animation States on Unity

Leprechaun animator setup for prepped animations on Unity

public class animationStateController : MonoBehaviour
{
    Animator anitor;
    int isWalkingHash; // for performance optimization
    int isRunningHash;
}
voidStart(){
    animator = GetComponent<Animator>();
    isWalkingHash = Animator.String("isWalking");
    isRunningHash = Animator.String("isRunning");
}
void Update(){
    bool isrunning = animator.GetBool(isRunningHash);
    bool isWalking = animator.GetBool(isWalkingHash);
    bool forwardPressed = Input.GetKey("W");
    bool runPressed = Input.GetKey("f");

    if( !isWalking && forwardPressed)
    {
        animator.SetBool(isWalkingHash, true);
    } 
    if( isWalking && !forwardPressed)
    {
        animator.SetBool(isWalkingHash, false);
    }
    if( !isrunning && runPressed)
    {
        animator.SetBool(isRunningHash, true);
    }
    if( isrunning && !runPressed)
    {
        animator.SetBool(isRunningHash, false);
    }
}

St.Patty Controller on Unity: Mouse

Testing controlling the Leprechaun by mouse

St.Patty Controller on Unity: Arduino Pull-up Buttons

Used Arduino Nano 33 IoT to control leprechaun using 2 pull-up buttons.

#include "Keyboard.h"

// set pin numbers for the five buttons:
const int oneButton = 2;
const int twoButton = 3;
const int threeButton = 4;
const int fourButton = 5;
const int fiveButton = 6;
const int otherButton = 7;

void setup() {
  // initialize the buttons' inputs
  // instead of "INPUT", we're using "INPUT_PULLUP"
  // this uses internal pull up resistors for the inputs
  
  pinMode(oneButton, INPUT_PULLUP);
  pinMode(twoButton, INPUT_PULLUP);
  pinMode(threeButton, INPUT_PULLUP);
  pinMode(fourButton, INPUT_PULLUP);
  pinMode(fiveButton, INPUT_PULLUP);
  pinMode(otherButton, INPUT_PULLUP);

  Keyboard.begin();
}

void loop() {
  // use the pushbuttons to control the keyboard
  // note how we are checking for LOW instead of the usual HIGH
  // this is how we check to see if a button is pressed when using INPUT_PULLUP mode
  
  if (digitalRead(oneButton) == LOW) {
    Keyboard.write('w');
  }
  if (digitalRead(twoButton) == LOW) {
    Keyboard.write('f');
  }
  if (digitalRead(threeButton) == LOW) {
    Keyboard.write('s');
  }
  if (digitalRead(fourButton) == LOW) {
    Keyboard.write('d');
  }
  if (digitalRead(fiveButton) == LOW) {
//    Keyboard.write(' ');
    Keyboard.write('f');
  }
  
  if (digitalRead(otherButton) == LOW) {
    Keyboard.press('q');
  } else {
    Keyboard.release('q');   
  }  
}

St.Patty Controller on Unity: Arduino Joystick

Joystick is effectively taking over the mouse input.

#include <Mouse.h>

// Global varibles:

const int mouseButton = 3;
const int buttonPin = 2;
const int ledPin = 6;             // Mouse control LED

int lastButtonState = LOW;        // state of the button last time you checked
boolean mouseIsActive = false;    // whether or not the Arduino is controlling the mouse

// joystick
const int xAxis = A0;         // joystick X axis
const int yAxis = A1;         // joystick Y axis

// parameters for reading the joystick:
int range = 512;               // output range of X or Y movement
int responseDelay = 5;        // response delay of the mouse, in ms
int threshold = range / 4;    // resting threshold
int center = range / 2;       // resting position value

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // make pin 2 an input, using the built-in pullup resistor:
  pinMode(buttonPin, INPUT_PULLUP); // switch function
  pinMode(mouseButton, INPUT_PULLUP);     // mouse left click input pin
  pinMode(ledPin, OUTPUT);         // the LED pin
  
  // initialize mouse control:
  Mouse.begin();
}

void loop() {
  // read the first pushbutton:
  int buttonState = digitalRead(buttonPin);

  // if it's changed and it's low, toggle the mouse state:
  if (buttonState != lastButtonState) {
      if (buttonState == HIGH) {
      // if mouseIsActive is true, make it false;
      // if it's false, make it true:
      mouseIsActive = !mouseIsActive;
      digitalWrite(ledPin, mouseIsActive);
      Serial.print("Mouse control state" );
      Serial.println(mouseIsActive);
    }
  }
  // save button state for next comparison:
  lastButtonState = buttonState;

  int xReading = readAxis(A0);
  int yReading = readAxis(A1);
  
  // print their values. Remove this when you have things working:
  Serial.print(xReading);
  Serial.print("  ");
  Serial.println(yReading); 

  if (mouseIsActive == true) {
    Mouse.move(xReading, yReading, 0);
  }
  // read the mouse button and click or not click:
  // if the mouse button is pressed:
  if (digitalRead(mouseButton) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.press(MOUSE_LEFT);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.release(MOUSE_LEFT);
    }
  }

  delay(responseDelay);
}
/*
  reads an axis (0 or 1 for x or y) and scales the analog input range to a range
  from 0 to <range>
*/

int readAxis(int thisAxis) {
  // read the analog input:
  int reading = analogRead(thisAxis);

  // map the reading from the analog input range to the output range:
  reading = map(reading, 0, 1023, 0, range);

  // if the output reading is outside from the rest position threshold, use it:
  int distance = reading - center;

  if (abs(distance) < threshold) {
    distance = 0;
  }

  // return the distance for this axis:
  return distance;
}

Takeaway

User Preferences

Playtest results showed a strong preference for the Punch prototype, guiding the focus for future iterations.

Punch Controller Enhancements

  • Bluetooth capability: Transition from a wired setup to Bluetooth to enhance user experience and reduce frustration.
  • Alternative Solutions: If Bluetooth isn't feasible, consider using a longer wire and design a wristband that incorporates the Arduino board for more comfort and less interference.

Gameplay Insights

The user test provided valuable insights into gameplay improvements, identifying critical areas for development.

  • Clear UI Message: Without seeing the happy dog or angry dogs, hard to know what is the point behind each doors.
  • Clear lose and win states: Timer and points accumulating could be highlighted better.