Advertisement

PlayFab Advanced Cloud Script

Director of Marketing at PlayFab, Inc.
Oct. 15, 2015
Advertisement

More Related Content

Advertisement

PlayFab Advanced Cloud Script

  1. a Platform-as-a-Service company Advanced Cloud Script Brendan Vanous Senior Developer Success Engineer
  2. Overview • “Getting Started with Cloud Script” Recap • Recap: Calling Cloud Script • Scenarios and Updates • Resources October 15, 2015 1
  3. “Getting Started with Cloud Script” Recap • Previous webinar “Getting Started with Cloud Script” • What is Cloud Script • Creating scripts • Revision Control • Webhooks • Debugging • Usage Scenarios • Limitations* * Updates! October 15, 2015 2
  4. [Basics] Calling a Cloud Script • Query for the active Cloud Script URL POST /client/GetCloudScriptUrl HTTP/1.1 Host: {{TitleID}}.playfabapi.com Content-Type: application/json X-Authentication:{{SessionTicket}} { "Testing": true } • Response { "code": 200, "status": "OK", "data": { "Url": "https://{{TitleID}}.playfablogic.com/1/test" } } October 15, 2015 3
  5. [Basics] Calling a Cloud Script • Launch the Cloud Script POST /1/prod/client/RunCloudScript HTTP/1.1 Host: {{TitleID}}.playfablogic.com Content-Type: application/json X-Authentication: {{SessionTicket}} { "ActionId": "onLevelComplete“, "ParamsEncoded": "{"level":1}" } • Response • Standard response code • Handler, version, revision • Custom response data • Log (for debugging) • Runtime October 15, 2015 4 { "code": 200, "status": "OK", "data": { "ActionId": "onLevelComplete", "Version": 1, "Revision": 43, "Results": { "rewards": [ { "PlayFabId": "14141CB68CBE4956", "Result": false, "ItemId": "TestItem2", "Annotation": "Given by completing level 1", "UnitPrice": 0 } ] }, "ResultsEncoded": "{"rewards":[{"PlayFabId":"14141CB68CBE4956","Result":false,"ItemId":"TestItem2","Annotati on":"Given by completing level 1","UnitPrice":0}]}", "ActionLog": "", "ExecutionTime": 0.1680013 } }
  6. In GitHub • Examples of the essentials • AsynchronousMatchmaker – Companion to our blog post • BasicSample – Provided with all new titles • Photon-Cloud-Integration – Shows using custom auth and webhooks • Rewards – Granting items and virtual currency to players October 15, 2015 5
  7. BasicSample – Hello World • Because there’s always “Hello World”! handlers.helloWorld = function (args) { // "currentPlayerId" is initialized to the PlayFab ID of the player logged-in on the game client. // Cloud Script handles authenticating the player automatically. var message = "Hello " + currentPlayerId + "!"; // You can use the "log" object to write out debugging statements. The "log" object has // three functions corresponding to logging level: debug, info, and error. log.info(message); // Whatever value you return from a CloudScript handler function is passed back // to the game client. It is set in the "Results" property of the object returned by the // RunCloudScript API. Any log statments generated by the handler function are also included // in the "ActionLog" field of the RunCloudScript result, so you can use them to assist in // debugging and error handling. return { messageValue: message }; } October 15, 2015 6
  8. [Basics] Scenario 1: Updates October 15, 2015 7 • Updating a title is more than just the executable • Changing items • User data • Save values • User Internal Data to track player data version • Have an “onLogin” handler • Calls current version handler (“updateToRevN”), passing in player data version • If not at this version, call previous version handler • Once returned, update for latest version • onLogin then sets player version Yeah, don’t do that…
  9. Updating the User Version • Roll the changes into one call handlers.playerLogin = function (args) { var currentVersion = "7"; var versionKey = "userVersion"; var playerData = server.GetUserInternalData({ PlayFabId: currentPlayerId, Keys: [versionKey] }); var userVersion = playerData.Data[versionKey]; if (userVersion != currentVersion) { UpdateToVersion7(userVersion, currentPlayerId); } var updateUserDataResult = server.UpdateUserInternalData({ PlayFabId: currentPlayerId, Data: { userVersion: currentVersion } }); October 15, 2015 8 log.debug("Set title version for player " + currentPlayerId + " to " + currentVersion); } function UpdateToVersion7(userVersion, userPlayFabId) { // Here's where you'd update the player's data // changing any items or stat values needed for this version return null; }
  10. [Basics] Scenario 2: Player Actions October 15, 2015 9 • For competitive games • Resolution of player action must be server authoritative • Player selects moves, which are sent to Cloud Script • Evaluate moves • Are they possible? • Does the player have the right items? • etc. • On failure, write info to User Internal Data • Use cheat tracking to have cheaters play together
  11. BasicSample – Validating Player Action • Has enough time passed? function processPlayerMove(playerMove) { var now = Date.now(); var playerMoveCooldownInSeconds = 15; var playerData = server.GetUserInternalData({ PlayFabId: currentPlayerId, Keys: ["last_move_timestamp"] }); var lastMoveTimestampSetting = playerData.Data["last_move_timestamp"]; if (lastMoveTimestampSetting) { var lastMoveTime = Date.parse(lastMoveTimestampSetting.Value); var timeSinceLastMoveInSeconds = (now - lastMoveTime) / 1000; log.debug("lastMoveTime: " + lastMoveTime + " now: " + now + " timeSinceLastMoveInSeconds: " + timeSinceLastMoveInSeconds); if (timeSinceLastMoveInSeconds < playerMoveCooldownInSeconds) { log.error("Invalid move - time since last move: " + timeSinceLastMoveInSeconds + "s less than minimum of " + playerMoveCooldownInSeconds + "s.") return false; } } October 15, 2015 10
  12. BasicSample – Validating Player Action • If so, update the statistics and the timestamp var playerStats = server.GetUserStatistics({ PlayFabId: currentPlayerId }).UserStatistics; if (playerStats.movesMade) playerStats.movesMade += 1; else playerStats.movesMade = 1; server.UpdateUserStatistics({ PlayFabId: currentPlayerId, UserStatistics: playerStats }); server.UpdateUserInternalData({ PlayFabId: currentPlayerId, Data: { last_move_timestamp: new Date(now).toUTCString() } }); return true; } October 15, 2015 11
  13. [Basics] Scenario 3: Rewards October 15, 2015 12 • Determination of rewards for player actions • Player actions are sent to Cloud Script • Evaluate actions • Has enough time passed? • Are the values reasonable? • etc. • On failure, write info to User Internal Data • Use cheat tracking to decide how to manage them
  14. Rewards – onLevelComplete • Optional: Define rewards in Title Internal Data var LevelRewards = [ ["TestItem1"], ["TestItem2"], ["TestItem3"], ["TestItem1", "TestItem2"], ["TestItem2", "TestItem2"], ["TestItem3", "TestItem3"] ] handlers.onLevelComplete = function(args) { var levelNum = args.level; // Do some basic input validation if(levelNum < 0 || levelNum >= LevelRewards.length) { log.info("Invalid level "+levelNum+" completed by "+currentPlayerId); return {}; } October 15, 2015 13
  15. Rewards – onLevelComplete • Also tracking level completion var levelCompleteKey = "LevelCompleted"+levelNum; // Get the user's internal data var playerInternalData = server.GetUserInternalData( { PlayFabId: currentPlayerId, Keys: [levelCompleteKey] }); // Did they already complete this level? if(playerInternalData.Data[levelCompleteKey]) { log.info("Player "+currentPlayerId+" already completed level "+levelNum); return {}; } October 15, 2015 14
  16. Rewards – onLevelComplete • Grant the reward var rewards = LevelRewards[levelNum]; var resultItems = null; if(rewards) { // Grant reward items to player for completing the level var itemGrantResult = server.GrantItemsToUser( { PlayFabId: currentPlayerId, Annotation: "Given by completing level "+levelNum, ItemIds: rewards }); resultItems = itemGrantResult.ItemGrantResults; } October 15, 2015 15
  17. Rewards – onLevelComplete • And finally update the level tracking and return the information on the reward // Mark the level as being completed so they can't get the reward again var saveData = {}; saveData[levelCompleteKey] = "true"; server.UpdateUserInternalData( { PlayFabId: currentPlayerId, Data: saveData }); // Return the results of the item grant so the client can see what they got return { rewards: resultItems }; } October 15, 2015 16
  18. [Basics] Scenario 4: Messaging October 15, 2015 17 • Push Messages • Require Server authority • Player attacking another player’s base • Player beat a friend’s score • etc. • Player-to-player messages • Write them to Shared Group Data, using ID of PlayFabId • Arbitrary messages, with arbitrary payloads
  19. Using the PlayFab ID as the Key, Part 1 • Really, you’d expect this to work, wouldn’t you? handlers.messageToPlayer = function (args) { var messageGroupId = args.toPlayerId + "_messages"; server.UpdateSharedGroupData( { "SharedGroupId": messageGroupId, "Data" : { currentPlayerId : args.messageText } } ); } October 15, 2015 18
  20. Using the PlayFab ID as the Key, Part 1 • But yeah, it doesn’t { "code": 200, "status": "OK", "data": { "Data": { "currentPlayerId": { "Value": "Hi there!", "LastUpdated": "2015-10-14T07:25:22.749Z", "Permission": "Private" } } } } October 15, 2015 19 Really, you need to watch: https://www.destroyallsoftware.com/talks/wat
  21. Using the PlayFab ID as the Key, Part 2 • Here’s how to do it handlers.messageToPlayer = function (args) { var messageGroupId = args.toPlayerId + "_messages"; var dataPayload = {}; var keyString = currentPlayerId; dataPayload[keyString] = args.messageText; server.UpdateSharedGroupData( { "SharedGroupId": messageGroupId, "Data" : dataPayload } ); } October 15, 2015 20
  22. Consuming the Message • Space is limited – once the message is received, remove it handlers.checkMessages = function (args) { var messageGroupId = currentPlayerId + "_messages"; var messageList = server.GetSharedGroupData({"SharedGroupId": messageGroupId}); var dataPayload = {}; for (var key in messageList.Data) { if (messageList.Data.hasOwnProperty(key)) { var message = JSON.parse(messageList.Data[key].Value); // Take action on the key - display to user, etc. var keyString = key; dataPayload[keyString] = null; } } server.UpdateSharedGroupData({ "SharedGroupId": messageGroupId, "Data" : dataPayload }); } October 15, 2015 21
  23. [Basics] Scenario 5: Extra Leaderboard Columns October 15, 2015 22 • Shared Group Data • Storage not tied to a particular player • Readable by any player (permission set Public) • Data value per player • Key is the PlayFab ID of the player • Write extra data when updating score • One API call to read players in display
  24. [New and Improved] Scenario 5: Web API Calls • Whether your own or a third party • The http function of Cloud Script allows for secure calls • Enables integration with any third-party Web API • The question to ask is, what are your needs? October 15, 2015 23
  25. Option 1: Use Session Ticket • Using basic authentication mechanism from PlayFab • Use Server/AuthenticateSessionTicket to validate handlers.externalCall = function (args) { var url = "https://api.yourdomainhere.com/playfab/someaction"; var method = "post"; var obj = {"dataValue1":value1, "dataValue2":value2}; var contentBody = JSON.stringify(obj); var contentType = "application/json"; var headers = {}; headers["Authorization"] = args.sessionTicket; var response = http.request(url,method,contentBody,contentType,headers); } October 15, 2015 24
  26. Option 2: OAuth2 • More secure solutions may require OAuth2, or similar • Which, as a Web API, we support – no problem • Normal process for OAuth2-type systems • Use a locally stored secret key to request a token from the OAuth2 service • Use that token to access secure calls • Recommendation: Short token lifetime October 15, 2015 25
  27. Obtaining the Bearer Token • Use a secret key to obtain a client-specific token • Optional: Store the secret key in PlayFab, and the client never even sees it function GetAccesToken(secretKey) { var url = "https://api.yourdomainhere.com/RequestToken"; var method = "post"; var contentBody = "token_type=Bearer"; var contentType = "application/x-www-form-urlencoded"; var headers = {}; headers["Authorization"] = "Basic "+secretKey; var response = http.request(url,method,contentBody,contentType,headers); var finalData = JSON.parse(response); var access_token = finalData["access_token"]; return access_token; } October 15, 2015 26
  28. Using the Bearer Token • Once you have the Bearer Token, use it to access custom functionality • Note: Do not store the Bearer Token for re-use – regenerate it each time handlers.externalCall = function (args) { var url = "https://api.yourdomainhere.com/playfab/someaction"; var method = "post"; var obj = {"dataValue1":value1, "dataValue2":value2}; var contentBody = JSON.stringify(obj); var contentType = "application/json"; var bearerAccessToken = GetAccessToken(args.secretKey); var headers = {}; headers["Authorization"] = "Bearer "+ bearerAccesToken; var response = http.request(url,method,contentBody,contentType,headers); } October 15, 2015 27
  29. Resources • PlayFab Cloud Script Tutorial • https://playfab.com/?post_type=pf_docs&p=1003 • GitHub • https://github.com/PlayFab/CloudScriptSamples • Dave & Buster’s usage • https://playfab.com/blog/dave-busters-goes-mobile-with-playfabs-help/ October 15, 2015 28
  30. QUESTIONS? Twitter @playfabnetwork

Editor's Notes

  1. 29
Advertisement