︎︎︎ Castles  



Tags :
︎ Game, Mobile App

Status :
︎In development

Role :
Director, Game Programmer, Game Designer


︎ Collaboration with Andrew Kovacs


More artists working on the game 

︎ Corie Yaguchi, Concept -Texture Artist 
︎ Zanem Mechem , 3D Generalist
︎ Andrew Chittenden, Character Animator 
︎ Andres Gandara, Animator
︎ Yu Han , 3D Artist


︎ “team work makes the dream work”

︎ 2020 08  

︎ Project Breakdown  

“Castles” is a project started in January 2020 and to be released in 2022.
“Castles” is a mobile game application that aims to include : 

. authentication 
. analytics 
. monetization 
. data collection 
. scalability 

. character customization
. inventory 
. procedural environments 
. in-game cinematic shorts 
. chat - duel multiplayer 

. augmented reality features 
. in-game store
. in-game social platform



︎ Procedural Environment  

. still in the early development, one of the key features is the procedural environment that the players inhabit

01. it starts with a tridimensional grids of Transform slots 

02. successively, a folder filled up by assets created by the team of artists populates the available positions according certain parameters

03. the circulation is created right after connecting the assets with each other

04. a number of objects to be collected are instantiated in the environment

︎ see code scrolling below

︎︎︎Unity screenshots

︎

. Review / comunication tool for Castles






︎ Gameplay 

. the game takes advantage of Portrait and Landscape mode on smartphone.

︎ Potrait mode 

zommed out explore mode.

︎ Landscape mode 

tilted TPV game mode.
︎︎︎ Unity screenshots
︎ Shared Code Sample :
Spawning the Castle

. this script collaborates takes care of the level management, the instantiation of the objects from the resources folder and the pooling/state of the objects already been instantiated in the scene. 

. this manager script allows to create a tridimensional grid of positons which will be successively filled by prebuilt assets. 

. the steps are very similar to the ones listed above in the overview description of the project.
Before is created a “core” that handles a sequence of “origins”. In a second moment one grid is instantiated on every origin position. 

. finally all of the available positions are filled asyncronously through batches of assets that will be defining how much the player’s environment will be growing.

. in progress there is also the possibility to track the position of the assets around the boundaries of the castle, to experiment with more in-game interactive behaviors. 

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using UnityEngine;

public class CastleSpawner : MonoBehaviour
{
    [Tooltip("The general spacing between the tridimensional grid")]
    public float gridSpacingOffset = 1f;
    [Tooltip("How many steps the core keeps creating grid or so called floors")]
    public int coreStep = 100;
    [Tooltip("The spacing")]
    public int coreSpacingOffset = 1;
    [Tooltip("The size of every grid floor in X direction")]
    public int gridX;
    [Tooltip("The size of every grid floor in Y direction")]
    public int gridY;
    [Tooltip("The minimum  range which the floors are distantiated")]
    public int rangeMin = 5;
    [Tooltip("The maximum range which the floors are distantiated")]
    public int rangeMax = 10;
    [Tooltip("The list of cores elements placeholder")]
    public List<GameObject> cubeCores;
    [Tooltip("The list of grid elements placeholder")]
    public List<GameObject> cubeGrids;
    [Tooltip("The received list of prefabs that needs to be instantiated")]
    public List<GameObject> prefabs;
    [Tooltip("The received list of prefabs that needs to be instantiated")]
    public List<GameObject> Instantiatedprefabs;
    [Tooltip("The scale factor for the instantiated assets")]
    public float scale = 1f;
    [Tooltip("The max amount of instantiated prefab")]
    public int maxAssetsAmount = 50;
    [Tooltip("The max amount of instantiated prefab repetitions")]
    public float assetsRepetition = 2f;


    GameObject[] assetPrefab;
    GameObject parentCores;
    GameObject parentGrids;
    GameObject parentPrefabs;
    GameObject parentCastle;
    GameObject parentActive;

    private Vector2 minMaxColumn, minMaxRow;

    private GridObject[] gridObjects = null;
    public struct GridObject
    {
        public Transform Transform;
        public float RowNo;
        public float ColumnNo;
    }

    [Tooltip("Starting amount of objects (level 1)"), SerializeField]
    private int startingAmountOfObjects = 1;

    [Tooltip("Increasing amount of objects after every level"), SerializeField]
    private int objectIncrementationPerLevel = 3;

    private int currentSpawningObjNo = 0;

    private void Awake()
    {
        // update the amount of active prefabs based on the level index 
        LevelManager.OnLevelChanged += UpdateObjectActivation;
    }
    private void OnDestroy()
    {
        LevelManager.OnLevelChanged -= UpdateObjectActivation;
    }

    void Start()
    {
        // here set some parent game objects to have some order 
        parentCores = new GameObject("parentCores");
        parentGrids = new GameObject("parentGrids");
        parentActive = new GameObject("parentActive");
        // start creation coroutine 
        StartCoroutine(coreSpawn(coreStep, cubeCores, rangeMin, rangeMax, parentCores));
        StartCoroutine(gridSpawn(cubeCores, cubeGrids, gridX, gridY, parentGrids, "Grid01"));
        StartCoroutine(ShuffleCastle(cubeGrids, scale, maxAssetsAmount, assetsRepetition, parentActive, 1, "Castle01", "Grid01"));
        // optimization - info 
        // since you loop through all the assets in the folder with a random sequence sometimes it could happen that 
        // since it's random in 20 iterations you get out 17 objects [for examples]
        // because it's looping 20 times but for 3 random times it picked some repeated random choice and as the script wants 
        // it didn't instantiate. 
    }
    // just to visually keeping track of the positions 
    void OnDrawGizmos()
    {
        if (parentActive.transform.childCount > 0)
        {
            DebugLines(parentActive, 5f);
        }
    }
    //phase 01 create cores - working in height first 
    IEnumerator coreSpawn(int coreSteps, List<GameObject> itemsToAdd, int rangeMin, int rangeMax, GameObject parent)
    {
        GameObject placeholder = GameObject.CreatePrimitive(PrimitiveType.Cube);
        int rangeRandom = Random.Range(rangeMin, rangeMax);
        for (int y = 0; y < coreSteps; y += rangeRandom)
        {
            GameObject cube = Instantiate(placeholder, new Vector3(0, y, 0), Quaternion.identity);
            rangeRandom = Random.Range(rangeMin, rangeMax);
            itemsToAdd.Add(cube);
            cube.transform.parent = parent.transform;
            cube.GetComponent<BoxCollider>().enabled = false;
        }
        Destroy(placeholder);
        yield return null;
    }
    // phase 2 create grids in every level that previously has been created by the cores  
    IEnumerator gridSpawn(List<GameObject> itemsToPos, List<GameObject> itemsToAdd, float xDir, float zDir, GameObject parent, string tag)
    {
        // here you get the maximum and minimum in x and z direction 
        // this will be dictating thhe rows and columns to move for the pooling 
        // vector 2 is for maximum and minimum 
        minMaxColumn = new Vector2(0, xDir * gridSpacingOffset);
        minMaxRow = new Vector2(0, zDir * gridSpacingOffset);

        GameObject placeholder = GameObject.CreatePrimitive(PrimitiveType.Cube);
        for (var i = 0; i < itemsToPos.Count; i++)
        {
            for (int x = 0; x < xDir; x++)
            {
                for (int z = 0; z < zDir; z++)
                {
                    Vector3 origin = new Vector3(x * gridSpacingOffset, itemsToPos[i].transform.position.y, z * gridSpacingOffset);// + newOrigin;
                    GameObject cube = Instantiate(placeholder, origin, Quaternion.identity);
                    itemsToAdd.Add(cube);
                    cube.transform.parent = parent.transform;
                    parent.tag = tag;
                    //disable collider and mesh renderer 
                    //cube.GetComponent<MeshRenderer>().enabled = false;
                    cube.GetComponent<BoxCollider>().enabled = false;
                }
            }
        }
        Destroy(placeholder);
        yield return null;
    }

    //phase 3 - shuffle on available slots 
    IEnumerator ShuffleCastle(
               List<GameObject> availableSlotsToLocate,
               float scaleFactor,
               int maxObjectsToInstantiate,
               float maxObjectSpawnTimes,
               GameObject parentActive,
               int castleNumber,
               string parentTag,
               string gridTag)
    {


        yield return new WaitForSeconds(1);
        // get the gameobject where is instantiated the prefabs to shuffle 
        GameObject parentObj = GameObject.FindGameObjectWithTag(parentTag);
        // get the gameobject where is instantiated the grid 
        GameObject gridObj = GameObject.FindGameObjectWithTag(gridTag);
        // create a dictionary to comare object and names 
        // this is only because you want to instantiate 1 object from the list once and 
        // not repeating - is a check every loop 
        Dictionary<GameObject, int> objectCountList = new Dictionary<GameObject, int>();
        // get the array of turned off prefabs transforms under the parent castle 
        List<GameObject> children = parentObj.GetChildren();
        List<GameObject> spawnPoints = gridObj.GetChildren();

        List<GridObject> instGridAssets = new List<GridObject>();

        // do not instantiate more than maxObjectsToInstantiate
        for (int i = 0; i < maxObjectsToInstantiate; i++)
        {
            // get a temporary reference that is a random element every loop 
            GameObject randomObj = children[currentSpawningObjNo];
            currentSpawningObjNo++;

            if (currentSpawningObjNo >= children.Count)
                currentSpawningObjNo = 0;

            // get a temporary reference that is a random position from the sapwn grid 
            // here's the thing - we check if an asset has been instantiated twice 
            // but we also need to check if this asset is occupying eventually the same position twice 

            GameObject randomSpawn = spawnPoints[UnityEngine.Random.Range(0, spawnPoints.Count)];

            // if the object to instantiate has a name that is the the list 
            // do not instantiated it again or viceversa 
            if (!objectCountList.ContainsKey(randomObj.gameObject))
            {
                objectCountList.Add(randomObj, 1);
            }
            else
            {
                objectCountList[randomObj]++;
                // Are you already hitting your limit? No, then you are allowed to spawn.
            }
            if (objectCountList[randomObj] <= maxObjectSpawnTimes)
            {
                // yield return new WaitForSeconds(0.2f); // cool timing effect to merge with scale
                // in this way you track the instantied object 
                // differently from only "instantiate" that do not track the new instance 
                GameObject assetInstantiated = Instantiate(randomObj, randomSpawn.transform.position, Quaternion.identity);        //Vector3.zero
                assetInstantiated.transform.parent = parentActive.transform;

                assetInstantiated.transform.localScale = new Vector3(
                    assetInstantiated.transform.localScale.x * scaleFactor,
                    assetInstantiated.transform.localScale.y * scaleFactor,
                    assetInstantiated.transform.localScale.z * scaleFactor);

                assetInstantiated.SetActive(false);

                // here it's tracked the tranform of every asset plus is tracked also the x and z positions 
                instGridAssets.Add(new GridObject()
                {
                    Transform = assetInstantiated.transform,
                    RowNo = randomSpawn.transform.position.z,
                    ColumnNo = randomSpawn.transform.position.x
                });

            }

        }

        gridObjects = instGridAssets.ToArray();
        yield return null;

        LevelManager.NextLevel();
    }
}

public static class ExtensionMethods
{

    public static List<GameObject> GetChildren(this GameObject go)
    {
        List<GameObject> children = new List<GameObject>();
        foreach (Transform tran in go.transform)
        {
            children.Add(tran.gameObject);
        }
        return children;
    }
}
︎ Design through Pooling

. another pivotal mechanic is the pooling system that shifts the location of environmental asset based on the player position.

01. the player approaches the boundary.

02. before to approach the last transform slot withing the castle boundary in a given direction the swap occurs.

03. in the portrait mode the assets are set active for keep “desiging through motion”.

︎ Shared Code Sample :
Isolate for shifting

. the method isolates a part of the castle for being moved somewhere else.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void MoveColumn(int columnAmount, bool moveToLeft)
    {
        // Detect with the Player on what Row + Column number they're walking now. If the Column Number of the player is larger/smaller than the minMaxColumn, then execute MoveColumn.

        List<GridObject> filteredAssets = new List<GridObject>(gridObjects);
        float fromColumnCoordinate = moveToLeft ? minMaxColumn.y - columnAmount : columnAmount;

        if (moveToLeft)
        {
            for (int i = 0; i < gridObjects.Length; i++)
                if (gridObjects[i].ColumnNo < fromColumnCoordinate)
                {
                    filteredAssets.Remove(gridObjects[i]);
                    gridObjects[i].Transform.gameObject.SetActive(false); 
                }
        }
        else
        {
            for (int i = 0; i < gridObjects.Length; i++)
                if (gridObjects[i].ColumnNo > fromColumnCoordinate)
                    filteredAssets.Remove(gridObjects[i]);
        }
    }
︎ Gameplay Update 

. currently focusing on the distribution of the asset and the game overall look 

︎︎︎ 09.01.2020