Well, i am here to ask you something i need for a game i am making but i dont know how to improve it or make it work as i wish it did.
In my game, i have a player and this player has its enemies. The enemies need to be spawned in a random position of the arena (counting as an arena the objects with have Navigation static active), avoiding the player to see the enemy spawn.
What i want? I want to improve this system, actually it works but not in the best way. It returns lots of times (making you wait sometimes seconds to spawn an enemy) and it spawns really away from the player. I mean, if the player is in a corner, the enemies spawn in the other corner (so they all came from only one place), and if the player is in the middle of the arena, the enemies dont spawn.
I tried changing the buffer distance, but it only works in numbers like 10, 15, 20. If i set it to 5, or 1, enemies dont spawn. Maybe someone can give me a hand checking the system and giving me the advice of how to make it work fine!
I think i should make the checks better, but i dont really know how to do it.
Here is the code so you can see how it works:
Code
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
namespace CompleteProject
{
public class EnemyManager : MonoBehaviour
{
public PlayerHealth playerHealth; // Reference to the player's heatlh.
public GameObject enemy; // The enemy prefab to be spawned.
public float spawnTime = 3; // How long between each spawn.
Vector3 spawnPosition = Vector3.zero;
public float bufferDistance = 20; // The distance from our Camera View Frustrum we want to spawn enemies to make sure they are not visisble when they spawn.
public float dificulty = 1; // The difficulty number for spawning the enemies
void Start()
{
// Call the Spawn function after a delay of the spawnTime and then continue to call after the same amount of time.
InvokeRepeating("Spawn", 2, 0.3f);
}
void Spawn()
{
// If the player has no health left...
if (playerHealth.currentHealth <= 0f)
{
// ... exit the function.
return;
}
if (DificultyManager.dificulty == dificulty)
{
// Find a random position roughly on the level.
Vector2 randomOnCircle = Random.insideUnitCircle;
Vector3 randomPosition = new Vector3(randomOnCircle.x, 0, randomOnCircle.y) * 35f;
// Find the closest position on the nav mesh to our random position.
// If we can't find a valid position return and try again.
NavMeshHit hit;
if (!NavMesh.SamplePosition(randomPosition, out hit, 5, 1))
{
return;
}
// We have a valid spawn position on the nav mesh.
spawnPosition = hit.position;
// Check if this position is visible on the screen, if it is we
// return and try again.
Vector3 screenPos = Camera.main.WorldToScreenPoint(spawnPosition);
if ((screenPos.x > -bufferDistance && screenPos.x < (Screen.width + bufferDistance)) &&
(screenPos.y > -bufferDistance && screenPos.y < (Screen.height + bufferDistance)))
{
return;
}
// Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
Instantiate(enemy, spawnPosition, Quaternion.identity);
}
}
}
}
PD: Sorry if i have grammar mistakes. ; I am helping myself with the Survival Shooter spawn system.
Well, i wait for your answer! Thank you very much for reading and for your help!
If you only want enemies to spawn in completely random locations that are truly not visible to the player and your game is in 3D with 3D obstacles(bridges/overhangs/etc). This problem is going to be much harder to solve than you probably expect. The easiest way I can think of is to use some sort of binary space partitioning tree. Generate a random position vector outside of the visible partitions on the navmesh and spawn an enemy.
Alternatively you could set up a spawner class and put them on gameobjects around your arena. Use a SpawnManager class to pick a random spawner object in the arena, create a random vector in a radius around the spawner and shoot a raycast from the vector to the player. If the vector is visible to the player, try again with a different spawner or random vector otherwise spawn the enemy. You could even use SphereCast or BoxCast to account for the enemies mesh dimensions.
you should also CancelInvoke() and then InvokeRepeating() the Spawn again when the spawn is not successfull, so it wont have to wait another 2 second before retrying to spawn again…
But it’s not advisable though since InvokeRepeating is not as memory-friendly as coroutine…
Since the positions are evenly distributed around the sphere, this will give you a lot of positions that are closer to the player than far away. If you want an even distribution on the XZ plane, you should use insideUnitCircle instead, and turn it into a Vector3 like this:
Woow, thanks! I have now updated the code! With that, the system works really better, but i keep having this problem. Despite the player dont see the enemies spawning, depending on the player positon, the enemies dont spawn.
In this picture, imagine the player is in the “x”, and enemies should spawn in all the “o” because the player cant see them but they dont. When the player is in the position, enemies dont spawn. Thats the problem, and if the player for example is in the left, enemies all come from the right, and it makes it easy. How can i improve this system? (The code you made me change improved the spawning but not the detection)
The code you’re using will get a random position (at world origin) inside a unit sphere/circle that is supposed to encompass your entire map. It will then check if the position is visible on the players screen. I seriously doubt your entire map only has a diameter of 35 and I question whether your map is even centered at origin.
The WorldToScreenPoint checks if it within the camera’s view cone, but not if there are intervening obstacles. So a spot on the other side of a wall (but that you’d be able to see if the wall wasn’t there) counts as not-spawnable.
Also, as far as failed attempts causing a longer wait, I’d make the Invoke manual rather than repeating (or use a coroutine) and have two wait times, one for after successful spawns (2 seconds in this case) and a shorter time to retry failed spawns (as short as you can make it without impacting performance; maybe 1/4 second?)