Ejecta icon indicating copy to clipboard operation
Ejecta copied to clipboard

Some suggestions about Ejecta's GameCenter utils

Open finscn opened this issue 11 years ago • 4 comments

  1. the defalut timeScope: apple sets the default timeScope is week. But the most games don't have too many active players , so the week view is empty ( or a small number ) in most of time. So I think let it be AllTime is better.
// showLeaderboard( category )
EJ_BIND_FUNCTION( showLeaderboard, ctx, argc, argv ) {
    if( argc < 1 || viewIsActive ) { return NULL; }
    if( !authed ) { NSLog(@"GameKit Error: Not authed. Can't show leaderboard."); return NULL; }

    GKLeaderboardViewController *leaderboard = [[[GKLeaderboardViewController alloc] init] autorelease];
    if( leaderboard ) {
        viewIsActive = true;
        leaderboard.leaderboardDelegate = self;
        leaderboard.category = JSValueToNSString(ctx, argv[0]);
// set the default to AllTime
        leaderboard.timeScope = GKLeaderboardTimeScopeAllTime;
        [scriptView.window.rootViewController presentModalViewController:leaderboard animated:YES];
    }

    return NULL;
}

Though , timeScope can be passed by user is better.

  1. the scores data of gameCenter is no player's base info , e.g. displayName So I wrote a method to merge scores data and players's data:

// get scores in range
//      args:  category, options(timeScope,friendsOnly,start,end), callback
EJ_BIND_FUNCTION(retrieveScores, ctx, argc, argv)
{
    GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
    if (leaderboardRequest != nil) {
        NSString *category = JSValueToNSString(ctx, argv[0]);
        JSObjectRef jsOptions = JSValueToObject(ctx, argv[1], NULL);
        JSObjectRef callback = JSValueToObject(ctx, argv[2], NULL);
        if (callback) {
            JSValueProtect(ctx, callback);
        }

        NSDictionary *options = (NSDictionary *)JSValueToNSObject(ctx, jsOptions);
        NSInteger start = [options[@"start"] integerValue];
        NSInteger end = [options[@"end"] integerValue];
        NSInteger timeScope = [options[@"timeScope"] integerValue];
        BOOL friendsOnly = [options[@"friendsOnly"] boolValue];
        if (!start) {
            start = 1;
        }
        if (!end) {
            end = start + 100 - 1;
        }

        switch (timeScope) {
            case 0:
                leaderboardRequest.timeScope = GKLeaderboardTimeScopeToday;
                break;

            case 1:
                leaderboardRequest.timeScope = GKLeaderboardTimeScopeWeek;
                break;

            case 2:
                leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
                break;
        }

        if (friendsOnly) {
            leaderboardRequest.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
        }
        else {
            leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
        }

        leaderboardRequest.category = category;
        leaderboardRequest.range = NSMakeRange(start, end);

        [leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
            NSMutableArray *identifiers = [[NSMutableArray alloc] init];
            NSMutableArray *scoreList = [[NSMutableArray alloc] init];
            if (scores != NULL) {
                for (GKScore * obj in leaderboardRequest.scores) {
                    [identifiers addObject:obj.playerID];
                    [scoreList addObject:obj];
                }
            }

            [self loadPlayersAndScores:identifiers scores:scoreList callback:callback];
        }];
    }
    return NULL;
}


- (void)loadPlayersAndScores:(NSArray *)identifiers scores:(NSArray *)scores callback:(JSObjectRef)callback {
    [GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler: ^(NSArray *players, NSError *error)
     {
         JSObjectRef jsScores = NULL;
         JSContextRef gctx = scriptView.jsGlobalContext;
         if (players != nil) {
             NSUInteger size = players.count;
             JSValueRef *jsArrayItems = malloc(sizeof(JSValueRef) * size);
             int count = 0;
             for (GKPlayer * player in players) {
                 GKScore *score = [scores objectAtIndex:count];
                 jsArrayItems[count++] = NSObjectToJSValue(gctx,
                                                           @{
                                                             @"alias": player.alias,
                                                             @"displayName": player.displayName,
                                                             @"playerID": player.playerID,
                                                             @"isFriend": @(player.isFriend),
                                                             @"category": score.category,
                                                             @"date": score.date,
                                                             @"formattedValue": score.formattedValue,
                                                             @"value": @(score.value),
                                                             @"rank": @(score.rank)
                                                             });
             }
             jsScores = JSObjectMakeArray(gctx, count, jsArrayItems, NULL);
         }
         JSValueRef params[] = { jsScores, JSValueMakeBoolean(gctx, error) };
         [scriptView invokeCallback:callback thisObject:NULL argc:2 argv:params];
         JSValueUnprotectSafe(gctx, callback);
     }];
}

I'm not a OC developer , so my code is too bad to pull request. I hope somebody could implement it and I think it's very useful.

Thanks.

finscn avatar Feb 20 '14 19:02 finscn

3 add GKGameCenterControllerDelegate & showGameCenter

// showGameCenter()
EJ_BIND_FUNCTION( showGameCenter, ctx, argc, argv ) {
    if(viewIsActive ) { return NULL; }
    if( !authed ) { NSLog(@"GameKit Error: Not authed. Can't show GameCenter."); return NULL; }

    GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init];
    if (gameCenterController != nil) {
        viewIsActive = true;
        gameCenterController.gameCenterDelegate = self;
        gameCenterController.viewState = GKGameCenterViewControllerStateDefault;
        [scriptView.window.rootViewController presentModalViewController: gameCenterController animated: YES ];
    }

    return NULL;
}

finscn avatar Feb 21 '14 10:02 finscn

4 some options to retrieveScores : timeScope: 0 1 2 friendsOnly : boolean withLocalPlayer: boolean localPlayerOnly: boolean start: int end: int

            if (withLocalPlayer){
                // Notice: Append the localPlayer's score-info to the array.
                //         So the array.length == (end-start+1)+1
                GKScore* localPlayer=leaderboardRequest.localPlayerScore;
                [identifiers addObject:localPlayer.playerID];
                [scoreList addObject:localPlayer];
            }

finscn avatar Feb 27 '14 09:02 finscn

5 supports game center's score.context. from Apple SDK document

The context property is stored and returned to your game, but is otherwise ignored by Game Center. It allows your game to associate an arbitrary 64-bit unsigned integer value with the score data reported to Game Center. You decide how this integer value is interpreted by your game. For example, you might use the context property to store flags that provide game-specific details about a player’s score, or you might use the context as a key to other data stored on the device or on your own server. The context is most useful when your game displays a custom leaderboard user interface.

// reportScore( category, score, [contextNum], cb )
EJ_BIND_FUNCTION( reportScore, ctx, argc, argv ) {
    if( argc < 2 ) { return NULL; }
    if( !authed ) { NSLog(@"GameKit Error: Not authed. Can't report score."); return NULL; }

    NSString *category = JSValueToNSString(ctx, argv[0]);
    int64_t score = JSValueToNumberFast(ctx, argv[1]);
    uint64_t contextNum=0;
    JSObjectRef callback = NULL;

    if( argc > 3) {
        contextNum = JSValueToNumberFast(ctx, argv[2]);
        if (JSValueIsObject(ctx, argv[3])){
            callback = JSValueToObject(ctx, argv[2], NULL);
        }
    }else if( argc == 3 ) {
        if (JSValueIsObject(ctx, argv[2])){
            callback = JSValueToObject(ctx, argv[2], NULL);
        }else{
            contextNum = JSValueToNumberFast(ctx, argv[2]);
        }
    }
    if(callback){
        JSValueProtect(ctx, callback);
    }

    GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
    if( scoreReporter ) {
        scoreReporter.value = score;
        scoreReporter.context= contextNum;
        [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
            if( callback ) {
                JSContextRef gctx = scriptView.jsGlobalContext;
                JSValueRef params[] = { JSValueMakeBoolean(gctx, error) };
                [scriptView invokeCallback:callback thisObject:NULL argc:1 argv:params];
                JSValueUnprotectSafe(gctx, callback);
            }
        }];
    }

    return NULL;
}

the context in game center manager:

2014-03-12 6 39 57

this value could help game developer to save/sync user's best-score and check whether is a cheat-score.

finscn avatar Mar 12 '14 10:03 finscn

Several things in this post, but +1 for the last item (score.context).

austinrussell avatar Oct 09 '14 02:10 austinrussell