Making a game with NEO + Unity: Part 3

Tim Riley
5 min readSep 22, 2018

--

Welcome to part 3 of the ‘A-Z’ series on making a game for the NEO blockchain with the Unity game engine. In part 2 we setup our own private net and wired it up in our Unity scene. In part 3 we’ll set things up so that our players can enter their own private keys and start interacting with the NEO Blockchain.

We’ll be keeping each part simple as to demonstrate what the workflow looks like in its *entirety*. We’ll be going quickly over certain aspects, but feel free to comment on anything that isn’t clear and I’ll be happy to edit.

Disclaimer

At this point we’ll start adding more custom logic and interacting with the NEO blockchain. It should go without saying for anyone familiar with this space, but this app is for TEACHING PURPOSES ONLY. A game essentially works like a wallet in many ways and as a user and developer you should take great care when eventually connecting things to the mainnet or delivering your apps to your users. Most user facing projects in this space go through a thorough testing + auditing period to check for bugs and malicous code and I suggest you spend a lot of time working with the community and doing the same. This tutorial will only cover the basics, and we’ll be storing things like private keys unsafely in local storage for the sake of simplicity. DO NOT DO THIS IN PRODUCTION APPS.

Wallet Manager

The next bit gets a bit heavy into Unity specific workflows, so I’m going to save you some time with a prefab + script you should be able to import directly into your project. You can download it directly here. This script will manage the instance of your NEO wallet and help you create a new one if you don’t have one already. The reason I include it as is is to demonstrate the importance of having a good onboarding experience. Many crypto currency projects (and some games) require the user to do a LOT of learning to start to learn how to use the app. In the long term we all need to get better about how we onboard new users and try to do so with as little friction as possible -> the fewer the clicks and less studying a user has to do to start playing a game they’ve already invested their time downloading, the less likely they will be to immediately delete it when it starts demanding yet more of their time. Don’t make them think.

Ideally I’d like to remove the first screens entirely and simply drop the user directly into gameplay. The problem though is obviously with a bearer asset you have to be very clear when and how this is communicated; you won’t just be able to go into the backend and help them reset + retrieve their data later on if they forget or lose their key. A good median solution might be to have some initial portion of the game not require a wallet at all, and only prompt the user for this information once they’ve had a chance to better try out your app. You’re both free and encouraged to explore ways of doing this on your own :)

Sending Tokens

In a later tutorial we’ll dive more deeply into smart contracts, but for now we just want to get quickly interacting with the NEO blockchain. We’ll reward our player with currency whenever they achieve a score above some threshold. As GAS is in some ways the de facto currency on the NEO blockchain, we’ll use that.

When creating a real game you’ll have to contend with exploits, and doing this while trying to keep your game fun and the economy fluid is no easy task. We’re simplifying here for demonstration purposes. In a real online game you’d want multiple anti-cheat mechanisms, but ultimately in games we know we can never prevent all exploits. Someone somewhere determined enough will break your game. Or you’ll miss a critical bug. We try to find a balance by preventing the most common exploits and not losing sleep over the rest, and we often split the economies up in such a way as so other players’ experiences aren’t marred in the process.

Create a GameObject called NEORewardManager and child it next to your other NEO managers. Create a script called NEORewardManager and copy + paste the following code:

using System;
using System.Collections;
using UniRx;
using UnityEngine;

public class NEORewardManager : MonoBehaviour
{
[SerializeField] private NEOManager neoManager;
[SerializeField] private CompleteProject.PlayerHealth playerHealth;

[SerializeField] private int rewardThreshold = 50;

private bool isGameOver;

private void Update()
{
if(playerHealth.currentHealth <= 0 && !isGameOver)
{
StartCoroutine(OnGameOver());
isGameOver = true;
}
else if(playerHealth.currentHealth > 0 && isGameOver)
{
isGameOver = false;
}
}

private IEnumerator OnGameOver()
{
yield return new WaitForSeconds(1);
Time.timeScale = 0;

if(CompleteProject.ScoreManager.score >= rewardThreshold)
{
StartCoroutine(TrySendGAS(1));
}
else
{
Time.timeScale = 1;
}
}

private IEnumerator TrySendGAS(int amount)
{
yield return null;

try
{
var tx = neoManager.API.SendAsset(neoManager.MasterKeyPair, neoManager.PlayerKeyPair.Value.address, NEOManager.AssetSymbol, (decimal)amount);

if (tx == null)
{
Debug.LogError("Null Transaction returned");
Time.timeScale = 1;
}
else
{
Debug.Log("TX received, checking sync...");
Observable.FromCoroutine(SyncBalance).Subscribe().AddTo(this);
}
}
catch (NullReferenceException exception)
{
Debug.LogError("There was a problem...");
Time.timeScale = 1;
}
catch (Exception exception)
{
Debug.LogError("There was a problem...");
Time.timeScale = 1;
}
}

private IEnumerator SyncBalance()
{
yield return null;

try
{
//var balances = neoManager.API.GetAssetBalancesOf(neoManager.PlayerKeyPair.Value);
//neoManager.GASBalance.Value = balances.ContainsKey(NEOManager.AssetSymbol) ? balances[NEOManager.AssetSymbol] : 0;
//if (Mathf.Approximately((float)NEOManager.GASBalance.Value, GameDataSystem.Coins.Value))
//{
// Debug.Log("GAS transferred successfully");
//}
//else
//{
// Debug.LogWarning("Something's not right." +
// //"\nCoins: " + GameDataSystem.Coins.Value +
// "\nGAS: " + NEOManager.GASBalance.Value);
//}
Debug.Log("Balance synced!");
Time.timeScale = 1;
}
catch (NullReferenceException exception)
{
Debug.LogWarning("Something's not right." +
//"\nCoins: " + GameDataSystem.Coins.Value +
"\nGAS: " + neoManager.GASBalance.Value);
Time.timeScale = 1;
}
}
}

This system waits for a game over state and, if the score crosses a certain threshold, will transfer GAS from the master account to the local players account. Wire up the NEOManager and PlayerHealth by dragging and dropping the references in the scene. You can also set your reward threshold to whatever value you like; play around with it and see. On subsequent replays you’ll see the amount update to reflect the fact that the GAS was transferred correctly. Congratulations, we’ve now hooked up our game to include some basic interactions with NEO, yay :)

In upcoming tutorials we’ll dive more deeply into smart contracts and explore what else the system is capable of.

--

--