So, I am making this little game, where the character constantly moves upwards with an autoscroll camera. The character jumps from platform to platform and as soon as a platform or a background tile is nolonger visible, i loop it back up. I assigned a range to my plattforms in which a randomizer choses a value from so that the player gets an individual set of plattforms every time he or she starts the game. the problem is the looping: since i do the randomizing in the start() functions, the random poision of the plattforms is only assigned once and then looped and looped again and again. so the game gets kinda boring after a few loops with is like after 20 seconds :D
Here is my code:
private float randomFloat = 0;
private int subOrAdd = 0;
// Use this for initialization
void Start () {
subOrAdd = Random.Range(1, 10);
randomFloat = Random.Range(0f, 1.4f);
// randomly add or subtract height of object
if (subOrAdd < 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y - randomFloat);
}
else if (subOrAdd >= 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y + randomFloat);
}
}
Basically, I am having a hardcoded range and then randomly decide to either add or subtract the number that came out of the range. so how would i make it so, that the objects that get looped always ask for a new position? Because start is only called once as you know and even after looping, the position remains the same. I hope I made myself clear here :)
Any help would be awesome!
Here is the the code that loops the platforms:
public class PlattformLooper : MonoBehaviour {
public float spacingBetweenLoops = 0f;
private void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Plattform")
{
Debug.Log("TRIGGERED Plattform!");
float heightOfBGObj = ((BoxCollider2D)collider).size.y;
Vector3 pos = collider.transform.position;
pos.y += heightOfBGObj * (5*5)+spacingBetweenLoops;
collider.transform.position = pos;
}
}
Just extract your randomization logic into a separate method.
void Start () {
RandomizeHeight()
}
public void RandomizeHeight() {
subOrAdd = Random.Range(1, 10);
randomFloat = Random.Range(0f, 1.4f);
// randomly add or subtract height of object
if (subOrAdd < 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y - randomFloat);
}
else if (subOrAdd >= 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y + randomFloat);
}
}
Then you can call it whenever you want:
public class PlattformLooper : MonoBehaviour {
public float spacingBetweenLoops = 0f;
private void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Plattform")
{
Debug.Log("TRIGGERED Plattform!");
float heightOfBGObj = ((BoxCollider2D)collider).size.y;
Vector3 pos = collider.transform.position;
pos.y += heightOfBGObj * (5*5)+spacingBetweenLoops;
collider.transform.position = pos;
collider.GetComponent<YourComponent>().RandomizeHeight();
}
}
Related
I am busy building a platformer(2d). And I really want procedural generation. I think that I came up with a smart system for spawning the platforms randomly(up for debate). But my platforms keep on picking to randomly spawn down. No matter how many times I restart the game
eg :
here is my code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlatformSpawner : MonoBehaviour
{
public GameObject[] platform;
public Vector2 SpawnGap;
public float randomspawn;
public float VerticalRandomness;
public float queueTime = 1.5f;
private float time = 0;
public Vector2 startspawn;
public GameObject flag;
Vector2 nextspawn;
Vector2 nextspawncentre;
Vector3 prevspawn;
public int platamount;
int amountspawned;
bool alreadyspawnedflag;
// Start is called before the first frame update
void Start()
{
platamount = Random.Range(5, 15); // random amount of platforms in level
alreadyspawnedflag = false;
}
// Update is called once per frame
void Update()
{
if (time > queueTime) // just for quality of life(so that if I maybe have 10000 platforms I can delay the spawning)
{
if(platamount >= amountspawned) // random amount of platform
{
GameObject go = Instantiate( platform[ Random.Range (0, platform.Length) ] ); // instantiating random object from list, to make it even more random(objects assigned in inspector)
Vector2 randomrange = new Vector2(Random.Range(1f, -1f) * randomspawn, Random.Range(2f, -0.25f/*perferes up, but stil not working*/) * VerticalRandomness);//calculates random position
Debug.Log(randomrange);// it is indeed choosing random pos, even on y but still giong down
nextspawncentre = new Vector2(prevspawn.x + SpawnGap.x + startspawn.x, prevspawn.y + SpawnGap.y + startspawn.y); // calculates next spawn without randomness
nextspawn = new Vector2(nextspawncentre.x + randomrange.x, nextspawncentre.y + randomrange.y);// adds randomness
go.transform.position = transform.position + new Vector3(nextspawn.x, nextspawn.y, 0);// places object at nextspawn variable
time = 0;// resets timer
prevspawn = go.transform.position;
amountspawned += 1;
randomrange = new Vector2(0, 0);// tried reseting the random, but to no success
}
else if(!alreadyspawnedflag)// almost same logic as before just for the flag to reload scene(handeled in another script)
{
GameObject spawnedflag = Instantiate(flag);
spawnedflag.transform.position = new Vector3(nextspawncentre.x + 1, 15, 0f);
alreadyspawnedflag = true;
}
}
time += Time.deltaTime;
}
}
look at comments explained, everything there.
In advance thanks for the help.
ps: Platforms do not have rigidbodies, just sprite renderer and boxcollider2d.
I think the issue is related to your startSpawn being a negative value (at least it looks like that from your first platform). So even if the Y is positive in the randomrange it might be overruled by the negative startSpawn Y value.
Actually I think you don't even add your startSpawn every time at all but rather only using it as the initial position
I think you should simply do e.g.
private bool firstPlatform = true;
and
...
if(firstPlatform)
{
nextspawn = startspawn;
firstPlatform = false;
}
else
{
nextspawncentre = prevspawn + SpawnGap;
nextspawn = nextspawncentre + randomrange;
}
...
Hi im working on a character animation / interaction with the environment. Im trying to spawn rocks of different sizes going from the ground to where the gravity direction is applied.
Im using c# for both of my scripts (character_movements_animation.cs & powerUp.cs)
My question is how to spawn objects around my character and not through it.
Im using the code below:
/* Variables Declaration */
public GameObject rock_Small;
public GameObject rock_Medium;
public GameObject rock_Large;
private float posX, posY, posZ;
private bool checkPos = false;
//Use this for initialization
void Start() {
//Empty for now
}
// Update is called once per frame
void Update() {
if (Random.Range(0, 100) < 10) {
checkPos = false;
posX = this.transform.position.x + Random.Range(-5.0f, 5.0f);
posY = this.transform.position.y;
posZ = this.transform.position.z + Random.Range(-5.0f, 5.0f);
if(posX > 3f && posY > 3f){
checkPos = true;
}
if (checkPos == true) {
Vector3 newPos = new Vector3(posX, posY, posZ);
Instantiate(rock_Small, newPos, rock_Small.transform.rotation);
}
}
}
Also see the example in the figure.
Seems like you want to spawn objects around your character, but avoid the character's own volume.
I'd suggest something like this:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance; //how far away from your character. 1, based on your original code
This will create a ring aorund your character. If you want to randomize things further, you can apply smaller random offsets to this value, eg:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance;
pos += Random.insideUnitCircle * _offset; //how wide the ring should be. 0.1 is probably sufficient.
So my endless 2d game has 2 layers of mountains in the background which I want to add parallax on, the Near layer needs to be slower than actor/camera, & the Far to be slowest. The problem is, I can't directly add script of movement to them as they are instantiated in play mode randomly according to the random theme colors, so they are being created one after other from the script below, but I want to add a movement on them on x axis slower than camera speed, while also letting it recreate continuously at the end of last one.
Here's the script creating new mountains :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PathManager : MonoBehaviour {
public static PathManager Instance;
public GameObject CoinFragments;
public GameObject SlideArrow;
public float parallaxSpeed = 5f; //
private GameObject lastPathObject;
private float PathXPosition;
private GameObject lastMountainFar;
private float MountainFarXPosition;
private GameObject lastMountainNear;
private float MountainNearXPosition;
private float StarXPostion;
private float lastCameraX; //
// Use this for initialization
void Start () {
Instance = this;
}
// Update is called once per frame
void Update()
{
}
private void LateUpdate()
{
// Check for new Near Mountain
if (lastMountainNear != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainNear.transform.position.x)
{
this.GenerateNearMountain();
}
// Check for new Far Mountain
if (lastMountainFar != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainFar.transform.position.x)
{
this.GenerateFarMountain();
}
}
// Start Creating Mountains
public void StartMountainCreation(){
MountainNearXPosition = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
MountainFarXPosition = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
this.GenerateNearMountain();
this.GenerateFarMountain();
}
private void GenerateNearMountain(){
Vector3 MountainPosition = new Vector3(MountainNearXPosition - 4f, -3.6f, 10f);
lastMountainNear = Instantiate(ThemeManager.Instance.SelectedMountainNear, MountainPosition, Quaternion.identity);
MountainNearXPosition = MountainNearXPosition + lastMountainNear.GetComponent<Renderer>().bounds.size.x - 0.01f;
//float deltaX = GamePlayCameraManager.Instance.MainCamera.transform.position.x - lastCameraX;
//lastCameraX = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
//lastMountainNear.transform.position += Vector3.right * (deltaX * parallaxSpeed);
}
private void GenerateFarMountain(){
Vector3 MountainPosition = new Vector3(MountainFarXPosition - 4f, -3.6f, 22f);
lastMountainFar = Instantiate(ThemeManager.Instance.SelectedMountainFar, MountainPosition, Quaternion.identity);
MountainFarXPosition = MountainFarXPosition + lastMountainFar.GetComponent<Renderer>().bounds.size.x - 0.01f;
}
Here's my camera movement script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GamePlayCameraManager : MonoBehaviour {
public static GamePlayCameraManager Instance; // Singleton Instance
public Camera MainCamera; // Main Camera
private Vector3 offset;
// Use this for initialization
void Start () {
Instance = this;
MainCamera.transform.position = new Vector3(0, 0, -10);
}
// Update is called once per frame
void Update () {
}
void LateUpdate()
{
if (GameStateManager.GameState == GameState.Set || GameStateManager.GameState == GameState.Playing)
MainCamera.transform.position = new Vector3(this.offset.x + ActorManager.Instance.Actor.transform.position.x + 0.8f, 0, -10);
}
// Fixed Update Method
void FixedUpdate(){
}
public void FindCameraOffset(){
this.offset = ActorManager.Instance.Actor.transform.position - MainCamera.transform.position + new Vector3(1.5f, 0f, 0f);
}
}
First, you should look into pooling your mountain objects so you don't waste cpu and memory endlessly creating them.
Once you have them pooled in your PathManager, you'll need to first know how far the camera moved in the last frame, and then tell the mountains to move according to that. You are going to have to have a reference to your PathManager in your camera Manager.
In your Camera Manager's LateUpdate:
if (GameStateManager.GameState == GameState.Set || GameStateManager.GameState == GameState.Playing) {
Vector3 newPosition = new Vector3(this.offset.x + ActorManager.Instance.Actor.transform.position.x + 0.8f, 0, -10);
float cameraMoveAmount = newPosition.x - MainCamera.transform.position.x;
MainCamera.transform.position = newPosition;
pathManager.MoveMountains(cameraMoveAmount)
}
Then, in your PathManager, use that moveAmount to change the positions of all of the mountains, as well as where the next one will be created. After this point is where you should check for any need to show new mountains, not in the PathManager's LateUpdate. This is because you need to move the mountains before you check to add new ones, and there is no guarantee which happens first if you have them in different LateUpdate calls.
Something like this:
// How fast the mountain layers are going to move in the direction of the camera.
// 0.0f - will not move with the camera, mountains will slide out of frame at normal speed.
// 1.0f - will move with the camera, no movement will be visible in frame.
public float nearMountainMoveSpeed = 0.5;
public float farMountainMoveSpeed = 0.9f;
. . .
public void MoveMountains(float cameraMoveAmount) {
float nearMountainParallaxMove = nearMountainMoveSpeed * cameraMoveAmount;
float farMountainParallaxMove = farMountainMoveSpeed * cameraMoveAmount;
// Move the near mountains & their next placement
MountainNearXPosition += nearMountainParallaxMove;
foreach (GameObject nearMountain in pooledNearMountains) {
nearMountain.transform.position = nearMountain.transform.position + new Vector3(nearMountainParallaxMove,0f,0f);
}
// Check for new Near Mountain
if (lastMountainNear != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainNear.transform.position.x) {
this.GenerateNearMountain();
}
// Move the far mountains & their next placement
MountainFarXPosition += farMountainParallaxMove;
foreach (GameObject farMountain in pooledFarMountains) {
farMountain.transform.position = farMountain.transform.position + new Vector3(farMountainParallaxMove,0f,0f);
}
// Check for new Far Mountain
if (lastMountainFar != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainFar.transform.position.x) {
this.GenerateFarMountain();
}
}
And as an aside, If you end up changing your code and having a single gameobject with all of the farMountains as children and one with all of the nearMountains as children, then you can just move the transform of those parent gameobjects instead of looping through each individual mountain. The amount you would need to move it is the same, of course.
I am making an endless runner style game in unity where the floor tiles spawn randomly and endlessly in front of the player as they run and delete themselves after a certain distance behind the player this is all working fine and as intended however the individual tiles spawn about half way inside each other and as much as I try to debug my code I can't seem to effect them. Ideally, I want the code to do exactly what it's doing, but the tiles spawn end to end rather than inside each other.
Any help would be greatly appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tile_Manager : MonoBehaviour
{
public GameObject[] tilePrefabs;
private Transform playerTransform;
private float spawnZ = 5.0f;
private float tileLength = 5.0f;
private float safeZone = 7.0f;
private int amtTilesOnScreen = 10;
private int lastPrefabIndex = 0;
private List<GameObject> activeTiles;
// Use this for initialization
void Start () {
activeTiles = new List<GameObject>();
playerTransform = GameObject.FindGameObjectWithTag ("Player").transform;
for (int i = 0; i < amtTilesOnScreen; i++)
{
if (i < 2)
SpawnTile(0);
else
SpawnTile();
}
}
// Update is called once per frame
void Update () {
if (playerTransform.position.z - safeZone > (spawnZ - amtTilesOnScreen * tileLength))
{
SpawnTile();
DeleteTile();
}
}
private void SpawnTile(int prefabIndex = -1)
{
GameObject go;
if (prefabIndex == -1)
go = Instantiate(tilePrefabs[RandomPrefabIndex()]) as GameObject;
else
go = Instantiate(tilePrefabs[prefabIndex]) as GameObject;
go.transform.SetParent(transform);
go.transform.position = Vector3.forward * spawnZ;
spawnZ += tileLength;
activeTiles.Add (go);
}
private void DeleteTile()
{
Destroy(activeTiles [0]);
activeTiles.RemoveAt (0);
}
private int RandomPrefabIndex()
{
if (tilePrefabs.Length <= 1)
return 0;
int randomIndex = lastPrefabIndex;
while (randomIndex == lastPrefabIndex)
{
randomIndex = Random.Range(0, tilePrefabs.Length);
}
lastPrefabIndex = randomIndex;
return randomIndex;
}
}
stacked tiles
You need to take the length of a tile into account. Try changing this
go.transform.position = Vector3.forward * spawnZ;
to this
go.transform.position = Vector3.forward * (spawnZ + tileLength / 2);
to add half the tile length to the spawn position.
Wouldn't you want
go.transform.Translate(Vector3.forward * spawnZ);
not position?
As you're spawning things relative to the world coordinate system.
https://docs.unity3d.com/ScriptReference/Transform.Translate.html
update...
First Class
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Wave
{
public GameObject enemyPrefab;
public float spawnInterval = 2;
public int maxEnemies = 20;
}
public class SpawnEnemy : MonoBehaviour
{
public GameObject[] waypoints;
public GameObject testEnemyPrefab;
public Wave[] waves;
public int timeBetweenWaves = 5;
private GameManagerBehavior gameManager;
private float lastSpawnTime;
private int enemiesSpawned = 0;
// Use this for initialization
void Start()
{
lastSpawnTime = Time.time;
gameManager =
GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
}
// Update is called once per frame
void Update()
{
// 1 Get the index of the current wave, and check if it’s the last one.
int currentWave = gameManager.Wave;
if (currentWave < waves.Length)
{
// 2 If so, calculate how much time passed since the last enemy spawn and whether it’s time to spawn an enemy. Here you consider two cases.
// If it’s the first enemy in the wave, you check whether timeInterval is bigger than timeBetweenWaves.
// Otherwise, you check whether timeInterval is bigger than this wave’s spawnInterval. In either case, you make sure you haven’t spawned all the enemies for this wave.
float timeInterval = Time.time - lastSpawnTime;
float spawnInterval = waves[currentWave].spawnInterval;
if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) ||
timeInterval > spawnInterval) &&
enemiesSpawned < waves[currentWave].maxEnemies)
{
// 3 If necessary, spawn an enemy by instantiating a copy of enemyPrefab. You also increase the enemiesSpawned count.
lastSpawnTime = Time.time;
GameObject newEnemy = (GameObject)
Instantiate(waves[currentWave].enemyPrefab);
newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints;
newEnemy.GetComponent<MoveEnemy>().JiggleWaypoints();
enemiesSpawned++;
}
// 4 You check the number of enemies on screen. If there are none and it was the last enemy in the wave you spawn the next wave.
// You also give the player 10 percent of all gold left at the end of the wave.
if (enemiesSpawned == waves[currentWave].maxEnemies &&
GameObject.FindGameObjectWithTag("Enemy") == null)
{
gameManager.Wave++;
gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f);
enemiesSpawned = 0;
lastSpawnTime = Time.time;
}
// 5 Upon beating the last wave this runs the game won animation.
}
else {
gameManager.gameOver = true;
GameObject gameOverText = GameObject.FindGameObjectWithTag("GameWon");
gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
}
}
}
Second Class
using UnityEngine;
using System.Collections;
public class MoveEnemy : MonoBehaviour
{
[System.NonSerialized]
public GameObject[] waypoints;
private int currentWaypoint = 0;
private float lastWaypointSwitchTime;
public float speed = 1.0f;
// Use this for initialization
void Start()
{
lastWaypointSwitchTime = Time.time;
}
// Update is called once per frame
void Update()
{
// 1
Vector3 startPosition = waypoints[currentWaypoint].transform.position;
Vector3 endPosition = waypoints[currentWaypoint + 1].transform.position;
// 2
float pathLength = Vector3.Distance(startPosition, endPosition);
float totalTimeForPath = pathLength / speed;
float currentTimeOnPath = Time.time - lastWaypointSwitchTime;
gameObject.transform.position = Vector3.Lerp(startPosition, endPosition, currentTimeOnPath / totalTimeForPath);
// 3
if (gameObject.transform.position.Equals(endPosition))
{
if (currentWaypoint < waypoints.Length - 2)
{
// 3.a
currentWaypoint++;
lastWaypointSwitchTime = Time.time;
RotateIntoMoveDirection();
}
else {
// 3.b
Destroy(gameObject);
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);
//<< deduct health
GameManagerBehavior gameManager =
GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
gameManager.Health -= 1;
//>>
}
}
}
public void JiggleWaypoints()
{
for (int i = 1; i < waypoints.Length; i++)
{
waypoints[i].transform.position = new Vector3(waypoints[i].transform.position.x + Random.Range(-3, 3), waypoints[i].transform.position.y + Random.Range(-3, 3), 0);
}
}
private void RotateIntoMoveDirection()
{
//1 It calculates the bug’s current movement direction by subtracting the current waypoint’s position from that of the next waypoint.
Vector3 newStartPosition = waypoints[currentWaypoint].transform.position;
Vector3 newEndPosition = waypoints[currentWaypoint + 1].transform.position;
Vector3 newDirection = (newEndPosition - newStartPosition);
//2 It uses Mathf.Atan2 to determine the angle toward which newDirection points, in radians, assuming zero points to the right.
// Multiplying the result by 180 / Mathf.PI converts the angle to degrees.
float x = newDirection.x;
float y = newDirection.y;
float rotationAngle = Mathf.Atan2(y, x) * 180 / Mathf.PI;
//3 Finally, it retrieves the child named Sprite and rotates it rotationAngle degrees along the z-axis.
// Note that you rotate the child instead of the parent so the health bar — you’ll add it soon — remains horizontal.
GameObject sprite = (GameObject)
gameObject.transform.FindChild("Sprite").gameObject;
sprite.transform.rotation =
Quaternion.AngleAxis(rotationAngle, Vector3.forward);
}
public float distanceToGoal()
{
float distance = 0;
distance += Vector3.Distance(
gameObject.transform.position,
waypoints[currentWaypoint + 1].transform.position);
for (int i = currentWaypoint + 1; i < waypoints.Length - 1; i++)
{
Vector3 startPosition = waypoints[i].transform.position;
Vector3 endPosition = waypoints[i + 1].transform.position;
distance += Vector3.Distance(startPosition, endPosition);
}
return distance;
}
}
Code is working 100% without errors, BUT....
After each spawn all objects get the same waypoint array. This can be seen on the screen as all objects jump to new waypoint in line together each time new object is spawned. I want the object which is already spawn to live life with it's own array of only once created waypoints.
You need to create a new array of waypoints each time you create one from the prefabricated object. You don't show your Instantiate method but I'm guessing that it has a line like this:
this.waypoints = prefab.waypoints;
This will mean that all object you create will share the same list of waypoints (as you've discovered).
What you need is something like this - assuming that the waypoints have X, Y, and Z properties):
this.waypoints = new GameObject[5];
for (int i = 0; i++ ; i < 5)
{
this.waypoints[i].X = prefab.waypoints[i].X;
this.waypoints[i].Y = prefab.waypoints[i].Y;
this.waypoints[i].Z = prefab.waypoints[i].Z;
}
(If you want your points to be a variable length you might want to consider using a list).
This means that each object has a list of unique points even if they start with the same values you can change each independently.
Based on ChrisFs' and Joe Blows' answers, do something like this in your MoveEnemy script:
private Vector3[] myWay;
public void JiggleWaypoints(GameObject[] waypoints)
{
myWay = new Vector3[waypoints.Length];
for(int i = 1; i < waypoints.Length; i++)
{
myWay[i] = new Vector3(waypoints[i].transform.position.x + Random.Range(-3, 4), waypoints[i].transform.position.y + Random.Range(-3, 4), 0);
}
}
myWay replaces the GameObject[].
In your SpawnEnemy script you do this:
GameObject e = (GameObject)Instantiate(enemyPrefab);
e.GetComponent<MoveEnemy>().JiggleWaypoints(waypoints);