Unity

Unity

Unity is a cross-platform game engine with a built-in IDE developed by Unity Technologies. It is a commercial development platform for creating apps, games and simulations with 3D, 2D, VR and AR content.
Sheldon  Grant

Sheldon Grant

1670355060

How to Animate Spritesheets in A Unity 2D Game

When it comes to developing a game, at some point in time you’re going to want to animate some component within the game. These animations could be character sprites or even elements that exist as part of the background.

A few months back, I wrote a tutorial titled, Animate Spritesheets in a Phaser Game, around creating a spritesheet and then animating it within a Phaser game. Phaser is an awesome framework, but it doesn’t compare to Unity on a professional level. So what if we wanted to animate a spritesheet in Unity?

In this tutorial we’re going to see how to animate a spritesheet, the same example from the previous tutorial, but this time with Unity, animation clips, animator states, and some basic C#.

To get an idea of what we want to accomplish, take a look at the following animated image:

Unity Spritesheet Animation Example

In the above animated image we have a plane with wind streaks to give the illusion that it is flying. After a certain amount of time or event, the plane disappears and an explosion-like animation plays before the entire sprite disappears from the screen. Both animations are part of the same spritesheet and the same game object within Unity.

Create a New Unity Project with the 2D Template

Before we get to the core material, it makes sense to start with a fresh Unity project. Within the Unity Hub, create a new project using the 2D template.

Unity New 2D Project

I’m going to skip a few steps in terms of image instructions, but you’re going to want to create an Animations, Scripts, and Textures directory within the project assets. You’re also going to want to create an empty game object within the scene.

Your project should look something like the following:

Unity 2D Project with Boilerplate

Even though we have a project with a game object, we’re not actually rendering anything to the screen. The game object is not technically a sprite and it has no animations associated to it.

Packing Spritesheets and Defining Animation Clips with Unity

To animate something in 2D, we’re going to need multiple frames to iterate over. This means we’re going to need a new image for each frame in our animation. The more unique images, the smoother the animation will appear because there will be more unique frames.

In Unity we could create, say ten images, and add each image into the project. The problem with this is that loading individual images is resource intensive and inappropriate for a game that is dependent on performance. For this reason, tiling each frame into a single image, otherwise known as a spritesheet, is a better option.

There are numerous ways to handle a spritesheet, but the simplest is to use a spritesheet where each sprite is exactly the same size as the other sprites. Sure it won’t be an optimized spritesheet, but it will be easy and better than nothing.

Take the following spritesheet for example:

Plane Spritesheet

The above spritesheet was created with a software called TexturePacker. You don’t need TexturePacker to create spritesheets, but it makes life significantly easier. The alternative is taking each of your sprite images and building a spritesheet manually with a tool like Adobe Photoshop.

Go ahead and download my spritesheet and add it to the Textures directory within the Unity project.

Within Unity, we need to define what exactly the image file is. This means defining each image in the spritesheet as well as the pixels per unit.

Define Sprites within Spritesheet in Unity

After selecting the image, change the Sprite Mode to Multiple and then select the Sprite Editor from the inspector. Within the Sprite Editor choose to create a grid based on the cell size. We know that each sprite within the spritesheet is 512x512, so defining this in the Sprite Editor will allow unity to access each sprite separately, whenever we want.

With the spritesheet properly referenced within Unity, click on the game object, and then choose Window -> Animation -> Animation from the menu.

Show Unity Animation Window

The Animation window is where we’re going to define each of our possible animation clips. Our example is going to have two animations, one for the plane flying and one for the plane exploding.

Within the Animation window, create a new animation titled, Flying, and save it to the Animations directory within the project assets. Create another animation titled, Exploding, and save it to the same directory.

Unity Animation Clip

Select one of the two new animation clips within the Animation window and drag the relevant sprites from the spritesheet into the window. These sprites will animate for that particular clip. Depending on how many sprites you have, you may need to lower the Samples field. In our example, seven is a good number to work with.

The animations are defined, but we need to be able to determine when they should play. This is where the animator comes into the scenario.

Managing Animation Clips with States in a Unity Animator Controller

The Unity Animator is where we define each of our animation states and how to transition between them. In our example, we’d transition to flying, and then from flying to exploding, but probably never exploding from another state.

Choose Window -> Animation -> Animator from the menu.

Open Unity Animator Window

You should be presented with a window that has a few states, two of which are named after the animation clips that we had created prior. You’ll also notice an Any State, Entry, and Exit state that come as part of the animator state lifecycle.

Unity Animator Transitions

The goal here is to define transitions between the states. For example, the Entry state leads to the default animation, which should be Flying in our example. The Any State state leads to where we can transition to on-demand. It’s probably not likely that we will want to transition from anywhere to the Exploding state.

If you click on any of the transitions, you can define conditions. However, before we can define conditions we need to declare possible parameters. Create two trigger parameters. The goal is to use these triggers as conditions when they enter the true state. The state of these triggers will eventually be defined in a C# script.

If you were to run your game right now, the plane would be animated as if it were flying. It will not explode and it will not disappear. It will only fly forever.

Changing the Unity Animation State for a Sprite in a C# Script

We have our possible animations and we have a state controller defined. Now we need to trigger state events from a C# script.

Create a Plane.cs script within the Scripts directory. This script should be added as a component on the game object that is representing our plane. By default the script should look like the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Plane : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Since collisions and other realistic events are out of the scope of this particular example, we’re going to change the animation based on a timer. However, you can easily change things based on your needs.

Within the Plane.cs file, change it to look like the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Plane : MonoBehaviour
{

    private Animator animator;

    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();

        StartCoroutine(ExplodeCoroutine());
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    IEnumerator ExplodeCoroutine()
    {
        yield return new WaitForSeconds(3);
        animator.SetTrigger("Explode");
        Destroy(gameObject, 1.5f);
    }
}

In the above code we are obtaining the animator that exists on our game object. Remember, this is the animator that has our two animations and all the states with transitions.

In the Start method we start a coroutine. In that coroutine we wait for three seconds before setting the trigger that transitions from the Flying animation to our Exploding animation. Since we don’t want to explode forever, we configure the game object to be destroyed after ninety seconds.

Give it a try! The plane should fly for a few seconds and then explode. Finally the plane and explosion should disappear because the game object was destroyed.

Conclusion

This tutorial went over a few different topics that brought us to animating a spritesheet. The goal was to take a single image spritesheet that contained several sprite images and animate them as frames. We took that spritesheet, parsed it in Unity, defined two different animations, and then controlled those animations with an animator and a script with logic.

Spritesheets can be optimized beyond what we saw in this tutorial and they can contain more sprites than what we used. However, spritesheets should definitely be used and they are quite easy to animate since most of what we did was point and click with configurations in Unity.

This tutorial was based on an example I had done with Phaser titled, Animate Spritesheets in a Phaser Game.

Original article source at: https://www.thepolyglotdeveloper.com

#unity #game #csharp 

 How to Animate Spritesheets in A Unity 2D Game
Sheldon  Grant

Sheldon Grant

1670350980

How to Designing and Developing 2D Game Levels with Unity and C#

If you’ve been keeping up with the game development series that me (Nic Raboy) and Adrienne Tacke have been creating, you’ve probably seen how to create a user profile store for a game and move a player around on the screen with Unity.

To continue with the series, which is also being streamed on Twitch, we’re at a point where we need to worry about designing a level for gameplay rather than just exploring a blank screen.

In this tutorial, we’re going to see how to create a level, which can also be referred to as a map or world, using simple C# and the Unity Tilemap Editor.

To get a better idea of what we plan to accomplish, take a look at the following animated image.

Unity Tilemap Level Example

You’ll notice that we’re moving a non-animated sprite around the screen. You might think at first glance that the level is one big image, but it is actually many tiles placed carefully within Unity. The edge tiles have collision boundaries to prevent the player from moving off the screen.

If you’re looking at the above animated image and wondering where MongoDB fits into this, the short answer is that it doesn’t. The game that Adrienne and I are building will leverage MongoDB, but some parts of the game development process such as level design won’t need a database. We’re attempting to tell a story with this series.

Using the Unity Tilemap Editor to Draw 2D Game Levels

There are many ways to create a level for a game, but as previously mentioned, we’re going to be using tilemaps. Unity makes this easy for us because the software provides a paint-like experience where we can draw tiles on the canvas using any available images that we load into the project.

For this example, we’re going to use the following texture sheet:

Plummeting People Doordash Level

Rather than creating a new project and repeating previously explained steps, we’re going to continue where we left off from the previous tutorial. The doordash-level.png file should be placed in the Assets/Textures directory of the project.

While we won’t be exploring animations in this particular tutorial, if you want the spritesheet used in the animated image, you can download it below:

Plummie Spritesheet

The plummie.png file should be added to the project’s Assets/Textures directory. To learn how to animate the spritesheet, take a look at a previous tutorial I wrote on the topic.

Inside the Unity editor, click on the doordash-level.png file that was added. We’re going to want to do a few things before we can work with each tile as independent images.

  • Change the sprite mode to Multiple.
  • Define the actual Pixels Per Unit of the tiles in the texture packed image.
  • Split the tiles using the Sprite Editor.

Define Spritesheet Information in Unity

In the above image, you might notice that the Pixels Per Unit value is 255 while the actual tiles are 256. By defining the tiles as one pixel smaller, we’re attempting to remove any border between the tile images that might make the level look weird due to padding.

When using the Sprite Editor, make sure to slice the image by the cell size using the correct width and height dimensions of the tiles. For clarity, the tiles that I attached are 256x256 in resolution.

Unity Sprite Editor

If you plan to use the spritesheet for the Plummie character, make sure to repeat the same steps for that spritesheet as well. It is important we have access to the individual images in a spritesheet rather than treating all the images as one single image.

With the images ready for use, let’s focus on drawing the level.

Within the Unity menu, choose Component -> Tilemap -> Tilemap to add a new tilemap and parent grid object to the scene. To get the best results, we’re going to want to layer multiple tilemaps on our scene. Right click on the Grid object in the scene and choose 2D Object -> Tilemap. You’ll want three tilemaps in total for this particular example.

Unity New Tilemap

We want multiple tilemap layers because it will add depth to the scene and more control. For example, one layer will represent the furthest part of our background, maybe dirt or floors. Another layer will represent any kind of decoration that will sit on top of the floors — say, for example, arrows. Then, the final tilemap layer might represent our walls or obstacles.

To make sure the layers get rendered in the correct order, the Tilemap Renderer for each tilemap should have a properly defined Sorting Layer. If continuing from the previous tutorial, you’ll remember we had created a Background layer and a GameObject layer. These can be used, or you can continue to create and assign more. Just remember that the render order of the sorting layers is top to bottom, the opposite of what you’d experience in photo editing software like Adobe Photoshop.

The next step is to open the Tile Palette window within Unity. From the menu, choose Window -> 2D -> Tile Palette. The palette will be empty to start, but you’ll want to drag your images either one at a time or multiple at a time into the window.

Unity Tile Palette Images

With images in the tile palette, they can be drawn on the scene like painting on a canvas. First click on the tile image you want to use and then choose the painting tool you want to use. You can paint on a tile-by-tile basis or paint multiple tiles at a time.

It is important that you have the proper Active Tilemap selected when drawing your tiles. This is important because of the order that each tile renders and any collision boundaries we add later.

Take a look at the following possible result:

Unity Tilemaps for Level Design

Remember, we’re designing a level, so this means that your tiles can exceed the view of the camera. Use your tiles to make your level as big and extravagant as you’d like.

Assuming we kept the same logic from the previous tutorial, Getting Started with Unity for Creating a 2D Game, we can move our player around in the level, but the player can exceed the screen. The player may still be a white box or the Plummie sprite depending on what you’ve chosen to do. Regardless, we want to make sure our layer that represents the boundaries acts as a boundary with collision.

Adding Collision Boundaries to Specific Tiles and Regions on a Level

Adding collision boundaries to tiles in a tilemap is quite easy and doesn’t require more than a few clicks.

Select the tilemap that represents our walls or boundaries and choose to Add Component in the inspector. You’ll want to add both a Tilemap Collider 2D as well as a Rigidbody 2D. The Body Type of the Rigidbody 2D should be static so that gravity and other physics-related events are not applied.

After doing these short steps, the player should no longer be able to go beyond the tiles for this layer.

We can improve things!

Right now, every tile that is part of our tilemap with the Tilemap Collider 2D and Rigidbody 2D component has a full collision area around the tile. This is true even if the tiles are adjacent and parts of the tile can never be reached by the player. Imagine having four tiles creating a large square. Of the possible 16 collision regions, only eight can ever be interacted with. We’re going to change this, which will greatly improve performance.

On the tilemap with the Tilemap Collider 2D and Rigidbody 2D components, add a Composite Collider 2D component. After adding, enable the Used By Composite field in the Tilemap Collider 2D component.

Unity Composite Collider 2D on Tilemap

Just like that, there are fewer regions that are tracking collisions, which will boost performance.

Following the Player While Traversing the 2D Game Level using C#

As of right now, we have our player, which might be a Plummie or might be a white pixel, and we have our carefully crafted level made from tiles. The problem is that our camera can only fit so much into view, which probably isn’t the full scope of our level.

What we can do as part of the gameplay experience is have the camera follow the player as it traverses the level. We can do this with C#.

Select the Main Camera within the current scene. We’re going to want to add a new script component.

Within the C# script that you’ll need to attach, include the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraPosition : MonoBehaviour
{

   public Transform player;

   void Start() {}

   void Update()
   {
      transform.position = new Vector3(player.position.x, 0, -10);
   }
}

In the above code, we are looking at the transform of another unrelated game object. We’ll attach that game object in just a moment. Every time the frame updates, the position of the camera is updated to match the position of the player in the x-axis. In this example, we are fixing the y-axis and z-axis so we are only following the player in the left and right direction. Depending on how you’ve created your level, this might need to change.

Remember, this script should be attached to the Main Camera or whatever your camera is for the scene.

Unity Camera to Player Transform

Remember the player variable in the script? You’ll find it in the inspector for the camera. Drag your player object from the project hierarchy into this field and that will be the object that is followed by the camera.

Running the game will result in the camera being centered on the player. As the player moves through the tilemap level, so will the camera. If the player tries to collide with any of the tiles that have collision boundaries, motion will stop.

Conclusion

You just saw how to create a 2D world in Unity using tile images and the Unity Tilemap Editor. This is a very powerful tool because you don’t have to create massive images to represent worlds and you don’t have to worry about creating worlds with massive amounts of game objects.

The assets we used in this tutorial are based around a series that myself (Nic Raboy) and Adrienne Tacke are building titled Plummeting People. This series is on the topic of building a multiplayer game with Unity that leverages MongoDB. While this particular tutorial didn’t include MongoDB, plenty of other tutorials in the series will.

If you feel like this tutorial skipped a few steps, it did. I encourage you to read through some of the previous tutorials in the series to catch up.

If you want to build Plummeting People with us, follow us on Twitch where we work toward building it live, every other week.

Original article source at: https://developer.mongodb.com/

#csharp #unity 

How to Designing and Developing 2D Game Levels with Unity and C#
Gordon  Matlala

Gordon Matlala

1670331018

How to Designing a Strategy to Develop a Game with Unity & MongoDB

When it comes to game development, you should probably have some ideas written down before you start writing code or generating assets. The same could probably be said about any kind of development, unless of course you’re just messing around and learning something new.

So what should be planned before developing your next game?

Depending on the type of game, you’re probably going to want a playable frontend, otherwise known as the game itself, some kind of backend if you want an online component such as multiplayer, leaderboards, or similar, and then possibly a web-based dashboard to get information at a glance if you’re on the operational side of the game and not a player.

Adrienne Tacke, Karen Huaulme, and myself (Nic Raboy) are in the process of building a game. We think Fall Guys: Ultimate Knockout is a very well-made game and thought it’d be interesting to create a tribute game that is a little more on the retro side, but with a lot of the same features. The game will be titled, Plummeting People. This article explores the planning, design, and development process!

Take a look at the Jamboard we’ve created so far:

Game Development Planning Board

The above Jamboard was created during a planning stream on Twitch where the community participated. The content that follows is a summary of each of the topics discussed and helpful information towards planning the development of a game.

Planning the Game Experience with a Playable Frontend

The game is what most will see and what most will ever care about. It should act as the driver to every other component that operates behind the scenes.

Rather than try to invade the space of an already great game that we enjoy (Fall Guys), we wanted to put our own spin on things by making it 2D rather than 3D. With Fall Guys being the basic idea behind what we wanted to accomplish, we needed to further break down what the game would need. We came to a few conclusions.

Levels / Arenas

We need a few arenas to be able to call it a game worth playing, but we didn’t want it to be as thought out as the game that inspired our idea. At the end of the day, we wanted to focus more on the development journey than making a blockbuster hit.

Fall Guys, while considered a battle royale, is still a racing game at its core. So what kind of arenas would make sense in a 2D setting?

Fall Guys Door Dash

Our plan is to start with the simplest level concepts to save us from complicated game physics and engineering. There are two levels in particular that have basic collisions as the emphasis in Fall Guys. These levels include “Door Dash” and “Tip Toe” which focus on fake doors and disappearing floor tiles. Both of which have no rotational physics and nothing beyond basic collisions and randomization.

Fall Guys Tip Toe

While we could just stick with two basic levels as our proof of concept, we have a goal for a team arena such as scoring goals at soccer (football).

Assets

The arena concepts are important, but in order to execute, game assets will be necessary.

We’re considering the following game assets a necessary part of our game:

  • Arena backgrounds
  • Obstacle images
  • Player images
  • Sound effects
  • Game music

To maintain the spirit of the modern battle royale game, we thought player customizations were a necessary component. This means we’ll need customized sprites with different outfits that can be unlocked throughout the gameplay experience.

Gameplay Physics and Controls

Level design and game assets are only part of a game. They are quite meaningless unless paired with the user interaction component. The user needs to be able to control the player, interact with other players, and interact with obstacles in the arena. For this we’ll need to create our own gameplay logic using the assets that we create.

Maintaining an Online, Multiplayer Experience with a Data Driven Backend

We envision the bulk of our work around this tribute game will be on the backend. Moving around on the screen and interacting with obstacles is not too difficult of a task as demonstrated in a previous tutorial that I wrote.

Instead, the online experience will require most of our attention. Our first round of planning came to the following conclusions:

Real-Time Interaction with Sockets

When the player does anything in the game, it needs to be processed by the server and broadcasted to other players in the game. This needs to be real-time and sockets is probably the only logical solution to this. If the server is managing the sockets, data can be stored in the database about the players, and the server can also validate interactions to prevent cheating.

Matchmaking Players with Games

When the game is live, there will be simultaneous games in operation, each with their own set of players. We’ll need to come up with a matchmaking solution so that players can only be added to a game that is accepting players and these players must fit certain criteria.

The matchmaking process might serve as a perfect opportunity to use aggregation pipelines within MongoDB. For example, let’s say that you have 5 wins and 1000 losses. You’re not a very good player, so you probably shouldn’t end up in a match with a player that has 1000 wins and 5 losses. These are things that we can plan for from a database level.

User Profile Stores

User profile stores are one of the most common components for any online game. These store information about the player such as the name and billing information for the player as well as gaming statistics. Just imagine that everything you do in a game will end up in a record for your player.

So what might we store in a user profile store? What about the following?:

  • Unlocked player outfits
  • Wins, losses, experience points
  • Username
  • Play time

The list could go on endlessly.

The user profile store will have to be carefully planned because it is the baseline for anything data related in the game. It will affect the matchmaking process, leaderboards, historical data, and so much more.

To get an idea of what we’re putting into the user profile store, check out a recorded Twitch stream we did on the topic.

Leaderboards

Since this is a competitive game, it makes sense to have a leaderboard. However this leaderboard can be a little more complicated than just your name and your experience points. What if we wanted to track who has the most wins, losses, steps, play time, etc.? What if we wanted to break it down further to see who was the leader in North America, Europe, or Asia? We could use MongoDB geospatial queries around the location of players.

As long as we’re collecting game data for each player, we can come up with some interesting leaderboard ideas.

Player Statistics

We know we’re going to want to track wins and losses for each player, but we might want to track more. For example, maybe we want to track how many steps a player took in a particular arena, or how many times they fell. This information could be later passed through an aggregation pipeline in MongoDB to determine a rank or level which could be useful for matchmaking and leaderboards.

Player Chat

Would it be an online multiplayer game without some kind of chat? We were thinking that while a player was in matchmaking, they could chat with each other until the game started. This chat data would be stored in MongoDB and we could implement Atlas Search functionality to look for signs of abuse, foul language, etc., that might appear throughout the chat.

Generating Reports and Logical Metrics with an Admin Dashboard

As an admin of the game, we’re going to want to collect information to make the game better. Chances are we’re not going to want to analyze that information from within the game itself or with raw queries against the database.

For this, we’re probably going to want to create dashboards, reports, and other useful tools to work with our data on a regular basis. Here are some things that we were thinking about doing:

MongoDB Atlas Charts

If everything has been running smooth with the game and the data-collection of the backend, we’ve got data, so we just need to visualize it. MongoDB Atlas Charts can take that data and help us make sense of it. Maybe we want to show a heatmap at different hours of the day for different regions around the world, or maybe we want to show a bar graph around player experience points. Whatever the reason may be, Atlas Charts would make sense in an admin dashboard setting.

Offloading Historical Data

Depending on the popularity of the game, data will be coming into MongoDB like a firehose. To help with scaling and pricing, it will make sense to offload historical data from our cluster to a cloud object storage in order to save on costs and improve our cluster’s performance by removing historical data.

In MongoDB Atlas, the best way to do this is to enable Online Archive which allows you to set rules to automatically archive your data to a fully-managed cloud storage while retaining access to query that data.

You can also leverage MongoDB Atlas Data Lake to connect your own S3 buckets and run Federated Queries to access your entire data set using MQL and the Aggregation Framework.

Conclusion

Like previously mentioned, this article is a starting point for a series of articles that are coming from Adrienne Tacke, Karen Huaulme, and myself (Nic Raboy), around a Fall Guys tribute game that we’re calling Plummeting People. Are we trying to compete with Fall Guys? Absolutely not! We’re trying to show the thought process around designing and developing a game that leverages MongoDB and since Fall Guys is such an awesome game, we wanted to pay tribute to it.

The next article in the series will be around designing and developing the user profile store for the game. It will cover the data model, queries, and some backend server code for managing the future interactions between the game and the server.

Want to discuss this planning article or the Twitch stream that went with it? Join us in the community thread that we created.

Original article source at: https://developer.mongodb.com/

#mongodb #game #unity 

How to Designing a Strategy to Develop a Game with Unity & MongoDB
Gordon  Matlala

Gordon Matlala

1669987687

How to Build A Space Shooter Game That Syncs with Unity, MongoDB Realm

When developing a game, in most circumstances you’re going to need to store some kind of data. It could be the score, it could be player inventory, it could be where they are located on a map. The possibilities are endless and it’s more heavily dependent on the type of game.

Need to sync that data between devices and your remote infrastructure? That is a whole different scenario.

If you managed to catch MongoDB .Live 2021, you’ll be familiar that the first stable release of the MongoDB Realm SDK for Unity was made available. This means that you can use Realm in your Unity game to store and sync data with only a few lines of code.

In this tutorial, we’re going to build a nifty game that explores some storage and syncing use-cases.

To get a better idea of what we plan to accomplish, take a look at the following animated image:

MongoDB Realm Space Shooter Example

In the above example, we have a space shooter style game. Waves of enemies are coming at you and as you defeat them your score increases. In addition to keeping track of score, the player has a set of enabled blasters. What you don’t see in the above example is what’s happening behind the scenes. The score is synced to and from the cloud and likewise are the blasters.

The Requirements

There are a lot of moving pieces for this particular gaming example. To be successful with this tutorial, you’ll need to have the following ready to go:

  • Unity 2021.2.0b3 or newer
  • A MongoDB Atlas M0 cluster or better
  • A Realm web application pointed at the Atlas cluster
  • Game media assets

This is heavily a Unity example. While older or newer versions of Unity might work, I was personally using 2021.2.0b3 when I developed it. You can check to see what version of Unity is available to you using the Unity Hub software.

Because we are going to be introducing a synchronization feature to the game, we’re going to need an Atlas cluster as well as a Realm application. Both of these can be configured for free with the MongoDB Cloud. Don’t worry about the finer details of the configuration because we’ll get to those as we progress in the tutorial.

As much as I’d like to take credit for the space shooter assets used within this game, I can’t. I actually downloaded them from the Unity Asset Store. Feel free to download what I used or create your own.

If you’re looking for a basic getting started tutorial for Unity with Realm, check out my previous tutorial on the subject.

Designing the Scenes and Interfaces for the Unity Game

The game we’re about to build is not a small and quick project. There will be many game objects and a few scenes that we have to configure, but none of it is particularly difficult.

To get an idea of what we need to create, make note of the following breakdown:

  • LoginScene
    • Camera
    • LoginController
    • RealmController
    • Canvas
      • UsernameField
      • PasswordField
      • LoginButton
  • MainScene
    • GameController
    • RealmController
    • Background
    • Player
    • Canvas
      • HighScoreText
      • ScoreText
    • BlasterEnabled
    • SparkBlasterEnabled
    • CrossBlasterEnabled
    • Blaster
    • CrossBlast
    • Enemy
    • SparkBlast

The above list represents our two scenes with each of the components that live within the scene.

Let’s start by configuring the LoginScene with each of the components. Don’t worry, we’ll explore the logic side of things for this scene later.

Within the Unity IDE, create a LoginScene and within the Hierarchy choose to create a new UI -> Input Field. You’ll need to do this twice because this is how we’re going to create the UsernameField and the PasswordField that we defined in the list above. You’re also going to want to create a UI -> Button which will represent our LoginButton to submit the form.

For each of the UI game objects, position them on the screen how you want them. Mine looks like the following:

Space Shooter Login Scene

Within the Hierarchy of your scene, create two empty game objects. The first game object, LoginController, will eventually hold a script for managing the user input and interactions with the UI components we had just created. The second game object, RealmController, will eventually have a script that contains any Realm interactions. For now, we’re going to leave these as empty game objects and move on.

Now let’s move onto our next scene.

Create a MainScene if you haven’t already and start adding UI -> Text to represent the current score and the high score.

Since we probably don’t want a solid blue background in our game, we should add a background image. Add an empty game object to the Hierarch and then add a Sprite Renderer component to that object using the inspector. Add whatever image you want to the Sprite field of the Sprite Renderer component.

Since we’re going to give the player a few different blasters to choose from, we want to show them which blasters they have at any given time. For this, we should add some simple sprites with blaster images on them.

Create three empty game objects and add a Sprite Renderer component to each of them. For each Sprite field, add the image that you want to use. Then position the sprites to a section on the screen that you’re comfortable with.

If you’ve made it this far, you might have a scene that looks like the following:

Space Shooter Basic MainScene

This might be hard to believe, but the visual side of things is almost complete. With just a few more game objects, we can move onto the more exciting logic things.

Like with the LoginScene, the GameController and RealmController game objects will remain empty. There’s a small change though. Even though the RealmController will eventually exist in the MainScene, we’re not going to create it manually. Instead, just create an empty GameController game object.

This leaves us with the player, enemies, and various blasters.

Starting with the player, create an empty game object and add a Sprite Renderer, Rigidbody 2D, and Box Collider 2D component to the game object. For the Sprite Renderer, add the graphic you want to use for your ship. The Rigidbody 2D and Box Collider 2D have to do with physics and collisions. We’re not going to burden ourselves with gravity for this example, so make sure the Body Type for the Rigidbody 2D is Kinematic and the Is Trigger for the Box Collider 2D is enabled. Within the inspector, tag the player game object as “Player.”

The blasters and enemies will have the same setup as our player. Create new game objects for each, just like you did the player, only this time select a different graphic for them and give them the tags of “Weapon” or “Enemy” in the inspector.

This is where things get interesting.

We know that there will be more than one enemy in circulation and likewise with your blaster bullets. Rather than creating a bunch of each, take the game objects you used for the blasters and enemies and drag them into your Assets directory. This will convert the game objects into prefabs that can be recycled as many times as you want. Once the prefabs are created, the objects can be removed from the Hierarchy section of your scene. As we progress, we’ll be instantiating these prefabs through code.

We’re ready to start writing code to give our game life.

Configuring MongoDB Atlas and MongoDB Realm for Data Synchronization

For this game, we’re going to rely on a cloud and synchronization aspect, so there is some additional configuration that we’ll need to take care of. However, before we worry about the cloud configurations, let’s install the Realm SDK for Unity.

Within Unity, select Window -> Package Manager and then click the little cog icon to find the Advanced Project Settings area.

Install Realm SDK in Unity

Here you’re going to want to add a new registry with the following information:

name: NPM
url: https://registry.npmjs.org
scope(s): io.realm.unity

Even though we’re working with Unity, the best way to get the Realm SDK is through NPM, hence the custom registry that we’re going to use.

Install Realm SDK in Unity

With the registry added, we can add an entry for Realm in the project’s Packages/manifest.json file. Within the manifest.json file, add the following to the dependencies object:

"io.realm.unity": "10.3.0"

You can swap the version of Realm with whatever you plan to use.

From a Unity perspective, Realm is ready to be used. Now we just need to configure Realm and Atlas in the cloud.

Within the MongoDB Cloud, assuming you already have a cluster to work with, click the Realm tab and then Create a New App to create a new Realm application.

Create New Realm Application

Name the Realm application whatever you’d like. The MongoDB Atlas cluster requires no special configuration to work with Realm, only that such a cluster exists. Realm will create the necessary databases and collections when the time comes.

Before we start configuring Realm, take note of your Realm App Id in the top left corner of the screen:

Find Realm App Id

The Realm App Id will be very important within the Unity project because it tells the SDK where to sync and authenticate with.

Next you’ll want to define what kind of authentication is allowed for your Unity game and the users that are allowed to authenticate. Within the Realm dashboard, click the Authentication tab followed by the Authentication Providers tab. Enable Email / Password if it isn’t already enabled. After email and password authentication is enabled for your application, click the Users tab and choose to Add New User with the email and password information of your choice.

The users can be added through an API request, but for this example we’re just going to focus on adding them manually.

With the user information added, we need to define the collections and schemas to sync with our game. Click the Schema tab within the Realm dashboard and choose to create a new database and collection if you don’t already have a space_shooter database and a PlayerProfile collection.

The schema for the PlayerProfile collection should look like the following:

{
    "title": "PlayerProfile",
    "bsonType": "object",
    "required": [
        "high_score",
        "spark_blaster_enabled",
        "cross_blaster_enabled",
        "score",
        "_partition"
    ],
    "properties": {
        "_id": {
            "bsonType": "string"
        },
        "_partition": {
            "bsonType": "string"
        },
        "high_score": {
            "bsonType": "int"
        },
        "score": {
            "bsonType": "int"
        },
        "spark_blaster_enabled": {
            "bsonType": "bool"
        },
        "cross_blaster_enabled": {
            "bsonType": "bool"
        }
    }
}

In the above schema, we’re saying that we are going to have five fields with the types defined. These fields will eventually be mapped to C# objects within the Unity game. The one field to pay the most attention to is the _partition field. The _partition field will be the most valuable when it comes to sync because it will represent which data is synchronized rather than Realm attempting to synchronize the entire MongoDB Atlas collection.

In our example, the _partition field should hold user email addresses because they are unique and the user will provide them when they log in. With this we can specify that we only want to sync data based on the users email address.

With the schema defined, now we can enable Realm Sync.

Within the Realm dashboard, click on the Sync tab. Specify the cluster and the field to be used as the partition key. You should specify _partition as the partition key in this example, although the actual field name doesn’t matter if you wanted to call it something else. Leaving the permissions as the default will give users read and write permissions.

Realm Sync will only sync collections that have a defined schema within Realm. You could have other collections in your MongoDB Atlas cluster, but they won’t sync automatically unless you have schemas defined for them.

At this point, Realm is configured in Unity and Realm is configured for synchronization of data. We can now focus on the actual game development.

Defining the Realm Data Model and Usage Logic

When it comes to data, Realm is going to manage all of it. We need to create a data model that matches the schema that we had just created for synchronization and we need to create the logic for our RealmController game object.

Let’s start by creating the model to be used.

Within the Assets folder of your project, create a Scripts folder with a PlayerProfile.cs script in it. The PlayerProfile.cs script should contain the following C# code:

using Realms;
using Realms.Sync;

public class PlayerProfile : RealmObject {

    [PrimaryKey]
    [MapTo("_id")]
    public string UserId { get; set; }

    [MapTo("high_score")]
    public int HighScore { get; set; }

    [MapTo("score")]
    public int Score { get; set; }

    [MapTo("spark_blaster_enabled")]
    public bool SparkBlasterEnabled { get; set; }

    [MapTo("cross_blaster_enabled")]
    public bool CrossBlasterEnabled { get; set; }

    public PlayerProfile() {}

    public PlayerProfile(string userId) {
        this.UserId = userId;
        this.HighScore = 0;
        this.Score = 0;
        this.SparkBlasterEnabled = false;
        this.CrossBlasterEnabled = false;
    }

}

What we’re doing is we are defining object fields and how they map to a remote document in a MongoDB collection. While our C# object looks like the above, the BSON that we’ll see in MongoDB Atlas will look like the following:

{
    "_id": "12345",
    "high_score": 1337,
    "score": 0,
    "spark_blaster_enabled": false,
    "cross_blaster_enabled": false
}

It’s important to note that the documents in Atlas might have more fields than what we see in our game. We’ll only be able to use the mapped fields in our game, so if we have for example an email address in our document, we won’t see it in the game because it isn’t mapped.

With the Realm model in place, we can focus on syncing, querying, and writing our data.

Within the Assets/Scripts directory, add a RealmController.cs script. This script should contain the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Realms;
using Realms.Sync;
using Realms.Sync.Exceptions;
using System.Threading.Tasks;

public class RealmController : MonoBehaviour {

    public static RealmController Instance;

    public string RealmAppId = "YOUR_REALM_APP_ID_HERE";

    private Realm _realm;
    private App _realmApp;
    private User _realmUser;

    void Awake() {
        DontDestroyOnLoad(gameObject);
        Instance = this;
    }

    void OnDisable() {
        if(_realm != null) {
            _realm.Dispose();
        }
    }

    public async Task<string> Login(string email, string password) {}

    public PlayerProfile GetPlayerProfile() {}

    public void IncreaseScore() {}

    public void ResetScore() {}

    public bool IsSparkBlasterEnabled() {}

    public bool IsCrossBlasterEnabled() {}

}

The above code is incomplete, but it gives you an idea of where we are going.

First, take notice of the RealmAppId variable. You’re going to want to use your Realm application so sync can happen based on how you’ve configured everything. This also applies to the authentication rules that are in place for your particular application.

The RealmController class is going to be used as a singleton object between scenes. The goal is to make sure it cannot be destroyed and everything we do is through a static instance of itself.

In the Awake method, we are saying that the game object that the script is attached to should not be destroyed and that we are setting the static variable to itself. In the OnDisable, we are doing cleanup which should really only happen when the game is closed.

Most of the magic will happen in the Login function:

public async Task<string> Login(string email, string password) {
    if(email != "" && password != "") {
        _realmApp = App.Create(new AppConfiguration(RealmAppId) {
            MetadataPersistenceMode = MetadataPersistenceMode.NotEncrypted
        });
        try {
            if(_realmUser == null) {
                _realmUser = await _realmApp.LogInAsync(Credentials.EmailPassword(email, password));
                _realm = await Realm.GetInstanceAsync(new SyncConfiguration(email, _realmUser));
            } else {
                _realm = Realm.GetInstance(new SyncConfiguration(email, _realmUser));
            }
        } catch (ClientResetException clientResetEx) {
            if(_realm != null) {
                _realm.Dispose();
            }
            clientResetEx.InitiateClientReset();
        }
        return _realmUser.Id;
    }
    return "";
}

In the above code, we are defining our Realm application based on the application id. Next we are attempting to log into the application using email and password authentication, something we had previously configured in the web dashboard. If successful, we are getting an instance of our Realm to work with going forward. The data to be synchronized is based on our partition field which in this case is the email address. This means we’re only synchronizing data for this particular email address.

If all goes smooth with the login, the id for the user is returned.

At some point in time, we’re going to need to load the player data. This is where the GetPlayerProfile function comes in:

public PlayerProfile GetPlayerProfile() {
    PlayerProfile _playerProfile = _realm.Find<PlayerProfile>(_realmUser.Id);
    if(_playerProfile == null) {
        _realm.Write(() => {
            _playerProfile = _realm.Add(new PlayerProfile(_realmUser.Id));
        });
    }
    return _playerProfile;
}

What we’re doing is we’re taking the current Realm instance and we’re finding a particular player profile based on the id. If one does not exist, then we create one using the current id. In the end, we’re returning a player profile, whether it be one that we had been using or a fresh one.

We know that we’re going to be working with score data in our game. We need to be able to increase the score, reset the score, and calculate the high score for a player.

Starting with the IncreaseScore, we have the following:

public void IncreaseScore() {
    PlayerProfile _playerProfile = GetPlayerProfile();
    if(_playerProfile != null) {
        _realm.Write(() => {
            _playerProfile.Score++;
        });
    }
}

First we get the player profile and then we take whatever score is associated with it and increase it by one. With Realm we can work with our objects like native C# objects. The exception is that when we want to write, we have to wrap it in a Write block. Reads we don’t have to.

Next let’s look at the ResetScore function:

public void ResetScore() {
    PlayerProfile _playerProfile = GetPlayerProfile();
    if(_playerProfile != null) {
        _realm.Write(() => {
            if(_playerProfile.Score > _playerProfile.HighScore) {
                _playerProfile.HighScore = _playerProfile.Score;
            }
            _playerProfile.Score = 0;
        });
    }
}

In the end we want to zero out the score, but we also want to see if our current score is the highest score before we do. We can do all this within the Write block and it will synchronize to the server.

Finally we have our two functions to tell us if a certain blaster is available to us:

public bool IsSparkBlasterEnabled() {
    PlayerProfile _playerProfile = GetPlayerProfile();
    return _playerProfile != null ? _playerProfile.SparkBlasterEnabled : false;
}

The reason our blasters are data dependent is because we may want to unlock them based on points or through a micro-transaction. In this case, maybe Realm Sync takes care of it.

The IsCrossBlasterEnabled function isn’t much different:

public bool IsCrossBlasterEnabled() {
    PlayerProfile _playerProfile = GetPlayerProfile();
    return _playerProfile != null ? _playerProfile.CrossBlasterEnabled : false;
}

The difference is we are using a different field from our data model.

With the Realm logic in place for the game, we can focus on giving the other game objects life through scripts.

Developing the Game-Play Logic Scripts for the Space Shooter Game Objects

Almost every game object that we’ve created will be receiving a script with logic. To keep the flow appropriate, we’re going to add logic in a natural progression. This means we’re going to start with the LoginScene and each of the game objects that live in it.

For the LoginScene, only two game objects will be receiving scripts:

  • LoginController
  • RealmController

Since we already have a RealmController.cs script file, go ahead and attach it to the RealmController game object as a component.

Next up, we need to create an Assets/Scripts/LoginController.cs file with the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class LoginController : MonoBehaviour {

    public Button LoginButton;
    public InputField UsernameInput;
    public InputField PasswordInput;

    void Start() {
        UsernameInput.text = "nic.raboy@mongodb.com";
        PasswordInput.text = "password1234";
        LoginButton.onClick.AddListener(Login);
    }

    async void Login() {
        if(await RealmController.Instance.Login(UsernameInput.text, PasswordInput.text) != "") {
            SceneManager.LoadScene("MainScene");
        }
    }

    void Update() {
        if(Input.GetKey("escape")) {
            Application.Quit();
        }
    }

}

There’s not a whole lot going on since the backbone of this script is in the RealmController.cs file.

What we’re doing in the LoginController.cs file is we’re defining the UI components which we’ll link through the Unity IDE. When the script starts, we’re going to default the values of our input fields and we’re going to assign a click event listener to the button.

When the button is clicked, the Login function from the RealmController.cs file is called and we pass the provided email and password. If we get an id back, we know we were successful so we can switch to the next scene.

The Update method isn’t a complete necessity, but if you want to be able to quit the game with the escape key, that is what this particular piece of logic does.

Attach the LoginController.cs script to the LoginController game object as a component and then drag each of the corresponding UI game objects into the script via the game object inspector. Remember, we defined public variables for each of the UI components. We just need to tell Unity what they are by linking them in the inspector.

The LoginScene logic is complete. Can you believe it? This is because the Realm SDK for Unity is doing all the heavy lifting for us.

The MainScene has a lot more going on, but we’ll break down what’s happening.

Let’s start with something you don’t actually see but that controls all of our prefab instances. I’m talking about the object pooling script.

In short, creating and destroying game objects on-demand is resource intensive. Instead, we should create a fixed amount of game objects when the game loads and hide them or show them based on when they are needed. This is what an object pool does.

Create an Assets/Scripts/ObjectPool.cs file with the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{

    public static ObjectPool SharedInstance;

    private List<GameObject> pooledEnemies;
    private List<GameObject> pooledBlasters;
    private List<GameObject> pooledCrossBlasts;
    private List<GameObject> pooledSparkBlasts;
    public GameObject enemyToPool;
    public GameObject blasterToPool;
    public GameObject crossBlastToPool;
    public GameObject sparkBlastToPool;
    public int amountOfEnemiesToPool;
    public int amountOfBlastersToPool;
    public int amountOfCrossBlastsToPool;
    public int amountOfSparkBlastsToPool;

    void Awake() {
        SharedInstance = this;
    }

    void Start() {
        pooledEnemies = new List<GameObject>();
        pooledBlasters = new List<GameObject>();
        pooledCrossBlasts = new List<GameObject>();
        pooledSparkBlasts = new List<GameObject>();
        GameObject tmpEnemy;
        GameObject tmpBlaster;
        GameObject tmpCrossBlast;
        GameObject tmpSparkBlast;
        for(int i = 0; i < amountOfEnemiesToPool; i++) {
            tmpEnemy = Instantiate(enemyToPool);
            tmpEnemy.SetActive(false);
            pooledEnemies.Add(tmpEnemy);
        }
        for(int i = 0; i < amountOfBlastersToPool; i++) {
            tmpBlaster = Instantiate(blasterToPool);
            tmpBlaster.SetActive(false);
            pooledBlasters.Add(tmpBlaster);
        }
        for(int i = 0; i < amountOfCrossBlastsToPool; i++) {
            tmpCrossBlast = Instantiate(crossBlastToPool);
            tmpCrossBlast.SetActive(false);
            pooledCrossBlasts.Add(tmpCrossBlast);
        }
        for(int i = 0; i < amountOfSparkBlastsToPool; i++) {
            tmpSparkBlast = Instantiate(sparkBlastToPool);
            tmpSparkBlast.SetActive(false);
            pooledSparkBlasts.Add(tmpSparkBlast);
        }
    }

    public GameObject GetPooledEnemy() {
        for(int i = 0; i < amountOfEnemiesToPool; i++) {
            if(pooledEnemies[i].activeInHierarchy == false) {
                return pooledEnemies[i];
            }
        }
        return null;
    }

    public GameObject GetPooledBlaster() {
        for(int i = 0; i < amountOfBlastersToPool; i++) {
            if(pooledBlasters[i].activeInHierarchy == false) {
                return pooledBlasters[i];
            }
        }
        return null;
    }

    public GameObject GetPooledCrossBlast() {
        for(int i = 0; i < amountOfCrossBlastsToPool; i++) {
            if(pooledCrossBlasts[i].activeInHierarchy == false) {
                return pooledCrossBlasts[i];
            }
        }
        return null;
    }

    public GameObject GetPooledSparkBlast() {
        for(int i = 0; i < amountOfSparkBlastsToPool; i++) {
            if(pooledSparkBlasts[i].activeInHierarchy == false) {
                return pooledSparkBlasts[i];
            }
        }
        return null;
    }
    
}

The above object pooling logic is not code optimized because I wanted to keep it readable. If you want to see an optimized version, check out a previous tutorial I wrote on the subject.

So let’s break down what we’re doing in this object pool.

We have four different game objects to pool:

  • Enemies
  • Spark Blasters
  • Cross Blasters
  • Regular Blasters

These need to be pooled because there could be more than one of the same object at any given time. We’re using public variables for each of the game objects and quantities so that we can properly link them to actual game objects in the Unity IDE.

Like with the RealmController.cs script, this script will also act as a singleton to be used as needed.

In the Start method, we are instantiating a game object, as per the quantities defined through the Unity IDE, and adding them to a list. Ideally the linked game object should be one of the prefabs that we previously defined. The list of instantiated game objects represent our pools. We have four object pools to pull from.

Pulling from the pool is as simple as creating a function for each pool and seeing what’s available. Take the GetPooledEnemy function for example:

public GameObject GetPooledEnemy() {
    for(int i = 0; i < amountOfEnemiesToPool; i++) {
        if(pooledEnemies[i].activeInHierarchy == false) {
            return pooledEnemies[i];
        }
    }
    return null;
}

In the above code, we loop through each object in our pool, in this case enemies. If an object is inactive it means we can pull it and use it. If our pool is depleted, then we either defined too small of a pool or we need to wait until something is available.

I like to pool about 50 of each game object even if I only ever plan to use 10. Doesn’t hurt to have excess as it’s still less resource-heavy than creating and destroying game objects as needed.

The ObjectPool.cs file should be attached as a component to the GameController game object. After attaching, make sure you assign your prefabs and the pooled quantities using the game object inspector within the Unity IDE.

The ObjectPool.cs script isn’t the only script we’re going to attach to the GameController game object. We need to create a script that will control the flow of our game. Create an Assets/Scripts/GameController.cs file with the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameController : MonoBehaviour {

    public float timeUntilEnemy = 1.0f;
    public float minTimeUntilEnemy = 0.25f;
    public float maxTimeUntilEnemy = 2.0f;

    public GameObject SparkBlasterGraphic;
    public GameObject CrossBlasterGraphic;

    public Text highScoreText;
    public Text scoreText;

    private PlayerProfile _playerProfile;

    void OnEnable() {
        _playerProfile = RealmController.Instance.GetPlayerProfile();
        highScoreText.text = "HIGH SCORE: " + _playerProfile.HighScore.ToString();
        scoreText.text = "SCORE: " + _playerProfile.Score.ToString();
    }

    void Update() {
        highScoreText.text = "HIGH SCORE: " + _playerProfile.HighScore.ToString();
        scoreText.text = "SCORE: " + _playerProfile.Score.ToString();
        timeUntilEnemy -= Time.deltaTime;
        if(timeUntilEnemy <= 0) {
            GameObject enemy = ObjectPool.SharedInstance.GetPooledEnemy();
            if(enemy != null) {
                enemy.SetActive(true);
            }
            timeUntilEnemy = Random.Range(minTimeUntilEnemy, maxTimeUntilEnemy);
        }
        if(_playerProfile != null) {
            SparkBlasterGraphic.SetActive(_playerProfile.SparkBlasterEnabled);
            CrossBlasterGraphic.SetActive(_playerProfile.CrossBlasterEnabled);
        }
        if(Input.GetKey("escape")) {
            Application.Quit();
        }
    }

}

There’s a diverse set of things happening in the above script, so let’s break them down.

You’ll notice the following public variables:

public float timeUntilEnemy = 1.0f;
public float minTimeUntilEnemy = 0.25f;
public float maxTimeUntilEnemy = 2.0f;

We’re going to use these variables to define when a new enemy should be activated.

The timeUntilEnemy represents how much actual time from the current time until a new enemy should be pulled from the object pool. The minTimeUntilEnemy and maxTimeUntilEnemy will be used for randomizing what the timeUntilEnemy value should become after an enemy is pooled. It’s boring to have all enemies appear after a fixed amount of time, so the minimum and maximum values keep things interesting.

public GameObject SparkBlasterGraphic;
public GameObject CrossBlasterGraphic;

public Text highScoreText;
public Text scoreText;

Remember those UI components and sprites to represent enabled blasters we had created earlier in the Unity IDE? When we attach this script to the GameController game object, you’re going to want to assign the other components in the game object inspector.

This brings us to the OnEnable method:

void OnEnable() {
    _playerProfile = RealmController.Instance.GetPlayerProfile();
    highScoreText.text = "HIGH SCORE: " + _playerProfile.HighScore.ToString();
    scoreText.text = "SCORE: " + _playerProfile.Score.ToString();
}

The OnEnable method is where we’re going to get our current player profile and then update the score values visually based on the data stored in the player profile. The Update method will continuously update those score values for as long as the scene is showing.

void Update() {
    highScoreText.text = "HIGH SCORE: " + _playerProfile.HighScore.ToString();
    scoreText.text = "SCORE: " + _playerProfile.Score.ToString();
    timeUntilEnemy -= Time.deltaTime;
    if(timeUntilEnemy <= 0) {
        GameObject enemy = ObjectPool.SharedInstance.GetPooledEnemy();
        if(enemy != null) {
            enemy.SetActive(true);
        }
        timeUntilEnemy = Random.Range(minTimeUntilEnemy, maxTimeUntilEnemy);
    }
    if(_playerProfile != null) {
        SparkBlasterGraphic.SetActive(_playerProfile.SparkBlasterEnabled);
        CrossBlasterGraphic.SetActive(_playerProfile.CrossBlasterEnabled);
    }
    if(Input.GetKey("escape")) {
        Application.Quit();
    }
}

In the Update method, every time it’s called, we subtract the delta time from our timeUntilEnemy variable. When the value is zero, we attempt to get a new enemy from the object pool and then reset the timer. Outside of the object pooling, we’re also checking to see if the other blasters have become enabled. If they have been, we can update the game object status for our sprites. This will allow us to easily show and hide these sprites.

If you haven’t already, attach the GameController.cs script to the GameController game object. Remember to update any values for the script within the game object inspector.

If we were to run the game, every enemy would have the same position and they would not be moving. We need to assign logic to the enemies.

Create an Assets/Scripts/Enemy.cs file with the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {

    public float movementSpeed = 5.0f;

    void OnEnable() {
        float randomPositionY = Random.Range(-4.0f, 4.0f);
        transform.position = new Vector3(10.0f, randomPositionY, 0);
    }

    void Update() {
        transform.position += Vector3.left * movementSpeed * Time.deltaTime;
        if(transform.position.x < -10.0f) {
            gameObject.SetActive(false);
        }
    }

    void OnTriggerEnter2D(Collider2D collider) {
        if(collider.tag == "Weapon") {
            gameObject.SetActive(false);
            RealmController.Instance.IncreaseScore();
        }
    }

}

When the enemy is pulled from the object pool, the game object becomes enabled. So the OnEnable method picks a random y-axis position for the game object. For every frame, the Update method will move the game object along the x-axis. If the game object goes off the screen, we can safely add it back into the object pool.

The OnTriggerEnter2D method is for our collision detection. We’re not doing physics collisions so this method just tells us if the objects have touched. If the current game object, in this case the enemy, has collided with a game object tagged as a weapon, then add the enemy back into the queue and increase the score.

Attach the Enemy.cs script to your enemy prefab.

By now, your game probably looks something like this, minus the animations:

Space Shooter Enemies

We won’t be worrying about animations in this tutorial. Consider that part of your extracurricular challenge after completing this tutorial.

So we have a functioning enemy pool. Let’s look at the blaster logic since it is similar.

Create an Assets/Scripts/Blaster.cs file with the following C# logic:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Blaster : MonoBehaviour {

    public float movementSpeed = 5.0f;
    public float decayRate = 2.0f;

    private float timeToDecay;

    void OnEnable() {
        timeToDecay = decayRate;
    }

    void Update() {
        timeToDecay -= Time.deltaTime;
        transform.position += Vector3.right * movementSpeed * Time.deltaTime;
        if(transform.position.x > 10.0f || timeToDecay <= 0) {
            gameObject.SetActive(false);
        }
    }

    void OnTriggerEnter2D(Collider2D collider) {
        if(collider.tag == "Enemy") {
            gameObject.SetActive(false);
        }
    }

}

Look mildly familiar to the enemy? It is similar.

We need to first define how fast each blaster should move and how quickly the blaster should disappear if it hasn’t hit anything.

In the Update method will subtract the current time from our blaster decay time. The blaster will continue to move along the x-axis until it has either gone off screen or it has decayed. In this scenario, the blaster is added back into the object pool. If the blaster collides with a game object tagged as an enemy, the blaster is also added back into the pool. Remember, the blaster will likely be tagged as a weapon so the Enemy.cs script will take care of adding the enemy back into the object pool.

Attach the Blaster.cs script to your blaster prefab and apply any value settings as necessary with the Unity IDE in the inspector.

To make the game interesting, we’re going to add some very slight differences to the other blasters.

Create an Assets/Scripts/CrossBlast.cs script with the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossBlast : MonoBehaviour {

    public float movementSpeed = 5.0f;

    void Update() {
        transform.position += Vector3.right * movementSpeed * Time.deltaTime;
        if(transform.position.x > 10.0f) {
            gameObject.SetActive(false);
        }
    }

    void OnTriggerEnter2D(Collider2D collider) { }

}

At a high level, this blaster behaves the same. However, if it collides with an enemy, it keeps going. It only goes back into the object pool when it goes off the screen. So there is no decay and it isn’t a one enemy per blast weapon.

Let’s look at an Assets/Scripts/SparkBlast.cs script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SparkBlast : MonoBehaviour {

    public float movementSpeed = 5.0f;

    void Update() {
        transform.position += Vector3.right * movementSpeed * Time.deltaTime;
        if(transform.position.x > 10.0f) {
            gameObject.SetActive(false);
        }
    }

    void OnTriggerEnter2D(Collider2D collider) {
        if(collider.tag == "Enemy") {
            gameObject.SetActive(false);
        }
    }

}

The minor difference in the above script is that it has no decay, but it can only ever destroy one enemy.

Make sure you attach these scripts to the appropriate blaster prefabs.

We’re almost done! We have one more script and that’s for the actual player!

Create an Assets/Scripts/Player.cs file and add the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{

    public float movementSpeed = 5.0f;
    public float respawnSpeed = 8.0f;
    public float weaponFireRate = 0.5f;

    private float nextBlasterTime = 0.0f;
    private bool isRespawn = true;

    void Update() {
        if(isRespawn == true) {
            transform.position = Vector2.MoveTowards(transform.position, new Vector2(-6.0f, -0.25f), respawnSpeed * Time.deltaTime);
            if(transform.position == new Vector3(-6.0f, -0.25f, 0.0f)) {
                isRespawn = false;
            }
        } else {
            if(Input.GetKey(KeyCode.UpArrow) && transform.position.y < 4.0f) {
                transform.position += Vector3.up * movementSpeed * Time.deltaTime;
            } else if(Input.GetKey(KeyCode.DownArrow) && transform.position.y > -4.0f) {
                transform.position += Vector3.down * movementSpeed * Time.deltaTime;
            }
            if(Input.GetKey(KeyCode.Space) && Time.time > nextBlasterTime) {
                nextBlasterTime = Time.time + weaponFireRate;
                GameObject blaster = ObjectPool.SharedInstance.GetPooledBlaster();
                if(blaster != null) {
                    blaster.SetActive(true);
                    blaster.transform.position = new Vector3(transform.position.x + 1, transform.position.y);
                }
            }
            if(RealmController.Instance.IsCrossBlasterEnabled()) {
                if(Input.GetKey(KeyCode.B) && Time.time > nextBlasterTime) {
                    nextBlasterTime = Time.time + weaponFireRate;
                    GameObject crossBlast = ObjectPool.SharedInstance.GetPooledCrossBlast();
                    if(crossBlast != null) {
                        crossBlast.SetActive(true);
                        crossBlast.transform.position = new Vector3(transform.position.x + 1, transform.position.y);
                    }
                }
            }
            if(RealmController.Instance.IsSparkBlasterEnabled()) {
                if(Input.GetKey(KeyCode.V) && Time.time > nextBlasterTime) {
                    nextBlasterTime = Time.time + weaponFireRate;
                    GameObject sparkBlast = ObjectPool.SharedInstance.GetPooledSparkBlast();
                    if(sparkBlast != null) {
                        sparkBlast.SetActive(true);
                        sparkBlast.transform.position = new Vector3(transform.position.x + 1, transform.position.y);
                    }
                }
            }
        }
    }

    void OnTriggerEnter2D(Collider2D collider) {
        if(collider.tag == "Enemy" && isRespawn == false) {
            RealmController.Instance.ResetScore();
            transform.position = new Vector3(-10.0f, -0.25f, 0.0f);
            isRespawn = true;
        }
    }

}

Looking at the above script, we have a few variables to keep track of:

public float movementSpeed = 5.0f;
public float respawnSpeed = 8.0f;
public float weaponFireRate = 0.5f;

private float nextBlasterTime = 0.0f;
private bool isRespawn = true;

We want to define how fast the player can move, how long it takes for the respawn animation to happen, and how fast you’re allowed to fire blasters.

In the Update method, we first check to see if we are currently respawning:

transform.position = Vector2.MoveTowards(transform.position, new Vector2(-6.0f, -0.25f), respawnSpeed * Time.deltaTime);
if(transform.position == new Vector3(-6.0f, -0.25f, 0.0f)) {
    isRespawn = false;
}

If we are respawning, then we need to smoothly move the player game object towards a particular coordinate position. When the game object has reached that new position, then we can disable the respawn indicator that prevents us from controlling the player.

If we’re not respawning, we can check to see if the movement keys were pressed:

if(Input.GetKey(KeyCode.UpArrow) && transform.position.y < 4.0f) {
    transform.position += Vector3.up * movementSpeed * Time.deltaTime;
} else if(Input.GetKey(KeyCode.DownArrow) && transform.position.y > -4.0f) {
    transform.position += Vector3.down * movementSpeed * Time.deltaTime;
}

When pressing a key, as long as we haven’t moved outside our y-axis boundary, we can adjust the position of the player. Since this is in the Update method, the movement should be smooth for as long as you are holding a key.

Using a blaster isn’t too different:

if(Input.GetKey(KeyCode.Space) && Time.time > nextBlasterTime) {
    nextBlasterTime = Time.time + weaponFireRate;
    GameObject blaster = ObjectPool.SharedInstance.GetPooledBlaster();
    if(blaster != null) {
        blaster.SetActive(true);
        blaster.transform.position = new Vector3(transform.position.x + 1, transform.position.y);
    }
}

If the particular blaster key is pressed and our rate limit isn’t exceeded, we can update our nextBlasterTime based on the rate limit, pull a blaster from the object pool, and let the blaster do its magic based on the Blaster.cs script. All we’re doing in the Player.cs script is checking to see if we’re allowed to fire and if we are pull from the pool.

The data dependent spark and cross blasters follow the same rules, the exception being that we first check to see if they are enabled in our player profile.

Finally, we have our collisions:

void OnTriggerEnter2D(Collider2D collider) {
    if(collider.tag == "Enemy" && isRespawn == false) {
        RealmController.Instance.ResetScore();
        transform.position = new Vector3(-10.0f, -0.25f, 0.0f);
        isRespawn = true;
    }
}

If our player collides with a game object tagged as an enemy and we’re not currently respawning, then we can reset the score and trigger the respawn.

Make sure you attach this Player.cs script to your Player game object.

If everything worked out, the game should be functional at this point. If something isn’t working correctly, double check the following:

  • Make sure each of your game objects is properly tagged.
  • Make sure the scripts are attached to the proper game object or prefab.
  • Make sure the values on the scripts have been defined through the Unity IDE inspector.

Play around with the game and setting values within MongoDB Atlas.

Conclusion

You just saw how to create a space shooter type game with Unity that syncs with MongoDB Atlas by using the Realm SDK for Unity and Realm Sync. Realm only played a small part in this game because that is the beauty of Realm. You can get data persistence and sync with only a few lines of code.

Want to give this project a try? I’ve uploaded all of the source code to GitHub. You just need to clone the project, replace my Realm ID with yours, and build the project. Of course you’ll still need to have properly configured Atlas and Realm in the cloud.

If you’re looking for a slightly slower introduction to Realm with Unity, check out a previous tutorial that I wrote on the subject.

If you’d like to connect with us further, don’t forget to visit the community forums.

This content first appeared on MongoDB.

Original article source at: https://www.thepolyglotdeveloper.com/

#mongodb #game #unity 

How to Build A Space Shooter Game That Syncs with Unity, MongoDB Realm
Gordon  Matlala

Gordon Matlala

1669983720

How Implement The Konami Keystroke Cheat Code in A Unity Game

Remember all the cheat codes you could use in old-school games back in the day? Remember the Konami cheat code? Ever wonder how you could add cheat codes to your own games?

Adding cheat codes to your game is a great way to leave your imprint. Think easter eggs, but even more secretive.

In this tutorial we’re going to see how to add keystroke driven cheat codes to a game built with Unity and C#. While the Konami cheat code will be the basis of this example, a lot of the logic and ideas can be applied to other aspects of your game.

To get an idea of what we want to accomplish, take a look at the following animated image:

Konami Cheat Code in Unity Game

In the above image, each keystroke is added to the history of keystrokes and displayed on the screen. If we happen to press the key combination “up, up, down, down, left, right, left, right, b, a” the Konami cheat code will be triggered. In our example it doesn’t do anything other than display on the screen, but you could do something like increasing the score, giving more lives, or something else.

There are a few ways to accomplish this task in Unity, but I’m going to demonstrate two different ways as well as their advantages or disadvantages.

Capturing Keystroke Information in Unity with the OnGUI Method

We’re first going to take a look at the OnGUI method that is baked into Unity. If you’ve never seen it, the method would look something like this:

void OnGUI() {
    Event e = Event.current;
    if(e.isKey && e.type == EventType.KeyUp) {
        Debug.Log(e.keyCode.ToString());
    }
}

You can add the above method to any script that extends MonoBehaviour and in the above example the keycode will be printed assuming that it is a KeyUp event and that it is a key and not a mouse click or something else.

While it is super convenient to be able to determine which key was pressed in only a few lines of code using the OnGUI method, it’s not without potential problems. The largest of the problems being that the OnGUI method could be executed up to two times per frame. This is not good for us because it means that numerous events could be registered even if we only intended on one.

To be clear, there are scenarios where if you push the “UpArrow” key, it could get registered as you pushing it twice. In the example of cheat codes where the key combination is important, having this variable amount of keystrokes per frame won’t work.

Using a Simple Loop to Capture Keystroke Information in Unity

Let’s take a look at another way to capture specifically which keys were pressed by the user. In this alternative, we’ll be making use of the Update method and some loops.

Let’s look at the function we can use for detecting which key was pressed:

private KeyCode DetectKeyPressed() {
    foreach(KeyCode key in Enum.GetValues(typeof(KeyCode))) {
        if(Input.GetKeyDown(key)) {
            return key;
        }
    }
    return KeyCode.None;
}

When executed, the DetectKeyPressed function will loop through all of the potential key codes to see if you’re pressing one of them. If you are, return the keycode for the next step in our pipeline.

So the problem with this is that you have to loop through all the potential keycodes every frame. There aren’t too many of them to stress out modern computers and devices, but it could become a bottleneck in your game if you’re not careful.

With the information and the DetectKeyPressed function in mind, we can use it in our Update method like this:

void Update() {
    KeyCode keyPressed = DetectKeyPressed();
    Debug.Log(keyPressed);
}

From a code length and readability perspective, this way of detecting user input isn’t too different from the OnGUI way. You just have to evaluate which of the tradeoffs and benefits work the best for your use-case.

Building a Game Scene with the Konami Cheat Code Implemented

Now that we have a way to capture keystrokes from the user, we can use that information to implement cheat codes in our game.

I won’t be going into a deep-dive on how to develop with Unity, so make sure you have the following in your scene for this example to be successful:

  • Camera
  • Canvas
    • KeyStrokeText
    • CheatCodeText
  • Controller

We’ll use game objects with the Text component to show our keystroke history or the cheat code that was entered into the game. The Controller game object will be empty with the exception that it contains a script attached to it.

Example Unity Scene for Konami Cheat Code

So now that we have our basic game objects with various components attached to them in our scene, let’s create a script that will be our controller for the scene. Create a new C# script named Controller.cs with the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;

public class Controller : MonoBehaviour {

    public Text keyStrokeText;
    public Text cheatCodeText;

    private List<string> _keyStrokeHistory;

    void Awake() {
        _keyStrokeHistory = new List<string>();
    }

    void Update() {
        KeyCode keyPressed = DetectKeyPressed();
        AddKeyStrokeToHistory(keyPressed.ToString());
        keyStrokeText.text = "HISTORY: " + GetKeyStrokeHistory();
        if(GetKeyStrokeHistory().Equals("UpArrow,UpArrow,DownArrow,DownArrow,LeftArrow,RightArrow,LeftArrow,RightArrow,B,A")) {
            cheatCodeText.text = "KONAMI CHEAT CODE DETECTED!";
            ClearKeyStrokeHistory();
        }
    }

    private KeyCode DetectKeyPressed() {
        foreach(KeyCode key in Enum.GetValues(typeof(KeyCode))) {
            if(Input.GetKeyDown(key)) {
                return key;
            }
        }
        return KeyCode.None;
    }

    private void AddKeyStrokeToHistory(string keyStroke) {
        if(!keyStroke.Equals("None")) {
            _keyStrokeHistory.Add(keyStroke);
            if(_keyStrokeHistory.Count > 10) {
                _keyStrokeHistory.RemoveAt(0);
            }
        }
    }

    private string GetKeyStrokeHistory() {
        return String.Join(",", _keyStrokeHistory.ToArray());
    }

    private void ClearKeyStrokeHistory() {
        _keyStrokeHistory.Clear();
    }

}

In the above example I am choosing to not use the OnGUI approach as I didn’t get the results I was looking for.

So let’s break down what’s happening, starting with the Awake method:

void Awake() {
    _keyStrokeHistory = new List<string>();
}

For most cheat codes to be successful, we need to be able to track any number of keystrokes made. For this example, we’re going to store every keystroke in a List variable. The reality is that we aren’t going to store every keystroke, but we could if we wanted to.

We’ve already seen the DetectKeyPressed method, but we haven’t seen how to add the keystrokes to our history. This is where the AddKeyStrokeToHistory method comes in:

private void AddKeyStrokeToHistory(string keyStroke) {
    if(!keyStroke.Equals("None")) {
        _keyStrokeHistory.Add(keyStroke);
        if(_keyStrokeHistory.Count > 10) {
            _keyStrokeHistory.RemoveAt(0);
        }
    }
}

When we attempt to add to the history we check to make sure it isn’t the None keystroke which pops up occasionally. If it’s not, we push the keystroke into the list and then check to see our list size. If our size exceeds our magic number, then we start removing history from the front of the list which represents the oldest keystrokes.

We’re going to need to be able to check what our keystroke history looks like, so we can do that with the GetKeyStrokeHistory method:

private string GetKeyStrokeHistory() {
    return String.Join(",", _keyStrokeHistory.ToArray());
}

Rather than doing something complicated, I just convert the list into a string delimited by the comma character. This dumps the entire list, but your logic might be different if your cheat codes have various combination sizes. In your circumstance you might want to return the first few keystrokes or the last few keystrokes.

If you look back at the Update method we can compare the keystroke history with a cheat code and if it matches, we can do something magical!

Conclusion

You just saw two of many possible ways to implement cheat codes into your Unity game. While the premise of this example was cheat codes, the keystroke capturing logic can be applied to different scenarios as well.

To keep your cheat code logic isolated from player logic, enemy logic, or something else, I find it best to keep the logic in the controller for your scene. You don’t have to do it this way, but I personally find it to keep things clean and organized.

You can find a video version of this tutorial below:

Original article source at: https://www.thepolyglotdeveloper.com/

#cheating #unity #game 

How Implement The Konami Keystroke Cheat Code in A Unity Game
Sheldon  Grant

Sheldon Grant

1669920300

How to interacting with Sprite and UI Buttons In A Unity Game

When you’re developing a game, whether it be 2D or 3D, you’re going to need to add menus with buttons at some point. These buttons could be as simple as a means to exit the game, or something more complex.

In the Unity game development framework, there are a few ways to accomplish buttons. You could create sprites and interact with them through mouse clicks and keyboard presses, or you could make use of the canvas and UI elements.

In this tutorial, we’re going to look at both options for creating buttons in a game.

Adding 2D Sprite Buttons to a Game

The first approach we’ll explore is using a sprite as a button. Since a sprite is more or less an image with other perks, you can use whatever image as you want to represent the button. Rather than getting fancy in this example, our sprite-based button will be nothing more than a white square.

To get an idea of what we’re going to build, take a look at the following image:

Sprite Button in Unity

Within your Unity IDE, choose GameObject -> 2D Object -> Sprites -> Square from the menu. Next, create a SpriteButton.cs script within your Assets directory. The SpriteButton.cs script should contain the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpriteButton : MonoBehaviour {

    void OnMouseDown() {
        Debug.Log("BUTTON PRESSED");
    }

}

Once the script is saved, you can either drag the script onto the sprite, or choose to add a new component to the sprite at which point you can select the script.

With the script attached, you can try running the game. You’ll notice that our sprite button does nothing.

The OnMouseDown method does nothing for a sprite until we add a collision box to the sprite.

In the Unity IDE, select the sprite and then choose to add a component. You’ll want to add a Box Collider 2D component to the sprite. If you try running the game again, clicking the button will result in a message being output in the logs.

Let’s explore our other option for on-screen buttons within Unity.

Adding Legacy UI Buttons to a Game

The next approach, my personal go-to, is to use a UI button. We’re going to start by exploring the legacy option before adventuring into the modern way to do business.

Within the Unity IDE, choose GameObject -> UI -> Legacy -> Button from the menu. This will create a button within a canvas as well as an event management system. You’ll probably need to adjust the display of the canvas to your liking, but that is out of the scope of this tutorial.

Here is an example of what my legacy button looks like:

UI Button in Unity

With the UI element added to the game, we need to focus on the script that will power the button. Create a LegacyButton.cs file within your Assets directory. Within the LegacyButton.cs file, add the following C# code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class LegacyButton : MonoBehaviour {

    private Button _button;

    void Awake() {
        _button = GetComponent<Button>();
        _button.onClick.AddListener(btnClick);
    }

    void btnClick() {
        Debug.Log("BUTTON PRESSED");
    }

}

How we work with UI buttons is a little different than how we work with sprite buttons. Instead of checking for mouse events we can add a listener to the button itself. When the listener is triggered, we can do a task.

This can all be done without using any collision boxes.

Attach the script to the button and run the game. You should be able to click it and see the message in your logs.

Adding a TextMeshPro Button to the Game

In the previous section we added a legacy button. Even though it’s legacy, I’m not ashamed to admit that it is still what I use. The good news is that we can add a more modern TextMeshPro button and use the same code.

From the Unity IDE, choose the GameObject -> UI -> Button menu item.

Here is the code we just saw:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ModernButton : MonoBehaviour {

    private Button _button;

    void Awake() {
        _button = GetComponent<Button>();
        _button.onClick.AddListener(btnClick);
    }

    void btnClick() {
        Debug.Log("BUTTON PRESSED");
    }

}

If you attach the script to the new button, you should be able to click the button and see the output in the logs.

Assigning Button Functions from the Unity Inspector

In the examples with the TextMeshPro button and the legacy button, we attached a script directly to the button which had its own listener information. There is an alternative method to executing functions on click with a button.

Let’s start by creating another script. We’ll call it MainController.cs and save it to the Assets directory. This MainController.cs file will contain the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainController : MonoBehaviour {

    public void ButtonClick() {
        Debug.Log("BUTTON PRESSED");
    }

}

Attach the script to the camera game object within your scene.

Open the inspector for one of your buttons and you’ll notice a section for On Click which allows you to add functions. Add the camera game object and then find the ButtonClick function in the drop down.

As long as your function is public, it should show up in the list and it should be executed when the button is pressed.

Conclusion

You just saw a few options when it comes to adding buttons to your Unity game. All will get the job done, but my personal preference is to use the UI button as most of what I do is more menu and overlay oriented. However, pick whatever makes the most sense to you.

A video version of this tutorial can be found below.

Original article source at: https://www.thepolyglotdeveloper.com/

#csharp #unity #game 

How to interacting with Sprite and UI Buttons In A Unity Game
Rachel Cole

Rachel Cole

1669430610

Unity Game Development Tutorial for Beginners

In this Unity game development tutorial, you will how to get started with Unity 3D, C# and making games. You will learn to create 3D and 2D games.
 

This covers the basic functionalities of Unity editor. This course is using totally new features and coding practices and is compatible to work with all the newer versions of unity.

Learn how to small and big video games using Unity, the world-leading free-to-use game development tool. With our online tutorials, you'll be amazed at how easy it is to create a game.

Anyone who wants to learn to create games: Unity is a fantastic platform that enables you to make production-quality games. Furthermore, these games can be created for Windows, macOS, iOS, Android, and Web from a single source!

Who this course is for:

  •        Beginner and Intermediate level unity Developers.
  •        Some programming experience required.
  •        Artists who want to learn to bring their assets into games.
  •        Developers who want to enhance their basic game development skills according to new practices.

You probably already have everything you need to get started but we will guide you from the basics. Unity is a free download. With regular access to an internet connection, you'll be able to engage in our thriving community. Even if you have no experience with coding, or with 3D packages, we will guide you through everything from first principles. Starting slow, then building on what you learn, you'll soon have a very solid working knowledge of Unity.

What you’ll learn

  •        Game Development In Unity

Are there any course requirements or prerequisites?

  •        Programming basics

Who this course is for:

  •        Unity developers who wants to learn game development

#unity #gamedevelopment #gamedev #csharp 

Unity Game Development Tutorial for Beginners
Royce  Reinger

Royce Reinger

1667876100

ML-agents: Unity ML-Agents toolkit

Unity ML-Agents Toolkit

(latest release) (all releases)

The Unity Machine Learning Agents Toolkit (ML-Agents) is an open-source project that enables games and simulations to serve as environments for training intelligent agents. We provide implementations (based on PyTorch) of state-of-the-art algorithms to enable game developers and hobbyists to easily train intelligent agents for 2D, 3D and VR/AR games. Researchers can also use the provided simple-to-use Python API to train Agents using reinforcement learning, imitation learning, neuroevolution, or any other methods. These trained agents can be used for multiple purposes, including controlling NPC behavior (in a variety of settings such as multi-agent and adversarial), automated testing of game builds and evaluating different game design decisions pre-release. The ML-Agents Toolkit is mutually beneficial for both game developers and AI researchers as it provides a central platform where advances in AI can be evaluated on Unity’s rich environments and then made accessible to the wider research and game developer communities.

Features

  • 18+ example Unity environments
  • Support for multiple environment configurations and training scenarios
  • Flexible Unity SDK that can be integrated into your game or custom Unity scene
  • Support for training single-agent, multi-agent cooperative, and multi-agent competitive scenarios via several Deep Reinforcement Learning algorithms (PPO, SAC, MA-POCA, self-play).
  • Support for learning from demonstrations through two Imitation Learning algorithms (BC and GAIL).
  • Easily definable Curriculum Learning scenarios for complex tasks
  • Train robust agents using environment randomization
  • Flexible agent control with On Demand Decision Making
  • Train using multiple concurrent Unity environment instances
  • Utilizes the Unity Inference Engine to provide native cross-platform support
  • Unity environment control from Python
  • Wrap Unity learning environments as a gym

See our ML-Agents Overview page for detailed descriptions of all these features.

Releases & Documentation

Our latest, stable release is Release 19. Click here to get started with the latest release of ML-Agents.

The table below lists all our releases, including our main branch which is under active development and may be unstable. A few helpful guidelines:

  • The Versioning page overviews how we manage our GitHub releases and the versioning process for each of the ML-Agents components.
  • The Releases page contains details of the changes between releases.
  • The Migration page contains details on how to upgrade from earlier releases of the ML-Agents Toolkit.
  • The Documentation links in the table below include installation and usage instructions specific to each release. Remember to always use the documentation that corresponds to the release version you're using.
  • The com.unity.ml-agents package is verified for Unity 2020.1 and later. Verified packages releases are numbered 1.0.x.
VersionRelease DateSourceDocumentationDownloadPython PackageUnity Package
main (unstable)--sourcedocsdownload----
Release 19January 14, 2022sourcedocsdownload0.28.02.2.1
Verified Package 1.0.8May 26, 2021sourcedocsdownload0.16.11.0.8

If you are a researcher interested in a discussion of Unity as an AI platform, see a pre-print of our reference paper on Unity and the ML-Agents Toolkit.

If you use Unity or the ML-Agents Toolkit to conduct research, we ask that you cite the following paper as a reference:

Juliani, A., Berges, V., Teng, E., Cohen, A., Harper, J., Elion, C., Goy, C., Gao, Y., Henry, H., Mattar, M., Lange, D. (2020). Unity: A General Platform for Intelligent Agents. arXiv preprint arXiv:1809.02627. https://github.com/Unity-Technologies/ml-agents.

Additional Resources

We have a Unity Learn course, ML-Agents: Hummingbirds, that provides a gentle introduction to Unity and the ML-Agents Toolkit.

We've also partnered with CodeMonkeyUnity to create a series of tutorial videos on how to implement and use the ML-Agents Toolkit.

We have also published a series of blog posts that are relevant for ML-Agents:

More from Unity

Community and Feedback

The ML-Agents Toolkit is an open-source project and we encourage and welcome contributions. If you wish to contribute, be sure to review our contribution guidelines and code of conduct.

For problems with the installation and setup of the ML-Agents Toolkit, or discussions about how to best setup or train your agents, please create a new thread on the Unity ML-Agents forum and make sure to include as much detail as possible. If you run into any other problems using the ML-Agents Toolkit or have a specific feature request, please submit a GitHub issue.

Please tell us which samples you would like to see shipped with the ML-Agents Unity package by replying to this forum thread.

Your opinion matters a great deal to us. Only by hearing your thoughts on the Unity ML-Agents Toolkit can we continue to improve and grow. Please take a few minutes to let us know about it.

For any other questions or feedback, connect directly with the ML-Agents team at ml-agents@unity3d.com.

Privacy

In order to improve the developer experience for Unity ML-Agents Toolkit, we have added in-editor analytics. Please refer to "Information that is passively collected by Unity" in the Unity Privacy Policy.

Download Details:

Author: Unity-Technologies
Source Code: https://github.com/Unity-Technologies/ml-agents 
License: View license

#machinelearning #deeplearning #unity

ML-agents: Unity ML-Agents toolkit

RestClient: A Promise Based REST and HTTP Client for Unity

RestClient for Unity 🤘

This HTTP/REST Client is based on Promises to avoid the Callback Hell ☠️ and the Pyramid of doom 💩 working with Coroutines in Unity 🎮, example:

var api = "https://jsonplaceholder.typicode.com";
RestClient.GetArray<Post>(api + "/posts", (err, res) => {
  RestClient.GetArray<Todo>(api + "/todos", (errTodos, resTodos) => {
    RestClient.GetArray<User>(api + "/users", (errUsers, resUsers) => {
      //Missing validations to catch errors!
    });
  });
});

But working with Promises we can improve our code, yay! 👏

RestClient.GetArray<Post>(api + "/posts").Then(response => {
  EditorUtility.DisplayDialog ("Success", JsonHelper.ArrayToJson<Post>(response, true), "Ok");
  return RestClient.GetArray<Todo>(api + "/todos");
}).Then(response => {
  EditorUtility.DisplayDialog ("Success", JsonHelper.ArrayToJson<Todo>(response, true), "Ok");
  return RestClient.GetArray<User>(api + "/users");
}).Then(response => {
  EditorUtility.DisplayDialog ("Success", JsonHelper.ArrayToJson<User>(response, true), "Ok");
}).Catch(err => EditorUtility.DisplayDialog ("Error", err.Message, "Ok"));

Features 🎮

  • Works out of the box 🎉
  • Make HTTP requests from Unity
  • Supports HTTPS/SSL
  • Built on top of UnityWebRequest system
  • Transform request and response data (JSON serialization with JsonUtility or other tools)
  • Automatic transforms for JSON Arrays.
  • Supports default HTTP Methods (GET, POST, PUT, DELETE, HEAD, PATCH)
  • Generic REQUEST method to create any http request
  • Based on Promises for a better asynchronous programming. Learn about Promises here!
  • Utility to work during scene transition
  • Handle HTTP exceptions and retry requests easily
  • Open Source 🦄

Supported platforms 📱 🖥

The UnityWebRequest system supports most Unity platforms:

  • All versions of the Editor and Standalone players
  • WebGL
  • Mobile platforms: iOS, Android
  • Universal Windows Platform
  • PS4 and PSVita
  • XboxOne
  • HoloLens
  • Nintendo Switch

Demo ⏯

Do you want to see this beautiful package in action? Download the demo here

Unity configuration Demo

Installation 👨‍💻

Unity package

Download and install the .unitypackage file of the latest release published here.

UPM package

Make sure you had installed C# Promise package or at least have it in your openupm scope registry. Then install RestClient package using this URL from Package Manager: https://github.com/proyecto26/RestClient.git#upm

NuGet package

Other option is download this package from NuGet with Visual Studio or using the nuget-cli, a NuGet.config file is required at the root of your Unity Project, for example:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="repositoryPath" value="./Assets/Packages" />
  </config>
</configuration>

The package to search for is Proyecto26.RestClient.

Getting Started 📚

The default methods (GET, POST, PUT, DELETE, HEAD) are:

RestClient.Get("https://jsonplaceholder.typicode.com/posts/1").Then(response => {
  EditorUtility.DisplayDialog("Response", response.Text, "Ok");
});
RestClient.Post("https://jsonplaceholder.typicode.com/posts", newPost).Then(response => {
  EditorUtility.DisplayDialog("Status", response.StatusCode.ToString(), "Ok");
});
RestClient.Put("https://jsonplaceholder.typicode.com/posts/1", updatedPost).Then(response => {
  EditorUtility.DisplayDialog("Status", response.StatusCode.ToString(), "Ok");
});
RestClient.Delete("https://jsonplaceholder.typicode.com/posts/1").Then(response => {
  EditorUtility.DisplayDialog("Status", response.StatusCode.ToString(), "Ok");
});
RestClient.Head("https://jsonplaceholder.typicode.com/posts").Then(response => {
  EditorUtility.DisplayDialog("Status", response.StatusCode.ToString(), "Ok");
});

Handling during scene transition

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {
  //Any API call using RestClient
});

Generic Request Method

And we have a generic method to create any type of request:

RestClient.Request(new RequestHelper { 
  Uri = "https://jsonplaceholder.typicode.com/photos",
  Method = "POST",
  Timeout = 10,
  Params = new Dictionary<string, string> {
    { "param1", "Query string param..." }
  },
  Headers = new Dictionary<string, string> {
    { "Authorization", "Bearer JWT_token..." }
  },
  Body = newPhoto, //Serialize object using JsonUtility by default
  BodyString = SerializeObject(newPhoto), //Use it instead of 'Body' to serialize using other tools
  BodyRaw = CompressToRawData(newPhoto), //Use it instead of 'Body' to send raw data directly
  FormData = new WWWForm(), //Send files, etc with POST requests
  SimpleForm = new Dictionary<string, string> {}, //Content-Type: application/x-www-form-urlencoded
  FormSections = new List<IMultipartFormSection>() {}, //Content-Type: multipart/form-data
  CertificateHandler = new CustomCertificateHandler(), //Create custom certificates
  UploadHandler = new UploadHandlerRaw(bytes), //Send bytes directly if it's required
  DownloadHandler = new DownloadHandlerFile(destPah), //Download large files
  ContentType = "application/json", //JSON is used by default
  Retries = 3, //Number of retries
  RetrySecondsDelay = 2, //Seconds of delay to make a retry
  RetryCallbackOnlyOnNetworkErrors = true, //Invoke RetryCallack only when the retry is provoked by a network error
  RetryCallback = (err, retries) => {}, //See the error before retrying the request
  ProgressCallback = (percent) => {}, //Reports progress of the request from 0 to 1
  EnableDebug = true, //See logs of the requests for debug mode
  IgnoreHttpException = true, //Prevent to catch http exceptions
  ChunkedTransfer = false,
  UseHttpContinue = true,
  RedirectLimit = 32,
  DefaultContentType = false, //Disable JSON content type by default
  ParseResponseBody = false //Don't encode and parse downloaded data as JSON
}).Then(response => {
  //Get resources via downloadHandler to get more control!
  Texture texture = ((DownloadHandlerTexture)response.Request.downloadHandler).texture;
  AudioClip audioClip = ((DownloadHandlerAudioClip)response.Request.downloadHandler).audioClip;
  AssetBundle assetBundle = ((DownloadHandlerAssetBundle)response.Request.downloadHandler).assetBundle;

  EditorUtility.DisplayDialog("Status", response.StatusCode.ToString(), "Ok");
}).Catch(err => {
  var error = err as RequestException;
  EditorUtility.DisplayDialog("Error Response", error.Response, "Ok");
});

RestClient.Get(new RequestHelper { Uri = fileUrl, DownloadHandler = new DownloadHandlerAudioClip(fileUrl, fileType) }).Then(res => { AudioSource audio = GetComponent(); audio.clip = ((DownloadHandlerAudioClip)res.Request.downloadHandler).audioClip; audio.Play(); }).Catch(err => { EditorUtility.DisplayDialog ("Error", err.Message, "Ok"); });


With all the methods we have the possibility to indicate the type of response, in the following example we're going to create a class and the **HTTP** requests to load **JSON** data easily:
```csharp
[Serializable]
public class User
{
  public int id;
  public string name;
  public string username;
  public string email;
  public string phone;
  public string website;
}
  • GET JSON
var usersRoute = "https://jsonplaceholder.typicode.com/users"; 
RestClient.Get<User>(usersRoute + "/1").Then(firstUser => {
EditorUtility.DisplayDialog("JSON", JsonUtility.ToJson(firstUser, true), "Ok");
});
  • GET Array (JsonHelper is an extension to manage arrays)
RestClient.GetArray<User>(usersRoute).Then(allUsers => {
EditorUtility.DisplayDialog("JSON Array", JsonHelper.ArrayToJsonString<User>(allUsers, true), "Ok");
});

Also we can create different classes for custom responses:

[Serializable]
public class CustomResponse
{
  public int id;
}
  • POST
RestClient.Post<CustomResponse>(usersRoute, newUser).Then(customResponse => {
EditorUtility.DisplayDialog("JSON", JsonUtility.ToJson(customResponse, true), "Ok");
});
  • PUT
RestClient.Put<CustomResponse>(usersRoute + "/1", updatedUser).Then(customResponse => {
EditorUtility.DisplayDialog("JSON", JsonUtility.ToJson(customResponse, true), "Ok");
});

Custom HTTP Headers, Params and Options 💥

HTTP Headers, such as Authorization, can be set in the DefaultRequestHeaders object for all requests

RestClient.DefaultRequestHeaders["Authorization"] = "Bearer ...";

Query string params can be set in the DefaultRequestParams object for all requests

RestClient.DefaultRequestParams["param1"] = "Query string value...";

Also we can add specific options and override default headers and params for a request

var currentRequest = new RequestHelper { 
  Uri = "https://jsonplaceholder.typicode.com/photos",
  Headers = new Dictionary<string, string> {
    { "Authorization", "Other token..." }
  },
  Params = new Dictionary<string, string> {
    { "param1", "Other value..." }
  }
};
RestClient.GetArray<Photo>(currentRequest).Then(response => {
  EditorUtility.DisplayDialog("Header", currentRequest.GetHeader("Authorization"), "Ok");
});

And we can know the status of the request and cancel it!

currentRequest.UploadProgress; //The progress by uploading data to the server
currentRequest.UploadedBytes; //The number of bytes of body data the system has uploaded
currentRequest.DownloadProgress; //The progress by downloading data from the server
currentRequest.DownloadedBytes; //The number of bytes of body data the system has downloaded
currentRequest.Abort(); //Abort the request manually

Additionally we can run a callback function whenever a progress change happens!

RestClient.Get(new RequestHelper {
  Uri = "https://jsonplaceholder.typicode.com/users", 
  ProgressCallback = percent => Debug.Log(percent)
});

Later we can clear the default headers and params for all requests

RestClient.ClearDefaultHeaders();
RestClient.ClearDefaultParams();

Example

  • Unity as Client
[Serializable]
public class ServerResponse {
public string id;
public string date; //DateTime is not supported by JsonUtility
}
[Serializable]
public class User {
public string firstName;
public string lastName;
}
RestClient.Post<ServerResponse>("www.api.com/endpoint", new User {
firstName = "Juan David",
lastName = "Nicholls Cardona"
}).Then(response => {
EditorUtility.DisplayDialog("ID: ", response.id, "Ok");
EditorUtility.DisplayDialog("Date: ", response.date, "Ok");
});
router.post('/', function(req, res) {
console.log(req.body.firstName)
res.json({
  id: 123,
  date: new Date()
})
});

Credits 👍

Contributing ✨

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated ❤️.
You can learn more about how you can contribute to this project in the contribution guide.

Supporting 🍻

I believe in Unicorns 🦄 Support me, if you do too.

Donate Ethereum, ADA, BNB, SHIBA, USDT, DOGE:

Wallet address

Wallet address: 0x3F9fA8021B43ACe578C2352861Cf335449F33427

Please let us know your contributions! 🙏

Enterprise 💼

Available as part of the Tidelift Subscription.

The maintainers of RestClient for Unity and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.

Security contact information 🚨

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.


Download Details:

Author: proyecto26
Source Code: https://github.com/proyecto26/RestClient

License: MIT license

#rest #http #unity 

RestClient: A Promise Based REST and HTTP Client for Unity

Top 10 Popular 2D Game Development Companies in USA 2022-23

Online games have become the best way of entertainment in the whole world. At present, with the advanced technology gaming world has become so fascinating and more attractive that more people are engaging on online platforms to play games. Most likely games are mobile games, video games, augmented reality and virtual reality games, and many more according to people's choices because a lot of new games are trending with time. The game development company has made the world full of entertainment with entertaining games. So, here in this article, we are going to introduce the best 2D game development company in the USA. 


The mobile game is used not only for entertainment purposes but also to earn money. In online games, 2D games are trending high due to their user-friendliness. 2D games have simple instructions and simple controls and are easy to play. 2D games also take less time and money to make. 

There are many tools and engines for 2D game development like UNITY, UNREAL ENGINE, HTML5, CRYENGINE, SWIFT, SmartFox Server, Cocos2Dx, Photon, and many more that offers us a platform to develop our advance and unique 2D game.

As we all know that there are a lot of 2D game companies who are offering their development services but finding one of the best of them is really hard. Each company is offering its unique services but there are only a few companies that offer us quality games.

Read Also - Top 10 Game Development Company in Los Angeles


Things To Know Before Hiring A 2d Game Development Company


As we all know that there are a lot of game development companies in the USA but before deciding or picking up the right one for your game you should check below given important aspects. 


To decide on the best and most reliable 2D game development company always consider -

  • 2D game development knowledge
  • Check portfolio
  • Know developer skills and experience
  • Payment options and security
  • Software license and customization

Tech Support and Maintenance

List of Best 2D Game Development Companies 


DataArt

DataArt is counted as the global game software provider company that offers us unique services that help us to stand in the competitive world. 20 years of experience in the game world makes them one step ahead of other companies.

BR Softech

BR Softech is one of the leading 2D game development companies in the market. Their 2D game developers are skilled and well-versed in 2D game development technologies and are equipped with all the necessary tools to fulfill your requirements. The developers are technical experts and are proficient in various 2D game engines. BR Softech also provides customized services to our clients. We ensure transparency in the development process and maintain proper communication with our clients to properly understand their needs.

iTechArt Group

iTechArt has more than 3000+ experienced engineers and they have been offering custom software since 2002. They are highly advanced in the digital world. Their scalable products are loved by all. They have a team of brilliant minds who are highly advanced in making mobile and web games.

Hyperlink InfoSystem

This company was founded in 2011 and they cover all kinds of development services like game development, web development, and app development for all kinds of platforms. They have already developed more than 4000+ mobile apps. Their demand in the gaming world is so high because they always produce advanced solutions to your problem which makes them advance in the gaming world. 

Andersen

Andersen is a well-known IT company that is known for QA engineers, business analysts, and game development. They have been present in the gaming world for more than one decade. They have the best team of IT professionals who deliver quality-oriented services that help you in your business. Their workforce is highly skilled and they are experts in their field. 

Innovecs

Innovecs is known as the best tech company that offers you 2D game services at the best rates. Their digital solutions always have a unique approach that makes your services simple and advanced. They always try to make your services efficient and easy so anyone can use them and you can easily target your audience.

 Endava

Endava is known for 2D game development and its services are highly advanced and technology-oriented. With the help of this company, you can achieve your target easily. They are having a well-known name in the field of the digital world. You can take these services to make your business more demanding.

Valtech

Valtech is a global digital solution provider company in the USA that is known for delivering its services with innovative ideas. Its incredible ideas give your business a new look and you can make a different approach in the tech world through their services. It is excellent at delivering 2D games.

Wunderman Thompson

In the advanced tech world, everyone is expecting a different digital approach to their solutions so Wunderman Thompson is the best solution for them. Their skilled technology expert always offers you advanced and rich services so you can perform excellently in your business. Their passion for 2D games is extreme. Their game is always simple and enchanting.

HData Systems

 It is counted in the top 10 2D game development companies in the USA. They are masters in 2D game engine platforms and that's why they make such rich games with simple and unique ideas. Its services are highly tech-friendly that always offer you fruitful results. You can count this for gaming and application solutions.

Read Also - How to Choose a Ludo Game Developer in India?

Conclusion:

After reading this article you can easily know which company will suit best for your 2D game development. Here, we discussed the top ten 2D game development companies so you can easily pick one of them for your gaming business. 
 

#gamedevelopment #gamedevelopers #unity 
 

Top 10 Popular 2D Game Development Companies in USA 2022-23
Rubalema  Sonia

Rubalema Sonia

1661745180

Zero Formatter: Infinitely Fast Deserializer for .NET and Unity

ZeroFormatter

Fastest C# Serializer and Infinitely Fast Deserializer for .NET, .NET Core and Unity.

Why use ZeroFormatter?

  • Fastest C# serializer, the code is extremely tuned by both implementation and binary layout(see: performance)
  • Deserialize/re-serialize is Infinitely fast because formatter can access to serialized data without parsing/packing(see: architecture)
  • Strongly Typed and C# Code as schema, no needs to other IDL like .proto, .fbs...
  • Smart API, only to use Serialize<T> and Deserialize<T>
  • Full set of general purpose, multifunctional serializer, supports Union(Polymorphism) and native support of Dictionary, MultiDictionary(ILookup)
  • First-class support to Unity(IL2CPP), it's faster than native JsonUtility

ZeroFormatter is similar as FlatBuffers but ZeroFormatter has clean API(FlatBuffers API is too ugly, see: sample; we can not use regularly) and C# specialized. If you need to performance such as Game, Distributed Computing, Microservices, etc..., ZeroFormatter will help you.

Demo

image

Note: this is unfair comparison, please see the performance section for the details.

Install

for .NET, .NET Core

for Unity(Interfaces can reference both .NET 3.5 and Unity for share types), Unity binary exists on ZeroFormatter/Releases as well. More details, please see the Unity-Supports section.

Visual Studio Analyzer

Quick Start

Define class and mark as [ZeroFormattable] and public properties mark [Index] and declare virtual, call ZeroFormatterSerializer.Serialize<T>/Deserialize<T>.

// mark ZeroFormattableAttribute
[ZeroFormattable]
public class MyClass
{
    // Index is key of serialization
    [Index(0)]
    public virtual int Age { get; set; }

    [Index(1)]
    public virtual string FirstName { get; set; }

    [Index(2)]
    public virtual string LastName { get; set; }

    // When mark IgnoreFormatAttribute, out of the serialization target
    [IgnoreFormat]
    public string FullName { get { return FirstName + LastName; } }

    [Index(3)]
    public virtual IList<int> List { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var mc = new MyClass
        {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
            List = new List<int> { 1, 10, 100 }
        };

        var bytes = ZeroFormatterSerializer.Serialize(mc);
        var mc2 = ZeroFormatterSerializer.Deserialize<MyClass>(bytes);

        // ZeroFormatter.DynamicObjectSegments.MyClass
        Console.WriteLine(mc2.GetType().FullName);
    }
}

Serializable target must mark ZeroFormattableAttribute, there public property must be virtual and requires IndexAttribute.

Analyzer

ZeroFormatter.Analyzer helps object definition. Attributes, accessibility etc are detected and it becomes a compiler error.

zeroformatteranalyzer

If you want to allow a specific type (for example, when registering a custom type), put ZeroFormatterAnalyzer.json at the project root and make the Build Action to AdditionalFiles.

image

This is a sample of the contents of ZeroFormatterAnalyzer.json.

[ "System.Uri" ]

Built-in support types

All primitives, All enums, TimeSpan, DateTime, DateTimeOffset, Guid, Tuple<,...>, KeyValuePair<,>, KeyTuple<,...>, Array, List<>, HashSet<>, Dictionary<,>, ReadOnlyCollection<>, ReadOnlyDictionary<,>, IEnumerable<>, ICollection<>, IList<>, ISet<,>, IReadOnlyCollection<>, IReadOnlyList<>, IReadOnlyDictionary<,>, ILookup<,> and inherited ICollection<> with paramterless constructor. Support type can extend easily, see: Extensibility section.

Define object rules

There rules can detect ZeroFormatter.Analyzer.

  • Type must be marked with ZeroformattableAttribute.
  • Public property must be marked with IndexAttribute or IgnoreFormatAttribute.
  • Public property's must needs both public/protected get and set accessor.
  • Public property's accessor must be virtual.
  • Class is only supported public property not field(If struct can define field).
  • IndexAttribute is not allowed duplicate number.
  • Class must needs a parameterless constructor.
  • Struct index must be started with 0 and be sequential.
  • Struct needs full parameter constructor of index property types.
  • Union type requires UnionKey property.
  • UnionKey does not support multiple keys.
  • All Union sub types must be inherited type.

The definition of struct is somewhat different from class.

[ZeroFormattable]
public struct Vector2
{
    [Index(0)]
    public float x;
    [Index(1)]
    public float y;

    // arg0 = Index0, arg1 = Index1
    public Vector2(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
}

Struct index must be started with 0 and be sequential and needs full parameter constructor of index property types.

eager/lazy-evaluation

ZeroFormatter has two types of evaluation, "eager-evaluation" and "lazy-evaluation". If the type is lazy-evaluation, deserialization will be infinitely fast because it does not parse. If the user-defined class or type is IList<>, IReadOnlyList<>, ILazyLookup<>, ILazyDicitonary<>, ILazyReadOnlyDictionary<>, deserialization of that type will be lazily evaluated.

// MyClass is lazy-evaluation, all properties are lazily
[ZeroFormattable]
public class MyClass
{
    // int[] is eager-evaluation, when accessing Prop2, all values are deserialized
    [Index(0)]
    public virtual int[] Prop1 { get; set; }

    // IList<int> is lazy-evaluation, when accessing Prop2 with indexer, only that index value is deserialized 
    [Index(1)]
    public virtual IList<int> Prop2 { get; set; }
}

If you want to maximize the power of lazy-evaluation, define all collections with IList<>/IReadOnlyList<>.

ILazyLookup<>, ILazyDicitonary<>, ILazyReadOnlyDictionary<> is special collection interface, it defined by ZeroFormatter. The values defined in these cases are deserialized very quickly because the internal structure is also serialized in its entirety and does not need to be rebuilt data structure. But there are some limitations instead. Key type must be primitive, enum or there KeyTuple only because the key must be deterministic.

[ZeroFormattable]
public class MyClass
{
    [Index(0)]
    public virtual ILazyDictionary<int, int> LazyDictionary { get; set; }

    [Index(1)]
    public virtual ILazyLookup<int, int> LazyLookup { get; set; }
}

// there properties can set from `AsLazy***` extension methods. 

var mc = new MyClass();

mc.LazyDictionary = Enumerable.Range(1, 10).ToDictionary(x => x).AsLazyDictionary();
mc.LazyLookup = Enumerable.Range(1, 10).ToLookup(x => x).AsLazyLookup();

As a precaution, the binary size will be larger because all internal structures are serialized. This is a tradeoff, please select the best case depending on the situation.

Architecture

When deserializing an object, it returns a byte[] wrapper object. When accessing the property, it reads the data from the offset information of the header(and cache when needed).

image

Why must we define object in virtual? The reason is to converts access to properties into access to byte buffers.

If there is no change in data, reserialization is very fast because it writes the internal buffer data as it is. All serialized data can mutate and if the property type is fixed-length(primitive and some struct), it is written directly to internal binary data so keep the reserialization speed. If property is variable-length(string, list, object, etc...) the type and property are marked dirty. And it serializes only the difference, it is faster than normal serialization.

If property includes array/collection, ZeroFormatter can not track data was mutated so always marks dirty initially even if you have not mutated it. To avoid it, declare all collections with IList<> or IReadOnlyList<>.

If you want to define Immutable, you can use "protected set" and "IReadOnlyList<>".

[ZeroFormattable]
public class ImmutableClass
{
    [Index(0)]
    public virtual int ImmutableValue { get; protected set; }

    // IReadOnlyDictionary, ILazyReadOnlyDictionary, etc, too.
    [Index(1)]
    public virtual IReadOnlyList<int> ImmutableList { get; protected set; }
}

Binary size is slightly larger than Protobuf, MsgPack because of needs the header index area and all primitives are fixed-length(same size as FlatBuffers, smaller than JSON). It is a good idea to compress it to shrink the data size, gzip or LZ4(recommended, LZ4 is fast compression/decompression algorithm).

Versioning

If schema is growing, you can add Index.

[ZeroFormattable]
public class Version1
{
    [Index(0)]
    public virtual int Prop1 { get; set; }
    [Index(1)]
    public virtual int Prop2 { get; set; }
    
    // If deserialize from new data, ignored.
}

[ZeroFormattable]
public class Version2
{
    [Index(0)]
    public virtual int Prop1 { get; set; }
    [Index(1)]
    public virtual int Prop2 { get; set; }
    // You can add new property. If deserialize from old data, value is assigned default(T).
    [Index(2)]
    public virtual int NewType { get; set; }
}

But you can not delete index. If that index is unnecessary, please make it blank(such as [0, 1, 3]).

Only class definition is supported for versioning. Please note that struct is not supported.

DateTime

DateTime is serialized to UniversalTime so lose the TimeKind. If you want to change local time, use ToLocalTime after converted.

// in Tokyo, Japan Local Time(UTC+9)
var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local);
Console.WriteLine(date);

// 1999/12/31 15:00:00(UTC)
var deserialized = ZeroFormatterSerializer.Deserialize<DateTime>(ZeroFormatterSerializer.Serialize(date));

// 2000/1/1 00:00:00(in Tokyo, +9:00)
var toLocal = deserialized.ToLocalTime();

If you want to save offset info, use DateTimeOffset instead of DateTime.

// in Tokyo, Japan Local Time(UTC+9)
var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local);

// 2000/1/1, +9:00
var target = new DateTimeOffset(date);

// 2000/1/1, +9:00
var deserialized = ZeroFormatterSerializer.Deserialize<DateTimeOffset>(ZeroFormatterSerializer.Serialize(target));

Union

ZeroFormatter supports Union(Polymorphic) type. It can define abstract class and UnionAttributes, UnionKeyAttribute.

public enum CharacterType
{
    Human, Monster
}

// UnionAttribute abstract/interface type becomes Union, arguments is union subtypes.
// It needs single UnionKey to discriminate
[Union(typeof(Human), typeof(Monster))]
public abstract class Character
{
    [UnionKey]
    public abstract CharacterType Type { get; }
}

[ZeroFormattable]
public class Human : Character
{
    // UnionKey value must return constant value(Type is free, you can use int, string, enum, etc...)
    public override CharacterType Type
    {
        get
        {
            return CharacterType.Human;
        }
    }

    [Index(0)]
    public virtual string Name { get; set; }

    [Index(1)]
    public virtual DateTime Birth { get; set; }

    [Index(2)]
    public virtual int Age { get; set; }

    [Index(3)]
    public virtual int Faith { get; set; }
}

[ZeroFormattable]
public class Monster : Character
{
    public override CharacterType Type
    {
        get
        {
            return CharacterType.Monster;
        }
    }

    [Index(0)]
    public virtual string Race { get; set; }

    [Index(1)]
    public virtual int Power { get; set; }

    [Index(2)]
    public virtual int Magic { get; set; }
}

You can use Union as following.

var demon = new Monster { Race = "Demon", Power = 9999, Magic = 1000 };

// use UnionType(Character) formatter(be carefule, does not use concrete type)
var data = ZeroFormatterSerializer.Serialize<Character>(demon);

var union = ZeroFormatterSerializer.Deserialize<Character>(data);

// you can discriminate by UnionKey.
switch (union.Type)
{
    case CharacterType.Monster:
        var demon2 = (Monster)union;
        demon2.Race...
        demon2.Power..
        demon2.Magic...
        break;
    case CharacterType.Human:
        var human2 = (Human)union;
        human2.Name...
        human2.Birth...
        human2.Age..
        human2.Faith...
        break;
    default:
        Assert.Fail("invalid");
        break;
}

If an unknown identification key arrives, an exception is thrown by default. However, it is also possible to return the default type - fallbackType.

// If new type received, return new UnknownEvent()
[Union(subTypes: new[] { typeof(MailEvent), typeof(NotifyEvent) }, fallbackType: typeof(UnknownEvent))]
public interface IEvent
{
    [UnionKey]
    byte Key { get; }
}

[ZeroFormattable]
public class MailEvent : IEvent
{
    [IgnoreFormat]
    public byte Key => 1;

    [Index(0)]
    public string Message { get; set; }
}

[ZeroFormattable]
public class NotifyEvent : IEvent
{
    [IgnoreFormat]
    public byte Key => 1;

    [Index(0)]
    public bool IsCritical { get; set; }
}

[ZeroFormattable]
public class UnknownEvent : IEvent
{
    [IgnoreFormat]
    public byte Key => 0;
}

Union can construct on execution time. You can mark DynamicUnion and make resolver on AppendDynamicUnionResolver.

[DynamicUnion] // root of DynamicUnion
public class MessageBase
{

}

public class UnknownMessage : MessageBase { }

[ZeroFormattable]
public class MessageA : MessageBase { }

[ZeroFormattable]
public class MessageB : MessageBase
{
    [Index(0)]
    public virtual IList<IEvent> Events { get; set; }
}

ZeroFormatter.Formatters.Formatter.AppendDynamicUnionResolver((unionType, resolver) =>
{
    //can be easily extended to reflection based scan if library consumer wants it
    if (unionType == typeof(MessageBase))
    {
        resolver.RegisterUnionKeyType(typeof(byte));
        resolver.RegisterSubType(key: (byte)1, subType: typeof(MessageA));
        resolver.RegisterSubType(key: (byte)2, subType: typeof(MessageB));
        resolver.RegisterFallbackType(typeof(UnknownMessage));
    }
});

Unity Supports

Put the ZeroFormatter.dll and ZeroFormatter.Interfaces.dll, modify Edit -> Project Settings -> Player -> Optimization -> Api Compatibillity Level to .NET 2.0 or higher.

image

ZeroFormatter.Unity works on all platforms(PC, Android, iOS, etc...). But it can 'not' use dynamic serializer generation due to IL2CPP issue. But pre code generate helps it. Code Generator is located in packages\ZeroFormatter.Interfaces.*.*.*\tools\zfc.exe. zfc is using Roslyn so analyze source code, pass the target csproj.

zfc arguments help:
  -i, --input=VALUE             [required]Input path of analyze csproj
  -o, --output=VALUE            [required]Output path(file) or directory base(in separated mode)
  -s, --separate                [optional, default=false]Output files are separated
  -u, --unuseunityattr          [optional, default=false]Unuse UnityEngine's RuntimeInitializeOnLoadMethodAttribute on ZeroFormatterInitializer
  -t, --customtypes=VALUE       [optional, default=empty]comma separated allows custom types
  -c, --conditionalsymbol=VALUE [optional, default=empty]conditional compiler symbol
  -r, --resolvername=VALUE      [optional, default=DefaultResolver]Register CustomSerializer target
  -d, --disallowinternaltype    [optional, default=false]Don't generate internal type
  -e, --propertyenumonly        [optional, default=false]Generate only property enum type only
  -m, --disallowinmetadata      [optional, default=false]Don't generate in metadata type
  -g, --gencomparekeyonly       [optional, default=false]Don't generate in EnumEqualityComparer except dictionary key
  -n, --namespace=VALUE         [optional, default=ZeroFormatter]Set namespace root name
  -f, --forcedefaultresolver    [optional, default=false]Force use DefaultResolver

Note: Some options is important for reduce code generation size and startup speed on IL2CPP, especially -f is recommend if you use only DefaultResolver.

// Simple Case:
zfc.exe -i "..\src\Sandbox.Shared.csproj" -o "ZeroFormatterGenerated.cs"

// with t, c
zfc.exe -i "..\src\Sandbox.Shared.csproj" -o "..\unity\ZfcCompiled\ZeroFormatterGenerated.cs" -t "System.Uri" -c "UNITY"

// -s
zfc.exe -i "..\src\Sandbox.Shared.csproj" -s -o "..\unity\ZfcCompiled\" 

zfc.exe can setup on csproj's PreBuildEvent(useful to generate file path under self project) or PostBuildEvent(useful to generate file path is another project).

Note: zfc.exe is currently only run on Windows. It is .NET Core's Roslyn workspace API limitation but I want to implements to all platforms...

Generated formatters must need to register on Startup. By default, zfc generate automatic register code on RuntimeInitializeOnLoad timing.

For Unity Unit Tests, the generated formatters must be registered in the SetUp method:

    [SetUp]
    public void RegisterZeroFormatter()
    {
        ZeroFormatterInitializer.Register();
    }

ZeroFormatter can not serialize Unity native types by default but you can make custom formatter by define pseudo type. For example create Vector2 to ZeroFormatter target.

#if INCLUDE_ONLY_CODE_GENERATION

using ZeroFormatter;

namespace UnityEngine
{
    [ZeroFormattable]
    public struct Vector2
    {
        [Index(0)]
        public float x;
        [Index(1)]
        public float y;

        public Vector2(float x, float y)
        {
            this.x = x;
            this.y = y;
        }
    }
}

#endif

INCLUDE_ONLY_CODE_GENERATION is special symbol of zfc, include generator target but does not include compile.

If you encounter InvalidOperationException such as

InvalidOperationException: Type is not supported, please register Vector3[]

It means not generated/registered type. Especially collections are not automatically registered if they are not included in the property. You can register manually such as Formatter.RegisterArray<UnityEngine.Vector3>() or create hint type for zfc.

using ZeroFormatter;

namespace ZfcHint
{
    [ZeroFormattable]
    public class TypeHint
    {
        // zfc analyzes UnityEngine.Vector3[] type and register it. 
        [Index(0)]
        public UnityEngine.Vector3[] Hint1;
    }
}

Performance

Benchmarks comparing to other serializers run on Windows 10 Pro x64 Intel Core i7-6700 3.40GHz, 32GB RAM. Benchmark code is here and full-result is here, latest compare with more serializers(Wire, NetSerializer, etc...) result is here.

Deserialize speed is Infinitely fast(but of course, it is unfair, ZeroFormatter's deserialize is delayed when access target field). Serialize speed is fair-comparison. ZeroFormatter is fastest(compare to protobuf-net, 2~3x fast) for sure. ZeroFormatter has many reasons why fast.

  • Serializer uses only ref byte[] and int offset, don't use MemoryStream(call MemoryStream api is overhead)
  • Don't use variable-length number when encode number so there has encode cost(for example; protobuf uses ZigZag Encoding)
  • Acquire strict length of byte[] when knows final serialized length(for example; int, fixed-length list, string, etc...)
  • Avoid boxing all codes, all platforms(include Unity/IL2CPP)
  • Reduce native string encoder methods
  • Don't create intermediate utility instance(XxxWriter/Reader, XxxContext, etc...)
  • Heavyly tuned dynamic il code generation: DynamicObjectFormatter.cs
  • Getting cached generated formatter on static generic field(don't use dictinary-cache because dictionary lookup is overhead): Formatter.cs
  • Enum is serialized only underlying-value and uses fastest cast technique: EnumFormatter.cs

The result is achieved from both sides of implementation and binary layout. ZeroFormatter's binary layout is tuned for serialize/deserialize speed(this is advantage than other serializer).

In Unity

Result run on iPhone 6s Plus and IL2CPP build.

ZeroFormatter is faster than JsonUtility so yes, faster than native serializer! Why MsgPack-Cli is slow? MsgPack-Cli's Unity implemntation has a lot of hack of avoid AOT issues, it causes performance impact(especially struct, all codes pass boxing). ZeroFormatter codes is full tuned for Unity with/without IL2CPP.

Single Integer(1), Large String(represents HTML), Vector3 Struct(float, float, float), Vector3[100]

image image

ZeroFormatter is optimized for all types(small struct to large object!). I know why protobuf-net is slow on integer test, currently protobuf-net's internal serialize method has only object value so it causes boxing and critical for performance. Anyway, ZeroFormatter's simple struct and struct array(struct array is serialized FixedSizeList format internally, it is faster than class array)'s serialization/deserialization speed is very fast that effective storing value to KeyValueStore(like Redis) or network gaming(transport many transform position), etc.

Compare with MessagePack for C#

Author also created MessagePack for C#. It is fast, compact general purpose serializer. MessagePack for C# is a good choice if you are looking for a JSON-like, general purpose fast binary serializer. Built-in LZ4 support makes it suitable for network communication and storage in Redis. If you need infintely fast deserializer, ZeroFormatter is good choice.

ZeroFormatterSerializer API

We usually use Serialize<T> and Deserialize<T>, but there are other APIs as well. Convert<T> is converted T to T but the return value is wrapped data. It is fast when reserialization so if you store the immutable data and serialize frequently, very effective. IsFormattedObject<T> can check the data is wrapped data or not.

Serialize<T> has some overload, the architecture of ZeroFormatter is to write to byte [], read from byte [] so byte[] method is fast, first-class method. Stream method is helper API. ZeroFormatter has non-allocate API, as well. int Serialize<T>(ref byte[] buffer, int offset, T obj) expands the buffer but do not shrink. Return value int is size so you can pass the buffer from array pooling, ZeroFormatter does not allocate any extra memory.

If you want to use non-generic API, there are exists under ZeroFormatterSerializer.NonGeneric. It can pass Type on first-argument instead of <T>.

NonGeneric API is not supported in Unity. NonGeneric API is a bit slower than the generic API. Because of the lookup of the serializer by type and the cost of boxing if the value is a value type are costly. We recommend using generic API if possible.

ZeroFormatterSerializer.MaximumLengthOfDeserialize is max length of array(collection) length when deserializing. The default is 67108864, it includes byte[](67MB). This limitation is for security issue(block of OutOfMemory). If you want to expand this limitation, set the new size.

Extensibility

ZeroFormatter can become custom binary layout framework. You can create own typed formatter. For example, add supports Uri.

// "<TTypeResolver> where TTypeResolver : ITypeResolver, new()" is a common rule. in details, see: configuration section.
public class UriFormatter<TTypeResolver> : Formatter<TTypeResolver, Uri>
    where TTypeResolver : ITypeResolver, new()
{
    public override int? GetLength()
    {
        // If size is variable, return null.
        return null;
    }

    public override int Serialize(ref byte[] bytes, int offset, Uri value)
    {
        // Formatter<T> can get child serializer
        return Formatter<TTypeResolver, string>.Default.Serialize(ref bytes, offset, value.ToString());
    }

    public override Uri Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
    {
        var uriString = Formatter<TTypeResolver, string>.Default.Deserialize(ref bytes, offset, tracker, out byteSize);
        return (uriString == null) ? null : new Uri(uriString);
    }
}

You need to register formatter on application startup.

// What is DefaultResolver? see: configuration section. 
ZeroFormatter.Formatters.Formatter<DefaultResolver, Uri>.Register(new UriFormatter<DefaultResolver>());

One more case, how to create generic formatter. For example, If implements ImmutableList<T>?

public class ImmutableListFormatter<TTypeResolver, T> : Formatter<TTypeResolver, ImmutableList<T>>
    where TTypeResolver : ITypeResolver, new()
{
    public override int? GetLength()
    {
        return null;
    }

    public override int Serialize(ref byte[] bytes, int offset, ImmutableList<T> value)
    {
        // use sequence format.
        if (value == null)
        {
            BinaryUtil.WriteInt32(ref bytes, offset, -1);
            return 4;
        }

        var startOffset = offset;
        offset += BinaryUtil.WriteInt32(ref bytes, offset, value.Count);

        var formatter = Formatter<TTypeResolver, T>.Default;
        foreach (var item in value)
        {
            offset += formatter.Serialize(ref bytes, offset, item);
        }

        return offset - startOffset;
    }

    public override ImmutableList<T> Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
    {
        byteSize = 4;
        var length = BinaryUtil.ReadInt32(ref bytes, offset);
        if (length == -1) return null;

        var formatter = Formatter<TTypeResolver, T>.Default;
        var builder = ImmutableList<T>.Empty.ToBuilder();
        int size;
        offset += 4;
        for (int i = 0; i < length; i++)
        {
            var val = formatter.Deserialize(ref bytes, offset, tracker, out size);
            builder.Add(val);
            offset += size;
        }
            
        return builder.ToImmutable();
    }
}

And register generic resolver on startup.

// append to default resolver.
ZeroFormatter.Formatters.Formatter.AppendFormatterResolver(t =>
{
    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ImmutableList<>))
    {
        var formatter = typeof(ImmutableListFormatter<>).MakeGenericType(t.GetGenericArguments());
        return Activator.CreateInstance(formatter);
    }

    return null; // fallback to the next resolver
});

Configuration

If you want to create Formatter based on special rules generated by ResolveFormatter and RegisterDynamicUnion with the same AppDomain, you need to implement ITypeResolver.

public class CustomSerializationContext : ITypeResolver
{
    public bool IsUseBuiltinDynamicSerializer
    {
        get
        {
            return true;
        }
    }

    public object ResolveFormatter(Type type)
    {
        // same as Formatter.AppendFormatResolver.
        return null;
    }

    public void RegisterDynamicUnion(Type unionType, DynamicUnionResolver resolver)
    {
        // same as Formatter.AppendDynamicUnionResolver
    }
}

ZeroFormatterSerializer.CustomSerializer<CustomSerializationContext>.Serialize/Deserialize methods use those contexts. By default, DefaultResolver is used.

WireFormat Specification

All formats are represented in little endian. There are two lengths of binary, fixed-length and variable-length, which affect the List Format.

Primitive Format

Primitive format is fixed-length(except string), eager-evaluation. C# Enum is serialized there underlying type. TimeSpan, DateTime is serialized UniversalTime and serialized format is same as Protocol Buffers's timestamp.proto.

TypeLayoutNote
Int16[short(2)] 
Int32[int(4)] 
Int64[long(8)] 
UInt16[ushort(2)] 
UInt32[uint(4)] 
UInt64[ulong(8)] 
Single[float(4)] 
Double[double(8)] 
Boolean[bool(1)] 
Byte[byte(1)] 
SByte[sbyte(1)] 
Char[ushort(2)]UTF16-LE
TimeSpan[seconds:long(8)][nanos:int(4)]seconds represents time from 00:00:00
DateTime[seconds:long(8)][nanos:int(4)]seconds represents UTC time since Unix epoch(0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z)
DateTimeOffset[seconds:long(8)][nanos:int(4)][offsetMinutes:short(2)]DateTime with a time difference
String[utf8Bytes:(length)]currently no used but reserved for future
Int16?[hasValue:bool(1)][short(2)] 
Int32?[hasValue:bool(1)][int(4)] 
Int64?[hasValue:bool(1)][long(8)] 
UInt16?[hasValue:bool(1)][ushort(2)] 
UInt32?[hasValue:bool(1)][uint(4)] 
UInt64?[hasValue:bool(1)][ulong(8)] 
Single?[hasValue:bool(1)][float(4)] 
Double?[hasValue:bool(1)][double(8)] 
Boolean?[hasValue:bool(1)][bool(1)] 
Byte?[hasValue:bool(1)][byte(1)] 
SByte?[hasValue:bool(1)][sbyte(1)] 
Char?[hasValue:bool(1)][ushort(2)]UTF16-LE
TimeSpan?[hasValue:bool(1)][seconds:long(8)][nanos:int(4)]seconds represents time from 00:00:00
DateTime?[hasValue:bool(1)][seconds:long(8)][nanos:int(4)]seconds represents UTC time since Unix epoch(0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z)
DateTimeOffset?[hasValue:bool(1)][seconds:long(8)][nanos:int(4)][offsetMinutes:short(2)]DateTime with a time difference
String?[length:int(4)][utf8Bytes:(length)]representes String, if length = -1, indicates null. This is only variable-length primitive.

Sequence Format

Sequence is variable-length, eager-evaluation. Sequence represents a multiple object. If field is declared collection type(except IList<T>, IReadOnlyList<T>), used this format.

TypeLayoutNote
Sequence<T>[length:int(4)][elements:T...]if length = -1, indicates null

List Format

List is variable-length, lazy-evaluation. If field is declared IList<T> or IReadOnlyList<T>, used this format.

TypeLayoutNote
FixedSizeList[length:int(4)][elements:T...]T is fixed-length format. if length = -1, indicates null
VariableSizeList[byteSize:int(4)][length:int(4)][elementOffset...:int(4 * length)][elements:T...]T is variable-length format. if byteSize = -1, indicates null. indexOffset is relative position from list start offset

Object Format

Object Format is a user-defined type.

Object is variable-length, lazy-evaluation which has index header.

Struct is eager-evaluation, if all field types are fixed-length which struct is marked fixed-length, else variable-length. This format is included KeyTuple, Tuple, KeyValuePair(used by Dictionary), IGrouping(used by ILookup).

TypeLayoutNote
Object[byteSize:int(4)][lastIndex:int(4)][indexOffset...:int(4 * lastIndex)][Property1:T1, Property2:T2, ...]used by class in default. if byteSize = -1, indicates null, indexOffset = 0, indicates blank. indexOffset is relative position from object start offset
Struct[Index1Item:T1, Index2Item:T2,...]used by struct in default. This format can be fixed-length. versioning is not supported.
Struct?[hasValue:bool(1)][Index1Item:T1, Index2Item:T2,...]used by struct in default. This format can be fixed-length. versioning is not supported.

Union Format

Union is variable-length, eager-evaluation, discriminated by key type to each value type.

TypeLayoutNote
Union[byteSize:int(4)][unionKey:TKey][value:TValue]if byteSize = -1, indicates null

Extension Format

Not a standard format but builtin on C# implementation.

TypeLayoutNote
Decimal[lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)]fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.
Decimal?[hasValue:bool(1)][lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)]fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.
Guid[bytes:byteArray(16)]fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.
Guid?[hasValue:bool(1)][bytes:byteArray(16)]fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.
LazyDictionary[byteSize:int(4)][length:int(4)][buckets:FixedSizeList<int>][entries:VariableSizeList<DictionaryEntry>]represents ILazyDictionary<TKey, TValue>, if byteSize == -1, indicates null, variable-length, lazy-evaluation
DictionaryEntry[hashCode:int(4)][next:int(4)][key:TKey][value:TValue]substructure of LazyDictionary
LazyMultiDictionary[byteSize:int(4)][length:int(4)][groupings:VariableSizeList<VariableSizeList<GroupingSemengt>>]represents ILazyLookup<TKey, TElement>, if byteSize == -1, indicates null, variable-length, lazy-evaluation
GroupingSegment[key:TKey] [hashCode:int(4)][elements:VariableSizeList<TElement>]substructure of LazyMultiDictionary

EqualityComparer

ZeroFormatter's EqualityComparer calculates stable hashCode for serialize LazyDictionary/LazyMultiDictionary. LazyDictionary and LazyMultiDictionary keys following there GetHashCode function.

C# Schema

The schema of ZeroFormatter is C# itself. You can define the schema in C# and analyze it with Roslyn to generate in another language or C#(such as zfc.exe).

namespace /* Namespace */
{
    // Fomrat Schemna
    [ZeroFormattable]
    public class /* FormatName */
    {
        [Index(/* Index Number */)]
        public virtual /* FormatType */ Name { get; set; }
    }

    // UnionSchema
    [Union(typeof(/* Union Subtypes */))]
    public abstract class UnionSchema
    {
        [UnionKey]
        public abstract /* UnionKey Type */ Key { get; }
    }
}

Cross Platform

Currently, No and I have no plans. Welcome to contribute port to other languages, I want to help your work!

ZeroFormatter spec has two stages + ex.

  • Stage1: All formats are eager-evaluation, does not support Extension Format.
  • Stage2: FixedSizeList, VariableSizeList and Object supports lazy-evaluation, does not support Extension Format.
  • StageEx: Supports C# Extension Format

List of port libraries

Author Info

Yoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan.
He is the Director/CTO at Grani, Inc.
Grani is a top social game developer in Japan.
He is awarding Microsoft MVP for Visual C# since 2011.
He is known as the creator of UniRx(Reactive Extensions for Unity)

Blog: https://medium.com/@neuecc (English)
Blog: http://neue.cc/ (Japanese)
Twitter: https://twitter.com/neuecc (Japanese)

Download details:
Author: neuecc
Source code: https://github.com/neuecc/ZeroFormatter
License: MIT license

#unity #gamedev  #csharp #dotnet

Zero Formatter: Infinitely Fast Deserializer for .NET and Unity

Cómo Usar Activos De Transmisión En Unity

Un activo puede ser cualquier recurso en Unity; esos recursos pueden ser imágenes, audio, video, guiones, texto, etc.

Al compilar un proyecto de Unity para cualquier plataforma, todos los activos de su juego se "empaquetarán" en un archivo (o más, según su plataforma) y el tamaño resultante de la compilación dependerá del tamaño de los activos que haya decidido incluir. paquete dentro de su juego.

No es lo mismo crear un juego con múltiples texturas HD que usar un Map Atlas, o usar video en 240p que usar 1080p. Se espera que cuanto mayor sea el tamaño de sus activos, mayor será el tamaño de su construcción final.

En general, es una buena práctica mantener su compilación lo más liviana posible, para que el jugador no se desanime a descargar su juego debido al tamaño, y sus escenas se ejecutarán considerablemente más rápido.

Existen varios consejos y trucos para lograrlo, y en este artículo hablaremos de algunos de ellos, comenzando con los activos de transmisión.

¿Qué son los activos de transmisión?

Digamos que tienes una buena escena en tu juego donde ocurre toda la acción. El jugador es la base enemiga, y en una habitación específica, decides poner un video que se reproduce en una computadora como un huevo de Pascua.

El video es realmente grande, como más de 300 MB, y el jugador puede o no ingresar a esa habitación específica para verlo. ¿Estaría bien cargarlo en tu escena por defecto? Eso podría causar una lentitud innecesaria en tu gran escena. Por lo tanto, sería genial cargarlo cuando realmente lo necesitemos.

Un activo de transmisión es solo eso: un activo colocado en una carpeta específica que Unity Player cargará cuando sea necesario. Ese activo se colocará en una dirección fácil de encontrar dentro de la plataforma de destino.

Tenga en cuenta que cualquier activo colocado en una carpeta de activos de transmisión (llamada StreamingAssets dentro de Unity) se copiará en la plataforma de destino, y si un usuario busca dentro de la carpeta del proyecto, podrá verlos.

Así se hacen algunos de los mods de algunos juegos.

¿Cambiar la textura de un personaje? Vaya a la carpeta de datos y busque la textura utilizada, luego modifíquela. ¿Cambiar las líneas de voz de un jefe duro? Simplemente vaya a la carpeta de datos y busque la pista de audio y reemplácela con otra con el mismo nombre.

Los activos de transmisión permiten que el juego funcione sin problemas sin cargar todos los activos de la memoria y sirven los más grandes en una ruta de fácil acceso.

Construyamos un ejemplo fácil de activos de transmisión

Vamos a mostrar cómo funcionan los activos de transmisión con un ejemplo sencillo al mostrar un video en su juego.

Descargué una vista previa de un video de iStockPhoto para este ejemplo.

Asegúrese de que el video que desea usar tenga una de las siguientes extensiones: .mov, .mpeg, .mp4, .avi, .asf.

Para nuestro ejemplo inicial, vamos a crear una nueva carpeta llamada Video y colocar nuestro video dentro de esa carpeta:

Video dentro de la carpeta

Luego, en nuestro panel de Jerarquía , configuraremos nuestro reproductor de video.

Haga clic derecho en su jerarquía y agregue un objeto de juego vacío . Llamé al mío VideoObject .

Dentro de él, creé un cubo donde vamos a reproducir el video; puede tener la forma que quieras, pero elegí un cubo por conveniencia. Yo lo llamo Video_Canvas .

Y al mismo nivel de Video_Canvas , dentro de VideoObject , agregué un nuevo objeto Video Player haciendo clic con el botón derecho en VideoObject y seleccionando Video , luego Video Player .

Estructura de carpetas de objetos de vídeo

Así es como configuro nuestro objeto Reproductor de video :

Configuración del reproductor de vídeo

Configuramos la fuente como un clip de video , y luego seleccionamos el clip de video de nuestra carpeta de video que creamos, luego seleccionamos Loop para que el video siguiera reproduciéndose infinitamente.

Luego, seleccione Modo de procesamiento como Anulación de material y arrastre y suelte el Objeto en el que desea reproducir el video, en nuestro caso, el cubo Video_Canvas . Deje que la propiedad del material sea _MainTex .

Si hace clic en Reproducir en el Editor de Unity, verá algo como esto: un video que se reproduce en cada lado del cubo.

Video en cada lado del cubo

También puedes crear un plano o reducir la escala del cubo a la forma que quieras si quieres una superficie más plana, como una pantalla.

Este video de demostración es de 3,5 MB.

Si creamos este proyecto para una plataforma independiente (Windows, Linux, MacOs), veremos que un proyecto en blanco con un video tiene 87,6 MB.

Vídeo del proyecto negro

Ahora, veamos cómo se ve usando el mismo video como recurso de transmisión.

Para hacerlo, vamos a crear una nueva carpeta llamada StreamingAssets en la carpeta de Activos ; esta es una carpeta con nombre especial que tratará nuestro archivo de video video_streamingAssets como un archivo normal sin opciones en la ventana de propiedades del editor.

Carpeta StreamingAssets

Luego, en nuestro objeto Reproductor de video , podríamos cambiar la Fuente del componente del juego del Reproductor de video a URL y dejar la URL real tal como está. Crearemos un nuevo Script llamado LoadVideo y lo adjuntaremos a nuestro Video Player GameObject como en la imagen.

Este script toma como parámetro un reproductor de video , por lo que arrastraremos y soltaremos nuestro mismo reproductor de video al que está adjunto este script.

Detalles del reproductor de vídeo

Aquí está el guión de LoadVideo:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= Application.streamingAssetsPath +"/"+ "video_streamingAssets" + ".mp4";
       myVideoPlayer.url = videoUrl;
   }
}

Básicamente, al comienzo de la aplicación, utilizará Application.streamingAssetsPath para obtener la ruta de la carpeta StreamingAssets en cualquier plataforma de destino en la que se haya integrado. Y luego hará referencia al nombre de nuestro video en esa carpeta y su extensión.

Luego, tomará myVideoPlayer (que ya tiene la referencia de nuestro reproductor de video de escena) y escribirá su propiedad url con la ruta de nuestro video.

Esto dará como resultado el mismo cubo con el video en reproducción.

Video en cada lado del cubo

Ahora, si construimos este proyecto, veremos que tiene el mismo tamaño que nuestro ejemplo anterior, pero con una gran diferencia.

Proyecto en blanco con una diferencia

Si vemos el contenido del paquete de esta compilación, podemos ver que en Contenidos/Recursos/Datos está nuestra carpeta StreamingAssets con nuestro video.

Carpeta StreamingAssets con video

Y si reemplazamos el video en nuestra carpeta StreamingAssets con otro del mismo nombre , no necesitaríamos construir nuestro proyecto nuevamente para ver los cambios reflejados en nuestro juego.

Cubo con diferencias

Ahora que sabemos cómo podemos separar nuestros activos/contenido de nuestra compilación. También podríamos llamar archivos remotos desde la URL en nuestro reproductor de video. Para hacerlo, vamos a usar este video como ejemplo . Dura casi 10 minutos y ocupa unos 151 MB.

Si cambiamos nuestro código a:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
       myVideoPlayer.url = videoUrl;
   }
}

…y eliminamos nuestro video original de la carpeta StreamingAssets , terminamos con algo como esto:

Cubo de vídeo nuevo

La misma funcionalidad, pero ahora los activos de video provienen de una fuente externa .

Ahora, si construimos nuestro proyecto sin el video local en nuestra carpeta StreamingAssets , veríamos una disminución en el tamaño de nuestra compilación.

Activos de transmisión de tamaño reducido

Tenga en cuenta que esto también se puede hacer con una llamada asíncrona o una UnityWebRequestllamada y una rutina, pero para simplificar, solo agregamos la URL exacta de un video para demostrar el poder de los recursos de transmisión.

Uso de una corrutina para obtener un video de una fuente externa

Podemos modificar nuestro LoadingScriptasí para usar una rutina en lugar de pasar el enlace directo a nuestro reproductor de video:

using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.Collections;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       StartCoroutine(LoadExternalVideo("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"));
   }

     IEnumerator LoadExternalVideo(string url){
      using (UnityWebRequest request = UnityWebRequest.Get(url))
      {
          //Fetches a page and displays the number of characters of the response.
          yield return request.SendWebRequest();
          myVideoPlayer.url = request.url;
      }
  }
}

Con este ejemplo, podríamos usar una rutina para consumir un punto final de API y recuperar la URL del video, pero para simplificar, este es un ejemplo simple de cómo hacerlo con un enlace externo.

El resultado será muy similar; solo debemos asegurarnos de que nuestro UnityWebRequestresultado se recupere del punto final consumido y asegurarnos de que la URL de un video esté presente antes de configurarlo en la propiedad URL del reproductor de video.

Pensamientos finales

Los activos de transmisión son solo una de las diferentes formas en que Unity maneja los activos para tu juego. Funciona muy bien cuando intenta involucrar a su base de jugadores al permitirles modificar su juego, o si desea tener una versión final liviana de su juego, y luego, al iniciarlo por primera vez, descargue el resto de los recursos de su juego. antes de que el jugador se dé cuenta.

Hay muchos juegos en línea que hacen este enfoque: sus juegos son muy ligeros, puedes terminar un pequeño tutorial del primer nivel y luego obtendrás una pantalla de carga que descarga el resto de los activos.

Tienes que tener en cuenta el mejor enfoque para tu juego. No puedes tener todos tus activos en un servidor remoto, porque ¿qué pasaría si la primera vez que el jugador juega tu juego, su dispositivo no tiene una buena conexión a Internet? ¿Cómo sería tu juego sin recursos?

Uno de los mejores enfoques es tener una versión muy liviana de sus recursos por defecto (como materiales, texturas, imágenes) como marcador de posición, y cuando esté seguro de que su reproductor disfrutará de ese contenido y el dispositivo está listo para descargar esa información, hacerlo de la forma más fluida posible.

¡Espero que te haya gustado este artículo y que te sea útil! ¡Feliz desarrollo de juegos!

Fuente: https://blog.logrocket.com/how-to-use-streaming-assets-unity/

#unity 

Cómo Usar Activos De Transmisión En Unity

Unity でストリーミング アセットを使用する方法

アセットは、Unity の任意のリソースにすることができます。これらのリソースは、画像、オーディオ、ビデオ、スクリプト、テキストなどです。

Unity プロジェクトを任意のプラットフォームにビルドすると、ゲーム内のすべてのアセットがファイル (プラットフォームによってはそれ以上) に「パッケージ化」され、ビルドの結果のサイズは、選択したアセットのサイズによって異なります。ゲーム内でパッケージ化します。

Map Atlas を使用せずに複数の HD テクスチャを使用してゲームを作成したり、1080p を使用する代わりに 240p のビデオを使用したりすることは同じではありません。アセットのサイズが大きいほど、最終的なビルドのサイズも大きくなることが予想されます。

一般に、ビルドをできる限り軽量に保つことをお勧めします。これにより、プレーヤーがサイズが原因でゲームのダウンロードを思いとどまらず、シーンがかなり高速に実行されるようになります。

これを実現するためのヒントとコツは複数あります。この記事では、ストリーミング アセットから始めて、それらのいくつかについて説明します。

ストリーミング アセットとは

ゲーム内に、すべてのアクションが発生する素晴らしいシーンがあるとします。プレイヤーは敵の基地であり、特定の部屋で、イースターエッグとしてコンピューターで再生されているビデオを配置することにしました。

ビデオは 300MB を超えるなど、非常に大きく、プレイヤーは実際にそれを見るためにその特定の部屋に入るかどうかはわかりません。デフォルトでシーンにロードしてもよろしいでしょうか? これにより、素晴らしいシーンが不必要に遅くなる可能性があります。したがって、実際に必要なときにロードするだけでよいでしょう。

ストリーミング アセットとは、必要に応じて Unity プレーヤーによって読み込まれる特定のフォルダーに配置されたアセットのことです。そのアセットは、ターゲット プラットフォーム内の見つけやすいアドレスに配置されます。

ストリーミング アセット フォルダー (Unity 内では StreamingAssets という名前) に配置されたアセットはすべてターゲット プラットフォームにコピーされ、ユーザーがプロジェクト フォルダー内で検索するとそれらを表示できることに注意してください。

これは、一部のゲームの mod の一部がどのように作成されるかです。

キャラクターの質感を変える?データ フォルダに移動し、使用されているテクスチャを見つけて変更します。タフなボスのセリフを変える?データ フォルダに移動し、オーディオ トラックを見つけて、同じ名前の別のトラックに置き換えます。

ストリーミング アセットを使用すると、すべてのアセットをメモリからロードせずにゲームをスムーズに実行でき、到達しやすいパスでより大きなアセットを提供できます。

ストリーミング アセットの簡単な例を作成してみましょう

ゲームでビデオを表示することにより、ストリーミング アセットがどのように機能するかを簡単な例で示します。

この例では、iStockPhoto からビデオのプレビューをダウンロードしました。

使用するビデオの拡張子が .mov、.mpeg、.mp4、.avi、.asf のいずれかであることを確認してください。

最初の例では、Video という名前の新しいフォルダーを作成し、そのフォルダー内にビデオを配置します。

フォルダー内のビデオ

次に、Hierarchyパネルで、ビデオ プレーヤーをセットアップします。

Hierarchy を右クリックし、Empty Game Objectを追加します。私はVideoObjectと呼びました。

その中に、ビデオを再生するキューブを作成しました。どんな形でも構いませんが、便宜上立方体を選びました。私はそれをVideo_Canvasと呼んでいます。

Video_Canvasの同じレベルで、VideoObject内に、VideoObjectを右クリックしてVideo、次にVideo Playerを選択して、新しいVideo Playerオブジェクトを追加しました。

ビデオ オブジェクトのフォルダ構造

Video Playerオブジェクトを設定する方法は次のとおりです。

ビデオ プレーヤーのセットアップ

ソースビデオ クリップとして設定し、作成したビデオフォルダからビデオ クリップを選択し、ループを選択して、ビデオが無限に再生され続けるようにしました。

次に、レンダリング モードマテリアル オーバーライドとして選択し、ビデオを再生するオブジェクト (この場合はVideo_Canvasキューブ) をドラッグ アンド ドロップします。マテリアル プロパティを_MainTexとします

Unity エディターで [再生] をクリックすると、次のようなものが表示されます: 立方体の両側でビデオが再生されます。

各キューブ面のビデオ

画面のように平らな表面が必要な場合は、平面を作成するか、立方体のスケールを必要な形状に縮小することもできます。

このデモビデオは 3.5MB です。

スタンドアロン プラットフォーム (Windows、Linux、MacOs) 用にこのプロジェクトをビルドすると、87.6MB のビデオを含む空のプロジェクトが表示されます。

ブラックプロジェクトビデオ

では、同じ動画をストリーミング アセットとして使用して、どのように表示されるか見てみましょう。

そのために、AssetsフォルダーにStreamingAssetsという新しいフォルダーを作成します。これは、ビデオ ファイルvideo_streamingAssetsをエディターのプロパティ ウィンドウにオプションがない通常のファイルとして扱う特別な名前のフォルダーです。

StreamingAssets フォルダー

次に、ビデオ プレーヤーオブジェクトで、ビデオ プレーヤー ゲーム コンポーネントのソースURLに変更し、実際の URL をそのままにしておくことができます。LoadVideoという新しいスクリプトを作成し、画像のようにVideo Player GameObjectにアタッチします。

このスクリプトはパラメーターとしてVideo Playerを受け取るため、このスクリプトがアタッチされているのと同じVideo Playerをドラッグ アンド ドロップします。

ビデオ プレーヤーの詳細

LoadVideoのスクリプトは次のとおりです。

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= Application.streamingAssetsPath +"/"+ "video_streamingAssets" + ".mp4";
       myVideoPlayer.url = videoUrl;
   }
}

基本的に、アプリケーションの開始時にApplication.streamingAssetsPathを使用して、組み込まれているターゲット プラットフォームのStreamingAssetsフォルダーのパスを取得します。そして、そのフォルダー内のビデオの名前とその拡張子を参照します。

次に、myVideoPlayer (既にシーン ビデオ プレーヤーの参照を持っています) を取得し、そのurlプロパティにビデオのパスを書き込みます。

これにより、再生中のビデオと同じキューブが作成されます。

各キューブ面のビデオ

ここで、このプロジェクトをビルドすると、前の例と同じサイズですが、大きな違いが 1 つあります。

1 つの違いがある空のプロジェクト

このビルドのパッケージ コンテンツを見ると、Contents/Resources/Dataに、ビデオを含むStreamingAssetsフォルダーがあることがわかります。

動画を含む StreamingAssets フォルダー

また、 StreamingAssetsフォルダー内のビデオを同じ名前の別のビデオに置き換えると、ゲームに反映された変更を確認するためにプロジェクトを再度ビルドする必要がなくなります。

違いのある立方体

アセット/コンテンツをビルドから切り離す方法がわかったので、ビデオ プレーヤーの URL からリモート ファイルを呼び出すこともできます。そのために、このビデオを例として使用します。長さはほぼ 10 分で、約 151 MB です。

コードを次のように変更すると:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
       myVideoPlayer.url = videoUrl;
   }
}

…そして、StreamingAssetsフォルダーの元のビデオを削除すると、次のようになります。

新しいビデオ キューブ

機能は同じですが、動画アセットは外部ソースから取得されます。

ここで、 StreamingAssetsフォルダーにローカル ビデオを含めずにプロジェクトをビルドすると、ビルドのサイズが小さくなります。

ストリーミング アセットの縮小サイズ

これは、非同期呼び出しまたは呼び出しとコルーチンでも実行できることに注意してくださいUnityWebRequest。ただし、簡単にするために、動画の正確な URL を追加して、ストリーミング アセットの力を示します。

コルーチンを使用して外部ソースからビデオをフェッチする

LoadingScriptビデオ プレーヤーへの直接リンクを渡す代わりに、コルーチンを使用するために、次のように変更できます。

using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.Collections;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       StartCoroutine(LoadExternalVideo("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"));
   }

     IEnumerator LoadExternalVideo(string url){
      using (UnityWebRequest request = UnityWebRequest.Get(url))
      {
          //Fetches a page and displays the number of characters of the response.
          yield return request.SendWebRequest();
          myVideoPlayer.url = request.url;
      }
  }
}

この例では、コルーチンを使用して API エンドポイントを使用し、ビデオの URL を取得できますが、簡単にするために、これは外部リンクでそれを行う方法の簡単な例です。

結果は非常に似ています。UnityWebRequest結果が消費されたエンドポイントから取得されていることを確認し、ビデオ プレーヤーの URL プロパティに設定する前にビデオの URL が存在することを確認する必要があります。

最終的な考え

ストリーミング アセットは、Unity がゲームのアセットを処理するさまざまな方法の 1 つにすぎません。ゲームの改造を許可することでプレイヤーベースを巻き込もうとしている場合、またはゲームの軽量な最終ビルドを作成し、最初の起動時にゲームの残りのアセットをダウンロードする場合に最適です。プレイヤーが気付く前に。

このアプローチを採用しているオンライン ゲームはたくさんあります。それらのゲームは非常に軽く、最初のレベルの小さなチュートリアルを完了すると、残りのアセットをダウンロードするロード画面が表示されます。

ゲームに最適なアプローチを心に留めておく必要があります。すべてのアセットをリモート サーバーに置くことはできません。プレイヤーがゲームを初めてプレイするときに、デバイスのインターネット接続が良好でない場合はどうなるでしょうか? アセットがなければ、あなたのゲームはどのように見えますか?

最良のアプローチの 1 つは、アセットの非常に軽量なバージョン (マテリアル、テクスチャ、画像など) をプレースホルダーとしてデフォルトとして用意し、プレーヤーがそのコンテンツを楽しんで、デバイスがその情報をダウンロードする準備ができていることを確認することです。可能な限りシームレスに行います。

この記事を気に入っていただき、お役に立てば幸いです。ハッピーゲームデベ!

ソース: https://blog.logrocket.com/how-to-use-streaming-assets-unity/

#unity 

Unity でストリーミング アセットを使用する方法
Iara  Simões

Iara Simões

1661473920

Como Usar Ativos De Streaming No Unity

Um ativo pode ser qualquer recurso no Unity; esses recursos podem ser imagens, áudio, vídeo, scripts, texto, etc.

Ao construir um projeto Unity para qualquer plataforma, todos os ativos do seu jogo serão “empacotados” em um arquivo (ou mais, dependendo da sua plataforma), e o tamanho resultante da compilação dependerá do tamanho dos ativos que você decidiu pacote dentro do seu jogo.

Não é a mesma coisa criar um jogo com várias texturas HD ao invés de usar um Atlas de Mapas, ou usar vídeo em 240p ao invés de usar 1080p. Espera-se que quanto maior o tamanho de seus ativos, maior o tamanho de sua construção final.

Em geral, é uma boa prática manter sua compilação o mais leve possível, para que o jogador não seja desencorajado a baixar seu jogo devido ao tamanho, e suas cenas serão executadas consideravelmente mais rápido.

Existem várias dicas e truques para conseguir isso e, neste artigo, falaremos sobre alguns deles, começando pelos ativos de streaming.

O que são ativos de streaming?

Digamos que você tenha uma cena legal em seu jogo onde toda a ação acontece. O jogador é a base inimiga, e em uma sala específica, você decide colocar um vídeo rodando em um computador como um easter egg.

O vídeo é muito grande, como mais de 300 MB, e o jogador pode ou não entrar naquela sala específica para realmente vê-lo. Seria bom carregá-lo em sua cena por padrão? Isso pode causar lentidão desnecessária em sua grande cena. Portanto, pode ser ótimo apenas carregá-lo quando realmente precisarmos.

Um ativo de streaming é exatamente isso: um ativo colocado em uma pasta específica que seria carregada pelo Unity Player quando necessário. Esse ativo será colocado em um endereço fácil de encontrar na plataforma de destino.

Observe que qualquer ativo colocado em uma pasta de ativos de streaming (chamado StreamingAssets no Unity) será copiado para a plataforma de destino e, se um usuário pesquisar na pasta do projeto, poderá vê-los.

É assim que alguns dos mods de alguns jogos são feitos.

Mudando a textura de um personagem? Vá para a pasta de dados e encontre a textura usada, então modifique-a. Mudar as falas de um chefe durão? Basta ir até a pasta de dados e encontrar a faixa de áudio e substituí-la por outra com o mesmo nome.

Os ativos de streaming permitem que o jogo funcione sem problemas sem carregar todos os ativos da memória e atendem aos maiores em um caminho de fácil acesso.

Vamos criar um exemplo fácil de ativos de streaming

Vamos mostrar como os ativos de streaming funcionam com um exemplo fácil, mostrando um vídeo em seu jogo.

Baixei uma prévia de um vídeo do iStockPhoto para este exemplo.

Certifique-se de que o vídeo que deseja usar tenha uma das seguintes extensões: .mov, .mpeg, .mp4, .avi, .asf.

Para nosso exemplo inicial, vamos criar uma nova pasta chamada Video e colocar nosso vídeo dentro dessa pasta:

Vídeo dentro da pasta

Então, em nosso painel Hierarquia , vamos configurar nosso player de vídeo.

Clique com o botão direito do mouse em sua Hierarquia e adicione um Empty Game Object . Eu chamei o meu VideoObject .

Dentro dele, criei um cubo onde vamos reproduzir o vídeo; pode ser qualquer forma que você quiser, mas eu escolhi um cubo por conveniência. Eu chamo de Video_Canvas .

E no mesmo nível de Video_Canvas , dentro de VideoObject , adicionei um novo objeto Video Player clicando com o botão direito do mouse em VideoObject e selecionando Video , depois Video Player .

Estrutura de pastas de objetos de vídeo

Aqui está como eu configuro nosso objeto Video Player :

Configuração do player de vídeo

Definimos a origem como um videoclipe e, em seguida, selecionamos o videoclipe de nossa pasta de vídeo que criamos e selecionamos Loop para que o vídeo continue sendo reproduzido infinitamente.

Em seguida, selecione Render Mode como Material Override e arraste e solte o objeto no qual deseja reproduzir o vídeo — no nosso caso, o cubo Video_Canvas . Deixe a propriedade do material ser _MainTex .

Se você clicar em Reproduzir no Editor do Unity, verá algo assim: um vídeo sendo reproduzido em cada lado do cubo.

Vídeo em cada lado do cubo

Você também pode criar um plano ou reduzir a escala do cubo para a forma desejada se desejar uma superfície mais plana, como uma tela.

Este vídeo de demonstração tem 3,5 MB.

Se construirmos este projeto para uma plataforma autônoma (Windows, Linux, MacOs) veremos que um projeto em branco com um vídeo tem 87,6 MB.

Vídeo do Projeto Negro

Agora, vamos ver como fica usando o mesmo vídeo como um recurso de streaming.

Para isso, vamos criar uma nova pasta chamada StreamingAssets na pasta Assets ; esta é uma pasta com nome especial que tratará nosso arquivo de vídeo video_streamingAssets como um arquivo normal sem opções na janela de propriedades do editor.

Pasta de recursos de streaming

Em seguida, em nosso objeto Video Player , poderíamos alterar a Origem do componente de jogo do player de vídeo para URL e deixar a URL real em paz. Vamos criar um novo Script chamado LoadVideo e anexá-lo ao nosso Video Player GameObject como na imagem.

Este script tem como parâmetro um Video Player , então vamos arrastar e soltar nosso mesmo Video Player ao qual este script está anexado.

Detalhes do player de vídeo

Aqui está o script de LoadVideo:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= Application.streamingAssetsPath +"/"+ "video_streamingAssets" + ".mp4";
       myVideoPlayer.url = videoUrl;
   }
}

Basicamente, no início do aplicativo, ele usará Application.streamingAssetsPath para obter o caminho da pasta StreamingAssets em qualquer plataforma de destino que tenha sido incorporada. E então ele fará referência ao nome do nosso vídeo nessa pasta e sua extensão.

Então, ele pegará myVideoPlayer (que já tem a referência do nosso player de cena) e escreverá sua propriedade url com o caminho do nosso vídeo.

Isso resultará no mesmo cubo com o vídeo em reprodução.

Vídeo em cada lado do cubo

Agora, se construirmos este projeto, veremos que ele tem o mesmo tamanho do nosso exemplo anterior, mas com uma grande diferença.

Projeto em branco com uma diferença

Se virmos o conteúdo do pacote desta compilação, podemos ver que em Contents/Resources/Data há nossa pasta StreamingAssets com nosso vídeo.

Pasta StreamingAssets com vídeo

E se substituirmos o vídeo em nossa pasta StreamingAssets por outro de mesmo nome , não precisaríamos construir nosso projeto novamente para ver as mudanças refletidas em nosso jogo.

Cubo com diferenças

Agora que sabemos como podemos separar nossos assets/conteúdos de nosso build. Também podemos chamar arquivos remotos da URL em nosso Video Player. Para isso, vamos usar este vídeo como exemplo . Tem quase 10 minutos de duração e cerca de 151 MB.

Se mudarmos nosso código para:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
       myVideoPlayer.url = videoUrl;
   }
}

…e excluir nosso vídeo original da pasta StreamingAssets , acabamos com algo assim:

Novo cubo de vídeo

A mesma funcionalidade, mas agora os recursos de vídeo vêm de uma fonte externa .

Agora, se construirmos nosso projeto sem o vídeo local em nossa pasta StreamingAssets , veremos uma diminuição no tamanho do nosso build.

Tamanho reduzido dos recursos de streaming

Lembre-se de que isso também pode ser feito com uma chamada assíncrona ou uma UnityWebRequestchamada e uma corrotina, mas para simplificar, apenas adicionamos o URL exato de um vídeo para demonstrar o poder dos ativos de streaming.

Usando uma corrotina para buscar um vídeo de uma fonte externa

Podemos modificar nosso LoadingScriptassim para usar uma corrotina em vez de passar o link direto para nosso player de vídeo:

using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.Collections;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       StartCoroutine(LoadExternalVideo("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"));
   }

     IEnumerator LoadExternalVideo(string url){
      using (UnityWebRequest request = UnityWebRequest.Get(url))
      {
          //Fetches a page and displays the number of characters of the response.
          yield return request.SendWebRequest();
          myVideoPlayer.url = request.url;
      }
  }
}

Com este exemplo, poderíamos usar uma corrotina para consumir um endpoint de API e recuperar a URL do vídeo, mas para simplificar, este é um exemplo simples de como fazer isso com um link externo.

O resultado será muito semelhante; só precisamos garantir que nosso UnityWebRequestresultado seja recuperado do endpoint consumido e garantir que a URL de um vídeo esteja presente antes de configurá-lo para a propriedade URL do player de vídeo.

Pensamentos finais

Os ativos de streaming são apenas uma das diferentes maneiras pelas quais o Unity lida com os ativos do seu jogo. Funciona muito bem quando você está tentando envolver sua base de jogadores, permitindo que eles modifiquem seu jogo, ou se você deseja ter uma versão final leve do seu jogo e, após o primeiro lançamento, baixe o restante dos recursos do seu jogo antes que o jogador perceba.

Existem muitos jogos online que fazem essa abordagem: seus jogos são muito leves, você pode terminar um pequeno tutorial do primeiro nível e, em seguida, obterá uma tela de carregamento que baixa o restante dos ativos.

Você deve ter em mente a melhor abordagem para o seu jogo. Você não pode ter todos os seus ativos em um servidor remoto, porque o que aconteceria se na primeira vez que o jogador jogasse seu jogo o dispositivo dele não tivesse uma boa conexão com a internet? Como seria o seu jogo sem recursos?

Uma das melhores abordagens é ter uma versão muito leve de seus ativos como padrão (como materiais, texturas, imagens) como espaço reservado e quando você tiver certeza de que seu player aproveitará esse conteúdo e o dispositivo estiver pronto para baixar essas informações, fazê-lo da forma mais tranquila possível.

Espero que tenha gostado deste artigo e que seja útil! Feliz gamedev!

Fonte: https://blog.logrocket.com/how-to-use-streaming-assets-unity/

#unity 

Como Usar Ativos De Streaming No Unity
顾 静

顾 静

1661472000

如何在 Unity 中使用流媒体资源

资产可以是 Unity 中的任何资源;这些资源可以是图像、音频、视频、脚本、文本等。

在任何平台上构建 Unity 项目时,游戏中的所有资源都将“打包”在一个文件中(或更多取决于您的平台),构建的结果大小将取决于您决定的资源大小打包在您的游戏中。

创建具有多个高清纹理的游戏而不是使用地图集,或者使用 240p 视频而不是使用 1080p 是不一样的。预计资产规模越大,最终构建的规模就越大。

一般来说,保持构建尽可能轻量级是一个很好的做法,这样玩家就不会因为大小而放弃下载游戏,而且场景运行速度也会大大加快。

实现这一点有多种提示和技巧,在本文中,我们将讨论其中的一些,从流媒体资产开始。

什么是流媒体资产?

假设您的游戏中有一个不错的场景,所有动作都在其中发生。玩家是敌人的基地,在一个特定的房间里,你决定把一段视频作为彩蛋放在电脑上播放。

视频真的很大,超过 300MB,播放器可能会也可能不会进入那个特定的房间才能真正看到它。默认情况下将其加载到您的场景中可以吗?这可能会导致您的精彩场景出现不必要的缓慢。所以当我们真正需要它时加载它可能会很棒。

流媒体资产就是这样:放置在特定文件夹中的资产,需要时由 Unity Player 加载。该资产将被放置在目标平台内易于找到的地址中。

请注意,放置在流资产文件夹(在 Unity 中名为 StreamingAssets)的任何资产都将被复制到目标平台,如果用户在项目文件夹中搜索,它将能够看到它们。

这就是一些游戏的一些模组的制作方式。

改变角色的纹理?进入数据文件夹并找到使用的纹理,然后对其进行修改。改变强硬老板的台词?只需转到数据文件夹并找到音轨并将其替换为另一个具有相同名称的音轨。

流媒体资产让游戏运行顺畅,无需从内存中加载所有资产,并以易于访问的路径提供更大的资产。

让我们构建一个流媒体资产的简单示例

我们将通过在您的游戏中显示视频,通过一个简单的示例来展示流媒体资产的工作原理。

从 iStockPhoto下载了此示例的视频预览。

确保您要使用的视频具有以下扩展名之一:.mov、.mpeg、.mp4、.avi、.asf。

对于我们的初始示例,我们将创建一个名为Video的新文件夹并将我们的视频放在该文件夹中:

文件夹内的视频

然后,在我们的Hierarchy面板上,我们将设置我们的视频播放器。

右键单击您的 Hierarchy 并添加一个Empty Game Object。我打电话给我的 VideoObject

在其中,我创建了一个立方体,我们将在其中播放视频;它可以是你想要的任何形状,但为了方便我选择了一个立方体。我称之为Video_Canvas

在Video_Canvas的同一级别,在VideoObject中,我通过右键单击VideoObject并选择Video,然后选择 Video Player添加了一个新的Video Player对象。

视频对象文件夹结构

以下是我设置视频播放器对象的方法:

视频播放器设置

我们将Source设置为Video Clip,然后从我们创建的Video Folder 中选择视频剪辑,然后选择Loop以便视频可以无限播放。

然后,选择Render Mode作为Material Override并拖放要在其上播放视频的对象——在我们的例子中是Video_Canvas立方体。让Material 属性_MainTex

如果您在 Unity 编辑器中单击播放,您将看到类似这样的内容:在立方体的每一侧播放一个视频。

每个立方体侧面的视频

如果您想要一个更平坦的表面(如屏幕),您还可以创建一个平面或将立方体的比例缩小到您想要的形状。

该演示视频为 3.5MB。

如果我们为独立平台(Windows、Linux、MacOs)构建这个项目,我们将看到一个带有视频的空白项目,大小为 87.6MB。

黑色项目视频

现在,让我们看看使用相同的视频作为流媒体资产的效果。

为此,我们将在Assets文件夹中创建一个名为StreamingAssets的新文件夹;这是一个特殊命名的文件夹,它将我们的视频文件video_streamingAssets视为常规文件,在编辑器的属性窗口中没有任何选项。

流资产文件夹

然后在我们的Video Player对象中,我们可以将Video Player 游戏组件的Source更改为URL并保留实际的 URL。我们将创建一个名为LoadVideo的新脚本,并将其附加到我们的视频播放器游戏对象,就像在图像中一样。

该脚本将视频播放器作为参数,因此我们将拖放与该脚本连接的相同视频播放器。

视频播放器详细信息

这是LoadVideo 的脚本:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= Application.streamingAssetsPath +"/"+ "video_streamingAssets" + ".mp4";
       myVideoPlayer.url = videoUrl;
   }
}

基本上,在应用程序启动时,它将使用Application.streamingAssetsPath来获取已内置的任何目标平台中StreamingAssets文件夹的路径。然后它将引用该文件夹中我们视频的名称及其扩展名。

然后,它将采用myVideoPlayer(已经有我们场景视频播放器的引用),并将其url属性写入我们视频的路径。

这将产生与播放视频相同的立方体。

每个立方体侧面的视频

现在,如果我们构建这个项目,我们将看到它的大小与前面的示例相同,但有一个主要区别。

一个差异的空白项目

如果我们看到这个构建的包内容,我们可以看到在Contents/Resources/Data上有我们的StreamingAssets文件夹和我们的视频。

带有视频的 StreamingAssets 文件夹

如果我们将StreamingAssets文件夹中的视频替换为另一个同名的视频,则无需再次构建项目即可看到游戏中反映的更改。

有差异的立方体

现在我们知道了如何将我们的资产/内容从我们的构建中分离出来。我们还可以从视频播放器上的 URL 调用远程文件。为此,我们将使用此视频作为示例。它大约有 10 分钟长,大约 151MB。

如果我们将代码更改为:

using UnityEngine;
using UnityEngine.Video;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       string videoUrl= "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
       myVideoPlayer.url = videoUrl;
   }
}

…并删除StreamingAssets文件夹的原始视频,我们最终得到如下内容:

新视频立方体

相同的功能,但现在视频资产来自外部来源

现在,如果我们在StreamingAssets文件夹中构建没有本地视频的项目,我们会看到构建的大小有所减少。

流媒体资产减小了大小

请记住,这也可以通过异步调用或UnityWebRequest调用和协程来完成,但为简单起见,我们只添加视频的确切 URL 以展示流媒体资产的强大功能。

使用协程从外部源获取视频

我们可以LoadingScript像这样修改我们的代码,以便使用协程而不是将直接链接传递给我们的视频播放器:

using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.Collections;

public class LoadVideo : MonoBehaviour
{
   public VideoPlayer myVideoPlayer;
   void Start()
   {
       StartCoroutine(LoadExternalVideo("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"));
   }

     IEnumerator LoadExternalVideo(string url){
      using (UnityWebRequest request = UnityWebRequest.Get(url))
      {
          //Fetches a page and displays the number of characters of the response.
          yield return request.SendWebRequest();
          myVideoPlayer.url = request.url;
      }
  }
}

在此示例中,我们可以使用协程来使用 API 端点并检索视频的 URL,但为简单起见,这是一个关于如何使用外部链接进行操作的简单示例。

结果将非常相似;我们只需要确保我们的UnityWebRequest结果是从消费端点检索到的,并确保在将视频的 URL 设置为视频播放器的 URL 属性之前存在。

最后的想法

流媒体资源只是 Unity 为您的游戏处理资源的不同方式之一。当您通过允许他们修改您的游戏来尝试让您的玩家群参与进来​​时,或者如果您想拥有游戏的轻量级最终构建,然后在首次启动时,下载游戏的其余资产,它非常有用在玩家注意到之前。

有很多网络游戏都是这样做的:他们的游戏很轻,你可以完成第一关的小教程,然后你会得到一个加载屏幕,下载其余的资产。

您必须牢记最适合您的游戏的方法。您不能将所有资产都放在远程服务器上,因为如果玩家第一次玩您的游戏时,他们的设备没有良好的互联网连接会发生什么?如果没有资产,你的游戏会是什么样子?

最好的方法之一是默认使用非常轻量级的资源版本(如材质、纹理、图像)作为占位符,并且当您确定播放器会喜欢该内容并且设备已准备好下载该信息时,尽可能无缝地这样做。

我希望你喜欢这篇文章并觉得它很有用!快乐的游戏开发!

来源:https ://blog.logrocket.com/how-to-use-streaming-assets-unity/

#unity 

如何在 Unity 中使用流媒体资源