First Project with Avatars
This tutorial will introduce how the Genies Avatar SDK can be used for basic gameplay.
It will cover these topics:
- Creating a UI for users to login to their Genies accounts.
- Once logged in, spawning the user's global Avatar.
- Downloading and using the sample scenes.
- Connecting the Avatar to a character controller.
- Creating an NPC with a locally stored Avatar definition.
- Giving the NPC a custom animation to wave at the player.
Setup the Scene
The first step is to create a scene for the player to move around in and have a camera following it.
Open Unity Project
Follow the Getting Started tutorial for directions on how to create a Unity project with the Genies Avatar SDK installed.
Open a New Scene
In the Project window, find a folder named Scenes and double click the SampleScene asset to open it.

This is scene is included by default when creating a new Unity project. If for some reason your Unity project does not have this folder or asset, then create a new folder named Scenes and create a new scene named SampleScene.
Add the Ground Plane
Create a ground object using the Create > 3D Object > Plane option in the Hierarchy window.

Add a Free Look Camera
Create a camera using the Create > Cinemachine > Targeted Cameras > FreeLook Camera option in the Hierarchy window. Make sure to disable it in the Inspector window.
A FreeLook Camera is useful tool from the Cinemachine package for creating third person cameras that follow the player.
Create a Login UI
The next step is to create a login screen UI for the user to login to their Genies account.
Open the Simulator Window
Click the top-left dropdown menu in the Game window and select Simulator. Then select a mobile device.

Create a UI Canvas
In the Hierarchy window, select the Create > UI > Canvas option and name it Login Canvas. In the Inspector window, set the UI Scale Mode property to Scale With Screen Size and set the Reference Resolution to X: 1080 and Y: 1920.

Create UI
Add the following UI elements as children to the Login Canvas:
- (Optional) Panel object for a background
- Text object named
Status Text - Button object named
Instant Login Button - Button object named
Logout Button - Input Field object named
Email Input Field - Button object named
Submit Email Button - Input Field object named
OTP Input Field - Button object named
Submit OTP Button - Button object named
Resend OTP Button - Button object named
Signup Button

You may need to import the TextMeshPro packages if the text is not appearing. See the Common Issues page for a breakdown.
Create the Login Controller Script
Now the login screen needs logic so it calls the API methods to login the user to their Genies account.
Create a New Script
In the Hierarchy window, select the Login Canvas object. In the Inspector window, click the Add Component button near the bottom and then select the bottom option to New Script. Name the script LoginController.

Add Code to the Script
Open the LoginController script.
Initial Code
Start by adding this initial code to create object references for the UI elements:
using UnityEngine;
using Genies.Sdk;
using TMPro;
using UnityEngine.UI;
public class LoginController : MonoBehaviour
{
[Header("UI References")]
[SerializeField] private TMP_Text statusText;
[SerializeField] private Button instantLoginButton;
[SerializeField] private Button logoutButton;
[SerializeField] private TMP_InputField emailInputField;
[SerializeField] private Button submitEmailButton;
[SerializeField] private TMP_InputField otpInputField;
[SerializeField] private Button submitOTPButton;
[SerializeField] private Button resendOTPButton;
[SerializeField] private Button signupButton;
}
On Submit Email Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Submit Email" button
private async void OnSubmitEmail()
{
string email = emailInputField.text;
statusText.text = $"Email submitted: {email}";
(bool success, string errorMessage) = await AvatarSdk.StartLoginEmailOtpAsync(email);
if (success)
{
statusText.text = "OTP sent successfully. Please check your email.";
}
else
{
statusText.text = "Error sending OTP: " + errorMessage;
}
}
This function uses StartLoginEmailOtpAsync to submit the inputted email and start the OTP login process. If it is a valid email, then the user should receive a email with an OTP verification code.
On Resend OTP Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Resend OTP" button
private async void OnResendOTP()
{
statusText.text = "Resending OTP...";
(bool success, string errorMessage) = await AvatarSdk.ResendEmailCodeAsync();
if (success)
{
statusText.text = "OTP resent successfully. Please check your email.";
}
else
{
statusText.text = "Error resending OTP: " + errorMessage;
}
}
This function uses ResendEmailCodeAsync to resend a new OTP to the email that requested the initial OTP. It's good practice to have this functionality in case the first OTP failed to send or the user deleted the message by mistake.
On Submit OTP Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Submit OTP" button
private async void OnSubmitOTP()
{
string otpCode = otpInputField.text;
statusText.text = $"OTP submitted: {otpCode}";
(bool success, string errorMessage) = await AvatarSdk.SubmitEmailOtpCodeAsync(otpCode);
if (success)
{
statusText.text = "OTP verified successfully. User logged in.";
}
else
{
statusText.text = "Error verifying OTP: " + errorMessage;
}
}
This function uses SubmitEmailOtpCodeAsync to submit the inputted OTP verification code. If the code is correct, then it will sign in the user to their Genies account and create autherization credential tokens so that user can instant login the next time.
On Instant Login Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Instant Login" button
private async void OnInstantLogin()
{
statusText.text = "Attempting instant login...";
var result = await AvatarSdk.TryInstantLoginAsync();
if (result.isLoggedIn)
{
statusText.text = "Instant login successful.";
}
else
{
statusText.text = "Instant login failed.";
}
}
This function uses TryInstantLoginAsync to login using stored autherization credential tokens instead of doing the OTP workflow. This will only work if the user has signed in before with the OTP workflow.
On User Login Function
Add this function to the LoginController class:
// Triggered when the user successfully logs in
private void OnUserLogin()
{
statusText.text = "User logged in.";
this.gameObject.SetActive(false);
}
This function deactivates the login UI object to hide it once the user is logged in.
On Logout Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Logout" button
private async void OnLogout()
{
statusText.text = "Logging out...";
await AvatarSdk.LogOutAsync();
statusText.text = "Logged out.";
}
This function uses LogOutAsync to log out the user if they are logged in to their Genies account.
On Signup Function
Add this function to the LoginController class:
// Triggered when the user clicks the "Sign Up" button
public void OnSignUp()
{
Application.OpenURL(AvatarSdk.UrlGeniesHubSignUp);
}
This function uses the UrlGeniesHubSignUp parameter to open the Developer Portal signup website on the user's browser.
Start Function
Add this function to the LoginController class:
private void Start()
{
statusText.text = "Ready";
// Button listeners
submitEmailButton.onClick.AddListener(OnSubmitEmail);
submitOTPButton.onClick.AddListener(OnSubmitOTP);
logoutButton.onClick.AddListener(OnLogout);
instantLoginButton.onClick.AddListener(OnInstantLogin);
resendOTPButton.onClick.AddListener(OnResendOTP);
signupButton.onClick.AddListener(OnSignUp);
// Event listeners
AvatarSdk.Events.UserLoggedIn += OnUserLogin;
}
This function will add listeners to all the button click events and also to the GeniesSdkAvatar.Events.UserLoggedIn event which is fired when a user logs in.
On Destroy Function
Add this function to the LoginController class:
private void OnDestroy()
{
// Remove button listeners
submitEmailButton.onClick.RemoveListener(OnSubmitEmail);
submitOTPButton.onClick.RemoveListener(OnSubmitOTP);
logoutButton.onClick.RemoveListener(OnLogout);
instantLoginButton.onClick.RemoveListener(OnInstantLogin);
resendOTPButton.onClick.RemoveListener(OnResendOTP);
signupButton.onClick.RemoveListener(OnSignUp);
// Clean up event listeners
AvatarSdk.Events.UserLoggedIn -= OnUserLogin;
}
This function will remove all button and event listeners when the script is destroyed. It is good coding practice to cleanup event listeners when destroying an object to avoid memory leak.
Add the UI References
Save the code and return to Unity. In the Hierarchy window, select the Login Canvas object. Drag and drop all the needed objects from the Hierarchy window into the reference input fields in the Inspector window.

Test the Code
Enter Play mode. You should be able to login which will hide the login UI.

Add the Character Controller
The next step is to spawn the user Avatar and control it like a player. This will use the Avatar Starter sample scene for its useful prefabs and scripts.
Import the Avatar Starter Sample Scene
From the top menu, select Windows > Package Manager window. Then select Packages: In Project on the top left dropdown and also select the Genies Avatar SDK package on the left side menu.
Click the Samples tab and click the Import button next to the Samples Starter Scene sample.
![]()
Add the Character Controller Prefab
In the Project window, open the Assets > Samples > Genies Avatar SDK > X.Y.Z > Sample Starter Scene > Avatar Starter > Prefabs folder. Drag the AvatarSpawn prefab into the scene.

This character controller prefab has script components that will control the Avatar by moving and jumping using user input.
Enable Touch Controls
In the Hierarchy, select the AvatarSpawn object. In the Inspector, make sure the Enable Touch Controls property is enabled.

This property allows mobile devices to use UI joystick and buttons for input.
Add the Load My Avatar Script
Create a new empty Game Object named LoadMyAvatar. In the Project window, open the Assets > Samples > Genies Avatar SDK > X.Y.Z > Sample Starter Scene > Avatar Starter > Scripts folder. Drag the LoadMyAvatar script into the LoadMyAvatar component list.
![]()
This script will load the Avatar once the user is logged in. It will also connect the animations and input systems to the character controller prefab scripts.
Add the Event System Prefab
In the Project window, open the Assets > Samples > Genies Avatar SDK > X.Y.Z > Sample Starter Scene > Common > Prefabs > MobileControls folder. Drag and drop the UI_EventSystem prefab into the scene.

If there is already an EventSystem game object in the Hierarchy, make sure to delete that one. This is usually added by default when creating a UI Canvas object. This prefab has defined properties to work with the character controller system.
Add the Touch Controls UI
In the Project window, open the Assets > Samples > Genies Avatar SDK > X.Y.Z > Sample Starter Scene > Common > Prefabs > MobileControls folder. Drag and drop the GeniesTouchZones prefab into the scene.

This prefab has the UI for the move joystick, run button, and jump button.
Find the Avatar Animator
In the Project window, open the Packages > Genies Avatar SDK > Internal > Avatars > Runtime > Assets > CommonAnimations folder. Inside is the GeniesAvatarAnimator asset needed in the next step.
![]()
This folder contains a default animator and animation assets used by the Avatar Starter sample scene to animate the Avatar as it moves and jumps.
Add the Component Properties
In the Hierarchy window, select the LoadMyAvatar object and open the Inspector window.
Add the following properties to the LoadMyAvatar component:
- Add the UI_EventSystem object to the Input System UI Input Module property.
- Add the AvatarSpawn object to the Loaded Controller property.
- Add the GeniesAvatarAnimator asset to the Optional Controller property.
- Add the FreeLook Camera object to the Cinemachine Free Look Settings property.
- Add the FreeLook Camera object to the Input Axis Controller property.

Test the Character Controller
Enter Play mode. After logging in, the Avatar should spawn and be controllable with the touch controls.

You may need to calibrate the FreeLook Camera properties for more optimal camera movement.
Add an NPC Avatar
The next step is to create an NPC Avatar. This will use the Using Multiple Avatars sample scene to create a locally stored Avatar definition. This definition can then be used to load an Avatar to represent the NPC.
Open the Sample Scene
In the Project window, open the Assets > Samples > Genies Avatar SDK > X.Y.Z > Sample Starter Scenes > Using Multiple Avatars > Scenes folder. Double click the MultipleAvatars scene to open it.

Play the Scene
Once the scene is opened, press the Play button. Once you are logged in, make sure the default values for the UIs are set to:
- Save Locally & Exit Editor
Test-Profile
Then click one of the Avatars to open the Avatar Editor.

These two UIs are setting the Avatar Editor to save a locally stored Avatar definition under the name Test-Profile once the user clicks the Save button.
Save a Definition
Use the Avatar Editor to create the look of the NPC. Once finished, click the Save button to locally store the definition you created. Exit Play mode.

Find the Test Profile Definition
In the Project window, open the Assets > Genies > AvatarEditor > Profiles > Resources folder. Make sure the Test-Profile definition is successfully created.

Create NPC Spawn Object
Open the SampleScene in the Assets > Scenes folder. Create a new empty Game Object named NpcSpawn.

Create NPC Controller Script
In the Hierarchy window, select the NpcSpawn object. In the Inspector window, click the Add Component button near the bottom and then select the bottom option to New Script. Name the script NpcController.
Then set the Position property to Z: 3 and Rotation property to Y: 180.

Add Code to the Script
Open the NpcController script and add this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Genies.Sdk;
public class NpcController : MonoBehaviour
{
void Start()
{
if (!AvatarSdk.IsLoggedIn)
{
AvatarSdk.Events.UserLoggedIn += LoadNpc;
return;
}
LoadNpc();
}
private void OnDestroy()
{
AvatarSdk.Events.UserLoggedIn -= LoadNpc;
}
private async void LoadNpc()
{
var npcAvatar = await AvatarSdk.LoadFromLocalAvatarDefinitionAsync("Test-Profile");
npcAvatar.Root.transform.parent = this.transform;
npcAvatar.Root.transform.position = this.transform.position;
npcAvatar.Root.transform.rotation = this.transform.rotation;
}
}
The AvatarSdk.LoadFromLocalAvatarDefinitionAsync("Test-Profile"); method is loading the locally stored Avatar definition that you created earlier.
Test the Code
Save the code and enter Play mode. The NPC Avatar should load in front of the player Avatar.

Add a Wave Animation
The next step is to add a custom animation to the NPC so it waves when the player gets near it.
Download a Wave Animation
Download a wave animation that is formatted for humanoid models. A recommended site is Mixamo by Adobe that offers a wide variety of animations for humanoids for free.

Create an Animator Controller
In the Project window, right click and select Create > Animator Controller. Name it NpcAnimator.
Add Idle Animation
Open the Animator window by double clicking the NpcAnimator asset. In the Project window, search for idle in the search bar in the Packages directory. Drag the Idle animation into the Animator window.

This is a default idle animation provided by the Avatar SDK. It's deeply nested in the Packages folders so its easier to search its name.
Apply Humanoid Rig to Wave Animation
Drag and drop the downloaded wave animation asset into the Project window. Select the wave animation asset and open the Inspector window. In the Rig tab, set the Animation Type property to Humanoid. Then click the Apply button.

The Genies Avatar will only work with animations that are rigged for humanoid models.
Add the Wave Animation
Open the Animator window by double clicking the NpcAnimator asset. Drag and drop the wave animation asset into the Animator window. Rename the animation state to Wave. Then add transitions to and from the idle animation state.

Add a Waving Trigger
In the Animator window, click the Parameters tab at the top left and click the plus sign. Create a new Trigger parameter named Waving.

Trigger the Wave Animation Transition
In the Animator window, select the transition going from the idle state to the wave state. In the Inspector window, make sure the Has Exit Time property is disabled and then add the Waving trigger to the Conditions list.

Add the Player Tag
Select the AvatarSpawn object and open the Inspector window. Set the Tag property to Player.

The player needs a tag so the NPC can determine if it did enter its collider and wave when the player is near.
Update the Code
Open the NpcController script and update the code to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Genies.Sdk;
public class NpcController : MonoBehaviour
{
[SerializeField] private RuntimeAnimatorController npcAnimatorController;
private Animator npcAnimator;
private GameObject player;
private float rotationSpeed = 5f;
void Start()
{
if (!AvatarSdk.IsLoggedIn)
{
AvatarSdk.Events.UserLoggedIn += LoadNpc;
return;
}
LoadNpc();
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
npcAnimator.SetTrigger("Waving");
player = other.gameObject;
}
}
private bool IsWaving()
{
AnimatorStateInfo stateInfo = npcAnimator.GetCurrentAnimatorStateInfo(0);
return stateInfo.IsName("Wave");
}
void Update()
{
if(player != null && IsWaving())
{
RotateToPlayer();
}
}
private void RotateToPlayer()
{
Vector3 direction = player.transform.position - transform.position;
direction.y = 0f; // prevent tilting
if (direction.sqrMagnitude < 0.001f) return;
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
private void OnDestroy()
{
AvatarSdk.Events.UserLoggedIn -= LoadNpc;
}
private async void LoadNpc()
{
var npcAvatar = await AvatarSdk.LoadFromLocalAvatarDefinitionAsync("Test-Profile");
npcAvatar.Root.transform.parent = this.transform;
npcAvatar.Root.transform.position = this.transform.position;
npcAvatar.Root.transform.rotation = this.transform.rotation;
npcAvatar.SetAnimatorController(npcAnimatorController);
npcAnimator = npcAvatar.Root.GetComponent<Animator>();
}
}
This new code will set the Animator Controller to the NPC. It will also rotate to face the player and trigger the wave animation if the player collides with the NPC's collider.
Add the Collider
Select the NpcSpawn object and open the Inspector window. Add a Sphere Collider component and set the Radius property to 1.5. Then set the Npc Animator property to the newly created Npc Animator Animator Controller.

Test the Project
Enter Play mode. The NPC should trigger the wave animation and rotate to the player when the player gets close enough.

Conclusion
This concludes the first project tutorial. Some next steps could be incorporating the Avatar Editor and custom UGC wearable gifting.