Skip to content

Pinball Game using Servos Motors & Unity

roboLea edited this page Jun 28, 2019 · 7 revisions

This tutorial explains how to build a Pinball machine using augmented reality with Unity, Raspberry Pi, and SpaceBrew.

Concept: A Pinball machine where the ball and the flippers are virtual

We also made this following these tutorials:

Hardware

Software and Frameworks

  • For the Raspberry Pi: Current distro with Python 2.7 and Spacebrew, WiFi/network is assumed to be working
  • For the AR scene: Unity 2017.3.0f3 with Vuforia and the YUXI examples
  • One Spacebrew server (we had a local instance running at 192.168.1.191)

System Overview

STEP 1: Wiring up the MCP3008 to the Raspberry PI

STEP 2: Wire up the 2 servos and the 2 buttons to the Raspberry PI

STEP 3: Connect the Raspberry Pi to Spacebrew

#!/usr/bin/env python
# Never mix tabs and spaces.

############################################################## 
# This script uses 2 buttons wired to pin 4 and pin 17.
# It publishes the button presses to Spacebrew to turn on the
# virtual pinball handler.
##############################################################
from __future__ import division
import sys
import time
from pySpacebrew.spacebrew import Spacebrew
import RPi.GPIO as GPIO
import Adafruit_PCA9685

pwm = Adafruit_PCA9685.PCA9685()

# Configure min and max servo position
servo_min1 = 150  # Min servo1 position
servo_max1 = 400  # Max servo1 position
servo_min2 = 400  # Min servo2 position
servo_max2 = 150  # Max servo2 position

# Helper function to make setting a servo pulse width simpler.
def set_servo_pulse(channel, pulse):
    pulse_length = 1000000    # 1,000,000 us per second
    pulse_length //= 60       # 60 Hz
    print('{0}us per period'.format(pulse_length))
    pulse_length //= 4096     # 12 bits of resolution
    print('{0}us per bit'.format(pulse_length))
    pulse *= 1000
    pulse //= pulse_length
    pwm.set_pwm(channel, 0, pulse)
    pwm.set_pwm(channel, 1, pulse)
# Set frequency to 60hz, good for servos.
pwm.set_pwm_freq(60)

# Setup Spacebrew with Publishers and Subscribers
brew = Spacebrew("Servo_Button", description="Python Light and Button controller",  server="192.168.1.191", port=9000)
brew.addSubscriber("flipLight", "boolean")
brew.addPublisher("button1Press", "boolean")
brew.addPublisher("button2Press", "boolean")

CHECK_FREQ = 0.1 # How often to check the hardware

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
#GREEN_LED = 1
#RED_LED = 4
#GPIO.setup(GREEN_LED, GPIO.OUT)
#GPIO.setup(RED_LED, GPIO.OUT)
GPIO.setup(17, GPIO.IN) #down is False
GPIO.setup(4, GPIO.IN) #down is False
lightOn = False 
alreadySent = False # to 'debounce' the button

def handleBoolean(value):
    global lightOn
    print("Received: "+str(value))
    if (value == 'true' or str(value) == 'True'):
        lightOn = not lightOn

# for handling messages coming through spacebrew 
brew.subscribe("flipLight", handleBoolean)

try:
    brew.start()
    print("Press Ctrl-C to quit.")
    while True:
 #       GPIO.output(GREEN_LED, False)
        if (GPIO.input(17) == False):
            if (alreadySent == False):
              print("Button1 Pushed")
              brew.publish('button1Press', True)
              alreadySent = True
    	      # Move servo on channel O between extremes.
    	      pwm.set_pwm(4, 0, servo_min1)
    	      time.sleep(0.1)
    	      pwm.set_pwm(4, 0, servo_max1) 
    	      time.sleep(0.2)
              brew.publish('button1Press', False)
        if (GPIO.input(4) == False):
            if (alreadySent == False):
              print("Button2 Pushed")
              brew.publish('button2Press', True)
              alreadySent = True
              # Move servo on channel O between extremes.
              pwm.set_pwm(8, 0, servo_min2)
              time.sleep(0.1)
              pwm.set_pwm(8, 0, servo_max2)
              time.sleep(0.2)
              brew.publish('button2Press', False)
 #      GPIO.output(GREEN_LED, lightOn)
        time.sleep(CHECK_FREQ)
        if (GPIO.input(17) == True):
            alreadySent = False
        if (GPIO.input(4) == True):
            alreadySent = False
finally:
    GPIO.cleanup()
brew.stop()

STEP 3: Unity scene

The unity scene is based on YUXI's BaseBoARd_LightsButtons scene.

To connect to Spacebrew:

using UnityEngine;
using System.Collections;

public class SpacebrewEvents : MonoBehaviour {

	SpacebrewClient sbClient;
	bool lightState = false;

	public GameObject LeftPaddle;
	public GameObject RightPaddle;
/*

Ok, let's do this all in one script?

BaseExample: None?
HelloWorld
	P: None
	S: launchSatellite, string
LightsButtons
	P: buttonPress, boolean
	S: flipLight, boolean
SenseHat
	P: letter, string
	S: direction, string
		up,down,left,right,middle (default)
	S: layer, string

p:2
s:4

 */


	// Use this for initialization
	void Start () {
		GameObject go = GameObject.Find ("SpacebrewObject"); // the name of your client object
		sbClient = go.GetComponent <SpacebrewClient> ();

		// register an event with the client and a callback function here.
		// COMMON GOTCHA: THIS MUST MATCH THE NAME VALUE YOU TYPED IN THE EDITOR!!
		sbClient.addEventListener (this.gameObject, "launchSatellite");
		sbClient.addEventListener (this.gameObject, "flipLight");
		sbClient.addEventListener (this.gameObject, "letters");
	}

	// Update is called once per frame
	void Update () {
		if (Input.GetKeyDown ("space")) {
			//print ("Sending Spacebrew Message");
			// name, type, value
			// COMMON GOTCHA: THIS MUST MATCH THE NAME VALUE YOU TYPED IN THE EDITOR!!
			sbClient.sendMessage("buttonPress", "boolean", "true");
		}
//		foreach (char c in Input.inputString) {
//			print("Just pressed: "+c.ToString());
//			if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
//				sbClient.sendMessage("letters", "string", c.ToString());
//				GameObject go = GameObject.Find ("MatrixContainer"); // the name of your client object
//				MatrixMaker grid = go.GetComponent <MatrixMaker> ();
//				grid.ParseIncomingLetter(c.ToString());
//				grid.delayLayer();
//				//grid.CreateLayer(true);
//				
//			}
//		}

		if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began) {
			sbClient.sendMessage("buttonPress", "boolean", "true");
		}
		if(Input.GetMouseButtonDown(0)){
			sbClient.sendMessage("buttonPress", "boolean", "true");
		}

		
	}

	public void OnSpacebrewEvent(SpacebrewClient.SpacebrewMessage _msg) {


		print ("Received Spacebrew Message");
		print (_msg);

		// Look for incoming Satellite messages
		if (_msg.name == "launchSatellite") {
			if (_msg.value == "true") {
				GameObject go = GameObject.Find("BaseBoARd/YourObjectsGoHere/CenterOfUniverse");
				OrbitManager om = go.GetComponent <OrbitManager> ();
				om.makeSatellite();
				print("Tried to launch Satellite");
			}
		}

		// Look for messages to turn the virtual lamp light on
		if (_msg.name == "letters") {
			GameObject go = GameObject.Find ("MatrixContainer"); // the name of your client object
			MatrixMaker grid = go.GetComponent <MatrixMaker> ();
			grid.ParseIncomingLetter(_msg.value[0].ToString());
			grid.delayLayer();
		}

		// Look for messages to turn the virtual lamp light on
		if (_msg.name == "flipLeft") {
//			Debug.Log ("flipLight!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
//			if (_msg.value == "true") {
				LeftPaddle = GameObject.Find("BaseBoARd/YourObjectsGoHere/LeftPaddle");
//				Debug.Log (LeftPaddle);
				LeftPaddle.GetComponent<leftPaddleScript> ().flipState ();
//				GameObject go = GameObject.Find("BaseBoARd/YourObjectsGoHere/LeftPaddle");
//				lightState = !lightState;
//				go.gameObject.SetActive(lightState);
//				Input.GetKeyDown("d");
//			}
		}

		if (_msg.name == "flipRight") {
			//			Debug.Log ("flipLight!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			//			if (_msg.value == "true") {
				RightPaddle = GameObject.Find("BaseBoARd/YourObjectsGoHere/RightPaddle");
			//				Debug.Log (RightPaddle);
				RightPaddle.GetComponent<rightPaddleScript> ().flipState ();
			//				GameObject go = GameObject.Find("BaseBoARd/YourObjectsGoHere/RightPaddle");
			//				lightState = !lightState;
			//				go.gameObject.SetActive(lightState);
			//				Input.GetKeyDown("d");
			//			}
		}



		//if (_msg.name == "letters") {
		//print(go);
		//if (_msg.value == "true") {
		// GameObject go = GameObject.Find ("MatrixContainer"); // the name of your client object
		// MatrixMaker grid = go.GetComponent <MatrixMaker> ();
		// grid.CreateLayer(true);
		// grid.ParseIncomingString(_msg.value);
	}

}

To move the right paddle

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class rightPaddleScript : MonoBehaviour {
	public float restPosition = 0f;
	public float pressedPosition = 45f;
	public float hitStrenght = 10000f;
	public float flipperDamper = 150f;

	bool rightPaddlePressed = false;

	// public string inputName;

	HingeJoint hinge;

	// Use this for initialization
	void Start () {
		hinge = GetComponent<HingeJoint> ();
		hinge.useSpring = true;
	}

	public void flipState(){
		rightPaddlePressed =!rightPaddlePressed;
		Debug.Log ("button Pressed!!!!!!!!!!!!!!!!!!!!!!");
		Debug.Log (rightPaddlePressed);
	}



	// Update is called once per frame
	void Update () {
		JointSpring spring = new JointSpring ();
		spring.spring = hitStrenght;
		spring.damper = flipperDamper;


		//			if (Input.GetKey ("space")){	
		//			Debug.Log("spacebar");
		//			spring.targetPosition = pressedPosition;
		//		}else{
		//			spring.targetPosition = restPosition;
		//		}
		//		Debug.Log (rightPaddlePressed);
		if (rightPaddlePressed == true){	
			//			Debug.Log("rightFlip");
			spring.targetPosition = pressedPosition;
		}else{
			spring.targetPosition = restPosition;
		}



		hinge.spring = spring;
		hinge.useLimits = true;
	}


}

To move the left paddle

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class leftPaddleScript : MonoBehaviour {
	public float restPosition = 0f;
	public float pressedPosition = 45f;
	public float hitStrenght = 10000f;
	public float flipperDamper = 150f;

	bool leftPaddlePressed = false;

	// public string inputName;

	HingeJoint hinge;

	// Use this for initialization
	void Start () {
		hinge = GetComponent<HingeJoint> ();
		hinge.useSpring = true;
	}

	public void flipState(){
		leftPaddlePressed =!leftPaddlePressed;
			Debug.Log ("button Pressed!!!!!!!!!!!!!!!!!!!!!!");
		Debug.Log (leftPaddlePressed);
	}

		

	// Update is called once per frame
	void Update () {
		JointSpring spring = new JointSpring ();
		spring.spring = hitStrenght;
		spring.damper = flipperDamper;

//		if (spring.angle > pressedPosition){
//			flipState ();
//		}


//			if (Input.GetKey ("space")){	
//			Debug.Log("spacebar");
//			spring.targetPosition = pressedPosition;
//		}else{
//			spring.targetPosition = restPosition;
//		}
//		Debug.Log (leftPaddlePressed);
		if (leftPaddlePressed == true){	
//			Debug.Log("leftFlip");
			spring.targetPosition = pressedPosition;
		}else{
			spring.targetPosition = restPosition;
		}



		hinge.spring = spring;
		hinge.useLimits = true;
	}


}

Flipper script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlipperScript : MonoBehaviour {
	public float restPosition = 0f;
	public float pressedPosition = 45f;
	public float hitStrenght = 10000f;
	public float flipperDamper = 150f;

	public string inputName;

	HingeJoint hinge;

	// Use this for initialization
	void Start () {
		hinge = GetComponent<HingeJoint> ();
		hinge.useSpring = true;
	}

	// Update is called once per frame
	void Update () {
		JointSpring spring = new JointSpring ();
		spring.spring = hitStrenght;
		spring.damper = flipperDamper;

		if (Input.GetAxis (inputName) == 1) {
			spring.targetPosition = pressedPosition;
		}else{
			spring.targetPosition = restPosition;
	}
		hinge.spring = spring;
		hinge.useLimits = true;
}
}

Generating new balls when they fall off

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AddElements : MonoBehaviour {
	public GameObject startGo;
	public GameObject copyGo;

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {

		if (Input.GetMouseButtonDown(0))
		{
			Debug.Log("spacebar");
			Vector3 startPos;
			Quaternion startRotation;

			startPos = startGo.transform.position;
			startRotation = startGo.transform.rotation;
			GameObject go = Instantiate(copyGo,startPos,startRotation);
			go.transform.localScale += new Vector3 (1.0f, 1.0f, 1.0f);
		}

	}
	public void makeObject ()
	{
		//Debug.Log("Make object called");
		Vector3 startPos;
		Quaternion startRotation;

		startPos = startGo.transform.position;
		startRotation = startGo.transform.rotation; 
		GameObject go = Instantiate(copyGo,startPos,startRotation);
		go.transform.localScale += new Vector3 (1.0f, 1.0f, 1.0f);
	}
}

Collision event

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollisionEvents : MonoBehaviour {
    public GameObject cloneScript;
    // Use this for initialization
    void Start () {
        
        
    }
    void OnCollisionEnter(Collision collision){
        Debug.Log("colliion started");
    }
    void OnCollisionStay (Collision collision){
        Debug.Log("Stay occuring..");
    }
    void OnCollisionExit (Collision collision)
    {
        if(collision.gameObject.name == "Base")        
        {
            //  Debug.Log("Exit called...");
            cloneScript.GetComponent<AddElements>().makeObject();
        }
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}

STEP 4: Connect to Spacebrew

Required:

  • Make sure that the Raspberry Pi is powered up and running.
  • Make sure that the unity scene is running.

Then:

  • Connect the left and right buttons (Raspberry) to their equivalent flipper (Unity).

Documentation: Set-up + behind the scenes

Clone this wiki locally