Sign in

Unity

This document will guide you on your way to integrate SCILL into your Unity based game.

In this guide we will only briefly describe each step. If things are unclear, then please have a look at Getting started with SCILL where we describe each step in more detail.

Let’s get started!

What you want, is challenges that track progress in real time. The basic concept of a challenge is pretty simple: You set a goal and ask the user to reach that goal within a certain period. Goals must be measurable with one simple integer. Every active challenge has a current state which is also an integer. A simple challenge for example is: “Invite 5 other users”. The goal here is 5, the current state will be 0 when the challenge is activated. After the user invited one other user, the current state will be 1.

You don’t want to expose the API key in client applications, as the API key gives a lot of power that you don’t want to share with your users. Therefore, it’s required to generate an access token based on your API key and some sort of user identification.

Generate Access Token

Generating an access token should be done in the backend, as this is the only place you can securely protect from your users. If you want to generate the access token in your client application you can skip this step and proceed with Sending Events.

If you already have a backend (i.e. HTTP server or Cloud Functions like Lambda or Google Cloud Functions) it’s easy to create such a backend webservice. If you don’t have any backend for your game, we suggest using Google Firebase which is very cheap and Cloud Functions can be easily created using JavaScript (NodeJS) or PHP.

Please note

The example code provided in this guide already contains our public demo API-Key that you can use to get started. The SCILL team has preconfigured some basic challenges that are used in this guide to get you started quickly. At any time you can sign up for free to create your own products, API keys and customize your challenges.

NodeJS

If your backend runs on NodeJS you can integrate sending events like this:

Step 1: Install JavaScript SDK

npm install @scillgame/scill-js --save

You will now have installed our NPM module and can use it in your NodeJS based code. Please upgrade the package regulary to get updates and bug fixes.

Step 2: Initialize module

Add this to the head of your NodeJS code or cloud function.

Importing SCILL class
const scill = require('@scillgame/scill-js');

Congratulations! You have configured a SCILL class that can be used to send events and generate access tokens. Refer to the API-Reference to learn what you can do with it.

Step 3: Getting access token

Please note

This step is only required if your user facing code is running in an unsecure client environment like a game, an iOS or Android app or a JavaScript based client web application (SPA with Angular, React, Vue, …). If your web site is created on the server side - i.e. with PHP you don’t need to create an access token. In this case, just use your API key (or the API key provided in this example).

You don’t want to expose User IDs and/or the API-key in unsecure environments as this would allow anyone with some basic knowledge of the browsers web console to extract those and being able to send requests without your permission. To secure this, you create an access token for each user for each client session.

In your NodeJS backend you need to provide a webservice that your client can call to get an access token. You will most likely already have some sort of webservice so this is just a very basic example code based on a simple express server.

NodeJS server example
// This example implements a NodeJS backend receiving sessions from your client and asking SCILL
// to generate an access token for the userId "encoded" in/via the session.

const express = require('express');
const bodyParser = require('body-parser');
const scill = require('scill');

// Generate an instance of the SCILL SDK with example API-Key
const auth = scill.getAuthApi('ai728S-1aSdgb9GP#R]Po[P!1Z(HSSTpdULDMUAlYX');

const app = express();
const port = 80;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/scill/generate-access-token', (req, res) => {
    // In this example we use a session sent to this endpoint from the client to extract a user id from the session
    // How you implement that is up to you and depends on your user system
    const session = req.body.session;
    const userId = '1234';

    // Call SCILL backend to generate an access token encoding user and API-Key
    return auth.generateAccessToken({
        userId: userId
    }).then(accessToken => {
        // return access token
        return res.send(accessToken);
    });
});

app.listen(port, () => {
    console.log('Example app listening at http://localhost:${port}');
});

The SCILL JavaScript SDK function generateAccessToken is used to generate an access token based on the user id you are providing. What happens here is that SCILL receives your API-Key together with the user id and then creates as well as signs a JWT token.

More info on this topic can be found in the Authentication document.

Please note: A cheater/hacker only needs to get his hands on userid and your api key to send events “on your behalf”. Everything else is public information and can be found in this documentation (i.e. SCILL endpoints to send events to).

Whatever your implementation looks like, make sure that you follow these basic rules:

  • Don’t expose any part of the access token generation to the user, especially the api key and the userid
  • Don’t think that any sort of client side “obfuscation” does help in any way - hackers overcome these things in minutes
  • Your design must be secure by design. If you follow this guide it definitely will be.
  • All communication must be done via HTTPS

PHP

If your backend runs on PHP you can integrate sending events like this:

Step 1: Install PHP SDK

composer require scill/scill-php

If you don’t use composer in your project you can also download the PHP SDK from Github and import the classes manually into your project.

Step 2: Initialize library

Use autoload.php to initialize PHP library and setup an instance of the SCILLClient class by providing your API key.

Getting SCILL Client instance
<?php
    require_once(__DIR__ . '/vendor/autoload.php');

    // Setup SCILL Events client instance    
    $scillClient = new \SCILL\SCILLClient('ai728S-1aSdgb9GP#R]Po[P!1Z(HSSTpdULDMUAlYX');
    $scillEvents = $scillClient->getEventsClient();    
?>

Congratulations! You have set up an instance of the SCILL SDK class, that can be used to send events and generate access tokens. Refer to the API-Reference to learn what you can do with it.

Step 3: Generate access token

You don’t want to expose User IDs and/or the API-key in unsecure environments as this would allow anyone with some basic knowledge of the browsers web console to extract those and being able to send requests without your permission. To secure this, you create an access token for each user for each client session.

This example is based on the Slim Framework to quickly create REST-APIs with PHP to get you an idea how to implement it.

Slim route to generate access token
<?php
    $app->get('/scill/generate-access-token', function (Request $request, Response $response) {
    
        // In this example we expect that the client sends us a session id that we decode to receive a user id
        // This could also be a JWT token or a Steam Session. Whatever it is, it's your job to receive an encoded
        // user auth from the client, to decode it into a user id
        $params = $request->getQueryParams();
        $userId = null;
        if (!$params || !$params["session"]) {
            $response->getBody()->write('No session provided');
            return $response;
        } else {
            // Decode the session - in this example we just hardcode it
            $userId = "1234";
        }
    
        // Create the request to generate an access token
        /** @var \SCILL\SCILLClient $scill */
        $scill = $this->get('scill');
    
        $payload = new \SCILL\Model\ForeignUserIdentifier();
        $payload->setUserId($userId);
    
        // Run the request and if everything worked fine we can return the access token to the client
        try {
            $result = $scill->getAuthClient()->generateAccessToken($payload);
            $accessToken = $result->getToken();
            $result = array(
                "success" => true,
                "token" => $accessToken
            );
            $response->getBody()->write(json_encode($result));
        } catch(\SCILL\ApiException $exception) {
            $result = array(
                "success" => false,
                "error" => $exception->getMessage()
            );
            $response->getBody()->write(json_encode($result));
        }
        return $response;
    });
?>

The function generateAccessToken is used to generate an access token based on the user id you are providing. What happens here is that SCILL receives your API-Key together with the user id and then creates as well as signs a JWT token.

More info on this topic can be found in the Authentication document.

Please note: A cheater/hacker only needs to get his hands on userid and your api key to send events “on your behalf”. Everything else is public information and can be found in this documentation (i.e. SCILL endpoints to send events to).

Whatever your implementation looks like, make sure that you follow these basic rules:

  • Don’t expose any part of the access token generation to the user, especially the api key and the userid
  • Don’t think that any sort of client side “obfuscation” does help in any way - hackers overcome these things in minutes
  • Your design must be secure by design. If you follow this guide it definitely will be.
  • All communication must be done via HTTPS

Sending events

SCILL requires events sent for every action that happens in your game and that is related to challenges that you have created in the Admin Panel. If you have created a challenge for Kills, your game needs to send kill-enemy events to the SCILL cloud.

We suggest you send events from the backend/server if you have one. If your game is a single player game without any server component, you’ll want to send events from the client. Please note, that hackers/developers could reverse engineer that pipeline sending events without your permission. This can only be prevented by having server side code.

As there are so many different ways of adding events to your game, we just give a simple example in C# code you can add to your Unity game or Photon Engine in the server:

Sending kill-enemy event
var eventsApi = _scillClient.GetEventsApi();
var payload = new EventPayload
{
    user_id = "1234",
    session_id = "123456",
    event_name = "kill-enemy",
    event_type = "single",
    meta_data = new EventMetaData
    {
        amount = 1,
        weapon_used = "AK47",
        map_name = "de_dust",
        enemy_type = "Terrorist",
        kill_type = "Headshot",
        player_character = "Counter Terrorist"
    }
};
var response = eventsApi.SendEvent(payload);
if (response.status == 200)
{
    // Event sent
}

Events are structured data that we have defined for many different games and applications. Every event has a predefined list of properties send with the meta_data object. The more data you provide here the more detailed challenges can be. In the Admin Panel you setup challenges and define values for these properties.

For example, a basic challenge: Kill 10 enemies in 5 minutes is easy and requires 10 kill-enemy events to be sent within the time period from activating the challenge. If user achieves this, the challenge will be marked as won and user can claim the reward. If challenge is lost, then the challenge is either removed for the user or if it’s set to be repeatable user will have another chance of activating it.

However, perhaps you also want to create a challenge: Kill 10 terrorists with AK47 on map de_dust in 5 minutes. Progress is only tracked for kill-enemy events, that have weapon_used == "AK47" and map_name == "de_dust" and enemy_type == "Terrorist".

We have predefined these event typed for two reasons:

  • Giving you piece of mind: Don’t scratch your head to find the best data structure for events for games and applications
  • Preparing Game Analytics (coming soon)

Of course events can also be sent using our JavaScript or PHP SDKs or by sending REST-API calls without touching our SDKs. You can authenticate the event by either using your API key (in the backend) or the access token generated earlier for this specific user.

More info on Events and different Event types can be found in the API Reference.

Adding SCILL to your game

Now that we have prepared the backend to generate access tokens, we can finally add SCILL challenges to your game.

Step 1: Add SCILL SDK

SCILL provides a C# SDK that you can add in many different way to your Unity application:

  • Clone the Github Repo into your Unity Asset Folder
  • Download SCILL SDK Unitypackage and import it using “Add custom package” in Unity.
  • Download from the Assetstore (coming soon)
  • Clone the Github Repo, build Solution and Copy & Paste DLL files in your Unity Asset Folder.

You can find the C# SDK here: https://github.com/scillgame/scill-csharp

Once you have added the SCILL SDK to your Unity project, you can use SCILL like this:

Step 2a: Retrieve access token from backend

Before you can use the SCILL SDK in your client, you have to retrieve an access token. In this guide, you already have prepared your backend for this in the previous step that we now consume in the client. Please note: This example should just give you an idea how this could look like and that it is consistent to the example backend created in this guide before. Depending on your framework and codebase your actual implementation might be different - in some cases more, in some cases less.

Retrieve access code from backend
public class Challenges : MonoBehaviour
{
    private string _appId = "YOUR_APP_ID"; 
    private SCILLClient _scillClient;
    private string _accessToken;
    private ChallengesApi _challengesApi;

    void Start()
    {
        _scillClient = new SCILLClient();
        StartCoroutine(GetAccessToken());
    }

    string GetSessionId()
    {
        // This is just a hardcoded value. In realtiy, you will need to provide some way to sending your session or
        // encoded user id to the backend - you don't want users to generate an access token for other users, so this
        // must be secure.
        return "123456"
    }
 
    IEnumerator GetAccessToken() {
        // Generate request for generating an access token using the sessionid we have in the game (for example Steams
        // session id)
        UnityWebRequest www = UnityWebRequest.Get("https://example.release.app.scillplay.com/scill/generate-access-token?environment=development&sessionid=" + GetSessionId());
        yield return www.SendWebRequest();
 
        if (www.result != UnityWebRequest.Result.Success) {
            Debug.Log(www.error);
        } else {
            // Show results as text
            AccessTokenResponse accessTokenResponse = JsonConvert.DeserializeObject<AccessTokenResponse>(www.downloadHandler.text);
            
            Debug.Log("Received access token: " + accessTokenResponse.token);
            _accessToken = accessTokenResponse.token;
            
            // Now that we have generated and stored an access token we can load personal challenges
            _challengesApi = _scillClient.GetChallengesApi(_accessToken);
            UpdateChallenges();
        }
    }

    void UpdateChallenges() {
        // Load Challenges
    }
}

This code basically just calls your backend sending the session id after a user signed up. The backend we provided earlier decodes the session id to retrieve the user id and sends that to SCILL to generate an access token. Then a SCILL client SDK instance is created using this access token and a product identifier (see Admin Panel).

Please note: As you can see in this example code, on client side neither the userid nor the api key is used in the code and therefore cannot be extracted in any way from the client. All things that need to be secured are running on servers and not on the client side.

Step 2b: Generate access token in game

If you skipped implementing a backend to generate the access token you can also generate the access token in your application. However, this exposes the API-Key in your application bundle which is not recommended as developers/hackers can decompile your binary and might be able to retrieve your API-key.

You can generate the access token with the AuthApi class:

Generating Access Code in Client
AuthApi authApi = _scillClient.getAuthApi("YOUR_API_KEY");
AccessToken response = authApi.GenerateAccessToken(foreignUser);
string accessToken = response.token;

foreignUser is the user id. The user id must stay the same as this connects data within SCILL to your user. However it’s best not to use an “auto increment” value from your database that allows hackers but it should be some sort of unique id.

Step 3: Retrieve challenges

You finally have an instance of the SCILL client sdk prepared, ready to go in your web app. Let’s query the challenges available for the product referenced earlier. Once you have signed up for a free account, you can create your own challenges and battle passes.

Loading challenges
    async void UpdateChallenges()
    {
        // In this example, personal challenges are loaded for the current app and challenge items are created
        // to a list in the User Interface

        int index = 0;
        List<ChallengeCategory> categories = await _challengesApi.GetPersonalChallengesAsync(_appId);
        foreach (ChallengeCategory category in categories)
        {
            // We just add all challenges to the list and skip grouping them in challenges for easier UI
            foreach (Challenge challenge in category.challenges)
            {
                GameObject listItem = Instantiate(challengeItemPrefab);
                float spawnY = index * -100;
                
                //newSpawn Position
                Vector3 pos = new Vector3(0, spawnY, 0);
                listItem.transform.localPosition = pos;

                ChallengeItem challengeItem = listItem.GetComponent<ChallengeItem>();
                challengeItem.challenge = challenge;
                
                listItem.transform.SetParent(this.gameObject.transform, false);

                index++;
            }
        }
    }

That’s it! You now have a collection of ChallengeCategory objects.

A typical rendering of challenges as we do in SCILL Play might look like this:

As only activated challenges will track progress and count against your included volume, they first need to be unlocked and activated. In this implementation the user has to unlock and activate a challenge manually. However, depending on your product you might also activate challenges automatically.

Step 4: Monitor challenge progress in real time

It’s important that users get real-time feedback about the current status of their challenges. We provide Webhook for this. You define a URL in your backend that is called from the SCILL Backend once anything changes with a challenge, i.e. if the progress increased, or the type changed (i.e. got canceled or activated). It’s up to you to implement a realtime update system based on this.

One way of doing it is providing a Websocket in your backend that notifies the respective client whenever it gets triggered. On client side then updates of the data area easy to do:

Implementing Webhook target in NodeJS
app.post('/scill/wh/challenges', (req, res) => {
    console.log("Webhook called", req.query, req.body);
    const data = req.body;
    if (data) {
        // Get user id from webhook payload
        const userId = data.new_challenge.user_id;
        if (clients[userId]) {
            // If user client is connected to our websocket, send the webhook payload
            clients[userId].send(JSON.stringify(data));
        }
    }

    res.send({message: 'OK'});
});

Of course there a multiple ways to update the client in real time. Another way is using realtime databases or pub/sub patterns provided by Firebase (Firestore or Realtime Database), pubsub.com or pusher.com.

Step 5: Unlock and activate a challenge

Challenges need to be unlocked and activated in order to track progress. Depending on your use case you can unlock and activate in one step (by calling the functions in subsequent order). The idea behind a two-step process is that some challenges might need to be purchased by the user. In this case, after purchasing the challenge you unlock them, but users still can decide when they want to start playing them.

Unlocking a challenge
    async Task<bool> UnlockChallenge(Challenge challenge)
    {      
        var response = await _challengesApi.UnlockPersonalChallenge(_appId, challenge.challenge_id);
        return (response.status == 200);
    }

    async Task<bool> ActivateChallenge(Challenge challenge)
    {
        var response = _challengesApi.ActivatePersonalChallenge(_appId, challenge.challenge_id);
        return (response.status == 200);
    }

    async void OnChallengeItemClicked(ChallengeItem challengeItem) {
        if (await UnlockChallenge(challengeItem.challenge)) {
            if (await ActivateChallenge(challengeItem.challenge)) {
                // Challenge unlocked and activated
            }
        }
    }

In this example, the challenge that the user selected is unlocked and automatically activated. Now, this challenge tracks progress, and it will show the current status (i.e. counter is 0).

Now comes the fun part: Send an event using the NodeJS backend that we prepared. And within 1-3 seconds, the counter in your application for this challenge will be +1. Proceed with sending events until you reached the goal of the challenge (in this demo it’s 5). Once you have send 5 events, your challenge will change its type to unclaimed.

Step 6: Claiming rewards

Once a challenge has been successfully achieved, it is ready to be claimed. You can claim the reward using this API.

Claiming a reward
    async bool ClaimChallenge(Challenge challenge)
    {
        var response = await _challengesApi.ClaimPersonalChallengeRewardAsync(_appId, challenge.challenge_id);
        if (response.status == 200) {
            // It's better to unlock items server side, but this is the right place to unlock the item for the user
            // if done client side (in the app)
            UnlockIngameItem(challenge.reward);
        }        
    }

    async void OnChallengeClaimClicked(ChallengeItem challengeItem) {
        await ClaimChallenge(challengeItem.challenge);
    }

When the claim API is called, three things happen:

  1. It returns with a boolean value to indicate that the challenge is successfully claimed
  2. If you have set up a Webhook this is called

In either of these places you can now unlock the reward for the user. If you have set up a Webhook, the Websocket notification shoule be sent after the Webhook returns. That means, that you can use the Webhook to unlock items for the user in the backend. Once the request is completed, you can safely update the UI for the user by reloading the users' status from the backend.

An example: The user has successfully completed a challenge and should get a new weapon added to his inventory as a reward. Let’s walk trough the process step by step:

  • The user presses the “Claim” button, and your client sends a claim request to the SCILL backend.
  • SCILL sets the challenge to be claimed, calls your Webhook and waits until this is processed
  • Your backend receives the Webhook and adds the weapon to the users inventory (server side) and then returns HTTP status 200
  • SCILL received Webhook status 200 - which means everything is fine and returns the claim challenge request
  • The client now reloads the users inventory from the server (which should now show the new weapon)

Step 7: Sign up to create challenges

You have completed the loop! Your users will now be motivated to invite other users and will be rewarded if they do so. Wasn’t that easy? But that’s just the beginning. The possibilities with challenges are endless. And they are an easy solution to motivate your users to continue doing actions in your app or game, thus prolonging the time they spend in your app or game.

Sign up for free to create or modify challenges and battle passes.

Fully working example

Tanks (Unity)

We took this simple but fun open source Unity tank game and added challenges to it. Use our example code as a starting point for your own Unity based project.

Learn more