Reviewing code

As you can see from the previous setup exercise and through testing, the Inventory service we imported already has the database wrapper attached. Let's review some of the script changes we imported and also get into details of the InventoryService script:

  1. Double-click on the CatchSceneController located in the Assets/FoodyGo/Scripts/Controllers folder in the Project window to open the script in the editor of your choice.
  2. The only thing that has thus far changed in the CatchSceneController is a new Start method calling our Inventory service. Perform the following reviewing method:
            void Start() 
            { 
                 var monster = InventoryService.Instance.CreateMonster(); 
                 print(monster); 
            }   
    
  3. Inside the Start method, the InventoryService is being called as a singleton using the Instance property. Then, the CreateMonster method is called to generate a new monster object. Finally, the monster object is printed to the Console window with the print method.
  4. The code in the Start method is essentially just temporary test code we will move later. However, appreciate the ease of access the singleton pattern is providing us with.
  5. We have one more step before we look at the InventoryService. Open the Monster script located in the Assets/FoodyGo/Scripts/Database folder. If you recall, we previously used the Monster class to track our spawn's location within the MonsterService. Instead, we decided to simplify the Monster class to use just for inventory/database persistence and promote our older class to a new MonsterSpawnLocation. The MonsterService script was also updated to use the new MonsterSpawnLocation.
  6. We will take a closer look at the new Monster object that we will use to persist to the inventory/database:
    public class Monster 
    { 
        [PrimaryKey, AutoIncrement] 
        public int Id { get; set; } 
        public string Name { get; set; } 
        public int Level { get; set; } 
        public int Power { get; set; } 
        public string Skills { get; set; } 
        public double CaughtTimestamp { get; set; } 
        public override string ToString() 
        { 
            return string.Format("Monster: {0}, Level: {1}, Power:{2}, Skills:{3}", 
                    Name, Level, Power, Skills); 
        } 
    } 
    
  7. The first thing that is obvious is the use of properties to define the Monster attributes, which is a divergence from Unity and the more traditional C#. Next, notice that the top Id property has a couple of attributes and PrimaryKey and AutoIncrement attached. If you are familiar with relational databases, you will understand this pattern right away.

    For those less familiar, all records/objects in our database need a unique identifier called a primary key. That identifier, in this case, called Id, allows us to quickly locate an object later. The attribute AutoIncrement allows us to know that the Id property, an integer, will be automatically incremented when a new object is created. This alleviates us from managing the Id of the objects ourselves and means the Id property will be automatically set by the database.

  8. We won't worry too much about the other properties right now, but instead, just take notice of the overridden ToString method. Overriding ToString allows us to customize the output of the object and is useful for debugging. Instead of having to inspect all the properties and print them to the console, we can instead simplify this to print(monster), as we saw in the CatchSceneController.Start method earlier.
  9. With the background out of the way, open up the InventoryService script located in the Assets/FoodyGo/Scripts/Services folder. As you can see, this class has a number of conditional sections in the Start method in order to account for various deployment platforms. We won't review that code, but we will take a look at the last few lines of the Start method:
    _connection = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
    Debug.Log("Final PATH: " + dbPath);
    if (newDatabase)
     {
     CreateDB();
     }else
     {
     CheckForUpgrade();
     }
  10. The first line creates a new SQLiteConnection, which creates a connection to the SQLite database. The connection is set by passing the database path (dbPath) and options. The options provided to the connection are requesting read/write privileges and creating the database, if necessary. Therefore, if no existing database is found at the dbPath, then a new empty database will be created. The next line just writes the database path to the Console.

    Note

    Debug.Log is equivalent to the print method. We previously used print for simplicity and will continue to do so wherever appropriate.

  11. After the connection is open, we check whether a new database was created by checking the newDatabase Boolean variable. The newDatabase variable was previously set above the section of code by determining whether an existing database was already present. If newDatabase is true, then we call CreateDB, otherwise, we call CheckForUpgrade.
  12. The CreateDB method does not create the physical database file on the device. That is instead done in the connection code we looked at earlier. The CreateDB method instantiates the object tables or schema in the database as follows:
    private void CreateDB() 
    { 
        Debug.Log("Creating database..."); 
        var minfo = _connection.GetTableInfo("Monster"); 
        if(minfo.Count>0) _connection.DropTable<Monster>();     
        _connection.CreateTable<Monster>(); 
        Debug.Log("Monster table created."); 
        var vinfo = _connection.GetTableInfo("DatabaseVersion"); 
        if(vinfo.Count>0) _connection.DropTable<DatabaseVersion>(); 
        _connection.CreateTable<DatabaseVersion>(); 
        Debug.Log("DatabaseVersion table created."); 
     
        _connection.Insert(new DatabaseVersion 
        { 
              Version = DatabaseVersion 
          }); 
          Debug.Log("Database version updated to " + DatabaseVersion); 
          Debug.Log("Database created."); 
       } 
     
    
  13. Don't be put off by the number of Debug.Log statements in this method; it is best to just think of them as helpful comments. After the install logging, we first determine whether the Monster table has already been created using the GetTableInfo method on the connection. GetTableInfo returns the columns/properties of the table; if no columns or properties have been set, minfo will have a count of 0. If the table is present, however, we will delete or drop it and create a new table using our current Monster properties.

    We follow the same pattern for the next table DatabaseVersion. If GetTableInfo returns vinfo.Count > 0, then delete the table, otherwise, just continue. You will see, as we add more objects to the InventoryService, we will need to create the new tables in the same manner.

    The SQLite4Unity3d wrapper provides us with an Object Relational Mapping (ORM) framework that allows us to map objects to relational database tables. This is why we will use the term object and table interchangeably at times. The following diagram shows how this mapping typically works:

    Reviewing code

    An ORM example of monster to database

  14. After the object tables are created, we create a new DatabaseVersion object and store it in the database using the Insert method on the _connection. The DatabaseVersion object is very simple and only has one property, called Version. We use this object/table to track the version of the database.
  15. Remember that, if we don't need to create a new database, then we will check for an upgrade with the CheckForUpgrade method, as follows:
    private void CheckForUpgrade() 
    { 
        try 
        { 
            var version = GetDatabaseVersion(); 
            if (CheckDBVersion(version)) 
            { 
                //newer version upgrade required 
                Debug.LogFormat("Database current version {0} - upgrading to {1}", version, DatabaseVersion); 
                UpgradeDB(); 
                Debug.Log("Database upgraded"); 
             } 
         } 
         catch (Exception ex) 
         { 
             Debug.LogError("Failed to upgrade database, running CreateDB instead"); 
             Debug.LogError("Error - " + ex.Message); 
             CreateDB(); 
         } 
      } 
    
  16. The CheckForUpgrade method first gets the current database file version and then compares it to the version required by the code in the CheckDBVersion method. If the code requires a newer database version, set by the DatabaseVersion setting on the InventoryService, then it upgrades the database. If the database doesn't require an upgrade, then the game uses the current database. However, if there is an error in the version check or some other error happens, then the code will assume something is wrong with the existing database and just create a new version. We will spend more time later on doing an actual database upgrade.
  17. Finally, we will review the CreateMonster method called by the CatchSceneController:
    public Monster CreateMonster() 
    { 
        var m = new Monster 
        { 
            Name = "Chef Child", 
            Level = 2, 
            Power = 10, 
            Skills = "French" 
        }; 
        _connection.Insert(m); 
        return m; 
    } 
    
  18. The CreateMonster method currently just creates a hardcoded Monster object and inserts it into the database using the _connection.Insert method. It then returns the new object to the calling code. If you have experience working with relational databases and writing SQL code, hopefully, you can appreciate the simplicity of the Insert here. We will update the CreateMonster and other operation methods, in the next section of this chapter.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.82.79