// 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); }
protected virtual void RaiseOnCreateLocationGameObjectEvent(DaggerfallLocation dfLocation) { if (OnCreateLocationGameObject != null) { OnCreateLocationGameObject(dfLocation); } }
// 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); }
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; } } }