Giter VIP home page Giter VIP logo

lyf-on-ar's Introduction

Lyf On AR

Lyf On is a social media like application that tries to bring social media into the real world using Augmented Reality technology.

It is made for hotel accommodation services like Lyf to strengthen and bring fun for its resident.

Features

  1. Quests System (Able to visit/explore different AR sites or other tasks and earn points )
  2. Discussion Board (Able to chat with other residents through a virtual board. Also able to paste stickers that you have received through quests)
  3. Shop/Point System (Buy rare collectibles using the points you have earned through completing of quests)

lyf-on-ar's People

Contributors

aster0 avatar

Watchers

Shamim Akhtar avatar  avatar

lyf-on-ar's Issues

Revamped UI

Revamped Hub UI a little on 10/JUN/2022

Before (Player Toolbar)
image

After (Player Toolbar)
image

Also added a nifty new scroll bar system to contents with a list.
gif

User Information (WIP):
image

Login Session Manager Implementation

In this documentation, I will be documenting on how the User Data Structure is created and stored within the application.

All data are fetched from Firebase. Firebase User Data Structure:
image

Using our LoginSessionManager.cs, we can determine when a user tries to login. If the user logins, we will run the method below to cache the details locally within the Lyf On mobile application -

private void CacheUserData()
    {
        FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
        
        Debug.Log("Done");
        
        Firebase.Auth.FirebaseUser user = auth.CurrentUser;
        
        DocumentReference docRef = db.Collection("users").Document(user.UserId); // we loop the user document, targeting the logged in user's id
        docRef.Listen(snapshot => {
            
            Debug.Log("Callback received document snapshot.");
            Debug.Log(String.Format("Document data for {0} document:", snapshot.Id));

            User user = new User(); // make a new instance of User object
            
            // and basically save all the details into the User Object. below.
            // we save things like quests, stickers, points, levels, etc.
            
            user.uuid = snapshot.Id;
            
            Dictionary<string, object> dict = snapshot.ToDictionary();

            user.username = dict["username"].ToString();
            
            user.emailAddress = dict["username"].ToString();
            user.username = dict["username"].ToString();


            User.Details details = new User.Details();

            Dictionary<string, object> detailsDict = dict["details"] 
                as Dictionary<string, object>;


            details.exp = int.Parse(detailsDict["exp"].ToString());

            details.points = int.Parse(detailsDict["points"].ToString());
            details.level = int.Parse(detailsDict["level"].ToString());


            details.ownedStickers = new List<Sticker>();

            
            try
            {
                List<object> stickerList = detailsDict["stickers"] as List<object>;

         

                foreach (string stickerName in stickerList)
                {
                    Sticker sticker = UnityEngine.Resources.Load("Stickers/StickersObject/" + stickerName,
                        typeof(Sticker)) as Sticker;
                
                    Debug.Log(sticker + " STICKER");
                    
                    details.ownedStickers.Add(sticker);
                }
                
                

            }
            catch (Exception e)
            {
                // no stickers found
            }

            Object[] stickers = 
                UnityEngine.Resources.LoadAll("Stickers/StickersObject/");

            foreach (Object obj in stickers)
            {

                Sticker sticker = (Sticker) obj;
                
                if(sticker.price == 0) 
                    details.ownedStickers.Add(sticker);
            }

            
            if (_gameManager.quests == null) // if it's the first time loading the quests,
                // we'll cache it from resources.
            {
                _gameManager.quests = new List<Quest>();

                object[] questObjects = UnityEngine.Resources.LoadAll("Quests");

                foreach (object obj in questObjects)
                {
                    Quest quest = (Quest) obj;
                
                    _gameManager.quests.Add(quest);
                
                }
            }

            details.inProgressQuests = new List<Quest>();
            
            try
            { 
                
                Dictionary<string, object> questDict = detailsDict["quests"] as Dictionary<string, object>;



                foreach (string questUID in questDict.Keys)
                {

           
                    Debug.Log(questUID + questDict[questUID] +  " DICT") ;
                    
                    foreach (Quest quest in _gameManager.quests)
                    {
                        if (questUID.Equals(quest.uid)) // if it matches the uid in the current
                        // quests, we set it to in progress because the player is currently using it.
                        {
                            Debug.Log("HIT");
                            quest.IsInProgress = true;

                            Dictionary<string, object> questValues = questDict[questUID] 
                                as Dictionary<string, object>;
                            
                            quest.currentValue = int.Parse(questValues["value"].ToString());

                            if (questValues["status"].ToString().Equals("CLAIMED"))
                            {
                                quest.claimed = true;
                            }

                            Debug.Log(quest.currentValue + " CURRENT VALUE");
                            details.inProgressQuests.Add(quest);
                            break;
                        }
                    }
                }
                
            }
            catch (Exception e)
            {
                    // NO QUESTS TAKEN
            }
            
          

            user.details = details;


            GameManager.Instance.user = user;
            
            
            //CustomSceneManager.LoadScene("Resources/Scenes/Hub");
        });
        
        
    }

View #5 - Points System Implementation on a more in-depth look on how the points are used.

Dynamic Store System Implementation

To swap between different shop types, we have a ShopTabManager.cs managing that.

With the code below, we can now create as many shop tabs as we want and assign this script for a new tab.
image

    [SerializeField]
    private Image tabFocusedImage;

    [SerializeField]
    private Color focusedColor, unfocusedColor;


    [SerializeField]
    private StorePopulator.StoreType storeType;


    [SerializeField] private ShopTabManager otherTab;

    [SerializeField] private StorePopulator storePopulator;
    // Start is called before the first frame update
    void Start()
    {
        GetComponent<Button>().onClick.AddListener(OnTabSwitch);
    }

    private void OnTabSwitch() // on player switches the shop tab
    {
        tabFocusedImage.color = focusedColor;
        UnfocusTab();

        storePopulator.storeType = this.storeType;
        
        storePopulator.UpdateStoreContents();
    }


    private void UnfocusTab() // when the other tab should be unfocused as it's switched away
    {
        otherTab.tabFocusedImage.color = otherTab.unfocusedColor;
    }

The store tab also changes the StoreType enum based on which tab we're currently in, so we know which shop to load in (avatars or stickers)

Found in the StorePopulator.cs,

    public enum StoreType // w hich type should be loaded up in the shop?
    {
        STICKER,
        AVATAR
    }

Then, we populate the store every time a tab is changed by running the UpdateStoreContents method

    public void UpdateStoreContents()
    {

        if (firstTimeOver)
        {
            foreach (Transform child in gameObject.transform)
            {
                Destroy(child.gameObject);
            }
        }
        
        Object[] objects = UnityEngine.Resources.LoadAll("StoreItems/Objects/"); // get all the store item scriptable objects

        
        
        foreach (Object obj in objects) // iterate the objects array
        {
            
            StoreObject storeObj = (StoreObject) obj; // cast it back to a sticker object cause we know they are stickers 

            if (storeObj.storeType == storeType)
            {
                if (!storeObj.purchasable) // if its not purchasable, 
                    continue; // we skip this iteration and dont show in the shop.


       
                GameObject storeGameObject = Instantiate(_gameManager.stickerStorePrefab, new Vector3(0,0),
                    Quaternion.identity); // create a new game object icon
            
                storeGameObject.transform.SetParent(this.gameObject.transform, false); // update the parent


                bool owned = false;

                bool containsInList = _gameManager.user.details.ownedStickers.Contains(storeObj) ||
                                      _gameManager.user.details.ownedAvatars.Contains(storeObj);

                if (containsInList) // if the player already owns the sticker
                {
                    owned = true; // set owned to true so they can't buy it again.
               
                }
          


                StoreIcon storeIcon = storeGameObject.GetComponent<StoreIcon>(); // get the store icon instance 
                // from the current newly instantiated game object
            
                storeIcon.UpdateStoreIcon(storeObj, owned); // update the sticker and the owned status.
            }

            
        }
    }

After creating the newly instantiated GameObject, we use the script from StoreIcon.cs to manage per Store Item's visual appearance. By running the UpdateStoreIcon method, we can update the item's visual representation, icons, names and whether it's already owned by the player.

   public void UpdateStoreIcon(StoreObject storeObj, bool owned)
    {
        nameText.text =    Regex.Replace(storeObj.name.Replace("Sticker", "").Replace("Avatar", ""), 
            "([a-z])([A-Z])", "$1 $2"); // putting a space before capital letters using Regex
        this.sticker = storeObj;
        

   

        this.owned = owned;
        
        if(!owned)
            priceText.text = storeObj.price.ToString();
        else
        {
            priceText.text = "OWNED";

            Destroy(GetComponent<Button>());
        }

        icon.sprite = storeObj.sprite;
    }

    public void UpdateStoreItemStatus()
    {
        priceText.text = "OWNED";
    }

A StoreButton.cs script is then use to see if a specific store item is clicked (purchased)

It'll check if the player has enough money, if not show an error prompt. If they have enough money, it'll query the Firebase data and update the new item in the inventory.

    private void OnStorePurchase() // when the player buys an item
    {

        
        
   
        string collectionName = "";
        string documentName = "";
        
 
            
        if (storeItem.owned)
        {
            return; // dont buy sticker
        }

        if (_gameManager.user.details.points < storeItem.sticker.price) // not enough money
        {
        
            ErrorPopupManager.GeneratePopup("Not enough points to buy this!");
            return;
        }
        
        collectionName = "users";
        documentName = _gameManager.user.uuid;


        int newPoints = _gameManager.user.details.points -
                        storeItem.sticker.price;
        
        _pointsManager.UpdatePointsText(newPoints); // update the new points visually
 
        
        _gameManager.user.UpdatePlayerDetails(newPoints, User.UpdateType.POINTS); // update the database with the new deducted points

        storeItem.owned = true;

        storeItem.UpdateStoreItemStatus();


        User.UpdateType updateType = User.UpdateType.STICKER;

        if (storePopulator.storeType == StorePopulator.StoreType.AVATAR)
        {
            updateType = User.UpdateType.AVATAR;
        }

        _gameManager.user.UpdatePlayerDetails(storeItem.sticker,updateType);
        
        

        Debug.Log("BOUGHT");

    }

Sending Chat Implementation

Found in the SendChat.cs, this is how we handle sending of chats.

    private void OnChatSend()
    {

        
        string chatModifier;

        if (messageType == MessageType.STICKER) // if its a sticker message
        {
            chatModifier = "STICKER//" + image.sprite.name; // we place a STICKER// so we can know in the future.
            

            GameObject.Find("Stickers").SetActive(false); 
        }
        else  // normal message
        {
            
            if (chatInput.text.Length == 0) // empty
                return; // dont send empty text
            
            chatModifier = chatInput.text; // just input whatever we typed normally
        }
        
        // to update the firebase below
        
        FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
        DocumentReference docRef = db.Collection("boards").Document(_gameManager.currentBoardName);


        
        
        Debug.Log("SENDING CHAT " + chatModifier + this.gameObject.name);

        _gameManager.currentChat = _gameManager.currentBoard[_gameManager.currentBoardName];
        
        Debug.Log("CHAT: " + _gameManager.currentBoardName);
       
        _gameManager.currentChat.Add(
            new Dictionary<string, object>()
            {
                {"contents", chatModifier},
                {"datePosted", System.DateTime.Today},
                {"user", GameManager.Instance.user.uuid}

            });
        
        Dictionary<string, List<object>> update = new Dictionary<string, 
            List<object>>
        {
            { "chats", _gameManager.currentChat}
        };
        docRef.SetAsync(update, SetOptions.MergeAll);


        ChatQuestEvent(); // for quest event handler
        
        if(messageType == MessageType.DEFAULT) // if its a text message (not sticker)
            chatInput.text = ""; // we reset the chat input to empty so they can easily type a new message without deleting the last.
    }

This is connected to #17 - Boards Optimization, as when we update the Firebase, the Boards Firebase Async snapshot listener picks up an update in the structure of the board and shows the newly sent chat. This all is very inter-linked and dynamic and rely on each other to function.

Quest System Implementation

In this documentation, I will be documenting on how the quest system are set out.

In the Quest.cs scriptable object script, we store various data structure that the quest uses such as; type of the quest, type of rewards, current value of quest, max value of quest, etc.

public class Quest : ScriptableObject
{
    public Sprite icon;
    public int currentValue, maxValue;

    public new string name, description;

    public string uid;

    public Reward reward;


    public QuestType questType;
    public bool IsInProgress { get; set; }
    public bool claimed { get; set; }

As seen above, this is the partial script of the Quest.cs, showing the data structure.

Let's now dive into how the rewards are given when a quest is completed.

 [Serializable]
    public struct Reward
    {

        public Sprite rewardIcon;
        public RewardType rewardType;


        public int rewardAmount; // for pts & exp rewards
        public Sticker targetedStickerReward; // for sticker rewards
        
        public enum RewardType // what type is the reward going to be?
        {
            EXP,
            POINTS,
            STICKER
        }


        public void OnRewardClaim() // on the user claiming the reward
        {
            if (rewardType == RewardType.EXP)
            {
                // give exp
                GameManager.Instance.user.UpdatePlayerDetails(
                    
                    GameManager.Instance.user.details.exp + rewardAmount, User.UpdateType.EXP);
            }
            else if (rewardType == RewardType.POINTS)
            {
                // give pts
                
                GameManager.Instance.user.UpdatePlayerDetails(
                    GameManager.Instance.user.details.points +
                    rewardAmount, User.UpdateType.POINTS);
            }
            else if (rewardType == RewardType.STICKER)
            {
                // give sticker
                
                Debug.Log(targetedStickerReward.name + " TARGETED STICKER");
                GameManager.Instance.user.UpdatePlayerDetails(targetedStickerReward,
                    User.UpdateType.STICKER);
                
                
            }
        }
        
    }

As you can see, there is an OnRewardClaim method that can be ran when a quest is completed. If the quest reward is EXP, simply give the user EXP and update the database accordingly. This goes the same for the POINTS and STICKERS reward.

Since these are scriptable objects, it could be easily created in the project folders by right clicking as seen in the photo below.
image

You can also easily tune the settings of the new quest then.
image

This is how we detect when a scan quest is completed using ScanQuestManager.cs

    public void OnScanImage()
    {
   
       
        foreach (Quest quest in _gameManager.user.details.inProgressQuests) // iterate all the in progress quest
        {
           
            if (quest.questType == Quest.QuestType.SCAN)
            {

                if (quest.uid.Equals(questUID)) // and find the current scan quest we're doing
                {
                    if (quest.currentValue < 1) // only if it's lower than one then we finish the quest.
                    {
                        QuestAchievementManager.Instance.UpdateQuest(quest); // show a prompt that the quest is completed
                        
                        _gameManager.user.UpdatePlayerDetails(_gameManager.user.details.inProgressQuests,
                            User.UpdateType.QUEST); // update via firebase.
                    }
                    
                }
               
               
            }
        }
    }

This is for when chatting, so we can keep track of chat quests as well.

    private void ChatQuestEvent()
    {
        foreach (Quest quest in _gameManager.user.details.inProgressQuests) // loop through all in progress chats
        {
            if (quest.questType == Quest.QuestType.CHAT)
            {
                QuestAchievementManager.Instance.UpdateQuest(quest); // update the quest value as we have just chatted
               
            }
        }
        
        _gameManager.user.UpdatePlayerDetails(_gameManager.user.details.inProgressQuests,
            User.UpdateType.QUEST); // update in firebase.
    }

Boards Optimizations

For how sending chat works, please visit #21.

The boards are now optimized to only load the data when it's scanned by the player.

When the tracking is lost, the boards will turn off the asynchronous update to the database. Meaning, it won't receive anymore updates from the board's chat.

image

*When the board loses its tracking - *

    public void OnBoardHide()
    {
        if(listener != null)
            listener.Dispose(); // dispose the async listener
    }

*When the board being tracked, it opens an asynchronous listener to Firebase. - *

   public void CacheBoard()
    {
        FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
        DocumentReference docRef = db.Collection("boards").Document(boardName);
        listener = docRef.Listen(snapshot => {




            foreach (Transform child in transform)
            {
                Destroy(child.gameObject);
            }
            
            
            Debug.Log("Callback received document snapshot.");
            Debug.Log(String.Format("Document data for {0} document:", snapshot.Id));

       
            
            Dictionary<string, object> dict = snapshot.ToDictionary();


            List<object> chats = 
                dict["chats"] as List<object>;


            //_gameManager.currentChat = chats; // 10/04/2022

            _gameManager.currentBoard[boardName] = chats;

            if (chats.Count == 0)
            {
                return;
            }
            
            foreach (Dictionary<string, object> chat in chats)
            {


                GameObject gameObject = _gameManager.chatBoardPrefab;
                

                if (chat["contents"].ToString().StartsWith("STICKER//"))
                {
                    gameObject = _gameManager.chatBoardStickerPrefab;
                }
                
                
             
             
                GameObject chatBoardObject = Instantiate(gameObject, new Vector3(0,0),
                    Quaternion.identity);
            
                chatBoardObject.transform.SetParent(this.gameObject.transform, false);



                ChatBoard chatBoard = chatBoardObject.GetComponent<ChatBoard>();
                
                
                FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
                DocumentReference docRef = db.Collection("users").Document(
                    chat["user"].ToString());

                docRef.GetSnapshotAsync().ContinueWithOnMainThread((task) =>
                {

                    DocumentSnapshot snapshot = task.Result;
                    Dictionary<string, object> userDictionary = snapshot.ToDictionary();


             
                    try 
                    {
                        // see if the player has an avatar first
                        Dictionary<string, object> userDetails = userDictionary["details"] as Dictionary<string, object>;
                        
                        if(userDetails != null) // null check
                            chatBoard.BuildChatBoard(userDictionary["username"].ToString(),
                                chat["contents"].ToString(), chat["user"].ToString(), userDetails["current_avatar"].ToString()); // load with player avatar
                    
                    }
                    catch (KeyNotFoundException avatarNotFound) // if the player has no avatar
                    {
                        chatBoard.BuildChatBoard(userDictionary["username"].ToString(),
                            chat["contents"].ToString(), chat["user"].ToString()); // load with no avatar as player do not have an avatar
                    
                    }

              
               
                   
                    // make sure the chat is finished building, then scroll to the bottom.
                    // this means that every new chat that is added (i.e. send), will bring the chat to the bottom too.
                    ScrollToBottom();
                    
                });
         

          
                
                chatBoardObject.transform.localScale = new Vector3(1, 1, 1);
                chatBoardObject.transform.localPosition = new Vector3(0, 0, -13);
                chatBoardObject.transform.localRotation = Quaternion.Euler(new Vector3(0,0));
                
               
            }
                
     



        });
        
        
    }

[IMPORTANT] Building an APK

This guide will teach you how to build an APK.

First, go to Assets -> External Dependency Manager -> Android Resolver -> Settings
image

Disable the ones highlighted in yellow, then scroll down and press "OK".
image

Points System Implementation

In this documentation, I will document on how the points are retrieved and used in the system.

Found in LoginSession.cs,

details.points = int.Parse(detailsDict["points"].ToString());

We cache the user's points from the Firestore and save it to our User Data as explained & documented in #2.

The points system is then used in shops. Let's zoom into the StickerStoreButton.cs script.

When the button is pressed, the points is deducted to buy the stickers from the shop. The points is both deducted both locally and in the database to show changes both to the player and TO THE SERVER.

    private void OnStorePurchase() // when the player buys an item
    {

        Debug.Log("PRESSED");

        
        
   
        string collectionName = "";
        string documentName = "";
        
        if (_storeItemType == StoreItemType.STICKER)
        {
            
            if (stickerStore.owned)
            {
                return; // dont buy sticker
            }

            if (_gameManager.user.details.points < stickerStore.sticker.price) // not enough money
            {
            
                ErrorPopupManager.GeneratePopup("Not enough points to buy this!");
                return;
            }
            
            collectionName = "users";
            documentName = _gameManager.user.uuid;


            int newPoints = _gameManager.user.details.points -
                            stickerStore.sticker.price;
            
            _pointsManager.UpdatePointsText(newPoints); // update the new points visually
     
            
            _gameManager.user.UpdatePlayerDetails(newPoints, User.UpdateType.POINTS); // update the database with the new deducted points

            stickerStore.owned = true;

            stickerStore.UpdateStickerStatus();
        }
        
        
        _gameManager.user.UpdatePlayerDetails(stickerStore.sticker, User.UpdateType.STICKER);
        
        

        Debug.Log("BOUGHT");

    }

View #12 - Sticker Shop Implementation for a more in-depth look on how the store system works.

Error Tolerance

As suggested by Mr Malcolm, I have added error tolerance to show when a mural is not scanned.

eb5aafeed09acd0b2266d9a1d05ab26f.mp4

[IMPORTANT] Opening the Project for the first time

Please ensure that the .zip size is around the image below, if not, it might be a corrupted download.
image

When you first open the project, you might see this prompt - this is because, I first started development with older plugin versions:
image

! Press yes - I have tested the project working still with the newly updated plugins.

Please note that these are harmless errors, it is because I coded for Android and not IOS as I do not have xcode.
image

The path for scripts are ARResources -> Scripts
image

Please note that since the library folder is deleted, you have to navigate to the scene yourself.

The path for scenes are Resources -> Scenes
image

NOTE: Start from SplashScreen Scene.

Since the application's UI is build for mobile & tablet devices, you have to change it to simulator mode to a mobile phone simulation in game play

NOTE that the application works on every mobile & tablet devices found in Unity's simulation. It just isn't optimized for a computer screen (as it's a mobile app), thus, you need to change it into a simulator.

image

LOGIN INFORMATION

username: [email protected]
password: 123456

Custom Error Popup Implementation

When there's an error, example when buying an item without enough points or logging in with the wrong credentials, it all uses the same Error Popup Implementation.

image

How it works:
Using a static method, the developer can call this method anywhere without a reference to any instance. When the method is called, a the popup game object prefab is instantiated into the UI Canvas of the scene. It'll then set the message to what the developer sets it to be.

    public static void GeneratePopup(string message)
    {
        GameObject contentCanvas = GameObject.FindWithTag("ContentCanvas");
        GameObject newObject = Instantiate(GameManager.Instance.popupPrefab, 
            
             new Vector3(0, 0), Quaternion.identity);


        
     
        newObject.GetComponent<ErrorPopupManager>().SetMessage(message);
        
        newObject.transform.SetParent(contentCanvas.transform, false);
  
        
    }

To call it, simply just use ErrorPopupManager.GeneratePopup(message).

[LEGACY] Sticker Shop Implementation

THIS DOCUMENTATION IS LEGACY. PLEASE SEE THE NEW ISSUE MADE #18 - DYNAMIC SHOP SYSTEM IMPLEMENTATION

In this documentation, I will be documenting on how the stickers are created and sold in the store.

Found in the StickerStorePopulator.cs, this is how we populate the stickers in the store;

Basically, when we open the store, these things populate for the first time using our Stickers Scriptable object which will be explained later.

Once it's properly updated the store, on which stickers the player can buy (i.e. not owned stickers), the StickerStoreButton.cs script comes into place. This is explained in #5 - Points System.

    void Start()
    {
        _gameManager = GameManager.Instance;
        
        Object[] stickerObjects = UnityEngine.Resources.LoadAll("Stickers/StickersObject/"); // get all the sticker scriptable objects

        
        foreach (Object obj in stickerObjects) // iterate the objects array
        {
            
            Sticker sticker = (Sticker) obj; // cast it back to a sticker object cause we know they are stickers 


            if (!sticker.purchasable) // if its not purchasable, 
                continue; // we skip this iteration and dont show in the shop.
            
            GameObject stickerObject = Instantiate(_gameManager.stickerStorePrefab, new Vector3(0,0),
                Quaternion.identity); // create a new game object icon
            
            stickerObject.transform.SetParent(this.gameObject.transform, false); // update the parent


            bool owned = false;

            if (_gameManager.user.details.ownedStickers.Contains(sticker)) // if the player already owns the sticker
            {
                owned = true; // set owned to true so they can't buy it again.
                Debug.Log("Owned");
            }


            StickerStore stickerStore = stickerObject.GetComponent<StickerStore>(); // get the stickerstore instance 
            // from the current newly instantiated game object
            
            stickerStore.UpdateStickerStoreIcon(sticker, owned); // update the sticker and the owned status.
        }
    }
stickerStore.UpdateStickerStoreIcon(sticker, owned); // update the sticker and the owned status.

This method is called from the StickerStore.cs script to update the visuals of the icon.

    public void UpdateStickerStoreIcon(Sticker sticker, bool owned)
    {
        nameText.text = sticker.name.Replace("Sticker", "");

        this.sticker = sticker;

        this.owned = owned;
        
        if(!owned)
            priceText.text = sticker.price.ToString();
        else
        {
            priceText.text = "OWNED";

            Destroy(GetComponent<Button>());
        }

        icon.sprite = sticker.stickerSprite;
    }

This is how we can easily and dynamically create any new stickers we want.

[CreateAssetMenu(fileName = "New Sticker", menuName = "LyfOn/Sticker/New Sticker", order = 1)]
public class Sticker : ScriptableObject
{
    public Sprite stickerSprite;

    [Range(0,1000)]
    public int price;

    public bool purchasable;

}

image

You can also easily tune the settings of the new sticker then.
image

User Data Structure Implementation

In regards of how the Login Session Manager is saved #1, this is how the user's data is stored when fetched from the Firebase Database.

This is the data structure of the User class blueprint where individual user's data is stored, such as owned stickers, current quests, points, exp, and more -

using System.Collections;
using System.Collections.Generic;
using Firebase.Firestore;
using UnityEngine;
using UnityEngine.PlayerLoop;

public class User {





    public struct Details
    {
        public int points { get; set; }
        public int exp { get; set; }
        public int level { get; set; }
        
        public int maxExp { get; set; }


        public List<Sticker> ownedStickers { get; set; }
        
        public List<Quest> inProgressQuests { get; set; }
        
        
    }
    
    
    public Details details { get; set; }
    
    public string emailAddress { get; set; }
    
    public string username { get; set; }
    
    public string uuid { get; set; }


    
    public enum UpdateType
    {
        POINTS,
        EXP,
        LEVEL,
        STICKER,
        QUEST
    }

    public void UpdatePlayerDetails(int newValue, UpdateType updateType)
    {

        string updateField = "";
        
        /*
         *
         *  updateField checks which details field we are updating, whether it
         *  being points, EXP, level, etc. (makes it dynamic)
         */
        
        
        if (updateType == UpdateType.POINTS)
        {
            updateField = "points";
        }
        else if (updateType == UpdateType.QUEST)
        {
            updateField = "quests";
        }
        
        
        /*
         *
         *
         *  Connect to the firebase and update.
         */
                
        FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
        DocumentReference docRef = db.Collection("users").Document(uuid);
        
        Dictionary<string, object> update = new Dictionary<string, object>();
        
        
        
        update.Add("details", new Dictionary<string, object>()
        {
            
            {updateField, newValue}
        });
        
        docRef.SetAsync(update, SetOptions.MergeAll);
    }
    
    public void UpdatePlayerDetails(List<Quest> newValue, UpdateType updateType)
    {

        string updateField = "";
        
        /*
         *
         *  updateField checks which details field we are updating, whether it
         *  being points, EXP, level, etc. (makes it dynamic)
         */
        
        
        if (updateType == UpdateType.QUEST)
        {
            updateField = "quests";
        }
        
        
        /*
         *
         *
         *  Connect to the firebase and update.
         */
                
        FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
        DocumentReference docRef = db.Collection("users").Document(uuid);
        
        Dictionary<string, object> update = new Dictionary<string, object>();


        Dictionary<string, object> questDict = new Dictionary<string, object>();

        foreach (Quest quest in newValue)
        {
            string status = "IN_PROGRESS";

            if (quest.claimed)
            {
                status = "CLAIMED";
            }
            
            questDict.Add(quest.uid, new Dictionary<string, object>()
            {
                {"value", quest.currentValue },
                {"status", status },
                
            });
        }
        
        update.Add("details", new Dictionary<string, object>()
        {
            
            {updateField, questDict}
        });
        
        docRef.SetAsync(update, SetOptions.MergeAll);
    }

    
    public void UpdatePlayerDetails(Sticker targetSticker, UpdateType updateType) // for stickers
    {

      
        
        /*
         *
         *  updateField checks which details field we are updating, whether it
         *  being points, EXP, level, etc. (makes it dynamic)
         */
        
        
        if (updateType == UpdateType.STICKER)
        {
          
            Dictionary<string, List<object>> stickersDict = new Dictionary<string, List<object>>();

            List<object> stickers = new List<object>();
            
            Debug.Log(targetSticker.name + " TARGET");

            this.details.ownedStickers.Add(targetSticker);
        
            foreach (Sticker sticker in this.details.ownedStickers)
            {
                
                Debug.Log(sticker.name);
            
                if(sticker.price != 0) // dont add the free stickers to the database because
                    // it might be subjected to changes in the future (free sticker rotations basically)
                    stickers.Add(sticker.name);
            }
        
       
            /*
             *
             *  Below is for the structure of the firebase database.
             *
             *  Github Documentation Link:
             */
            stickersDict.Add("stickers", stickers);

            
        /*
        *
        *
        *  Connect to the firebase and update.
        */
                
            FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
            DocumentReference docRef = db.Collection("users").Document(uuid);
        
            Dictionary<string, object> update = new Dictionary<string, object>();



            update.Add("details", stickersDict);



            
            
            docRef.SetAsync(update, SetOptions.MergeAll);
        }
        
        
       
    }




    
    
}

We are also able to access this class through the GameManager Singleton and run POST methods to Firebase using UpdatePlayerDetails method and parsing through the UpdateType enum to update the correct field in Firebase.

public User user { get; set; }

This is the property used in the Singleton to get the current logged in Instance of the User Data Structure.

Custom Scene Manager

The loading scene is loaded addictively to the current scene, in order to mask out the new scene that needs to be loaded.

The new scene is loaded asynchronously, addictively to the loading scene so it can load up in the background. Once the new scene is loaded finish, the loading scene then unloads and only shows the new scene.

async LoadScene method can be called anywhere as it is a static method. It'll be called on a separate thread so the mobile application won't freeze while trying to load a new scene.

Usage: CustomSceneManager.LoadScene(string scenePath)

   public static async void LoadScene(string scenePath)
    {


        Time.timeScale = 1;



        string currentPath = SceneManager.GetActiveScene().path;
        
        AsyncOperation loadingScene = SceneManager.LoadSceneAsync("Resources/Scenes/LoadingScreen", LoadSceneMode.Additive);
        // load a loading scene async, added on the current scene (ADDICTIVE load)



        
       
        
       
        AsyncOperation scene = SceneManager.LoadSceneAsync(scenePath, LoadSceneMode.Additive); 
        // load the target scene async, added on the current scene (ADDICTIVE load)

        
        scene.allowSceneActivation = false;

        while (scene.progress < 0.9f || loadingScene.progress < 0.9f) // if the scene has not loaded finish
        {
            
            await Task.Delay(100); // delay to make the loading more believable.
           


            
            
   
            
            
        }
        
        
        LoadingBehavior loadingBehavior = FindObjectOfType<LoadingBehavior>();

        
        //Debug.Log(loadingBehavior + " loading behavior");
      
        //loadingBehavior.previousScenePath = currentPath;

        GameManager.Instance.previousScenePath = currentPath;
   


      

        await Task.Delay(2000);
        
        //SceneManager.UnloadSceneAsync("Resources/Scenes/LoadingScreen");

        
        GameManager.Instance.animateLoadingScreen = true;
        
        scene.allowSceneActivation = true;

   
  
    }

Web API - Retrieving/Changing User Data

โš ๏ธ If you're looking at the Web API series for the first time, please look at #10 - How to sign into a user's account.

In this documentation, I'll be documenting how other AR games can hook onto my user web API to change/obtain information.

GET API - gets all users' information
https://firestore.googleapis.com/v1/projects/lyf-on/databases/(default)/documents/users

GET API - gets a specific user's information
https://firestore.googleapis.com/v1/projects/lyf-on/databases/(default)/documents/users/USER_ID

(example: https://firestore.googleapis.com/v1/projects/lyf-on/databases/(default)/documents/users/nLt9Amn3jpfPbYINxFbdb3avRqw1)

Currently, I have disabled changing user data as I do not want the firestore data structure to be accidentally messed up.

User Profile Hub System

In this documentation, I'll show how the user profile in the hub is updated.

Through the UserProfileManager.cs script, we update whenever the user switches scene to the Hub. We update the visual appearance of the User Profile, such as the avatar, name of player, level of player, etc.

   void Start()
    {

        // these are to update the visual of the user profile, e.g., name of player, level of player, etc.
        user = GameManager.Instance.user;
        
        gameManager = GameManager.Instance;
        
        usernameText.text = user.username;
        levelText.text = user.details.level.ToString();

        int maxExp = user.details.level * expMultiplier;

        expSlider.maxValue = maxExp;
        expSlider.value = user.details.exp;


        expText.text = expSlider.value + "/" + expSlider.maxValue;


        coinsText.text = user.details.points.ToString();


        UpdateAvatar();

    }

    public void UpdateAvatar() // to update the user's avatar visually
    {
       
   
        if (user.details.currentAvatar != null)
        {
            avatarImage.sprite = user.details.currentAvatar.sprite;
        }
    }

[IMPORTANT] Github Labels Documentation

In this documentation, I will document how my issues are being documented.

image
Implementation Label is for features that are going to be or has been implemented in the project.


image
image
Completed & Not Completed Label is an addon label for Implementation that determines if an implementation has been completed or not.

In the projects tab, you can find a detailed progress chart.
https://github.com/Aster0/Lyf-On-AR/projects/1
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.