Im currently making a game where I want to spawn a lot of different enemies. Currently I have a lot Data containers for units I create through ScriptableObjects. These units contain different sorts of data such as HP, loot, damage etc. I've thought of adding another type of data which is going to be their individual % to spawn or something similar but I am kind off stuck not knowing how to implement something like that.
Here is my current code where I spawn enemies completely randomize from two different lists of either Melee or Ranged enemies.
IEnumerator SpawnEnemies()
{
//Spawn enemies while amount of enemies are under predetermined enemy amount.
while (spawnedEnemies < totalEnemiesInCurrentWave)
{
//Randomize if Melee or Ranged enemy.
whichEnemyType = Random.Range(0, 2);
GameObject enemy = waves[currentWave].enemyPrefab[whichEnemyType];
spawnedEnemies++;
enemiesInWaveLeft++;
//Can I get a probability % to spawn each enemy? Currently completely random.
switch (whichEnemyType)
{
case 0:
GameObject Melee = Instantiate(enemy, spawnPoint.position, Quaternion.identity);
Melee.GetComponent<Melee>().unit = meleeUnits[Random.Range(0, meleeUnits.Count)];
break;
case 1:
GameObject Ranged = Instantiate(enemy, spawnPoint.position, Quaternion.identity);
Ranged.GetComponent<Ranged>().unit = rangedUnits[Random.Range(0, rangedUnits.Count)];
break;
default:
break;
}
yield return new WaitForSeconds(timeBetweenEnemies);
}
yield return null;
}
Can someone point me to a better way to handle randomizing enemies or if possible, give me some sort of idea in how to do this.
Thank you.
This code doesn't take into account your separate list of ranged and melee units, but you could use this to assign a weight to every enemy's Scriptable Object, and then the enemies that spawn will be based on the weights of all of your enemies. I recommend just having one list of enemies, rather than having separate lists for melee and ranged enemies.
This code assumes your Scriptable Object is called Enemy and you add a weight field to it. If you want your weights to be specific to each wave (instead of each enemy type), then move the weight field onto the enemy's prefab instead of its ScriptableObject.
IEnumerator SpawnEnemies()
{
int totalWeight = 0;
foreach (var enemy in waves[currentWave].enemyPrefab)
{
totalWeight += enemy.GetComponent<Enemy>().weight;
}
// Spawn enemies while amount of enemies are under predetermined enemy amount.
while (spawnedEnemies < totalEnemiesInCurrentWave)
{
spawnedEnemies++;
enemiesInWaveLeft++; // I think you should be decrementing instead of incrementing?
// Determine which enemy to spawn, based on all enemy weights
var enemy = GetEnemyToSpawn(totalWeight);
var spawnedEnemy = Instantiate(enemy, spawnPoint.position, Quaternion.identity);
// Here you'll have to handle adding the enemy to the appropriate
// "enemies" list
yield return new WaitForSeconds(timeBetweenEnemies);
}
yield return null;
}
// Suppose:
// enemy A: weight 2
// enemy B: weight 6
// enemy C: weight 3
// Then
// enemy A: 2/11 chance of spawning
// enemy B: 6/11 chance of spawning
// enemy C: 3/11 chance of spawning
private GameObject GetEnemyToSpawn(int totalWeight)
{
int weight = Random.Range(1, totalWeight);
foreach (var enemy in waves[currentWave].enemyPrefab)
{
if (weight <= enemy.GetComponent<Enemy>().weight)
{
return enemy;
}
weight -= enemy.GetComponent<Enemy>().weight;
}
}
Instead of using Random.Range((int)0, (int)2) which only return 0 or 1. Use Random.Range(0f, 1f) instead which yields a float between 0 and 1:
IEnumerator SpawnEnemies()
{
const float MeleeSpawnRatio = 0.8f; // assuming a 8:2 ratio between melee/ranged
//Spawn enemies while amount of enemies are under predetermined enemy amount.
while (spawnedEnemies < totalEnemiesInCurrentWave)
{
//Randomize if Melee or Ranged enemy.
whichEnemyType = Random.Range(0f, 1f) >= MeleeSpawnRatio ? 0 : 1;
Related
I am student and I learn with Unity3D engine. I working on small 2D game for beginning.
I need help with some game logic. Specifically how to generate game object reasonably in 4 columns. In this game I spawn game object that falling down from top of the screen. I calculate 4 columns that cover screen. I spawn randomly game object in this 4 columns. But game play is not good. I need a advice how to do better.
My idea is spawn one or more game object in row and reasonably rotate columns. I already spawn object in row. But how to create logic for switch columns? Something like switch (now first columns, now third, now first and fourth etc.)
There is my spawn methods:
// Use this for initialization
void Start ()
{
// width of one colums
widthColum = ScreenCalc.DivideScreenInColums(colums);
...
}
public void StartSpawn()
{
spawn = StartCoroutine(Spawn());
}
IEnumerator Spawn()
{
yield return new WaitForSeconds(1.5f);
time = GameController.instance.time;
while (true)
{
// Random select from prefabs
GameObject geometryObject = spawnObjects[Random.Range(0, spawnObjects.Length)];
// X is middle of random picked columns
float x = ScreenCalc.GenerateColumPos(geometryObject, colums);
Vector3 spawnPosition = new Vector3(x, transform.position.y, 0.0f);
Quaternion spawnRotation = Quaternion.identity;
// Random object in row, will be spawned in one column
int rnd = Random.Range(1, 6);
// Spawn random count game object in one row
for (int i = 0; i < rnd; i++)
{
GameObject temp = Instantiate(geometryObject, spawnPosition, spawnRotation);
// Get Sprite render
SpriteRenderer sr = temp.GetComponent<SpriteRenderer>();
// Change color of game object
sr.color = colors[Random.Range(0, colors.Length)];
// Get script for falling down
FallingDown fl = temp.GetComponent<FallingDown>();
// Increase fall
fl.fallSpeed += 0.1f;
// Wait between spawns
yield return new WaitForSeconds(Random.Range(0.35f, 1.0f));
}
yield return new WaitForSeconds(1.0f);
}
I tried do more Spawn method, one for each column and random generate wait time. But my logic was bad, because too much object was generate or too low and collide one column each other.
Here is a picture of my idea.
On the picture you can see screen divided to 4 columns, every column is spawn position. I need to help with game logic how to switch spawn of object randomly.
For example spawn 3 object in row in column one. Then spawn 1 object in column two. Then spawn 5 object in column two and at the same time 2 object in column four.
I do not know how to explain it better. I am sorry for my English.
I think you want to spawn in each column independent of other ones.
You can add 4 spawner script for 4 columns.
Set unique column position(x) on those 4 scripts.
Or you can Start 4 coroutines for 4 columns like:
public void StartSpawn()
{
StartCoroutine(Spawn(columnPos1));
StartCoroutine(Spawn(columnPos2));
StartCoroutine(Spawn(columnPos3));
StartCoroutine(Spawn(columnPos4));
}
IEnumerator Spawn(float columnPos)
{
yield return new WaitForSeconds(1.5f);
time = GameController.instance.time;
while (true)
{
// Random select from prefabs
GameObject geometryObject = spawnObjects[Random.Range(0, spawnObjects.Length)];
// X is middle of random picked columns
float x = columnPos;
Vector3 spawnPosition = new Vector3(x, transform.position.y, 0.0f);
Quaternion spawnRotation = Quaternion.identity;
// Random object in row, will be spawned in one column
int rnd = Random.Range(1, 6);
// Spawn random count game object in one row
for (int i = 0; i < rnd; i++)
{
GameObject temp = Instantiate(geometryObject, spawnPosition, spawnRotation);
// Get Sprite render
SpriteRenderer sr = temp.GetComponent<SpriteRenderer>();
// Change color of game object
sr.color = colors[Random.Range(0, colors.Length)];
// Get script for falling down
FallingDown fl = temp.GetComponent<FallingDown>();
// Increase fall
fl.fallSpeed += 0.1f;
// Wait between spawns
yield return new WaitForSeconds(Random.Range(0.35f, 1.0f));
}
yield return new WaitForSeconds(1.0f);
}
You probably asking for an algorithm that spawn gameobject in a way that your game look interesting, Maybe this pattern will help you (you can add more steps).
Generate from right to left
Generate from left to right
Repeat above two line for random time period
Now randomly generate in four position at random point
Now Go to Step 1 repeat
I'm new to unity 3d and I want to make a very simple obstacle course game. I don't want it to have multiple levels but instead there will be only one scene which will generate randomly each time someone starts the game.
This is a picture to better explain the idea:
In each highlighted section there will be a wall which will be generated every time the application starts and the player can only get through a gap which will be randomly generated in any of the areas a, b or c of each section.
I tried looking this up but there wasn't really much of this example.
If have any questions, please don't hesitate. I'm always notified for responses.
Thanks for your time!
Basic concept:
Create a prefab from your obstacle
Create a script (e.g. WallSpawner) with a couple of parameters (distance between each wall, possible positions, etc.) and attach it to an object in your scene (in your case Walls for example).
In the Start or Awake method, create copies of your prefab with Instantiate and pass in the randomly picked position.
Example Script:
public class WallSpawner : MonoBehaviour
{
// Prefab
public GameObject ObstaclePrefab;
// Origin point (first row, first obstacle)
public Vector3 Origin;
// "distance" between two rows
public Vector3 VectorPerRow;
// "distance" between two obstacles (wall segments)
public Vector3 VectorPerObstacle;
// How many rows to spawn
public int RowsToSpawn;
// How many obstacles per row (including the one we skip for the gap)
public int ObstaclesPerRow;
void Start ()
{
Random r = new Random();
// loop through all rows
for (int row = 0; row < RowsToSpawn; row++)
{
// randomly select a location for the gap
int gap = r.Next(ObstaclesPerRow);
for (int column = 0; column < ObstaclesPerRow; column++)
{
if (column == gap) continue;
// calculate position
Vector3 spawnPosition = Origin + (VectorPerRow * row) + (VectorPerObstacle * column);
// create new obstacle
GameObject newObstacle = Instantiate(ObstaclePrefab, spawnPosition, Quaternion.identity);
// attach it to the current game object
newObstacle.transform.parent = transform;
}
}
}
}
Example parameters:
Example result:
I can't seem to find a answer to this, i believe it's impossible to do by now.
i have a player that collects gameobjects, when the gameobjects are collected they become a child of the player. All gameobjects have a rigidbody with different mass but duplicate gameobjects have the same mass offcourse.
The player grows in mass when gameobjects are collected.
What i want to do is when the player reaches a certain amount of mass and collect another gameobject i want the gameobject with smallest mass to set inactive.
I already got this:
Component[] objects = GetComponentsInChildren(typeof(Rigidbody), false);
foreach (Rigidbody joint in objects)
{
float smallest = Mathf.Min(joint.mass);
if (smallest <= mass / 100 && joint.gameObject.activeInHierarchy)
{
int i = 0;
bool plus = false;
while (i < transform.childCount)
{
if (transform.GetChild(i).gameObject.activeInHierarchy && plus == false)
{
transform.GetChild(i).gameObject.SetActive(false);
Debug.Log("disabled");
plus = true;
}
i++;
}
}
}
This sets the first child gameobject of the player inactive after it reaches a certain amount of mass.
but this isn't what i really want, i want to get the gameobject with the smallest mass to set inactive but i can't convert the "float smallest = Mathf.Min(joint.mass);" back to an gameobject.
Is this possible to achieve or is it just impossible?
Of course it is possible my friend
using System.Linq // < --- at top of script
List<Rigidbody> objects = GetComponentsInChildren<Rigidbody>(false)
.OrderBy(i => i.mass)
.ToList();
Rigidbody smallest = objects.FirstOrDefault();
I have 3 players in a room, and 3 set spawn positions, but would like each player to be spawned in one of these positions and not on the same position. I've got this, but how can I make sure that another player won't also spawn in this spawnpoint?
number = UnityEngine.Random.Range(1, 3);
if (number == 1)
{
spawnpoint = GameObject.FindWithTag("spawnpoint1");
}
if (number == 2)
{
spawnpoint = GameObject.FindWithTag("spawnpoint2");
}
if (number == 3)
{
spawnpoint = GameObject.FindWithTag("spawnpoint3");
}
GameObject myPlayer = PhotonNetwork.Instantiate(playerprefabname, spawnpoint.transform.position, spawnpoint.transform.rotation, 0);
Put the list of available spawn positions in a stack; when you need a new position pop the item off the stack and use that spawn positions.
You need to ensure that you have more spawn positions than players -- obviously.
And you also probably want to somehow randomize the stack each time.
You can take the concept and use any data structure you'd like but the important idea is to remove the "consumed" element until you are ready to reset.
You could create an array of spawn points, and randomize them and spawn your object there. With this method you can have as many spawn points as you want without having to change the code.
using UnityEngine;
using System.Linq;
public class Test : MonoBehaviour {
[SerializeField] Vector3[] spawnPoints;
[SerializeField] GameObject spawnObject;
void Start(){
InitRandom ();
}
void InitRandom(){
System.Random rnd = new System.Random ();
Vector3[] items = (
from i in spawnPoints
orderby rnd.Next()
select i
).ToArray();
foreach (Vector3 v in items) {
Instantiate(spawnObject, v, Quaternion.identity);
}
}
}
You could try getting the number of players connected to a room and use that number to select the particular spawn point.
Something like this -
var playerID = PhotonNetwork.otherPlayers.Length;
var player = PhotonNetwork.Instantiate(name, spawns[playerID].transform.position, spawns[playerID].transform.rotation, 0);
player.name = "Player " + (playerID + 1);
In my 2D game I have randomized objects which are spawned as 4 to 5 clones each time I run the game. My problem is that I also have a different object that I want to spawn as 1 clone, and position it to appear after the last clone of the randomized objects I have in my game.
The objects randomization works perfectly in my game, I just need to separate that from the object that I want it to be spawned independently and after the last clone of the randomized objects.
This is the code I am using with 1 line of attempt to spawn the independent object: (The code was taken from this tutorial)
using UnityEngine;
using System;
using System.Collections.Generic; //Allows us to use Lists.
using Random = UnityEngine.Random; //Tells Random to use the Unity Engine random number generator.
namespace Completed
{
public class BoardManager : MonoBehaviour
{
// Using Serializable allows us to embed a class with sub properties in the inspector.
[Serializable]
public class Count
{
public int minimum; //Minimum value for our Count class.
public int maximum; //Maximum value for our Count class.
//Assignment constructor.
public Count (int min, int max)
{
minimum = min;
maximum = max;
}
}
public int columns = 7; //Number of columns in our game board.
public Count random1Count = new Count (1, 2); //Lower and upper limit for our random number of objects
public Count random2Count = new Count (1, 1);
public Count random3Count = new Count (1, 1);
public Count random4Count = new Count (1, 1);
public GameObject[] randomObject1; //Array of objects prefabs.
public GameObject[] randomObject2;
public GameObject[] randomObject3;
public GameObject[] randomObject4;
public GameObject obj; // the independent object declaration
private List <Vector3> gridPositions = new List <Vector3> (); //A list of possible locations to place objects.
//Clears our list gridPositions and prepares it to generate a new board.
void InitialiseList ()
{
//Clear our list gridPositions.
gridPositions.Clear ();
//Loop through x axis (columns).
for(int x = 2; x < columns; x++)
{
//At each index add a new Vector3 to our list with the x and y coordinates of that position.
gridPositions.Add (new Vector3(x, 0.3f, 0f));
Instantiate(obj); // my attempt to instantiate the separate object
Debug.Log(obj.transform.position.x); // my attempt to track the position of the separate object
}
}
//RandomPosition returns a random position from our list gridPositions.
Vector3 RandomPosition ()
{
//Declare an integer randomIndex, set it's value to a random number between 0 and the count of items in our List gridPositions.
int randomIndex = Random.Range (0, gridPositions.Count);
//Declare a variable of type Vector3 called randomPosition, set it's value to the entry at randomIndex from our List gridPositions.
Vector3 randomPosition = gridPositions[randomIndex];
//Remove the entry at randomIndex from the list so that it can't be re-used.
gridPositions.RemoveAt (randomIndex);
//Return the randomly selected Vector3 position.
return randomPosition;
}
//LayoutObjectAtRandom accepts an array of game objects to choose from along with a minimum and maximum range for the number of objects to create.
void LayoutObjectAtRandom (GameObject[] tileArray, int minimum, int maximum)
{
//Choose a random number of objects to instantiate within the minimum and maximum limits
int objectCount = Random.Range (minimum, maximum+1);
//Instantiate objects until the randomly chosen limit objectCount is reached
for(int i = 0; i < objectCount; i++)
{
//Choose a position for randomPosition by getting a random position from our list of available Vector3s stored in gridPosition
Vector3 randomPosition = RandomPosition();
//Choose a random tile from tileArray and assign it to tileChoice
GameObject tileChoice = tileArray[Random.Range (0, tileArray.Length)];
//Instantiate tileChoice at the position returned by RandomPosition with no change in rotation
Instantiate(tileChoice, randomPosition, Quaternion.identity);
}
}
//SetupScene initializes our level and calls the previous functions to lay out the game board
public void SetupScene (int level)
{
//Reset our list of gridpositions.
InitialiseList ();
//Instantiate a random number of objects based on minimum and maximum, at randomized positions.
LayoutObjectAtRandom (randomObject1, random1Count.minimum, random1Count.maximum);
LayoutObjectAtRandom (randomObject2, random2Count.minimum, random2Count.maximum);
LayoutObjectAtRandom (randomObject3, random3Count.minimum, random3Count.maximum);
LayoutObjectAtRandom (randomObject4, random4Count.minimum, random4Count.maximum);
}
}
}
When you instantiate your final GameObject, you're not giving it a position. As I understand it you want to position it in the same place as your last randomly positioned GameObject, but offset in the x axis. To do this you'll need the Vector3 that contains the position of the last randomly spawned GameObject, I'll call this lastRandomPos. I assume you know how to get this value, if not leave a comment and I'll include it in my answer. Then you can pass the position when instantiating your final GameObject:
var objPos = lastRandomPos;
objPos.x = objPos.x + <however much you want to offset in x>;
Instantiate(obj, objPos, Quaternion.identity);
EDIT: The easiest way for you to get the position of the last random object is to just set a class-level variable every time you Instantiate an object in LayoutObjectAtRandom. Declare it in the class scope, for example underneath your declaration of gridPositions:
private List <Vector3> gridPositions = new List <Vector3> (); //A list of possible locations to place objects.
private Vector3 lastRandomPos;
Then when you Instantiate a random object you can set this variable to keep track of where it was spawned:
Instantiate(tileChoice, randomPosition, Quaternion.identity);
lastRandomPos = randomPosition;