My Tutorial Using Facebook Connect w/ Cocos2d « cocos2d for iPhone

<div class="posterous_bookmarklet_entry">
  ### My Tutorial: Using Facebook Connect w/ Cocos2d (39 posts) (20 voices) Started 11 months ago by Jeston Latest reply from jorgevmendoza

 

 

Jeston Member

</div>
Greetings Cocos2d Community, I wrote a tutorial after a lot of frustration finding information to let your game post to facebook users’ walls. Hopefully I don’t butcher the formatting on this post, but just in case I have the tutorial hosted on my webserver at http://www.loudgames.net/?p=17 So bear with me, I may have to edit this post a few times and I apologize in advance for that :) ======================== I have recently made a game on Iphone using Cocos2D that is Facebook Connect enabled. The app, when given permission by a Facebook user, has the capabilities of posting to the user’s profile pages. This feature set is very compelling for a developer to undertake as your game can post achievements, highscores, and various tidbits for everyone to see; and that makes the marketing team quite pleased. What is less known is how this is accomplished and more importantly the code that drives it. I don’t pretend to do anything fancy with facebook’s API, but I did discover some unobvious quirks when I went through the process. I had a lot of terminology to sort through, and found that I was treading an undocumented path. In the end the results were nice: I had a login button, my app asked for correct permissions, and I was able to silently post to user’s walls without bothering the players. So, I am writing this tutorial in hopes that others may benefit and save time. This tutorial isn’t a catch all and there are certainly a lot of facebook features to mull through, but at a minnimum I want to cover the process in hooking up facebook connect to a cocos2d made game with the ultimate goal of having the ability to post achievements to the web. Step 0. Pre-reqs & getting the Facebook iphone SDK Get it. Unzip it, and put the appropriate files into your code tree. I added a ‘Facebook’ grouping to my xcode project and included the entire /src/ subtree from the SDK at http://developers.facebook.com/connect.php?tab=iphone Step 1. Rendering the Login/Logout buttons. The first step is putting the login/logout buttons in. Everyone’s game architecture is different. To quickly describe mine I have: 1. Game Layer – I treat as the master of all logic 2. MenuLayer – Menu screen complete with a Play Button, Help, Facebook login buttons 3. Loading Screen, etc … For my tutorial, I intend to have information about the real facebook status and various tidbits sent to my menu layer. The main concern here is that the menu layer manages the visual state of the login buttons: show 1 button when logged in, the other when I am logged out. This should be familiar territory to cocos2d developers and straight foward, creating the sprites, menus and setting up button callbacks. The code is as follows. MenuLayer.h
//**
 //Create two sprites for the facebook buttons
 //**
 MenuItemSprite FaceBookButtonLogin;
 MenuItemSprite FaceBookButtonLogout;
 Menu FaceBookMenu;

     //**
 // Fired when the buttons get pushed
 //**
 -(void)FaceBookLoginPushed:(id)sender;
 -(void)FaceBookLogoutPushed:(id)sender;

     //**
 // Status of our login, this will be ‘told’ to us from
 // another layer, Yes this can probably be a property :)
 //*
 Boolean LoggedIn;
 -(void)SetLoggedIn:(Boolean)status;
</div>

MenuLayer.m

<div class="CodeRay">
//**
 // Creation
 //**
 - (id) init
 {
     self = [super init];
     if( self != nil )
     {
         LoggedIn = NO;

             //**
         // Images are provided in the bundle with connect sdk
         //**
         FaceBookButtonLogin = [MenuItemSprite itemFromNormalSprite:[Sprite spriteWithFile:@"FBConnect.bundle/images/login.png"] selectedSprite:nil disabledSprite:nil target:self selector:@selector(FaceBookLoginPushed:)];
         FaceBookButtonLogout = [MenuItemSprite itemFromNormalSprite:[Sprite spriteWithFile:@"FBConnect.bundle/images/logout.png"] selectedSprite:nil disabledSprite:nil target:self selector:@selector(FaceBookLogoutPushed:)];
             FaceBookMenu = [[Menu menuWithItems:FaceBookButtonLogout, FaceBookButtonLogin, nil] retain];
         [self addChild:FaceBookMenu z:1];
             //set positions and default states
         [FaceBookButtonLogin setPosition:ccp(-1995, -215)];

             [FaceBookButtonLogout setPosition:ccp(-1995, -215)];
     }
     return self;
 }

    // the rest is up to the game and how/when it shows the buttons, mine hides
// the buttons and turns them on when the menu layer is shown by the gamelayer
-(void)HideFaceBookButtons
{
  [FaceBookButtonLogout setIsEnabled:NO];
  [FaceBookButtonLogout setVisible:NO];
  [FaceBookButtonLogin setIsEnabled:NO];
  [FaceBookButtonLogin setVisible:NO];
  [FaceBookButtonLogin setPosition:ccp(-1995, -215)];
  [FaceBookButtonLogout setPosition:ccp(-1995, -215)];
}
-(void)ArrivedAtMenu
{
   [self SetLoggedIn:LoggedIn];
       //some animation, why not make it look nice?
   [FaceBookButtonLogout setPosition:ccp(FaceBookButtonLogout.position.x - 200, FaceBookButtonLogout.position.y)];
   [FaceBookButtonLogout runAction:[Sequence actions:[DelayTime actionWithDuration:0.8],[MoveBy actionWithDuration:0.3 position:ccp(220,0)],[MoveBy actionWithDuration:0.15 position:ccp(-20,0)],nil]];
   [FaceBookButtonLogin setPosition:ccp(FaceBookButtonLogin.position.x - 200, FaceBookButtonLogin.position.y)];
   [FaceBookButtonLogin runAction:[Sequence actions:[DelayTime actionWithDuration:0.8],[MoveBy actionWithDuration:0.3 position:ccp(220,0)],[MoveBy actionWithDuration:0.15 position:ccp(-20,0)],nil]];
}
//**
// Display appropriate button
//**
-(void)SetLoggedIn:(Boolean)status
{
   LoggedIn = status;

       [FaceBookButtonLogin setIsEnabled:YES];
   [FaceBookButtonLogin setVisible:YES];
   [FaceBookButtonLogout setIsEnabled:YES];
   [FaceBookButtonLogout setVisible:YES];
       if( LoggedIn )
   {
      [FaceBookButtonLogin setPosition:ccp(-1999,0)];
      [FaceBookButtonLogout setPosition:ccp(-105, -215)];
   }
   else
   {
      [FaceBookButtonLogout setPosition:ccp(-1999,0)];
      [FaceBookButtonLogin setPosition:ccp(-105, -215)];
   }
}

     //**
 // And lastly, what to do when the buttons are pushed
 // Lets defer the action to the owner of logic
 //**
 -(void)FaceBookLoginPushed: (id)sender
 {
     [[NSNotificationCenter defaultCenter] postNotificationName:@"LoginFaceBook" object:nil];
 }
     -(void)FaceBookLogoutPushed: (id)sender
 {
     [[NSNotificationCenter defaultCenter] postNotificationName:@"LogOutFaceBook" object:nil];
 }
</div>

Step 2. Managing the Login state

In the last step we defferred the logic of the actual process of logging in to the GameLayer. Lets get to managing that now.

There are some pre-requisits to meet first, namely we must know about when a login occurs and fails. This is accomplished by registering our GameLayer with some notifications and delegates. After which, we will need to manage the session object. The session is important to be familiar with, it does all the actual communicating to the web and delegates the responses back to your app. In essence, this is our connection mule. With the following code we intend to keep track of this session, and put some ground work for our delegates.

GameLayer.h

<div class="CodeRay">
#import "FBConnect/FBConnect.h"

    //Register the delegates with this layer
@interface GameLayer : Layer
 {}

    //**
// This is the facebook session that we use to interface
// with facebook, EVERYTHING facebook related relies
// on this: Login, Publishing Stories, etc…
//**
FBSession _session;

    //**
// Facebook notifications from MenuLayer to do the work
//**
-(void)LogOutFaceBook:(NSNotification )notification;
-(void)LoginFaceBook:(NSNotification *)notification;
</div>

GameLayer.m

<div class="CodeRay">
//**
// These two strings are required for all facebook apps,
// and you get them when you create an app on facebook,
// You can research what the key / secret is on your own
// but don’t need covering here
//**
static NSString kApiKey = @"XXXXXXXXXXXXXXXXXXXXXXXXXXX";
static NSString kApiSecret = @"YYYYYYYYYYYYYYYYYYYYYYYYY";

    //**
// Creation
// Now, we need to create the session, when and how you
// decide to do that is up to you. I have a message fired
// when my game is done loading, and that is when I decided
// to create my session. [See below]
//**
- (id) init
{
    self = [super init];
    if (self != nil)
   {
  _session = nil;
  MainMenu = [[MenuLayer alloc] init];

      [self.parent addChild:MainMenu z:51];

      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(LoginFaceBook:) name:@"LoginFaceBook" object:nil];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(LogOutFaceBook:) name:@"LogOutFaceBook" object:nil];  }
return self;
}

    //**
// Finished loading, create session and tell it to do its
// job with the Facebook api
//**
-(void)FinishedLoading
{
 _session = [[FBSession sessionForApplication:kApiKey secret:kApiSecret delegate:self] retain];
 [_session resume];
}
//**
// When logout is pushed, nothing fancy needs to happen; just
// call a facebook api logout on our session
//**
-(void)LogOutFaceBook:(NSNotification )notification
{
 if( _session != nil )
 {
  [_session logout];

     }
}

    //**
// When we push login, we display a facebook api dialog with our
// session, results will be sent to delegates ( coming up)
//**
-(void)LoginFaceBook:(NSNotification )notification
{
    FBLoginDialog* dialog = [[[FBLoginDialog alloc] initWithSession:_session] autorelease];
    [dialog show];
}
</div>

As you can see, there is a piece still missing. The session is created, we know what to do when the buttons get pushed and we have our notifications in place now, but where does the actual login take place? You can spend your time digging through the facebook docs but the short answer is the facebook delegates get fired. This is where we need to update our GameLayer and add all needed delegates. We can certainly add the code for our fail / success case of a login now though.

GameLayer.m addition

<div class="CodeRay">
///////////////////////////////////////////////////////////////////////////////////////////////////
// FBDialogDelegate
- (void)dialog:(FBDialog)dialog didFailWithError:(NSError)error
{

    }
///////////////////////////////////////////////////////////////////////////////////////////////////
// FBSessionDelegate
- (void)session:(FBSession)session didLogin:(FBUID)uid
{
 [MainMenu SetLoggedIn:YES];
}

    - (void)sessionDidNotLogin:(FBSession)session
{
 [MainMenu SetLoggedIn:NO];
}

    - (void)sessionDidLogout:(FBSession)session
{
 [MainMenu SetLoggedIn:NO];
}

    //FYI:
//Called when the facebook login dialog button is pushed
- (void)dialogDidSucceed:(FBDialog)dialog
{
}

    //FYI:
//Called when the facebook login cancel button is pushed
- (void)dialogDidCancel:(FBDialog)dialog
{

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////
// FBRequestDelegate

    - (void)request:(FBRequest)request didLoad:(id)result
{
}

    - (void)request:(FBRequest)request didFailWithError:(NSError)error
{
 NSLog([NSString stringWithFormat:@"Error(%d) %@", error.code, error.localizedDescription]);
}
</div>

Either of two functions [didLogin or didNotLogin] The facebook APIs handle these cases for us, so that is a big bonus. They should be called relatively quickly after our call to resume on the session when its first created.

Step 4\. Logged In but can we do anything? Permissions.

As dictated by Facebook, and assuming the user logged in correctly, we still can&rsquo;t start making posts and publishing to the user&rsquo;s wall. The last detail that remains is handling permissions. This means that the user has to still allow our game the right to publish. This is up to a designer on how to present then but this tutorial presents this scenario when the user logs in. We want to ask for permission to post things at the time of first login.

Tim Stephenson wrote a useful blog post about managing the permission state of a logged in user and did so in such an elegant way that I can&rsquo;t honestly improve on it, so I followed his code as

posted on: http://www.raddonline.com/blogs/geek-journal/iphone-sdk-using-facebook-connect-for-iphone-working-with-extended-permissions-part-2-of-2/

I highly suggest reading his post if you want to know how it works. To make things shorter I will just show it and use it on blind faith.

PermissionStatus.h

<div class="CodeRay">
#import#import "FBRequest.h"

    @protocol PermissionStatusDelegate;

    @interface PermissionStatus : NSObject  {
BOOL userHasPermission;
id delegate;
}

    @property (nonatomic, assign) BOOL userHasPermission;
@property (nonatomic, retain) id delegate;

    - (PermissionStatus *)initWithUserId:(long long int)uid;

    @end

    #pragma mark method to call after response
@protocol PermissionStatusDelegate- (void)statusWasSet:(BOOL)status;
@end
</div>

PermissionStatus.m

<div class="CodeRay">
#import "PermissionStatus.h"

    @implementation PermissionStatus
@synthesize userHasPermission;
@synthesize delegate;

    - (PermissionStatus )initWithUserId:(long long int)uid {
self = [super init];

    if (self) {
NSString fql = [NSString stringWithFormat:
@"select status_update from permissions where uid == %lld", uid];
NSDictionary params = [NSDictionary dictionaryWithObject:fql forKey:@"query"];
[[FBRequest requestWithDelegate:self] call:@"facebook.fql.query" params:params];
userHasPermission = NO;
}
return self;
}

    #pragma mark FBRequestDelegate
- (void)request:(FBRequest)request didLoad:(id)result {
NSArray permissions = result;
NSDictionary permission = [permissions objectAtIndex:0];
userHasPermission = [[permission objectForKey:@"status_update"] boolValue];
[delegate statusWasSet:userHasPermission];
}

    @end
</div>

With this usefull class, we can go back to our logic layer and keep track of the permissions.

GameLayer.h

<div class="CodeRay">
#import "PermissionStatus.h"

    // Here we add another delegate to use Tim’s permission status
@interface GameLayer : Layer
 {}

    // Create a property to keep track of the status
PermissionStatus permissionStatusForUser;
@property (nonatomic, retain) PermissionStatus permissionStatusForUser;
</div>

GameLayer.m

<div class="CodeRay">
@synthesize permissionStatusForUser;
- (id) init
{
     //… Our previous code
    permissionStatusForUser = nil;
}

    //**
//delegate as described in the permissions class
// The end result will have two outcomes
// Yes: Great, the user gave us permissions to post content
//          so we dont need to do anything else!
// No: We should display and ask for permissions
//**
- (void)statusWasSet:(BOOL)status
{
    //Instantiate the PermissionStatus class with the user id.
    if ( !permissionStatusForUser.userHasPermission )
    {
          FBPermissionDialog dialog = [[[FBPermissionDialog alloc] init] autorelease];
          dialog.delegate = self;
          dialog.permission = @"publish_stream";
          [dialog show];
    }
    else
   {

       }
}
//**
// This time when our login occurs we will now see and ask for
// permission status
//*
- (void)session:(FBSession)session didLogin:(FBUID)uid
{
      //Instantiate the PermissionStatus class with the user id.
     permissionStatusForUser = [[PermissionStatus alloc] initWithUserId:session.uid];
     permissionStatusForUser.delegate = self;
     [MainMenu SetLoggedIn:YES];
}

    //**
// Our log out needs to change a little bit so we can track permissions
//*

    -(void)LogOutFaceBook:(NSNotification *)notification
{
     if( _session != nil )
     {
          [_session logout];
          if( permissionStatusForUser != nil )
          {
               [permissionStatusForUser dealloc];
               permissionStatusForUser = nil;
          }
     }
}
</div>

Now things are looking up. We have login status being tracked and we know if our app has permissions to post

Step 5\. Posting Content

Now we get to make use of our hard work thus far. At this point, the user will be playing our game and perhaps achieved a high score or completed some great achievement. This would be a good place to fire off and automatically make a post to their Facebook wall. Many games choose to do this at their discretion and some will even create different dialogs. The intent of this article was to show how to make a wall post, and this game will just silently post without bothering the user. The facebook sdk has many methods and options of posting content and I will leave it up to you to go dig those methods up and learn your options. To finish our original goal we n

GameLayer.h

<div class="CodeRay">
//**
// Facebook stream publishing functions
//**
-(void)postFaceBookStory:(NSString)text:(NSString)imgUrl;
</div>

GameLayer.m

<div class="CodeRay">
//**
// Facebook stream publishing functions
//**
-(void)postFaceBookStory:(NSString)text:(NSString)imgUrl
{
//Check to see if the user is letting us make the post
if( permissionStatusForUser != nil && permissionStatusForUser.userHasPermission )
{
//Set up the json encoded message as defined by Facebook SDK
NSMutableDictionary FeedStoryParams = [[[NSMutableDictionary alloc] init] autorelease];
[FeedStoryParams setObject:@"1.0" forKey:@"v"];
[FeedStoryParams setObject:text forKey:@"message"];

    // all of my facebook achievement images are 200, 200
NSString attachment = [NSString stringWithFormat:@"{"media": [{"type": "image", "src": "%@", "href": "http://apps.facebook.com/YOURAPP/&quot;, "width":"200","height":"200" }]}", imgUrl];
[FeedStoryParams setObject:attachment forKey:@"attachment"];
[[FBRequest requestWithDelegate:self] call:@"facebook.stream.publish" params:FeedStoryParams];
}
}
</div>

And finally, making the appropriate call to post the story:

<div class="CodeRay">
strwhich = [NSString stringWithFormat:@"Achievement: My Game Posted on Facebook"];

    [self postFaceBookStory:[NSString stringWithFormat:@"played a game that I gave permissions to post to my wall"]:[NSString stringWithFormat:@"http://www.someurl.com/some_image.jpg&quot;]];
</div>

That should be it! I hope to see some posts on facebook from some cocos2d games now !

~Jeston Furqueron~

</div>

</div>

nice tutorial …

comments powered byDisqus