游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享制作Facebook社交游戏的基本要素

发布时间:2014-01-17 17:06:56 Tags:,,,

作者:Paul Firth

今天我将讲解制作一款基础级的Facebook社交游戏的过程。

希望你已经知道如何制作Flash游戏,或者至少拥有一点PHP/数据库经验,这样在本文结尾时你就可以制作出自己的首个Facebook应用。

这是一个包含某些执行核心社交游戏子系统的技术贴,在此我们并不讨论某些社交游戏机制的道德问题,以及如何设计一款社交游戏——这些问题要留给游戏设计师和媒体来判断。

我将制作的这款游戏名为《Pet My Kitty》。

Pet My Kitty

《Pet My Kitty》将含有社交游戏的最基本必要运行功能:

*交互机制

*数据库存留

*购买游戏内置道具的功能

*邀请好友

*社交营销刺激

让我们快速讨论每个要点,并详细说明为何它们是制作社交游戏的关键。

交互机制

无论多简单的Facebook游戏都必须有一些交互元素,否则就不能称其为游戏。另外,每个交互行为都要伴随一些视觉元素和回报,这样用户才会知道自己做对了。

在这款有点讽刺的游戏中,其单个机制就是“抚摸”角色,在屏幕上将鼠标拖动到角色所在位置。其回报就是指角色以动画、音效作出回应,另外游戏也会通过角色头顶的动态红心飘到屏幕顶端的心条栏中显示用户升级。

真正的游戏中可能有许多不同的补充和重叠机制,在此我们仅以简单的机制为例。

数据库存留

社交游戏需要在玩法之间保留持续状态,以便保存用户进程,让他们觉得自己获得了一些成就。

知道用户玩游戏的频率也是一项棘手任务,如果你玩过许多社交游戏,你就会很熟悉游戏为连续的日常体验行为所提供奖励的技巧。这是以人为方式鼓励玩家留存——我的意思是指游戏鼓励玩家次日再返回游戏,而不是玩家潜意识中自发回到游戏中。

daily Play Bonus(from wildbunny)

daily Play Bonus(from wildbunny)

购买游戏内置道具的功能

社交游戏的核心商业模型就是微交易。如果没有这个功能,它们就无法在租借服务器以及宽带成本上支持庞大的用户群体。在Facebook中,唯一可行的游戏内置支付手段就是使用Facebook Credits。

在《Pet My Kitty》中,玩家抚摸角色的行为一天仅限一次(模仿《Cow Clicker》的做法)。但是,用户可以用Facebook Credits购买额外的抚摸次数。

邀请好友

传统社交游戏的部分工具套件就是赋予资深用户邀请好友一起玩游戏的能力。需要指出的是,由于Facebook开发者政策的原因,包括好友邀请在内的许多内容都无法通过任何社交渠道广泛传播。但是,只要让用户告知好友游戏消息的这一过程更为便捷,就仍可为开发者带来好处。

社交营销刺激

所有的Facebook游戏都会利用沟通渠道来向用户好友推广游戏。为用户提供使用这些渠道的动力是社交游戏不可或缺的一部分。在这个例子中,用户可以通过“赞”一下游戏而获得一次额外的抚摸机会。这会在用户的news feed中创造一个新的动万态,并因此向其他用户推荐游戏。

like(from wildbunny)

like(from wildbunny)

Facebook开发者政策允许这种类型的刺激手段,不过你可以自己评估它是否适合你的游戏,因为它只是一种“一次性”的输入操作。

执行——服务器端

那我们来谈谈社交游戏的执行细节。首先,所有Facebook应用都必须通过SSL运行,这意味着如果你打算自己托管这款应用,就需要一个SSL安全证书,或者利用Heroku与Facebook的合作,以SSL免费托管应用。

其次,你将选择自己的开发环境栈,我使用的就是传统的LAMP栈。

之后你就可以通过这篇教程开始制作应用,直到它能够正常运行。在这个过程中,如果你愿意,就可以选择Heroku来托管你的应用。

授权

授权过程如下:

authorisation process(from wildbunny)

authorisation process(from wildbunny)

在PHP SDK中,Facebook想设置会话详情,所以其他数据就不可能在API已经初始化或者会话并没有正确存留的前提下输出,以下就是代码:

// 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
}

在数据库中存储新用户

这个简单的例子是由两个数据库表格配置而成的,一个用于存储应用的用户,另一个存储每个用户的会报情况。以下就是表格结构的转储:

– 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;

用户表格的入口允许我执行应用的限制条件,以便获得用户游戏习惯的信息,留存用户在游戏中的进展。

在以下授权之后,新用户就会存储在数据库中:

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

无论是新老用户,这个函数会一直被调用,以避免覆盖表格行,InsertUser()使用了INSERT IGNORE查询语法,这不会覆盖现成的记录。

执行——客户端

当用户被存储到数据库中时,系统就会为Flash客户端渲染HTML,将一系列临界参数传送到FlashVars变量,以便它们通过Flash客户端呈现可读性。

这些变量允许客户端选择游戏状态,尤其是:

*角色处于沉睡还是清醒状态

*角色应该家传达什么信息

*用户是否已经赞过该应用

sleepy(from wildbunny)

sleepy(from wildbunny)

Flash客户端也应该可以访问Facebook API,为了实现这一点,我使用了Google Code的第三方API,我强烈推荐这种做法。

为了令这个API生效,就要在HTML页面的<head>中引用一些脚本:

<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>

如果没有这些脚本,初始化Facebook API的调用就不会返回。

在更复杂的游戏中可能有通过HTTP POST网络请求的持续信息交换和验证,以及一些初始状态。

Flash客户端主要功能

适合Facebook绑定的主客户端功能是:

*以Facebook Credits对话开启购买

*回应用户的购买行为

*留意“赞”事件

*打开邀请好友对话

*更新服务器上的游戏状态

让我们来探讨它们的各自实现方式:

Facebook Credits对话

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);
}

该请求会引发一系列事件,第一个就是Facebook服务器查询一个回调URL,这是你在Facebook开发者应用中配置应用过程时的详细说明。该查询会向服务器询问某个被请求商品的价格,这样就可以避免恶意用户修改你的商品价格。

Facebook接收到有效回应后,其Credtis对话就会出现。

credits(from wildbunny)

credits(from wildbunny)

用户之后就可以选择继续前进与否。如果他们继续,Facebook就会向你指定的脚本制作另一个调用,以便你记录交易并开启任何与该用户在数据库中有关的信息。如果他们没有继续,Facebook就会调用你的客户端回调函数,在此可称为“OnBuyResponse”。

回应用户购买行为

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));
}
}

在OnBuyResponse中,如果交易成功了,我们就会开启游戏中的另一个抚摸会话。

留意“赞”事件

应用会留意“赞”事件,以便我们执行简单的社交营销刺激。这可以通过为所谓的“edge.create”事件附上一个事件侦听器而实现:

Facebook.addJSEventListener(“edge.create”, OnUserLikedApp);

启动这一功能后,应用会开启另一个抚摸会话,但它仅限于用户首次“赞”过该应用。如果没有这次检查,用户就可以反复“赞”和取消“赞”应用,从而获得无数次的额外抚摸。

facebook_like_button(from wildbunny)

facebook_like_button(from wildbunny)

开启邀请好友对话

打开邀请好友对话极其简单:

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!’});
}

更新服务器上的游戏状态

为了留存游戏中的用户进程,必须与服务器进行沟通以便将其记录在数据库中。这会发生在在每次抚摸结束时,客户端向服务器发出一个HTTP POST网络请求,并传送玩家的“级别”,之后会留存玩家是否“赞”过应用的信息,以免被恶意用户重复“赞”和取消“赞”。

like Dislike(from wildbunny)

like Dislike(from wildbunny)

此时服务器还执行一系列计算追踪玩家数据,以实施游戏规则。游戏规则要求玩家一天只能抚摸一次(游戏邦注:除非他们购买了额外的次数,或者通过赞应用而获得了额外次数)。为了实施这一规则,服务器会追踪用户“最后一次访问时间”,即服务器记录数据在客户端持续的最后一次时间。服务器还使用这一数据计算用户抚摸角色的持续天数,这一信息被游戏中用于每天向用户展示不同的留言。

例如,游戏留言内容可能是:

第一天:“我很伤心,没有人摸我一下……你愿意做我的朋友吗?”

第二天:“我当然很高兴拥有像你一样的朋友。”

第三天:“嘿!我们现在是好朋友了。这让小猫很开心!”

第四天:“嗨!我昨天抓到一只老鼠了!它吃起来真美味!”

第五天:“我爱你!真高兴又看到你了!”

第六天:“我们成为好友已经将近一个星期了!这真开心……”

第七天:“我们现在是好朋友了!我这周每天都有看到你!”

如果用户某天没有开启应用,就会出现如下留言:

“我昨天好想你!很高兴你又回来看我了。”

这些促使功能生效的变量计算如下:

<?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;
}
}

?>

在真正的游戏中,你可以使用这一信息来展示日常游戏奖励对话。

在客户端执行POST请求非常容易,这里就是我使用的包装:

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…”) );
}
}
}

网络请求的安全性

服务器端的网络请求只能更新当前用户的状态,必须避免用户拦截和更改回调参数这种恶意使用行为。为了实现这一点,客户端会与请求一起发送Facebook API访问标记。这一标记允许服务器初始化API,并在无需传送更多数据的情况下,获得相关用户的Facebook UID。这可以确保只有当前登录的Facebook用户的状态获得更新。

可扩展性

任何人想到Facebook应用,就会认为每天都有成百上千万用户访问。对于Facebook上的大型应用发行商来说的确如此,但对大多数应用来说却并非如此。

即便如此,我们也还是最好制定一个针对应用成功之后的扩展计划。任何Facebook应用的弱点都是数据库,这几乎是访问的最大瓶颈,尤其是带有充满成百上千万栈表格的普通MySql数据库。扩展MySql后端的一个热门方法就是使用Memcached,这是一个介于用户和数据库之间的内置存储关键值组存储。你应该仔细研究分区,它在多个数据库中分裂你的数据。

你也可以选择歙用NoSql解决方案,其中有许多不同的选项。

scaled Kitties(from wildbunny)

scaled Kitties(from wildbunny)

这些工具的选择

很显然《Pet My Kitty》是一款极其简单以及带有讽刺意味的社交游戏,实在很难划入“游戏”类别中。从商业角度来看,如此简单的内容在Facebook获得成功的机遇已经不复存在了,这也是个好现象。但是,这些基本原理仍是社交游戏在Facebook平台运行的核心。

总结

我在本文概括了一款社交游戏的框架,以便你:

*以Facebook Credits形式收取付款

*邀请好友体验你的应用

*通过刺激手段应用社交营销

*在数据库中留存用户进展

*在Flash和PHP中沟通

使用这一框架,你将可以创造出一款Facebook游戏——你所需要的就是一个优秀可靠的游戏理念,以及对目标用户的了解,出色的美术人员和许多运气!

原文发表于2012年11月30日,所涉事件及数据以当时为准。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How to make a Facebook social game

by Paul Firth

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!(source:wildbunny


上一篇:

下一篇: