// Create new location game object
        private GameObject CreateLocationGameObject(int terrain, out DFLocation locationOut)
        {
            locationOut = new DFLocation();

            // Terrain must have a location
            DaggerfallTerrain dfTerrain = terrainArray[terrain].terrainObject.GetComponent <DaggerfallTerrain>();

            if (!dfTerrain.MapData.hasLocation)
            {
                return(null);
            }

            // Get location data
            locationOut = dfUnity.ContentReader.MapFileReader.GetLocation(dfTerrain.MapData.mapRegionIndex, dfTerrain.MapData.mapLocationIndex);
            if (!locationOut.Loaded)
            {
                return(null);
            }

            // Spawn parent game object for new location
            float      height         = dfTerrain.MapData.averageHeight * TerrainScale;
            GameObject locationObject = new GameObject(string.Format("DaggerfallLocation [Region={0}, Name={1}]", locationOut.RegionName, locationOut.Name));

            locationObject.transform.parent   = this.transform;
            locationObject.hideFlags          = HideFlags.HideAndDontSave;
            locationObject.transform.position = terrainArray[terrain].terrainObject.transform.position + new Vector3(0, height, 0);
            DaggerfallLocation dfLocation = locationObject.AddComponent <DaggerfallLocation>() as DaggerfallLocation;

            dfLocation.SetLocation(locationOut, false);

            return(locationObject);
        }
Exemple #2
0
 protected virtual void RaiseOnCreateLocationGameObjectEvent(DaggerfallLocation dfLocation)
 {
     if (OnCreateLocationGameObject != null)
     {
         OnCreateLocationGameObject(dfLocation);
     }
 }
Exemple #3
0
        // Create new location game object
        private GameObject CreateLocationGameObject(int terrain, out DFLocation locationOut)
        {
            locationOut = new DFLocation();

            // Terrain must have a location
            DaggerfallTerrain dfTerrain = terrainArray[terrain].terrainObject.GetComponent <DaggerfallTerrain>();

            if (!dfTerrain.MapData.hasLocation)
            {
                return(null);
            }

            // Get location data
            locationOut = dfUnity.ContentReader.MapFileReader.GetLocation(dfTerrain.MapData.mapRegionIndex, dfTerrain.MapData.mapLocationIndex);
            if (!locationOut.Loaded)
            {
                return(null);
            }

            // Get sampled position of height as more accurate than scaled average - thanks Nystul!
            Terrain terrainInstance = dfTerrain.gameObject.GetComponent <Terrain>();
            float   scale           = terrainInstance.terrainData.heightmapScale.x;
            float   xSamplePos      = (TerrainHelper.terrainTileDim - 1) / 2.0f; // get center terrain tile of block
            float   ySamplePos      = (TerrainHelper.terrainTileDim - 1) / 2.0f; // get center terrain tile of block
            Vector3 pos             = new Vector3(xSamplePos * scale, 0, ySamplePos * scale);
            float   height          = terrainInstance.SampleHeight(pos + terrainArray[terrain].terrainObject.transform.position);

            // Spawn parent game object for new location
            //float height = dfTerrain.MapData.averageHeight * TerrainScale;
            GameObject locationObject = new GameObject(string.Format("DaggerfallLocation [Region={0}, Name={1}]", locationOut.RegionName, locationOut.Name));

            locationObject.transform.parent = this.transform;
            //locationObject.hideFlags = HideFlags.HideAndDontSave;
            locationObject.transform.position = terrainArray[terrain].terrainObject.transform.position + new Vector3(0, height, 0);
            DaggerfallLocation dfLocation = locationObject.AddComponent <DaggerfallLocation>() as DaggerfallLocation;

            dfLocation.SetLocation(locationOut, false);

            // Raise event
            RaiseOnCreateLocationGameObjectEvent(dfLocation);

            return(locationObject);
        }
        // Sets player to ground level near a location
        // Will spawn at a random edge facing location
        // Can use start markers if present
        private void PositionPlayerToLocation(
            int mapPixelX,
            int mapPixelY,
            DaggerfallLocation dfLocation,
            Vector3 origin,
            int mapWidth,
            int mapHeight,
            bool useNearestStartMarker = false)
        {
            // Randomly pick one side of location to spawn
            // A better implementation would base on previous coordinates
            // e.g. if new location is east of old location then player starts at west edge of new location
            UnityEngine.Random.seed = System.DateTime.Now.Millisecond;
            int side = UnityEngine.Random.Range(0, 4);

            // Get half width and height
            float   halfWidth  = (float)mapWidth * 0.5f * RMBLayout.RMBSide;
            float   halfHeight = (float)mapHeight * 0.5f * RMBLayout.RMBSide;
            Vector3 centre     = origin + new Vector3(halfWidth, 0, halfHeight);

            // Sometimes buildings are right up to edge of block
            // Extra distance places player a little bit outside location area
            float extraDistance = RMBLayout.RMBSide * 0.1f;

            // Start player in position
            // Will also SendMessage to receiver called SetFacing on player gameobject with forward vector
            // You should implement this if using your own mouselook component
            Vector3 newPlayerPosition = centre;

            switch (side)
            {
            case 0:             // North
                newPlayerPosition += new Vector3(0, 0, (halfHeight + extraDistance));
                LocalPlayerGPS.SendMessage("SetFacing", Vector3.back, SendMessageOptions.DontRequireReceiver);
                //Debug.Log("Spawned player north.");
                break;

            case 1:             // South
                newPlayerPosition += new Vector3(0, 0, -(halfHeight + extraDistance));
                LocalPlayerGPS.SendMessage("SetFacing", Vector3.forward, SendMessageOptions.DontRequireReceiver);
                //Debug.Log("Spawned player south.");
                break;

            case 2:             // East
                newPlayerPosition += new Vector3((halfWidth + extraDistance), 0, 0);
                LocalPlayerGPS.SendMessage("SetFacing", Vector3.left, SendMessageOptions.DontRequireReceiver);
                //Debug.Log("Spawned player east.");
                break;

            case 3:             // West
                newPlayerPosition += new Vector3(-(halfWidth + extraDistance), 0, 0);
                LocalPlayerGPS.SendMessage("SetFacing", Vector3.right, SendMessageOptions.DontRequireReceiver);
                //Debug.Log("Spawned player west.");
                break;
            }

            // Adjust to nearest start marker if requested
            if (useNearestStartMarker)
            {
                float        smallestDistance = float.MaxValue;
                int          closestMarker    = -1;
                GameObject[] startMarkers     = dfLocation.StartMarkers;
                for (int i = 0; i < startMarkers.Length; i++)
                {
                    float distance = Vector3.Distance(newPlayerPosition, startMarkers[i].transform.position);
                    if (distance < smallestDistance)
                    {
                        smallestDistance = distance;
                        closestMarker    = i;
                    }
                }
                if (closestMarker != -1)
                {
                    PositionPlayerToTerrain(mapPixelX, mapPixelY, startMarkers[closestMarker].transform.position);
                    return;
                }
            }

            // Just position to outside location
            PositionPlayerToTerrain(mapPixelX, mapPixelY, newPlayerPosition);
        }
        // Update terrain data
        // Spreads loading across several frames to reduce gameplay stalls
        // This can also be done using true multi-threading, but at much greater
        // complexity for only minor visible gains.
        // Only yields after initial init complete
        private IEnumerator UpdateTerrains()
        {
            // First stage updates terrain heightmaps
            for (int i = 0; i < terrainArray.Length; i++)
            {
                if (terrainArray[i].active && terrainArray[i].updateHeights)
                {
                    UpdateTerrainHeights(terrainArray[i]);
                    terrainArray[i].updateHeights = false;
                    if (!init)
                    {
                        yield return(new WaitForEndOfFrame());
                    }
                }
            }

            // Wait for physics update when streaming
            if (!init)
            {
                yield return(new WaitForFixedUpdate());
            }

            // Second stage updates terrain nature
            for (int i = 0; i < terrainArray.Length; i++)
            {
                if (terrainArray[i].active && terrainArray[i].updateNature)
                {
                    UpdateTerrainNature(terrainArray[i]);
                    terrainArray[i].updateNature = false;
                    if (!init)
                    {
                        yield return(new WaitForEndOfFrame());
                    }
                }
            }

            // Get key for central player terrain
            int playerKey = TerrainHelper.MakeTerrainKey(MapPixelX, MapPixelY);

            // Third stage updates location if present
            // Vast majority of terrains will not have a location
            // Locations are not optimised as yet and are quite heavy on drawcalls
            for (int i = 0; i < terrainArray.Length; i++)
            {
                // Get key for this terrain
                int key = TerrainHelper.MakeTerrainKey(terrainArray[i].mapPixelX, terrainArray[i].mapPixelY);

                if (terrainArray[i].active && terrainArray[i].hasLocation)
                {
                    // Create location if not present
                    if (!locationDict.ContainsKey(key))
                    {
                        // Create location object
                        DFLocation location;
                        GameObject locationObject = CreateLocationGameObject(i, out location);
                        if (!locationObject)
                        {
                            continue;
                        }

                        // Add location object to dictionary
                        locationDict.Add(key, locationObject);

                        // Create location beacon
                        // This is parented to location and shares its lifetime
                        if (AddLocationBeacon)
                        {
                            const float beaconHeight   = 900f;
                            const float beaconOffset   = (MapsFile.WorldMapTerrainDim * MeshReader.GlobalScale) / 2f;
                            GameObject  locationMarker = (GameObject)GameObject.Instantiate(Resources.Load <GameObject>("LocationBeacon"));
                            locationMarker.hideFlags               = HideFlags.HideAndDontSave;
                            locationMarker.transform.parent        = locationObject.transform;
                            locationMarker.transform.localPosition = new Vector3(beaconOffset, beaconHeight, beaconOffset);
                        }

                        // Add one nature batch for entire location
                        // This is parented to location and shares its lifetime
                        GameObject natureBatchObject = new GameObject("NatureBatch");
                        natureBatchObject.hideFlags               = HideFlags.HideAndDontSave;
                        natureBatchObject.transform.parent        = locationObject.transform;
                        natureBatchObject.transform.localPosition = Vector3.zero;
                        DaggerfallBillboardBatch natureBatch = natureBatchObject.AddComponent <DaggerfallBillboardBatch>();
                        int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue);
                        natureBatch.SetMaterial(natureArchive);

                        // RMB blocks are laid out in centre of terrain to align with ground
                        int     width   = location.Exterior.ExteriorData.Width;
                        int     height  = location.Exterior.ExteriorData.Height;
                        float   offsetX = ((8 * RMBLayout.RMBSide) - (width * RMBLayout.RMBSide)) / 2;
                        float   offsetZ = ((8 * RMBLayout.RMBSide) - (height * RMBLayout.RMBSide)) / 2;
                        Vector3 origin  = new Vector3(offsetX, 2.0f * MeshReader.GlobalScale, offsetZ);

                        // Perform layout and yield after each block is placed
                        DaggerfallLocation dfLocation = locationObject.GetComponent <DaggerfallLocation>();
                        for (int y = 0; y < height; y++)
                        {
                            for (int x = 0; x < width; x++)
                            {
                                // Set origin for billboard batch add
                                // This causes next additions to be offset by this position
                                Vector3 blockOrigin = origin + new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide));
                                natureBatch.origin = blockOrigin;

                                // Add block and yield
                                string     blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y));
                                GameObject go        = RMBLayout.CreateGameObject(blockName, true, natureBatch);
                                go.hideFlags               = HideFlags.HideAndDontSave;
                                go.transform.parent        = locationObject.transform;
                                go.transform.localPosition = blockOrigin;
                                dfLocation.ApplyClimateSettings();
                                if (!init)
                                {
                                    yield return(new WaitForEndOfFrame());
                                }
                            }
                        }

                        // If this is the player terrain we may need to reposition player
                        if (playerKey == key && repositionPlayer)
                        {
                            // Position to location and use start marker for large cities
                            bool useStartMarker = (dfLocation.Summary.LocationType == DFRegion.LocationTypes.TownCity);
                            PositionPlayerToLocation(MapPixelX, MapPixelY, dfLocation, origin, width, height, useStartMarker);
                            repositionPlayer = false;
                        }

                        // Apply nature batch
                        natureBatch.Apply();
                    }
                }
                else if (terrainArray[i].active)
                {
                    if (playerKey == key && repositionPlayer)
                    {
                        PositionPlayerToTerrain(MapPixelX, MapPixelY, Vector3.zero);
                        repositionPlayer = false;
                    }
                }
            }

            // If this is an init we can use the load time to unload unused assets
            // Keeps memory usage much lower over time
            if (init)
            {
                Resources.UnloadUnusedAssets();
            }

            // Finish by collecting stale data and setting neighbours
            CollectTerrains();
            CollectLocations();
            UpdateNeighbours();
        }
        // Sets player to ground level near a location
        // Will spawn at a random edge facing location
        // Can use start markers if present
        private void PositionPlayerToLocation(
            int mapPixelX,
            int mapPixelY,
            DaggerfallLocation dfLocation,
            Vector3 origin,
            int mapWidth,
            int mapHeight,
            bool useNearestStartMarker = false)
        {
            // Randomly pick one side of location to spawn
            // A better implementation would base on previous coordinates
            // e.g. if new location is east of old location then player starts at west edge of new location
            UnityEngine.Random.seed = System.DateTime.Now.Millisecond;
            int side = UnityEngine.Random.Range(0, 4);

            // Get half width and height
            float halfWidth = (float)mapWidth * 0.5f * RMBLayout.RMBSide;
            float halfHeight = (float)mapHeight * 0.5f * RMBLayout.RMBSide;
            Vector3 centre = origin + new Vector3(halfWidth, 0, halfHeight);

            // Sometimes buildings are right up to edge of block
            // Extra distance places player a little bit outside location area
            float extraDistance = RMBLayout.RMBSide * 0.1f;

            // Start player in position
            Vector3 newPlayerPosition = centre;
            PlayerMouseLook mouseLook = LocalPlayerGPS.GetComponentInChildren<PlayerMouseLook>();
            if (mouseLook)
            {
                switch (side)
                {
                    case 0:         // North
                        newPlayerPosition += new Vector3(0, 0, (halfHeight + extraDistance));
                        mouseLook.SetFacing(180, 0);
                        //LocalPlayerGPS.gameObject.SendMessage("SetFacing", Vector3.back, SendMessageOptions.DontRequireReceiver);
                        //Debug.Log("Spawned player north.");
                        break;
                    case 1:         // South
                        newPlayerPosition += new Vector3(0, 0, -(halfHeight + extraDistance));
                        mouseLook.SetFacing(0, 0);
                        //LocalPlayerGPS.gameObject.SendMessage("SetFacing", Vector3.forward, SendMessageOptions.DontRequireReceiver);
                        //Debug.Log("Spawned player south.");
                        break;
                    case 2:         // East
                        newPlayerPosition += new Vector3((halfWidth + extraDistance), 0, 0);
                        mouseLook.SetFacing(270, 0);
                        //LocalPlayerGPS.gameObject.SendMessage("SetFacing", Vector3.left, SendMessageOptions.DontRequireReceiver);
                        //Debug.Log("Spawned player east.");
                        break;
                    case 3:         // West
                        newPlayerPosition += new Vector3(-(halfWidth + extraDistance), 0, 0);
                        mouseLook.SetFacing(90, 0);
                        //LocalPlayerGPS.gameObject.SendMessage("SetFacing", Vector3.right, SendMessageOptions.DontRequireReceiver);
                        //Debug.Log("Spawned player west.");
                        break;
                }
            }

            // Adjust to nearest start marker if requested
            if (useNearestStartMarker)
            {
                float smallestDistance = float.MaxValue;
                int closestMarker = -1;
                GameObject[] startMarkers = dfLocation.StartMarkers;
                for (int i = 0; i < startMarkers.Length; i++)
                {
                    float distance = Vector3.Distance(newPlayerPosition, startMarkers[i].transform.position);
                    if (distance < smallestDistance)
                    {
                        smallestDistance = distance;
                        closestMarker = i;
                    }
                }
                if (closestMarker != -1)
                {
                    //PositionPlayerToTerrain(mapPixelX, mapPixelY, startMarkers[closestMarker].transform.position);
                    RepositionPlayer(mapPixelX, mapPixelY, startMarkers[closestMarker].transform.position);
                    return;
                }
            }

            // Just position to outside location
            //PositionPlayerToTerrain(mapPixelX, mapPixelY, newPlayerPosition);
            RepositionPlayer(mapPixelX, mapPixelY, newPlayerPosition);
        }
        // Position player outside first dungeon door of this location
        private void PositionPlayerToDungeonExit(DaggerfallLocation location = null)
        {
            // Attempt to get location from current terrain transform
            if (!location)
                location = GetPlayerLocation();

            DaggerfallStaticDoors[] doors = location.StaticDoorCollections;

            // If no doors found then just position to origin
            if (doors == null || doors.Length == 0)
            {
                RepositionPlayer(MapPixelX, MapPixelY, Vector3.zero);
                return;
            }

            // Find vertically lowest dungeon door, this should be the ground-level door
            int foundIndex = -1;
            float lowestHeight = float.MaxValue;
            DaggerfallStaticDoors foundCollection = null;
            Vector3 foundDoorNormal = Vector3.zero;
            foreach(var collection in doors)
            {
                for (int i = 0; i < collection.Doors.Length; i++)
                {
                    if (collection.Doors[i].doorType == DoorTypes.DungeonEntrance)
                    {
                        Vector3 pos = collection.GetDoorPosition(i);
                        if (pos.y < lowestHeight)
                        {
                            lowestHeight = pos.y;
                            foundCollection = collection;
                            foundDoorNormal = collection.GetDoorNormal(i);
                            foundIndex = i;
                        }
                    }
                }
            }

            // Position player outside door position
            if (foundCollection)
            {
                Vector3 startPosition = foundCollection.GetDoorPosition(foundIndex);
                startPosition += foundDoorNormal * 1f;
                RepositionPlayer(MapPixelX, MapPixelY, startPosition);
            }
            else
            {
                RepositionPlayer(MapPixelX, MapPixelY, Vector3.zero);
            }

            // Set player facing away from door
            PlayerMouseLook playerMouseLook = GameManager.Instance.PlayerMouseLook;
            if (playerMouseLook)
            {
                playerMouseLook.SetFacing(foundDoorNormal);
            }
        }
 protected virtual void RaiseOnCreateLocationGameObjectEvent(DaggerfallLocation dfLocation)
 {
     if (OnCreateLocationGameObject != null)
         OnCreateLocationGameObject(dfLocation);
 }
Exemple #9
0
        private IEnumerator UpdateLocation(int index, bool allowYield)
        {
            int  key             = TerrainHelper.MakeTerrainKey(terrainArray[index].mapPixelX, terrainArray[index].mapPixelY);
            int  playerKey       = TerrainHelper.MakeTerrainKey(MapPixelX, MapPixelY);
            bool isPlayerTerrain = (key == playerKey);

            if (terrainArray[index].active && terrainArray[index].hasLocation && terrainArray[index].updateLocation)
            {
                // Disable update flag
                terrainArray[index].updateLocation = false;

                // Create location object
                DFLocation location;
                GameObject locationObject = CreateLocationGameObject(index, out location);
                if (locationObject)
                {
                    // Add location object to dictionary
                    LocationDesc locationDesc = new LocationDesc();
                    locationDesc.locationObject = locationObject;
                    locationDesc.mapPixelX      = terrainArray[index].mapPixelX;
                    locationDesc.mapPixelY      = terrainArray[index].mapPixelY;
                    locationList.Add(locationDesc);

                    // Create billboard batch game objects for this location
                    // Streaming world always batches for performance, regardless of options
                    int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue);
                    TextureAtlasBuilder      miscBillboardAtlas    = dfUnity.MaterialReader.MiscBillboardAtlas;
                    DaggerfallBillboardBatch natureBillboardBatch  = GameObjectHelper.CreateBillboardBatchGameObject(natureArchive, locationObject.transform);
                    DaggerfallBillboardBatch lightsBillboardBatch  = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.LightsTextureArchive, locationObject.transform);
                    DaggerfallBillboardBatch animalsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.AnimalsTextureArchive, locationObject.transform);
                    DaggerfallBillboardBatch miscBillboardBatch    = GameObjectHelper.CreateBillboardBatchGameObject(miscBillboardAtlas.AtlasMaterial, locationObject.transform);

                    // Set hide flags
                    natureBillboardBatch.hideFlags  = HideFlags.HideAndDontSave;
                    lightsBillboardBatch.hideFlags  = HideFlags.HideAndDontSave;
                    animalsBillboardBatch.hideFlags = HideFlags.HideAndDontSave;
                    miscBillboardBatch.hideFlags    = HideFlags.HideAndDontSave;

                    // RMB blocks are laid out in centre of terrain to align with ground
                    int     width   = location.Exterior.ExteriorData.Width;
                    int     height  = location.Exterior.ExteriorData.Height;
                    float   offsetX = ((8 * RMBLayout.RMBSide) - (width * RMBLayout.RMBSide)) / 2;
                    float   offsetZ = ((8 * RMBLayout.RMBSide) - (height * RMBLayout.RMBSide)) / 2;
                    Vector3 origin  = new Vector3(offsetX, 2.0f * MeshReader.GlobalScale, offsetZ);

                    // Get location data
                    DaggerfallLocation dfLocation = locationObject.GetComponent <DaggerfallLocation>();

                    // Perform layout and yield after each block is placed
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // Set block origin for billboard batches
                            // This causes next additions to be offset by this position
                            Vector3 blockOrigin = origin + new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide));
                            natureBillboardBatch.BlockOrigin  = blockOrigin;
                            lightsBillboardBatch.BlockOrigin  = blockOrigin;
                            animalsBillboardBatch.BlockOrigin = blockOrigin;
                            miscBillboardBatch.BlockOrigin    = blockOrigin;

                            // Add block and yield
                            string     blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y));
                            GameObject go        = GameObjectHelper.CreateRMBBlockGameObject(
                                blockName,
                                false,
                                dfUnity.Option_CityBlockPrefab,
                                natureBillboardBatch,
                                lightsBillboardBatch,
                                animalsBillboardBatch,
                                miscBillboardAtlas,
                                miscBillboardBatch);
                            go.hideFlags               = HideFlags.HideAndDontSave;
                            go.transform.parent        = locationObject.transform;
                            go.transform.localPosition = blockOrigin;
                            dfLocation.ApplyClimateSettings();
                            if (allowYield)
                            {
                                yield return(new WaitForEndOfFrame());
                            }
                        }
                    }

                    // If this is the player terrain we may need to reposition player
                    if (isPlayerTerrain && repositionPlayer)
                    {
                        // Position to location and use start marker for large cities
                        bool useStartMarker = (dfLocation.Summary.LocationType == DFRegion.LocationTypes.TownCity);
                        PositionPlayerToLocation(MapPixelX, MapPixelY, dfLocation, origin, width, height, useStartMarker);
                        repositionPlayer = false;
                    }

                    // Apply billboard batches
                    natureBillboardBatch.Apply();
                    lightsBillboardBatch.Apply();
                    animalsBillboardBatch.Apply();
                    miscBillboardBatch.Apply();
                }
            }
            else if (terrainArray[index].active)
            {
                if (playerKey == key && repositionPlayer)
                {
                    PositionPlayerToTerrain(MapPixelX, MapPixelY, Vector3.zero);
                    repositionPlayer = false;
                }
            }
        }