Thomas Ang, Mad Hatter Technologies
David Neumann, TVO
FITC Screens 2013 | Friday October 4, 2013
15,000 DOWNLOADS IN 15 DAYS
1. Solid understanding of multi-device development for iOS2. 4 concepts for creating an engaging experience
WHAT YOU WILL WALK AWAY WITH
1. TVO’s IdeaShaker Innovation Lab2. Intro the Ping Pong App3. 4 engagement concepts4. Mad Hatter Technology5. Under the hood of the Ping Pong App6. Q/A
THE AGENDA
TVO’s IdeaShaker Lab
TVO’s IdeaShaker team explores emerging digital trends that have “disruptive” potential and approaches all projects through the lens of how technology can impact and enhance TVO’s educational resources for children, current affairs content and other TVO content.
““
IdeaShaker Portfolio
Ping Pong App
One of the few multi-device apps in the store
Featured by Apple in January 2013
Over 15,000 downloads in 15 days
Non-branded version developed for international
markets
Built to support the North American premiere of
the documentary Ping Pong: Never Too Old For
Gold
Overview
Engaging Experience
Understand the underlying themes that can be woven through your entire cross-media narrative
““
Concept 1
Engaging Experience
Find the technologies that can deliver on your themes. Transmedia is great when done right.
““
Concept 2
Engaging Experience
When packaging a brand new concept for a user, make sure you wrap it up in something they are familiar with.
““
Concept 3
Engaging Experience
Almost everything has been done before. Do it better.
““
Concept 4
IdeaShaker + Mad Hatter
• Prototyped in both Android and iOS - > proof of concept
• Due to hard deadline had to bring in an awesome team to help execute the project
Working together
WHO IS ?Mad Hatter Technology specializes in marketing & communications technology.
Team of specialists in a variety of things:-Digital design-Web dev-Mobile dev-Social media
Strategy is our means. Creativity is our craft. Technology is our passion.
““
WHAT IS UP TO?
• TVO
• Ivanhoe Cambridge
• Balsillie School of International Affairs
• Centre for International Governance Innovation
• Alternatives Journal
• The Regional of Waterloo
• University of Waterloo
• University of Ontario
• Waterloo Regional Police Services
• Thunder Bay Police Services
Game Dev BasicsStates for single player
Game Dev BasicsSimplified game loop for single player
#define LOOP_INTERVAL 0.033f
[NSTimer scheduledTimerWithTimeInterval:LOOP_INTERVAL target:self selector:@selector(gameLoop)
userInfo:nil repeats:YES];
- (void)gameLoop{
switch(gameState){
case kStateGameServe:
//if recent motion data tells us swing gesture was made
//go to Waiting state
break;
case kStateGameWaiting:
//Calculate and display AI player's response
//go to Return state
break;
Game Dev BasicsSimplified game loop for single player
case kStateGameReturn:
//if we've taken too long to return the shot
//rally is over: give AI player a point, go into appropriate Serve or Return state
//else if recent motion data tells us swing gesture was made
//if it was a bad swing
//rally is over: give AI player a point, go into appropriate Serve or Return state
//else go to Waiting state
break;
default:
break;
}
}
Pairing//This was built foriOS 6
//Things have changed in iOS 7
GKPeerPickerController* picker;
picker = [[GKPeerPickerController alloc] init];
picker.delegate = self;
[picker show];
Pairing - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session
//store info from session that we’ll need, like opponent’s ID
// Make sure we have a reference to the game session and it is set up to receive data
// Done with the Peer Picker so dismiss it.
// startGame
-(void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
//do any cleanup required and send us back to menu
// No need to implement -peerPickerController:didSelectConnectionType: delegate method since this app does not
//support multiple connection types.
- (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type
//init and return a GKSession that holds a game session ID and my display name sessionMode is
//GKSessionModePeer
GKPeerPickerControllerDelegate
2 Player LogicStates for two player
2 Player LogicGame loop for two player
- (void)gameLoop{
switch (networkState) {
case kStateNetworkMatching:
break;
case kStateNetworkPlaying:
switch(gameState){
case kStateGameSyncServe:
case kStateGameSyncReturn:
//if phones have been pointing at each other for long enough
//save current phone direction
//if we’re in SyncServe go to Serve state
//if we’re in SyncReturn go to Waiting state
// send SYNC_SUCCESS to other phone with direction
//else send GESTURE_SYNC with direction
break;
2 Player Logiccase kStateGameServe:
//if recent motion data tells us swing gesture was made
//if phone ended up pointing away from other player
//rally is over: give out points, go into appropriate Serve or Return state send HIT_FAIL message
//else go to Waiting state, display random arrow, send HIT_SUCCESS with orientation of arrow
break;
case kStateGameWaiting:
//make sure the arrow is still pointing the right way even if phone orientation changes
break;
case kStateGameReturn:
//if we've taken too long to return the shot
//rally is over: give out points, go into appropriate Serve or Return state send HIT_FAIL message
//else if recent motion data tells us swing gesture was made
//if phone ended up pointing away from other player or had wrong orientation
//rally is over: give out points, go into appropriate Serve or Return state send HIT_FAIL message
//else go to Waiting state, display random arrow, send HIT_SUCCESS with orientation of arrow
break;
2 Player Logic// once every 8 updates check if we have a recent heartbeat from the other player, and
send a //heartbeat packet with current state
//if the last heartbeat is too old go to NetworkReconnect state
break;
case kStateNetworkReconnect:
// we have lost a heartbeat for too long, so pause game and notify user while we wait for next //heartbeat or session disconnect.
break;
}
Sending Datatypedef enum {
NETWORK_ACK, // no packet
NETWORK_PLAYER_INFO,
NETWORK_PASS_SERVE, //let the other device know that its player serves first
NETWORK_GESTURE_SYNC, //packets we send when syncing device directions
NETWORK_SYNC_SUCCESS,
NETWORK_HIT_SUCCESS, // the result of a swing
NETWORK_HIT_FAIL,
NETWORK_WIN,
NETWORK_HEARTBEAT
} PacketCode;
Sending Data - (void)sendNetworkPacket:(GKSession *)session packetID:(PacketCode)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)reliable
static unsigned char networkPacket[kMaxPacketSize];
const unsigned int packetHeaderSize = 2 * sizeof(int); // we have two "ints" for our header
if(length < (kMaxPacketSize - packetHeaderSize)) { // make sure packet isn’t bigger than buffer
int *pIntData = (int *)&networkPacket[0];
pIntData[0] = gamePacketNumber++; // header info
pIntData[1] = packetID;
memcpy( &networkPacket[packetHeaderSize], data, length ); // copy data in after the header
NSData *packet = [NSData dataWithBytes: networkPacket length: (length+packetHeaderSize)];
if(reliable== YES) {
[session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId]
withDataMode:GKSendDataReliable error:nil];
} else {
[session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId]
withDataMode:GKSendDataUnreliable error:nil];}
Receiving Data - (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context
//unpack our data into local variables, make sure they arrive in order, etc.
switch( packetID ) {
case NETWORK_PLAYER_INFO:
//save whatever opponent info you need
//coin toss to see who serves first (device with smaller ID does this)
//If we're not serving first send PASS_SERVE message
break;
case NETWORK_PASS_SERVE:
//set our state to SyncServe (the default is SyncReturn)
break;
case NETWORK_GESTURE_SYNC:
//if devices are not pointing in opposite directions restart timer
//else if devices are not pointing horizontally restart timer
//else let players know to continue holding that position
break;
Receiving Data case NETWORK_SYNC_SUCCESS:
//save the successful sync direction and go to Serve or Waiting state
break;
case NETWORK_HIT_SUCCESS:
//store orientation we have to match and go to Return state
break;
case NETWORK_HIT_FAIL:
//increment our score
//if we won send NETWORK_WIN else figure out who serves next and go to appropriate state
break;
case NETWORK_WIN:
//update stats, etc.
break;
case NETWORK_HEARTBEAT:
// update our record of most recent heartbeat
// if we were trying to reconnect, set the state back to multiplayer as the peer is back
break;
Syncing Directions CMDeviceMotion has a CMAttitude, which has a CMQuaternion
There’s more information than we want here. We just want a direction vector representing the normal of plane of device screen.
Apply each phone’s rotation (encoded by quaternion) to an arbitrary vector and compare the resulting vectors.
Let v = [0, 0, 0, 1], and q is our quaternion
v´ = q v q-1
if the two resulting v´ vectors are roughly opposite, we’re good
Detecting Swing Motion
Check that userAcceleration exceeds threshold
Check gravity to see that phone is mostly on its side
Check that rotation in all three axes is small
Dealing With RejectionWe found that your app exhibited one or more bugs, when reviewed on the iPhone 5 and the iPad (3rd Gen) running iOS 6.0.1, on both Wi-Fi and cellular networks, which is not in compliance with the App Store Review Guidelines.
The steps to reproduce are:
1) Launch the application on two devices
2) Tap the "Play" button on both devices
3) Connect the devices
The application hangs at a blue screen and no additional content is loaded.
Dealing With Rejection
We've been rejected twice for bugs found when the two phones are really close to each other at the start of the game. We found the cause of these bugs to be interference to the Bluetooth antenna and the magnetometer, which arises when the two phones are close together in certain positions. While we have introduced new code to ensure these failures are handled gracefully from a user's perspective, it would still be best if you test this app as it is intended to be used: each phone held by a different player, standing a few feet to a few yards apart. Thanks, and enjoy!
Featured!
THANK YOU!
GOOD BYE!
BUT COME SAY HELLO!Thomas Ang - @madhattermobileDavid Neumann - @everyoneminus1