The GX.games API Library introduced in the Challenges tutorial can also be used to retrieve and display information about the player's profile. This can be used to increase immersion and make the gameplay experience more personal to the player by displaying their name and avatar in-game.
In this tutorial, we'll use the GX.games API Library to retrieve the user's name and avatar URL, then load the avatar image through the URL and display it with a round shape using surfaces:
Before starting, please download and import the GX.games API Library into your GMS2 project. For instructions on importing this asset, please read the "Setting Up The GX.games Library" section of the Challenges tutorial.
Retrieving Data
We first need to retrieve the user's name and avatar URL from the query parameters provided in the game's URL, which we can do using the gxc_get_query_param() function; however this will not work if the game is being tested locally (as query parameters are only provided on GX.games). In case the query parameters are not found, we'll request profile information from GXC using the asynchronous gxc_profile_get_info() function.
Create a new object (or use an existing one) to handle loading the user information, and add this in its Create event:
/// Create event
spriteRequest = noone;
avatarSprite = noone;
username = "";
Here we're creating three variables: spriteRequest, which stores the ID of our asynchronous request to load the user's avatar image, avatarSprite, which stores the ID of the user's avatar sprite (once loaded), and username, which simply stores the user's name as a string.
Now let's do this in the same event:
var _avatarUrl = gxc_get_query_param("avatarUrl");
var _username = gxc_get_query_param("username");
if (is_undefined(_avatarUrl) || is_undefined(_username))
{
gxc_profile_get_info(function(_status, _result)
{
if (_status == 200)
{
var _avatarUrl = _result.data.avatarUrl;
var _username = _result.data.username;
spriteRequest = sprite_add(_avatarUrl, 0, 0, 0, 0, 0);
username = _username;
}
});
}
else
{
spriteRequest = sprite_add(_avatarUrl, 0, 0, 0, 0, 0);
username = _username;
}
This will first load the avatar URL and username from the query parameters, and then run a condition to check if either of them is undefined. If that is true, it means that the query parameters are not available for any reason, so it calls the gxc_profile_get_info() function to asynchronously request profile data from GX.games.
The argument for this function is a method (known as a "callback") which runs when a response is received from the server. The method gets two arguments: _status and _result, where _status stores the status code of the HTTP response. If that status is equal to 200 it means that our request was successful, and we can continue to retrieve data from the_result variable which is a struct.
We then read the avatar URL and username from the result struct, and call sprite_add() on the retrieved avatar URL to start loading it into memory asynchronously. This means that once the image is done loading, it will run the Async - Image Loaded event so we know that the image download process is complete. The value returned from the sprite_add() call is stored in the spriteRequest variable so the request can later be identified in the Async - Image Loaded event.
Finally, we store the username in an instance variable so it can be drawn later.
Image Loaded
We now need to wait for a response in the Async - Image Loaded event and once we receive one, check the response ID against our request ID:
if (async_load[? "id"] == spriteRequest)
{
if (async_load[? "status"] >= 0)
{
avatarSprite = spriteRequest;
}
else
{
show_debug_message("[WARNING] image loaded event, failed to load image");
}
}
First of all, this checks if the ID of the Async response is equal to our sprite request; in that case it ensures that the image has loaded by checking the status value (which is 0 or larger if successful). It then saves the request ID in the avatarSprite variable, as the ID of the loaded sprite is the same as the ID of the request to load it.
In case the sprite was not loaded successfully, it displays a debug message in the log letting us know.
NOTE: You cannot load your user avatar while testing locally due to CORS; the game has to be uploaded to GX.games and run through there.
Clean Up
Make sure to destroy the avatar sprite (if loaded) in the Clean Up event of the object to avoid memory leaks!
/// Clean Up event
if (sprite_exists(avatarSprite))
{
sprite_delete(avatarSprite);
}
Drawing the Image
We'll now draw the avatar and username in the Draw GUI event, as this event draws everything above the rest of the game, directly to the screen (instead of drawing in the room). This means that any elements drawn through Draw GUI will not be affected by camera movement.
In the Draw GUI event, we'll add this:
// Check if avatar is loaded
if (sprite_exists(avatarSprite))
{
// Draw avatar
draw_sprite(avatarSprite, 0, x, y);
// Draw username
draw_set_font(fnt_default);
draw_text(x, y + sprite_get_height(avatarSprite) + 30, username);
}
This will first check if the avatar sprite exists, which will return true once the image has been loaded. In that case it will draw the avatar sprite, and then the username using a custom font.
We draw the username only after the avatar sprite has been loaded as it uses the size of the image to determine where the text draws. In a later example we will draw the username regardless of the image being loaded, as we will use a constant size for the avatar.
Currently, the image is loaded in the same size as it was uploaded to GX.games by the user; so, for example, if you uploaded a 256x256 image as your avatar, that's what you'll get, but if you uploaded a high-quality 1920x1920 image, you will receive the image in those exact dimensions.
Testing
You can now upload your game to GX.games DevCloud, enable the Private version and open the given Sharable URL. For instructions on uploading and accessing your game on GX.games, please read this tutorial.
Once you've opened your game URL, you can hit play and see your example in action!
Round Avatar
Now that we're able to load the user's avatar in our game, we'll make use of surfaces and circles to draw a round avatar. For this we'll first need to create the following variables in the Create event of our object:
// Surface
avatarSurface = -1;
// Circle drawing
avatarRadius = 160;
avatarCircleThickness = 8;
avatarCircleColors = [ $00FEBC, $26171C ];
First we're creating avatarSurface, which will store the ID of the surface used to draw the circular avatar.
Then we're creating three more variables; avatarRadius is the radius of the circle that the avatar is drawn on, avatarCircleThickness is the thickness of the border around the avatar circle, and avatarCircleColors are the colors for the border and inside of the circle respectively (note that the inside will only be visible when the image hasn't loaded). These colors are based on the GX.games theme.
Now remove the previous code from the Draw GUI event and add this there:
// Draw the background placeholder
var _outerRadius = avatarRadius + avatarCircleThickness;
var _innerRadius = avatarRadius;
var _color1 = avatarCircleColors[0],
_color2 = avatarCircleColors[1];
draw_circle_color(x, y, _outerRadius, _color1, _color1, false); // Outer circle (with border)
draw_circle_color(x, y, _innerRadius, _color2, _color2, false); // Inner circle
This first calculates the radius of the outer circle (including the border) and the inner circle, and gets the colors for both from the array. It then draws both circles using draw_circle_color(), which allows you to draw a circle of a specific radius with a given color gradient (however since both colors specified in a single draw_circle_color() call are the same, we will not see a gradient).
If you run the game with just this code, you will see the circle with a border:
We just need to draw the user's avatar over this!
In the Draw GUI event, add the following code (below the previously added code):
// Check if sprite has been loaded
if (sprite_exists(avatarSprite))
{
// Check if the avatar surface doesn't exist, so it can be created
if (!surface_exists(avatarSurface))
{
// Get width and height of the loaded avatar
var _width = sprite_get_width(avatarSprite);
var _height = sprite_get_height(avatarSprite);
// Calculate scale multiplier of the image
var _scale = (avatarRadius * 2) / min(_width, _height);
// Create surface for circular avatar
avatarSurface = surface_create(avatarRadius * 2, avatarRadius * 2);
// Draw circle on surface
surface_set_target(avatarSurface);
draw_circle(avatarRadius, avatarRadius, avatarRadius, false);
// Draw scaled image
gpu_set_colorwriteenable(1, 1, 1, 0);
draw_sprite_ext(avatarSprite, 0, 0, 0, _scale, _scale, 0, -1, 1);
gpu_set_colorwriteenable(1, 1, 1, 1);
surface_reset_target();
}
// Draw surface to screen
draw_surface(avatarSurface, x - avatarRadius, y - avatarRadius);
}
This block of code does the following things:
- Check if the avatar has been loaded
- Check if the avatar surface does not exist, in which case we will create it
- Get the width and height of the loaded image, so we know how big/small it is and how much it has to be scaled down/up to fit our circle
- Calculate the scale of the avatar image based on the width or height (whichever is smaller); this means that the image will not be distorted if it uses a non-square aspect ratio, but it will simply look as if it was cropped
- Create the avatar surface, whose size is just enough to fit the circle (avatarRadius * 2)
- Set the draw target to the avatar surface and draw a simple circle on it
- Disable the alpha channel; if we draw anything now, it will only be "projected" onto the circle that is already in the surface, as any transparent pixels will not be affected
- Draw the avatar sprite scaled to fit the circle
- Re-enable the alpha channel and reset the draw target
- Finally, draw the surface; its coordinates are offset so it's drawn centered on the instance
- Check if the avatar surface does not exist, in which case we will create it
We now need to draw the username below the avatar, so let's add this in the same event:
// Change font and alignment
draw_set_font(fnt_default);
draw_set_halign(fa_middle);
// Draw username
draw_text(x, y + avatarRadius + 25, username);
// Reset alignment
draw_set_halign(fa_left);
This draws the username below the avatar, using a middle horizontal alignment.
Now upload your game to GX.games again, run it and you will see your image in a nice circle!
You will notice that the circle shape looks "low poly", and we can fix this by increasing the circle precision value.
In your Create event, run the following function:
draw_set_circle_precision(64);
This means that all drawn circles will now have 64 sides and will look much smoother!
Customise Your Profile Display
You can now modify your Draw GUI code to display the user avatar in a way that fits in with the style of your game! For example, you can display the username to the right of a small, round avatar...
Primitive shapes do look very jagged though, so you can use a custom frame and mask sprite to make it look much more professional and smoother:
This uses a custom frame image which is drawn behind the avatar, and a custom mask which is drawn to the surface (instead of a primitive circle) before the avatar, so the image blends more smoothly.
Doing this will require modified code, which is included in the demo project given at the bottom of this tutorial.
Using this custom masking technique you can create stylized frames for displaying user avatars to make it more FUN!
Tip: Not all users will have an avatar set, so make sure to display a placeholder image until an avatar is loaded for the player, which will keep on displaying in case the player has no avatar.
Click here to download the project demonstrated in this tutorial, which contains all the code you need to implement avatars into your game. It contains multiple objects each with a different layout of the username and avatar as shown above.
Happy GameMaking!