Skip to content

Instantly share code, notes, and snippets.

@GleasonK
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GleasonK/cf3b70692c3628b32d31 to your computer and use it in GitHub Desktop.
Save GleasonK/cf3b70692c3628b32d31 to your computer and use it in GitHub Desktop.
[Blog] Turn Your Smartphone into an Elegant Gamepad with PubNub and Polymer
<!-- First Import: -->
<link rel="import" href="paper-fab.html">
...
<!-- Then use: -->
<paper-fab icon="send"></paper-fab>
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
</head>
<body>
...
<link href="../bower_components/polymer/polymer.html" rel="import">
<link href="../bower_components/paper-button/paper-button.html" rel="import">
<link href="../bower_components/paper-fab/paper-fab.html" rel="import">
<link href="../bower_components/iron-icons/iron-icons.html" rel="import">
<link href="../bower_components/iron-icons/hardware-icons.html" rel="import">
<dom-module id="pub-control">
<style>
...
</style>
<template>
...
</template>
</dom-module>
<script>
// element registration
Polymer({
is: "pub-control",
properties: {
...
},
ready: function(){
...
}
});
</script>
<template>
<div id="gamepad" class="card">
<div id="arrow-btns">
<p id="channel" hidden="{{!isConfigured}}">{{channel}}</p>
<paper-button data-key="UP" on-down="handleButton">
<iron-icon data-key="UP" icon="hardware:keyboard-arrow-up"></iron-icon>
</paper-button> <br>
<paper-button data-key="LEFT" on-down="handleButton">
<iron-icon data-key="LEFT" icon="hardware:keyboard-arrow-left"></iron-icon>
</paper-button>
<paper-button data-key="RIGHT" on-down="handleButton">
<iron-icon data-key="RIGHT" icon="hardware:keyboard-arrow-right"></iron-icon>
</paper-button> <br/>
<paper-button data-key="DOWN" on-down="handleButton">
<iron-icon data-key="DOWN" icon="hardware:keyboard-arrow-down"></iron-icon>
</paper-button> <br>
<img src="../pubnub.png" style="width: 225px; margin: 15px;"></canvas>
</div>
<div id="action-btns">
<paper-fab icon="icons:close" data-key="X" on-down="handleButton"></paper-fab>
<paper-fab icon="icons:radio-button-unchecked" data-key="O" on-tap="handleButton"></paper-fab>
<br>
<paper-fab mini icon="polymer" style="background:black" on-tap="changeChannel"></paper-fab>
</div>
</div>
</template>
<script>
// element registration
Polymer({
is: "pub-control",
properties: {
channel: {
type : String,
value: "demo"
},
isConfigured: {
type : Boolean,
value: false
}
},
// add properties and methods on the element's prototype
handleButton: function(e){
...
},
changeChannel: function(e){
...
},
ready: function(){
...
}
});
</script>
paper-fab {
background:red;
}
...
<paper-fab mini icon="polymer" style="background:black" on-tap="changeChannel"></paper-fab>
<!DOCTYPE html>
<html>
<head>
...
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link href="element/pub-control.html" rel="import">
</head>
<body fullbreed unresolved>
<pub-control></pub-control>
</body>
</html>
<link rel="import" href="bower_components/pubnub-polymer/pubnub-element.html">
<template>
...
<core-pubnub
publish_key="your_pub_key"
subscribe_key="your_sub_key">
<core-pubnub-publish id="pub" channel="{{channel}}" message="Hello"> </core-pubnub-publish>
</core-pubnub>
</template>
Polymer({
...
ready: function(){
this.$.pub.message = {"type":"<type>", "data":"<data>"};
this.$.pub.publish();
}
});
Polymer({
is: "pub-control",
properties: {
...
},
// add properties and methods on the element's prototype
handleButton: function(e){
this.$.pub.message = {"type":"button", "data":e.currentTarget.dataset.key};
this.$.pub.publish();
},
changeChannel: function(e){
var channel = prompt("Enter a channel: ");
if (channel=="" || channel==null) channel="demo";
this.channel = channel;
this.isConfigured = true;
// Changing this will reveal our <p hidden={{!isConfigured}}>{{channel}}</p>
// and display the value of this.channel
},
ready: function(){
...
}
});
Polymer({
...
properties: {
aX: {
type : Number,
value: 0
},
aY: {
type : Number,
value: 0
},
aZ: {
type : Number,
value: 0
},
xPos: {
type : Number,
value: 0
},
yPox: {
type : Number,
value: 0
},
speed: {
type : Number,
value: 0
},
...
}
...
});
Polymer({
...
ready: function(){
// Tilt of canvas
var iOS = /(iPad|iPhone|iPod)/g.test( navigator.userAgent );
var lastUpdate = Date.now();
var SHAKE_THRESHOLD = 1250;
...
}
});
ready: function(){
...
if (window.DeviceMotionEvent == undefined) {
// No accelerometer is present. Use buttons.
alert("no accelerometer");
}
else {
// Hooray, we have an accelerometer! Attach a decivemotion listener.
alert("accelerometer found");
window.addEventListener("devicemotion", accelerometerUpdate, true);
}
}
ready : function(){
...
var self = this;
function accelerometerUpdate(e) {
var timeNow = Date.now();
var timeDiff = timeNow - lastUpdate;
if (timeDiff > 100) {
lastUpdate = timeNow;
var aX = event.accelerationIncludingGravity.x*1;
var aY = event.accelerationIncludingGravity.y*1;
var aZ = event.accelerationIncludingGravity.z*1;
// Fix for iOS
aX = iOS ? -1*aX : aX;
//The following two lines are just to calculate a
// tilt. Not really needed.
self.xPos = Math.atan2(aY, aZ);
self.yPos = Math.atan2(aX, aZ);
var dX = Math.abs(aX - self.aX);
var dY = Math.abs(aY - self.aY);
var dZ = Math.abs(aZ - self.aZ);
console.log("dX:" + dX + " " + "aX:" + aX + " this.aX:" + self.aX);
self.speed = Math.abs(aX + aY + aZ - self.aX - self.aY - self.aZ)/ timeDiff * 10000;
if (dX > 0.45) {
self.aX = Math.floor(aX*2)/2.0;
self.$.pub.message = {'type':'aX', 'data':self.aX};
self.$.pub.publish();
}
if (dY > 0.25) {
self.aY = Math.floor(aY*2)/2.0;
//sendData(controller.channel,"aY",controller['aY']);
}
if (dZ > 0.25) {
self.aZ = Math.floor(aZ*2)/2.0;
//sendData(controller.channel,"aZ",controller['aZ']);
}
if (self.speed > SHAKE_THRESHOLD) {
self.$.pub.message = {'type':'shake', 'data':self.speed};
self.$.pub.publish();
}
}
}
}
// Simulates clicking a key
function fireKey(el, key) {
if (document.createEventObject) {
var eventObj = document.createEventObject();
eventObj.keyCode = key;
el.fireEvent("onkeydown", eventObj);
} else if (document.createEvent) {
var eventObj = document.createEvent("Events");
eventObj.initEvent("keydown", true, true);
eventObj.which = key;
eventObj.keyCode = key;
el.dispatchEvent(eventObj);
}
}
pubnub.subscribe({
channel : channelName,
message : function(message){
if (message.type == "button") {
switch(message.data){
case "UP":
fireKey(el, 38); // Up Arrow Key
break;
case "DOWN":
fireKey(el, 40); // Down Arrow Key
break;
case "LEFT":
fireKey(el, 37); // Left Arrow Key
break;
case "RIGHT":
fireKey(el, 39); // Right Arrow Key
break;
case "X":
fireKey(el, 78); // N Key
break;
case "O":
fireKey(el, 78); // N Key
break;
default:
break;
}
}
}
});
<!DOCTYPE html>
<html>
<head>
<meta name=viewport content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<title>PubMote - Smartphone Controller</title>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-fab/paper-fab.html">
<link rel="import" href="bower_components/core-icons/core-icons.html">
<link rel="import" href="bower_components/core-icons/hardware-icons.html">
<link rel="import" href="bower_components/pubnub-element/pubnub-element.html">
<style>
#arrow-btns, #action-btns {
text-align: center;
}
paper-button{
background: rgba(217, 217, 217, 1);
padding: 3px 15px;
margin: 5px;
}
paper-fab{
margin: 5px 10px;
}
.fullscreen {
padding-top: 25px;
width: 100%;
height: 100vh;
}
.fullscreen img{
margin: 50px;
}
#channel {
padding:20em;
font-size: 15px;
margin: 0 auto;
}
.card {
margin:auto;
margin-top:5px;
background-color:#FFFFFF;
border-radius:2px;
color:#000000;
border:1px solid #d8d8d8;
padding:10px;
text-align:center;
position:relative;
}
</style>
</head>
<!-- Body tags fullbreed ensures that the template takes the entire screen, unresolved prevents FUOC -->
<body fullbreed unresolved>
<!-- Create a template to handle the controller's functions -->
<template is="auto-binding" id="controller-template">
<paper-shadow id="gamepad" z="1" class="card">
<template if="{{isConfigured}}" id=channel> <!-- Only shows when boolean is true. -->
<p style="text-align: center;"><i>Channel: {{channel}}</i></p>
</template>
<!-- The arrow buttons of the game pad. Use HTML5 data-* to tell which button is clicked -->
<div id="arrow-btns">
<!-- Uses Polymer's on-tap to link the template's function handleButton() -->
<paper-button data-key="UP" on-tap="{{handleButton}}">
<core-icon data-key="UP" icon="hardware:keyboard-arrow-up"></core-icon>
</paper-button> <br>
<paper-button data-key="LEFT" on-tap="{{handleButton}}">
<core-icon data-key="LEFT" icon="hardware:keyboard-arrow-left"></core-icon>
</paper-button>
<paper-button data-key="RIGHT" on-tap="{{handleButton}}">
<core-icon data-key="RIGHT" icon="hardware:keyboard-arrow-right"></core-icon>
</paper-button> <br/>
<paper-button data-key="DOWN" on-tap="{{handleButton}}">
<core-icon data-key="DOWN" icon="hardware:keyboard-arrow-down"></core-icon>
</paper-button> <br>
<!-- Your custom controller icon -->
<img src="pubnub.png" style="width:100px; margin: 50px;">
</div>
<!-- The three floating action buttons, X O and choose channel -->
<div id="action-btns">
<paper-fab icon="close" data-key="X" on-tap="{{handleButton}}"></paper-fab>
<paper-fab icon="radio-button-off" data-key="O" on-tap="{{handleButton}}"></paper-fab>
<br>
<paper-fab mini icon="polymer" style="background:black" on-tap="{{changeChannel}}"></paper-fab>
</div>
</paper-shadow>
<!-- The PubNub Polymer element, handles push and sucscribe from one element -->
<core-pubnub publish_key="pub-c-3a6515b4-cc50-4515-82ea-80d76f361027" subscribe_key="sub-c-a6b102dc-00a0-11e5-8fd4-0619f8945a4f">
<core-pubnub-publish id="pub" channel="{{channel}}" message="test"></core-pubnub-publish>
</core-pubnub>
</template>
</body>
<script src="http://cdn.pubnub.com/pubnub-3.7.1.min.js"></script>
<script type="text/javascript">
var controller = document.querySelector('#controller-template');
controller.isConfigured = false;
controller.channel="demo";
controller.aX=0;
controller.aY=0;
controller.aZ=0;
controller.speed=0;
controller.handleButton = function(e) {
this.$.pub.message = {"type":"button", "data":e.target.getAttribute('data-key') };
this.$.pub.publish();
};
controller.changeChannel = function(e){ // Prompt a user to change the channel
this.isConfigured = true;
var channel = prompt("Enter a channel: "); // Creates the prompt
if (channel == "" || channel == null) channel="demo";
this.channel = channel; // Ignores the change if null or empty
}
controller.ready = function() { // This code executes once the controller template is loaded
var iOS = /(iPad|iPhone|iPod)/g.test( navigator.userAgent );
var lastUpdate = Date.now(); // Last update to threshold PubNub publishes
var SHAKE_THRESHOLD = 1250; // Change this value depending on what you consider a shake
if (window.DeviceMotionEvent == undefined) {
//No accelerometer is present. Can only use buttons.
alert("no accelerometer");
}
else {
// Device has an accelerometer. Can use everything.
alert("accelerometer found");
window.addEventListener("devicemotion", accelerometerUpdate, true);
}
function accelerometerUpdate(e) { // The eventListener function that updates the aX value and shake
if (Date.now() - lastUpdate > 100) { // Only checks the accelerometer value ever 100ms
lastUpdate = timeNow;
var aX = event.accelerationIncludingGravity.x*1; // Used for tilt
var aY = event.accelerationIncludingGravity.y*1;
var aZ = event.accelerationIncludingGravity.z*1;
// Since iOS flips the value, this will flip them to an Android standard
aX = iOS ? -1*aX : aX;
// Calculate the change in X, Y, and Z. Used to check if there was a shake.
var dX = Math.abs(aX - controller.aX);
var dY = Math.abs(aY - controller.aY);
var dZ = Math.abs(aZ - controller.aZ);
// Only change if the dX was large enough, avoids noise. Publish if there was a change.
if (dX > 0.45) {
controller.aX = Math.floor(aX*2)/2.0; // Floor to the nearest half.
controller.$.pub.message = {'type':'aX', 'data':controller.aX};
controller.$.pub.publish();
}
if (dY > 0.45) controller.aY = Math.floor(aY*2)/2.0;
if (dZ > 0.45) controller.aZ = Math.floor(aZ*2)/2.0;
// Calculate the controller's total speed. This will tell whether there was a shake.
controller.speed = Math.abs(aX + aY + aZ - controller.aX - controller.aY - controller.aZ)/ timeDiff * 10000;
// Play around with SHAKE_THRESHOLD. If speed is larger than ST, publish a shake.
if (controller.speed > SHAKE_THRESHOLD) {
controller.$.pub.message = {'type':'shake', 'data':controller.speed};
controller.$.pub.publish();
}
}
}
}
</script>
</html>
/* CSS rules for your element */
#arrow-btns, #action-btns {
text-align: center;
}
#canvas{
margin: 10px auto;
border: 3px solid blue;
}
paper-button{
background: rgba(217, 217, 217, 1);
padding: 3px 15px;
margin: 5px;
}
paper-fab{
margin: 5px 10px;
background: rgb(210, 63, 49);
}
.fullscreen {
padding-top: 25px;
width: 100%;
height: 100vh;
}
#channel {
font-size: 15px;
margin: 10px auto;
font-style: italic;
}
.card {
margin:auto;
margin-top:5px;
background-color:#FFFFFF;
border-radius:2px;
color:#000000;
border:1px solid #d8d8d8;
padding:10px;
text-align:center;
position:relative;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment