Quantcast
Channel: Wildbunny blog
Viewing all articles
Browse latest Browse all 19

How to make a Facebook social game

$
0
0

Hi and welcome back to my blog!

This time I’m going to talk about the process of making a very basic social game as a Facebook app.

It’s my hope that if you know how to make a Flash game and have a little bit of PHP/database experience by the end of the article you will be set to make your first Facebook app.

This is a technical article covering the implementation of some of the core social game subsystems and not intended to be a discussion about moral justification of social game mechanics or even about how to design a social game – I’ll leave those aspects to game-designers and the media :)

The game I’m going to be making is called ‘Pet My Kitty’.

Click to play the live version of the game on Facebook

Pet My Kitty

In a nod to the now defunct mock-social game, Cow Clicker Pet My Kitty will have an absolute minimal set of functionality necessary to operate as a social game:

  • Interactive mechanic
  • Database persistence
  • Ability to purchase an in-game item
  • Invite friends
  • Social marketing incentive

Let’s quickly review each one of these points to elaborate on why they are key to a social game.

Interactive mechanic

There has to be something interactive about a Facebook game, no matter how simple otherwise it wouldn’t classify as a game. Furthermore, there needs to be something visually rich and rewarding associated with each interaction, so the user understands they are doing things the right way.

You can read more about how I define ‘a game’, here.

In this somewhat cynical game, the single mechanic is ‘petting’ the character, by dragging the mouse over the location of the character on screen. The reward is that the character responds with animation, sound effects and a secondary indication of the user levelling up as shown by animating hearts travelling from the character’s head towards the heart-bar at the top of the screen.

A heart animates towards the heart-bar

In a real game, there would be many different complimentary and overlapping mechanics. For a good analysis of social game mechanics, see this post on the What Games Are blog.

Database persistence

A social game needs to have persistent state between plays so that a user’s progress can be saved and they feel like they’re achieving something.

Being able to know how regularly a user has been playing the game is also very handy; if you’ve played many social games you will be familiar with the technique of offering rewards for each successive daily play of the game. This encourages retention by artificial means – by artificial I mean that suggestion to return tomorrow has come from the game, rather than the user’s subconscious desire.

Daily play bonus from CityVille

Ability to purchase an in-game item

Core to the business model of social games are micro-transactions. Without this aspect, they simply wouldn’t be able to support the vast quantity of users in terms of server rental and bandwidth costs. In Facebook, the only permitted method of purchasing something in-game is to use Facebook Credits.

In Pet My Kitty, the petting session with the character is limited to once per day (in homage to Cow Clicker). However, the user can purchase an additional petting session with Facebook Credits.

Invite friends

Part of the traditional social game tool set is the ability for enthusiastic users to invite their friends to play the game. It should be noted that as part of the Facebook developer policy, no content can be gated behind any of the social channels, which includes the friend invite. However, just making the process of telling friends about the game easier for users is still beneficial.

Read more about the Facebook developer policy here.

Social marketing incentive

For better or worse, nearly all Facebook games take advantage of the communication channels in order to market the game to a user’s friends. Giving users an incentive to use these channels is part and parcel of a social game. In this example a user can get an additional petting session by ‘Liking’ the game. This will create a story on that user’s news feed, thereby marketing the game to other users.

A user can get another petting session by 'Liking' the app

This type of incentive is permitted by the Facebook developer policy, although you may want to evaluate whether you feel it is right for your own game as it is rather a ‘once only’ type action.

Implementation – server side

Ok, so lets talk about the implementation details of a social game. Firstly, all Facebook apps must operate over SSL which means you need an SSL security certificate if you plan to host this app yourself, or you can take advantage of Heroku’s partnership with Facebook for free hosting with SSL.

Secondly, you will need to chose your development environment stack; I’m using the traditional LAMP stack.

Then you should work through this official tutorial until you have your app shell up and running. You can choose Heroku to host your app during this process if you like.

Authorisation

The authorisation process looks like this:

Figure 1

In the PHP SDK, Facebook wants to set the session details, so no other data must be output before the API is initialised or the session won’t persist correctly. Here is the code:

// initialise facebook api
$this->m_facebook = new Facebook(array(	'appId'  => FB_APP_ID,
					'secret' => FB_APP_SECRET,
					'cookie' => false));
 
// emit some html headers
?>
	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html xmlns="http://www.w3.org/1999/xhtml" >
		<head>
		...
		</head>
<?php
 
$facebookUid = $this->m_facebook->getUser();
$authorised = $facebookUid != 0;
 
// must have a session to be able to continue
if ($authorised)
{
	// display app content
}
else
{
	// redirect user to app authorisation page
	?>
		<script type="text/javascript">
			top.location.href = "<?php echo $this->m_facebook->getLoginUrl( array('redirect_uri' => FB_APP_URL));?>";
		</script>
	<?php
}

Storing new users in the database

This simple example is configured with two database tables, one to store the users of the app and one to store each user’s transactions. Here is a dump of the table structure:

-- phpMyAdmin SQL Dump
-- version 3.4.11.1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Nov 29, 2012 at 09:20 AM
-- Server version: 5.5.28
-- PHP Version: 5.2.17
 
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
 
--
-- Database: `wildbun1_petMyKitty`
--
 
-- --------------------------------------------------------
 
--
-- Table structure for table `transactions`
--
 
DROP TABLE IF EXISTS `transactions`;
CREATE TABLE IF NOT EXISTS `transactions` (
  `order_uid` varchar(128) NOT NULL,
  `buyer_uid` int(10) unsigned NOT NULL,
  `receiver_uid` int(10) unsigned NOT NULL,
  `order_info` varchar(128) NOT NULL,
  `date` datetime NOT NULL,
  PRIMARY KEY (`order_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
-- --------------------------------------------------------
 
--
-- Table structure for table `users`
--
 
DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
  `facebook_uid` int(10) unsigned NOT NULL,
  `last_access` datetime NOT NULL,
  `kitty_love` int(11) NOT NULL DEFAULT '0',
  `consecutive_daily_play` int(11) NOT NULL DEFAULT '0',
  `likes` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`facebook_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

The entries in the users table enable me to enforce the constraints of the app, to gather information about the play habits of the user and to persist the user’s in game progress.

New users are stored in the database after the app is authorised like this:

// insert user into the database
$this->m_dataBase = new Database(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$this->m_dataBase->InsertUser($facebookUid);

This function is always called, no matter if the user is old or new; to prevent overwritten table rows, InsertUser() uses the INSERT IGNORE query syntax, which will never overwrite an existing record.

Implementation – client side

Once the user has been stored in the database, the system then renders the HTML for the Flash client, passing a number of critical parameters to the FlashVars variable so they will be readable by the Flash client.

These variables allow the client to make choices about the state of the game; particularly:

  • Whether the character should be asleep or awake
  • What message the character should display to the player
  • Whether the user has already liked the app

Sleepy kitty

The Flash client needs to be able to access the Facebook API as well; in order to facilitate this, I’m using the third-party API on Google Code which I highly recommend as opposed to trying to cross-communicate with javascript.

In order for this API to function, a couple of scripts need to be referenced in the <head> of the HTML page:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>

Without these scripts, the call to initialise the Facebook API will never return.

In a more complex game, it’s likely there would be a constant exchange and verification of information via HTTP POST web requests as well as some initial state setting.

Flash client main functions

The main client functions as pertains to the Facebook integration are:

  • To open the buy with Facebook Credits dialog
  • React to a user’s purchase
  • Listen for ‘Like’ events
  • Open the invite friends dialog
  • Update the state of the game on the server

Let’s explore how each of these are achieved in practice:

Facebook Credits dialog

import com.facebook.graph.*;
 
...
 
/**
 * 
 * @param e
 *
 */
private function OnClickBuy(e:MouseEvent):void
{
	var obj:Object = 
	{
		method:'pay', 
		action:'buy_item', 
		// You can pass any string, but your payments_get_items must
		// be able to process and respond to this data.
		order_info:{'item_id':'1a'}, 
		dev_purchase_params:{'oscif':true}
	};
 
	Facebook.ui("pay", obj, OnBuyResponse);
}

The request sets off a series of events, the first of which is Facebook’s server’s querying a callback URL which you specify during the configuration of your app in the Facebook developer app. This query ask’s your server for the price details of the requested item; doing things this way prevents malicious users from altering the pricing of your items.

After a valid response has been received by Facebook, the Credits dialogue will appear.

Facebook Credits dialogue

The user can then choose to proceed or not. If they proceed, Facebook makes another call to your specified script which enables you to log the transaction and to unlock anything pertaining to that user in the database. If they proceed or not, Facebook will then call your client side callback function, in this case called ‘OnBuyResponse’.

React to a user’s purchase

import com.facebook.graph.*;
 
...
 
/**
 * 
 * @param data
 *
 */
private function OnBuyResponse(data:Object):void
{
	if (data.order_id != undefined)
	{
		// unlock another go of the app
		...
	}
	else if (data.error_code != undefined)
	{
		DialogManager.Get().ShowMessageDialog("Transaction failed!", "Message from Facebook: " + data.error_message);
	}
	else 
	{
		DialogManager.Get().ShowErrorDialog("Something odd happened: " + Helpers.PrintR(data));
	}
}

In OnBuyResponse, if the transaction was successful we unlock another petting session in game.

Here is an official tutorial which will describes the process of configuring your app to accept Facebook Credits and also details the callback code that your server will need have have.

Listen for ‘Like’ events

The app listens for Like events so that we can implement our simple social marketing incentive. This is achieved by attaching an event listener for the event called “edge.create”:

Facebook.addJSEventListener("edge.create", OnUserLikedApp);

Once this fires, the app unlocks another petting session, but only if this is the first time the user has ‘liked’ the app. Without this check, a user could repeatedly like and unlike the app in order to gain unlimited extra petting sessions.

Open the invite friends dialog

Opening the invite friends dialogue is extremely simple:

import com.facebook.graph.*;
 
...
 
/**
 * 
 * @param e
 *
 */
private function OnClickInvite(e:MouseEvent):void
{
	Facebook.ui("apprequests", {message:'Kitty needs your love. Come and pet the cute kitty!'});
}

Update the state of the game on the server

In order to persist the user’s progress in game, it must be communicated to the server so it can be logged in the database. This takes place after each petting session has ended; the client makes a HTTP POST web request to the server and transmits the ‘level’ of the player, which is then persisted along with whether the user has ‘liked’ the app, to prevent cheating by repeatedly liking and unliking.

At this point the server also does a number of calculations to track player statistics and to enforce the rules of the game. The rules of the game state the player is only allowed one petting session per day (unless they buy addition sessions, or gain a session by liking the app). In order to enforce this rule, the server tracks the ‘last access time’ of the user, which is the last time at which the server logged data persisted from the client. The server also uses this data to compute the number of consecutive days the user has petted the character; this information is used in game to display a different message to the user on each consecutive day.

For example, messages might be:

Day 1: “I’m sad, no one has petted me for a while… Will you be my friend?”
Day 2: “I’m sure glad to have a friend like you.”
Day 3: “Hey! We’re getting to be good friends now. That makes kitty happy!”
Day 4: “Hi again! I caught a mouse yesterday! It tasted gooooood!”
Day 5: “I love you! So happy to see you again!”
Day 6: “We’ve been friends for nearly a week now! That makes me purrrr…”
Day 7: “We’re best buddies now! I’ve seen you everyday this week!”

And if the user misses a day:

“I missed you yesterday! Very happy you’re back again to see me.”

The calculation of these variables which enable the functionality looks like this:

<?php
 
/**
 * Keep in sync with client!
 */
final class eLastAccess
{
	const Yesterday = 1;
	const Today = 0;
	const MoreThanOneDay = 2;
}
 
/**
 *
 * @param type $user
 * @param type $dataBase
 * @return type 
 */
function UpdateDailyPlay($user, $dataBase, $facebookUid, $updateData=true)
{
	//
	// calculate the number of consecutive log-ins
	//
 
	$lastAccessDT = $user['last_access'];
	$timeNowUnix = GetTime();
	$timeNowDT = UnixTimeToDateTime($timeNowUnix, DATE_FORMAT);
 
	$lastAccessTime = strtotime($lastAccessDT);
 
	$lastAccessDay = UnixTimeToDateTime($lastAccessTime, DATE_FORMAT_DAY_ONLY);
	$timeNowDay = UnixTimeToDateTime($timeNowUnix, DATE_FORMAT_DAY_ONLY);
 
	$lastAccessDayTime = strtotime($lastAccessDay);
	$timeNowDayTime = strtotime($timeNowDay);
 
	if ($timeNowDayTime == ($lastAccessDayTime+ONE_DAY_IN_SECONDS))
	{
		if ($updateData)
		{
			// consecutive login
			$dataBase->Query(	"UPDATE users SET consecutive_daily_play=consecutive_daily_play+1, last_access=? WHERE facebook_uid=?",
								array("si", $timeNowDT, $facebookUid));
 
			// update in data
			$user['consecutive_daily_play']++;
		}
 
		return eLastAccess::Yesterday;
	}
	else if ($timeNowDayTime > $lastAccessDayTime+ONE_DAY_IN_SECONDS)
	{
		if ($updateData)
		{
			// clear consecutive
			$dataBase->Query(	"UPDATE users SET consecutive_daily_play=0, last_access=? WHERE facebook_uid=?",
								array("si", $timeNowDT, $facebookUid));
 
			// update in data
			$user['consecutive_daily_play']=0;
		}
 
		return eLastAccess::MoreThanOneDay;
	}
	else
	{
		if ($updateData)
		{
			// access on the same day
			$dataBase->Query(	"UPDATE users SET last_access=? WHERE facebook_uid=?",
								array("si", $timeNowDT, $facebookUid));
		}
 
		return eLastAccess::Today;
	}
}
 
?>

In a real game you could use this information to display the daily play bonus dialogue.

Performing the POST request on the client is quite easy; here is the wrapper I use:

package Code.System
{
	import flash.net.*;
	import flash.events.*;
 
	/**
	 * Class for performing web-requests
	 */
	public class WebRequest
	{
		protected var m_success:Function;
		protected var m_failure:Function;
 
		/**
		Perform a web request
 
		@param <String> url The url of the web request
		@param <URLVariables> parameters Parameters for this web request
		@param <Function> success Function to perform on success - must accept one parameter of type Object which is the response from the web request
		@param <Function> failure Function to perform on failure - must accept one parameter of type PayDirtError
		@param <String> method Of type URLRequestMethod which determins whether this is to be a POST or GET request
		*/
		public function WebRequest( url:String, parameters:URLVariables, success:Function, failure:Function, method:String = URLRequestMethod.POST )
		{
			m_success = success;
			m_failure = failure;
 
			var loader : URLLoader = new URLLoader();  
			var request : URLRequest = new URLRequest(url);  
 
			request.method = method;  
			request.data = parameters;
 
			//  Handlers  
			loader.addEventListener(Event.COMPLETE, OnSuccess);  
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, OnSecurityError);
			loader.addEventListener(IOErrorEvent.IO_ERROR, OnIoError);
 
			// do it
			loader.load(request);  
		}
 
 
		/**
		 Called when the web request was successful, calls out to the given callback functions in the constructor.
 
		 @param <Event> e Response from web request
		*/
		protected function OnSuccess( e:Event ):void
		{
			var loader:URLLoader = URLLoader(e.target);
			m_success( loader.data );
		}
 
		/**
		 Gets called when there is a security error
 
		 @param <SecurityErrorEvent> e Security error
		*/
		private function OnSecurityError(e:SecurityErrorEvent):void 
		{
			m_failure( new PayDirtError("Security Error", ""+e) );
		}
 
		/**
		 Gets called when there is a io error - usually the server is inaccessable.
 
		 @param <IOErrorEvent> e Io error
		*/
		private function OnIoError(e:IOErrorEvent):void 
		{
			m_failure( new PayDirtError("IO Error", "Whoops, something went wrong trying to communicate with the server...") );
		}
	}
}

Security in the web-request

The web-request on the server has to update only the stats of the current user and must be protected against malicious use by a user intercepting and altering the parameters of the callback. In order to facilitate this, the client sends the Facebook API access token along with the request. This token enables the server to initialise the API and grab the associated user’s Facebook UID without the need to transmit any more data. This ensures that only the currently logged in Facebook user can have their stats updated.

Scalability

When anyone thinks about Facebook apps, they think millions of users daily. This is true for the really big players on Facebook, but for the vast majority it isn’t the case.

Even so, it does make sense to have a plan to scale your app should it become successful. The weak point in any Facebook app is the database; almost always the largest bottleneck is access to it, especially if it’s one plain MySql database with a table full with millions of rows. Popular techniques for scaling a MySql back-end are to use Memcached which is an in-memory key-value pair store which sits between the user and the database. You should also read up on sharding, which is splitting up your data across multiple databases.

Or you could elect to use a NoSql solution instead, of which there are a lot of different options. Here is a good comparison of some of the options available.

The choice of what to do with these tools

Obviously Pet My Kitty is an extremely simple and somewhat cynical example of a social game, teetering on the very edge of the classification of ‘game’. From a business perspective, the time when something as simple as this could have been a success on Facebook is long since passed, which is probably a good thing. However, the basic principles are core to how a social game operates on the platform – to not use these tools negates the benefit of choosing Facebook in the first place.

In conclusion

In this article I’ve shown you how you can build the skeleton of a social game, allowing you to:

  • Take payment in the form of Facebook Credits
  • Invite friends to your app
  • Apply social marketing through an incentive
  • Persist users progress in a database
  • Communicate between Flash and PHP

Using this framework you will be able to go and create the next big sensation – all you need is a good, solid game idea, an understanding of your target audience, a decent artist and a lot of luck!

Buy the app

As ever, you can buy the example source which accompanies this article; in this case you get the completed app! Including everything you need to make a social game, database tables, source code, assets, the lot.

Click to play the live version of the game on Facebook

The only thing not included is the graphic of the kitten, which I only have publishing rights over, not distribution rights.

USD 49.99

Until next time, have fun!

Cheers, Paul.


Viewing all articles
Browse latest Browse all 19

Trending Articles