Skip to content

Commit e1a244a

Browse files
committed
Okay, now foes actually do spawn
1 parent 54e73a7 commit e1a244a

5 files changed

Lines changed: 153 additions & 156 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<linker>
2+
<assembly fullname="Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
3+
<type fullname="UnityEngine.AddressableAssets.Addressables" preserve="all" />
4+
</assembly>
5+
<assembly fullname="Unity.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
6+
<type fullname="UnityEngine.Localization.Locale" preserve="all" />
7+
<type fullname="UnityEngine.Localization.Tables.SharedTableData" preserve="all" />
8+
<type fullname="UnityEngine.Localization.Tables.StringTable" preserve="all" />
9+
<type fullname="UnityEngine.Localization.LocaleIdentifier" preserve="nothing" serialized="true" />
10+
<type fullname="UnityEngine.Localization.Metadata.MetadataCollection" preserve="nothing" serialized="true" />
11+
<type fullname="UnityEngine.Localization.Tables.DistributedUIDGenerator" preserve="nothing" serialized="true" />
12+
<type fullname="UnityEngine.Localization.Tables.SharedTableData/SharedTableEntry" preserve="nothing" serialized="true" />
13+
<type fullname="UnityEngine.Localization.Tables.TableEntryData" preserve="nothing" serialized="true" />
14+
</assembly>
15+
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
16+
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
17+
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
18+
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
19+
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" preserve="all" />
20+
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" preserve="all" />
21+
</assembly>
22+
<assembly fullname="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
23+
<type fullname="UnityEngine.Object" preserve="all" />
24+
</assembly>
25+
</linker>

Assets/AddressableAssetsData/link.xml.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Scripts/Game/Questing/Actions/CreateFoe.cs

Lines changed: 76 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,9 @@ public override void Update(Task caller)
154154
}
155155

156156
// Try to deploy a pending spawns
157-
if (spawnInProgress)
158-
{
159-
if(TryPlacement())
160-
GameManager.Instance.RaiseOnEncounterEvent();
157+
if(spawnInProgress){
158+
TryPlacement();
159+
GameManager.Instance.RaiseOnEncounterEvent();
161160
}
162161
}
163162

@@ -180,7 +179,7 @@ void CreatePendingFoeSpawn(Foe foe)
180179
pendingFoesSpawned = 0;
181180
}
182181

183-
bool TryPlacement()
182+
bool TryPlacement(bool placeEnemyBehindPlayer = true)
184183
{
185184
PlayerEnterExit playerEnterExit = GameManager.Instance.PlayerEnterExit;
186185

@@ -195,19 +194,19 @@ bool TryPlacement()
195194
// Place in world near player depending on local area
196195
if (playerEnterExit.IsPlayerInsideBuilding)
197196
{
198-
return PlaceFoeBuildingInterior(pendingFoeGameObjects, playerEnterExit.Interior);
197+
return PlaceFoeBuildingInterior(pendingFoeGameObjects, playerEnterExit.Interior, placeEnemyBehindPlayer);
199198
}
200199
else if (playerEnterExit.IsPlayerInsideDungeon)
201200
{
202-
return PlaceFoeDungeonInterior(pendingFoeGameObjects, playerEnterExit.Dungeon);
201+
return PlaceFoeDungeonInterior(pendingFoeGameObjects, playerEnterExit.Dungeon, placeEnemyBehindPlayer);
203202
}
204203
else if (!playerEnterExit.IsPlayerInside && GameManager.Instance.PlayerGPS.IsPlayerInLocationRect)
205204
{
206-
return PlaceFoeExteriorLocation(pendingFoeGameObjects, GameManager.Instance.StreamingWorld.CurrentPlayerLocationObject);
205+
return PlaceFoeExteriorLocation(pendingFoeGameObjects, GameManager.Instance.StreamingWorld.CurrentPlayerLocationObject, placeEnemyBehindPlayer);
207206
}
208207
else
209208
{
210-
return PlaceFoeWilderness(pendingFoeGameObjects);
209+
return PlaceFoeWilderness(pendingFoeGameObjects, placeEnemyBehindPlayer);
211210
}
212211
}
213212

@@ -217,7 +216,7 @@ bool TryPlacement()
217216

218217
// Place foe somewhere near player when inside a building
219218
// Building interiors have spawn nodes for this placement so we can roll out foes all at once
220-
bool PlaceFoeBuildingInterior(GameObject[] gameObjects, DaggerfallInterior interiorParent)
219+
bool PlaceFoeBuildingInterior(GameObject[] gameObjects, DaggerfallInterior interiorParent, bool placeEnemyBehindPlayer = true)
221220
{
222221
// Must have a DaggerfallLocation parent
223222
if (interiorParent == null)
@@ -229,115 +228,108 @@ bool PlaceFoeBuildingInterior(GameObject[] gameObjects, DaggerfallInterior inter
229228
// Always place foes around player rather than use spawn points
230229
// Spawn points work well for "interior hunt" quests but less so for "directly attack the player"
231230
// Feel just placing freely will yield best results overall
232-
return PlaceFoeFreely(gameObjects, interiorParent.transform);
231+
return PlaceFoeFreely(gameObjects, interiorParent.transform, 5, 20, placeEnemyBehindPlayer);
233232
}
234233

235234
// Place foe somewhere near player when inside a dungeon
236235
// Dungeons interiors are complex 3D environments with no navgrid/navmesh or known spawn nodes
237-
bool PlaceFoeDungeonInterior(GameObject[] gameObjects, DaggerfallDungeon dungeonParent)
236+
bool PlaceFoeDungeonInterior(GameObject[] gameObjects, DaggerfallDungeon dungeonParent, bool placeEnemyBehindPlayer = true)
238237
{
239-
return PlaceFoeFreely(gameObjects, dungeonParent.transform);
238+
return PlaceFoeFreely(gameObjects, dungeonParent.transform, 5, 20, placeEnemyBehindPlayer);
240239
}
241240

242241
// Place foe somewhere near player when outside a location navgrid is available
243242
// Navgrid placement helps foe avoid getting tangled in geometry like buildings
244-
bool PlaceFoeExteriorLocation(GameObject[] gameObjects, DaggerfallLocation locationParent)
243+
bool PlaceFoeExteriorLocation(GameObject[] gameObjects, DaggerfallLocation locationParent, bool placeEnemyBehindPlayer = true)
245244
{
246-
return PlaceFoeFreely(gameObjects, locationParent.transform);
245+
return PlaceFoeFreely(gameObjects, locationParent.transform, 5, 20, placeEnemyBehindPlayer);
247246
}
248247

249248
// Place foe somewhere near player when outside and no navgrid available
250249
// Wilderness environments are currently open so can be placed on ground anywhere within range
251-
bool PlaceFoeWilderness(GameObject[] gameObjects)
250+
bool PlaceFoeWilderness(GameObject[] gameObjects, bool placeEnemyBehindPlayer = true)
252251
{
253252
// TODO this false will need to be true when start caching enemies
254253
GameManager.Instance.StreamingWorld.TrackLooseObject(gameObjects[pendingFoesSpawned], false, -1, -1, true);
255-
return PlaceFoeFreely(gameObjects, null, 8f, 25f);
254+
return PlaceFoeFreely(gameObjects, null, 8f, 25f, placeEnemyBehindPlayer, false);
256255
}
257256

258257
// Uses raycasts to find next spawn position just outside of player's field of view
259-
bool PlaceFoeFreely(GameObject[] gameObjects, Transform parent, float minDistance = 5f, float maxDistance = 20f)
258+
bool PlaceFoeFreely(GameObject[] gameObjects, Transform parent, float minDistance = 5f, float maxDistance = 20f, bool placeEnemyBehindPlayer = true, bool lineOfSight = true, int spawnTries = 15)
260259
{
261-
const float overlapSphereRadius = 0.65f;
262-
const float separationDistance = 1.25f;
263260
const float maxFloorDistance = 4f;
264261

265262
// Must have received a valid array
266263
if (gameObjects == null || gameObjects.Length == 0)
267264
return false;
268265

269-
// Set parent - otherwise caller must set a parent
270-
if (parent)
271-
gameObjects[pendingFoesSpawned].transform.parent = parent;
272-
273-
// Select a left or right direction outside of camera FOV
274-
Quaternion rotation;
275-
float directionAngle = GameManager.Instance.MainCamera.fieldOfView;
276-
directionAngle += UnityEngine.Random.Range(0f, 4f);
277-
if (UnityEngine.Random.Range(0f, 1f) > 0.5f)
278-
rotation = Quaternion.Euler(0, -directionAngle, 0);
279-
else
280-
rotation = Quaternion.Euler(0, directionAngle, 0);
281-
282-
// Get direction vector and create a new ray
283-
Vector3 angle = (rotation * Vector3.forward).normalized;
284-
Vector3 spawnDirection = GameManager.Instance.PlayerObject.transform.TransformDirection(angle).normalized;
285-
Ray ray = new Ray(GameManager.Instance.PlayerObject.transform.position, spawnDirection);
286-
287-
// Check for a hit
288-
Vector3 currentPoint;
289-
RaycastHit initialHit;
290-
if (Physics.Raycast(ray, out initialHit, maxDistance))
291-
{
292-
float cos_normal = Vector3.Dot(- spawnDirection, initialHit.normal.normalized);
293-
if (cos_normal < 1e-6)
294-
return false;
295-
float separationForward = separationDistance / cos_normal;
296-
297-
// Must be greater than minDistance
298-
float distanceSlack = initialHit.distance - separationForward - minDistance;
299-
if (distanceSlack < 0f)
300-
return false;
301-
302-
// Separate out from hit point
303-
float extraDistance = UnityEngine.Random.Range(0f, Mathf.Min(2f, distanceSlack));
304-
currentPoint = initialHit.point - spawnDirection * (separationForward + extraDistance);
305-
}
306-
else
266+
// Skip this foe if destroyed (e.g. player left building where pending)
267+
if (!gameObjects[pendingFoesSpawned])
307268
{
308-
// Player might be in an open area (e.g. outdoors) pick a random point along spawn direction
309-
currentPoint = GameManager.Instance.PlayerObject.transform.position + spawnDirection * UnityEngine.Random.Range(minDistance, maxDistance);
269+
pendingFoesSpawned++;
270+
return true;
310271
}
311272

312-
// Must be able to find a surface below
313-
RaycastHit floorHit;
314-
ray = new Ray(currentPoint, Vector3.down);
315-
if (!Physics.Raycast(ray, out floorHit, maxFloorDistance))
316-
return false;
317-
318-
// Ensure this is open space
319-
Vector3 testPoint = floorHit.point + Vector3.up * separationDistance;
320-
Collider[] colliders = Physics.OverlapSphere(testPoint, overlapSphereRadius);
321-
if (colliders.Length > 0)
322-
return false;
273+
// Set parent - otherwise caller must set a parent
274+
if (parent)
275+
gameObjects[pendingFoesSpawned].transform.parent = parent;
323276

324-
// This looks like a good spawn position
325-
pendingFoeGameObjects[pendingFoesSpawned].transform.position = testPoint;
326-
FinalizeFoe(pendingFoeGameObjects[pendingFoesSpawned]);
327-
gameObjects[pendingFoesSpawned].transform.LookAt(GameManager.Instance.PlayerObject.transform.position);
277+
var enemyCC = gameObjects[pendingFoesSpawned].GetComponent<CharacterController>();
278+
var playerCC = GameManager.Instance.PlayerController;
279+
// Set parent if none specified already
280+
if (!gameObjects[pendingFoesSpawned].transform.parent)
281+
gameObjects[pendingFoesSpawned].transform.parent = GameObjectHelper.GetBestParent();
282+
283+
for(int i =0; i < spawnTries; ++i){
284+
float fov = GameManager.Instance.MainCamera.fieldOfView;
285+
float randomAngle;
286+
if(!lineOfSight || i > spawnTries/3) // give up on line of sight checks after we've tried a few times
287+
randomAngle = UnityEngine.Random.Range(-180, 180);
288+
else if(!placeEnemyBehindPlayer)
289+
randomAngle = UnityEngine.Random.Range(-fov, fov);
290+
else
291+
randomAngle = UnityEngine.Random.Range(fov, 180f) * (UnityEngine.Random.value < 0.5f ? -1f : 1f);
292+
Quaternion rot = Quaternion.Euler(0, randomAngle, 0);
293+
Vector3 angle = (rot * Vector3.forward).normalized;
294+
Vector3 spawnDirection = GameManager.Instance.PlayerObject.transform.TransformDirection(angle).normalized;
295+
Vector3 playerFootPosition = playerCC.transform.position + playerCC.center - playerCC.height/2f * Vector3.down;
296+
Vector3 spawnPos = playerFootPosition + spawnDirection * UnityEngine.Random.Range(minDistance, maxDistance);
297+
enemyCC.transform.rotation = playerCC.transform.rotation;
298+
spawnPos += enemyCC.height/2f * Vector3.up + enemyCC.center;
299+
300+
// Must be able to find a surface below
301+
if (!Physics.Raycast(spawnPos, Vector3.down, out RaycastHit floorHit, maxFloorDistance, DFULayerMasks.CorporealMask))
302+
continue;
303+
304+
// Ensure this is open space
305+
spawnPos = floorHit.point + enemyCC.center + (enemyCC.height/2f + .5f)*Vector3.up;
306+
Collider[] colliders = Physics.OverlapCapsule(spawnPos - enemyCC.height/2f * Vector3.up, spawnPos + enemyCC.height/2f * Vector3.up, enemyCC.radius, DFULayerMasks.CorporealMask);
307+
if (colliders.Length > 0)
308+
continue;
309+
310+
Debug.Log($"CreateFoe: Found an enemy spawn point in {i} tries");
311+
312+
// This looks like a good spawn position
313+
pendingFoeGameObjects[pendingFoesSpawned].transform.position = spawnPos;
314+
FinalizeFoe(pendingFoeGameObjects[pendingFoesSpawned]);
315+
gameObjects[pendingFoesSpawned].transform.LookAt(GameManager.Instance.PlayerObject.transform.position);
316+
317+
// Send msg message on first spawn only
318+
if (msgMessageID != -1)
319+
{
320+
ParentQuest.ShowMessagePopup(msgMessageID, oncePerQuest:true);
321+
msgMessageID = -1;
322+
}
328323

329-
// Send msg message on first spawn only
330-
if (msgMessageID != -1)
331-
{
332-
ParentQuest.ShowMessagePopup(msgMessageID, oncePerQuest:true);
333-
msgMessageID = -1;
324+
// Increment count
325+
pendingFoesSpawned++;
326+
return true;
334327
}
335328

336-
// Increment count
337-
pendingFoesSpawned++;
338-
return true;
329+
// Couldn't find a spawn point
330+
Debug.Log($"CreateFoe: Couldn't find a valid enemy spawn point after {spawnTries} tries.");
331+
return false;
339332
}
340-
341333
// Fine tunes foe position slightly based on mobility and enables GameObject
342334
void FinalizeFoe(GameObject go)
343335
{

0 commit comments

Comments
 (0)