I am creating a zoo simulation game in Unity 5.4.0f3 using C#. I am trying to spawn a bear prefab clone, do some math, wait for a period of time, do some more math, then destroy the clone object all in one function. Here is what I have right now. The math that I am trying to do in WaitForBearLife() does not happen. Thanks in advance!
using UnityEngine;
using System.Collections;
public class BuyBearButton : MonoBehaviour
{
[SerializeField] GameManager gameManager;
[SerializeField] GameObject bearPrefab;
[SerializeField] Visitor visitor;
GameObject bearClone;
float xMin = -15;
float xMax = 15;
float yMin = 5;
float yMax = 6;
public void BuyBear()
{
Vector2 pos = new Vector2 (Random.Range (xMin, xMax), Random.Range (yMin, yMax));
if (gameManager.myMoney >= gameManager.bearCost)
{
gameManager.numberOfBears++;
gameManager.myMoney = gameManager.myMoney - gameManager.bearCost;
visitor.spawnTime = visitor.spawnTime / visitor.bearAttraction;
bearClone = (GameObject) Instantiate (bearPrefab, pos, transform.rotation);
StartCoroutine (WaitForBearLife ());
Destroy (bearClone, gameManager.bearLife);
}
}
IEnumerator WaitForBearLife()
{
yield return new WaitForSeconds (gameManager.bearLife);
visitor.spawnTime = visitor.spawnTime * visitor.bearAttraction;
}
}
you need to move Destroy function from BuyBear to WaitForBearLife
The problem is that now StartCouroutine call immediately returns and Destroy() gets called right away
I've just copied your code in an empty project, and made placeholder classes for GameManager and Visitor, just with random values in the variables we need in this script. It works just fine... really.So, I'll make you some questions (as I don't have the reputation yet to comment in your post...)
First, there's a script in the bear prefab of any sorts?
This is because it maybe have something that could interrupt the coroutine. It seems unlikely, but you can't know for sure until you try.
Next, how is the declaration of Visitor and GameManager?
The very beginning, when you define the class name and such. In fact, I would like to know more about the variables you used, like gameManager.bearLife and such.
Now, let's see the values.
I've tried with:
public int myMoney = 50000;
public int bearCost = 50;
public int numberOfBears = 0;
public float bearLife = 2.0f;
In GameManager. And:
public float spawnTime = 5;
public float bearAttraction = 2;
In Visitor. It does some weird math, and the logic of this script itself is clearly in development right now, I suppose. But the point is, it works, and with this:
IEnumerator WaitForBearLife()
{
yield return new WaitForSeconds (gameManager.bearLife);
print (visitor.spawnTime);
visitor.spawnTime = visitor.spawnTime * visitor.bearAttraction;
print (visitor.spawnTime);
}
I get 2.5 and 5 in the correspondant prints if I only click once. Some prints (As #Benjamin-lecomte stated) could help you to know if it's even executing that part.
For now, I can't help you without knowing much more. So I'll wait for your answer, then.
Related
I add my codes below. What is my fault, can anyone help me?
I want to when SpawnRandomBall function run two times, spawnInternal turn into spawnInternal2. So I create a new variable, called 'check'. The variable increase when SpawnRandomBall function run. I set the variable as a public. In this way I can see that 'check' variable increase or doesn't increase. 'Check' variable is increasing without problem. When the veriable value equal to 3, it must be 'else if' run. But unfortunately it doesn't work.
I guess problem is I run my codes in Start() function. But I don't know how can I do properly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnManagerX : MonoBehaviour
{
public GameObject[] ballPrefabs;
private float spawnLimitXLeft = 14.5f;
private float spawnLimitXRight = 24;
private float spawnPosY = 10;
private float startDelay = 1.0f;
private float spawnInterval = 4.0f;
private float spawnInterval2 = 2.0f;
public int check;
// Start is called before the first frame update
void Start()
{
if (check <= 2)
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval);
}
else if (check > 2)
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval2);
}
}
// Spawn random ball at random x position at top of play area
void SpawnRandomBall ()
{
// Generate random ball index and random spawn position
Vector3 spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
int ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation);
check += 1;
}
}
I want to change SpawnInternal variable into SpawnInternal2
The situation you describe is that you want a method to run. And for the first two iterations, you’d like one particular delay interval, and then after that you’d like a different delay interval. With that being the case, I would suggest you’re using the wrong tools. I would personally start a Coroutine like this:
public class SpawnManagerX : MonoBehaviour
{
public GameObject[] ballPrefabs;
private float spawnLimitXLeft = 14.5f;
private float spawnLimitXRight = 24;
private float spawnPosY = 10;
private float startDelay = 1.0f;
private float spawnInterval = 4.0f;
private float spawnInterval2 = 2.0f;
public int check;
void Start()
{
StartCoroutine ( SpawnRandomBall () );
}
// Spawn random ball at random x position at top of play area
IEnumerator SpawnRandomBall ()
{
while ( true )
{
// yielding here will produce an initial delay when the coroutine is run.
yield return new WaitForSeconds (
(check++ < 2) ? spawnInterval : spawnInterval2 );
// Generate random ball index and random spawn position
var spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
var ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate( ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation );
}
}
}
It’s also possible to run the Start method as a coroutine as well, but I feel, especially for newcomers, that the intent can sometimes be lost.
What this is saying is, start a coroutine, and in that coroutine, keep looping while (true) (which means “forever”). The line that change the amount of time that it waits is the last line, the “return”. We WaitForSeconds and the number of seconds we wait is based on the current count value. NB: written on phone but not tested.
Edited to create an initial delay in the coroutine.
If Start gets called once then the else if is never run. The code doesn't just wait at the else if waiting for check to update.
You just need to manage what code gets run in the SpawnRandomBall method.
You're going to need to try something like this this:
// Start is called before the first frame update
void Start()
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval2);
}
// Spawn random ball at random x position at top of play area
void SpawnRandomBall()
{
check++;
if (check == 1 || check == 3)
return;
// Generate random ball index and random spawn position
Vector3 spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
int ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation);
}
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
This question already has an answer here:
Move GameObject back and forth
(1 answer)
Closed 5 years ago.
I'm trying to move multiple objects, simultaneously from Point A to Point B and back again, looped to serve as obstacles in blocking the player.
I've tried
StartCoroutine(Oscillate(OscillationFunction.Sine, 1f));
public IEnumerator Oscillate(OscillationFunction method, float scalar)
right = GameObject.FindGameObjectsWithTag("MovingObs2");
foreach (GameObject r in right)
{
startPos = r.transform.position;
v = startPos;
v.x = (r.transform.position.x + (Mathf.Cos(Time.time) * scalar));
r.transform.position = v;
yield return new WaitForSeconds(2);
r.transform.position = startPos;
}
and others, but they're all difficult to contain within a desirable distance and speed from the starting position. It's too fast, and too far.
and I tried a seemingly simpler line, which is easier to understand, for me.
v.x = l.transform.position.x + speed * Time.deltaTime;
l.transform.position = v;
but since i'm using an array in a foreach loop, i don't know how to preserve each GameObjects' transform.position, so that it can be used as a condition to reverse the direction of the objects' movement every time it reaches either Point A, or point B.
if (l.transform.position.x <= startPos.x || l.transform.position.x >= startPos.x + endPos.x)
{
speed *= -1;
}
edit: I apologize if I asked a duplicate question, I thought it was different due to the number of objects involved in an array.
I tried to assume to resolve all your question, please see the code below.
Time.deltaTime is not needed, since your code (and my) using the Time.time to calculate each time the position of the object.
I also would recommend (if possible) to not put all in on script, but give each object a swing script and also try to not use the CoRoutine. But to answer your question I show you how to do it.
I have created an internal class, to store in the "Init" the targeted gameobjects and their startPosition in a list. Later you can simply loop throu this list and always have the start position.
You need an endless loop in the "Osciallate" routine and need to wait each turn. In your example you put the Wait on the wrong place, so it waits after moving each object and stops after the first run throu all of this objects.
Here the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OscilateObs : MonoBehaviour {
// internal class where all gameobjects and their startposition will be saved
internal class OscGameObjects
{
public GameObject gameObject;
public Vector3 startPosition;
}
// Here the information of all gameObjects stored in a list
private List<OscGameObjects> objectList;
public float updateSpeed = 0.05f;
public float oscScalar = 2f;
public enum OscillationFunction {
Sine = 1
}
void Start () {
// First, the gameobjects have to saved to our internal List
InitializeOscGameObjects();
// Start the Corotine
StartCoroutine(Oscillate(OscillationFunction.Sine, oscScalar));
}
private void InitializeOscGameObjects()
{
var objects = GameObject.FindGameObjectsWithTag("MovingObs2");
objectList = new List<OscGameObjects>();
foreach (var o in objects)
{
var oscObject = new OscGameObjects();
oscObject.gameObject = o;
oscObject.startPosition = o.transform.position;
objectList.Add(oscObject);
}
}
public IEnumerator Oscillate(OscillationFunction method, float scalar)
{
// Loop forever
while(true)
{
foreach (var element in objectList)
{
var currentPosition = element.gameObject.transform.position;
currentPosition.x = element.startPosition.x + Mathf.Cos(Time.time) * scalar;
element.gameObject.transform.position = currentPosition;
}
yield return new WaitForSeconds(updateSpeed);
}
}
}
EDIT:
I forgot to meantion:
1) I would recommend to use "Animation" Component for the movement and not using C# at all, so you can change the behaviour if needed and you are more flexible.
2) I would also recommend if possible to make a parent "GameObject" and moving only this and put the "MovingOb2" simply as child objects.
EDIT2:
Adding a delay increment for each object, so they not running syncron:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OscilateObs : MonoBehaviour {
// internal class where all gameobjects and their startposition will be saved
internal class OscGameObjects
{
public GameObject gameObject;
public Vector3 startPosition;
public float waitCount;
}
// Here the information of all gameObjects stored in a list
private List<OscGameObjects> objectList;
public float updateSpeed = 0.05f;
public float oscScalar = 2f;
public float waitIncrementTime = 0.01f;
public enum OscillationFunction {
Sine = 1
}
void Start () {
// First, the gameobjects have to saved to our internal List
InitializeOscGameObjects();
// Start the Corotine
StartCoroutine(Oscillate(OscillationFunction.Sine, oscScalar));
}
private void InitializeOscGameObjects()
{
var objects = GameObject.FindGameObjectsWithTag("MovingObs2");
objectList = new List<OscGameObjects>();
float i = 0;
foreach (var o in objects)
{
i += waitIncrementTime;
var oscObject = new OscGameObjects();
oscObject.gameObject = o;
oscObject.startPosition = o.transform.position;
oscObject.waitCount = i;
objectList.Add(oscObject);
}
}
public IEnumerator Oscillate(OscillationFunction method, float scalar)
{
// Loop forever
while(true)
{
foreach (var element in objectList)
{
var currentPosition = element.gameObject.transform.position;
currentPosition.x = element.startPosition.x + Mathf.Cos(Time.time + element.waitCount) * scalar;
element.gameObject.transform.position = currentPosition;
}
yield return new WaitForSeconds(updateSpeed);
}
}
}
they're all difficult to contain within a desirable distance and speed from the starting position. It's too fast, and too far
Before you start monkeying with the structure of your code, I suggest you stick with the cosine function but educate yourself on the (very simple) manner in which you can manipulate output frequency and amplitude. This knowledge will serve you well (if this question is typical of your average project) and will solve the problem trivially.
If it's too fast, you can reduce the frequency (or increase the wavelength/period) by multiplying the input (which is always absolute time) by a constant coefficient between 0.0 and +1.0 (not inclusive).
If it's too far, you can reduce the amplitude by multiplying the output by a constant coefficient between 0.0 and +1.0 (not inclusive).
You just need to pick two constants and adjust to taste by running the program and seeing how you like it.
I've recently started learning c# and unity and we're supposed to make a game where a ball rolls around a maze, is in danger of being destroyed by an enemy, and a message pops up when you reach the end. I have most of this done; however, my enemies, who are supposed to move back and forth and destroy the ball when touched, don't work. The walls and floor explodes out when the game is started and I'm not even sure they work at all. In our current assignment, we have to add classes and add another player (which I'm pretty sure I know how to do already). Here's my current code for my enemy class:
using UnityEngine;
using System;
[System.Serializable]
public class Boundary
{
public float xMin, xMax;
}
public class BadGuyMovement : MonoBehaviour
{
public Transform transformx;
private Vector3 xAxis;
private float secondsForOneLength = 1f;
public Boundary boundary;
void Start()
{
xAxis = Boundary;
}
void Update()
{
transform.position = new Vector3(Mathf.PingPong(Time.time, 3), xAxis);
}
void OnTriggerEnter(Collider Player)
{
Destroy(Player.gameObject);
}
}
On lines 21 (xAxis = Boundary;) and 26 (transform.position = new Vector 3) there are errors that I just completely don't understand. If you guys know how to fix it or know of a better way to do something, or at least a better way to move an object back and forth, please let me know!
Thank you so much for taking the time to answer this!
You're getting errors for two reasons, but fairly trivial.
The first error, on line 21 (xAxis = Boundary), you're getting an error because you're assigning a value of type Boundary to a variable of type Vector3.
This is like trying to say an Apple equals an Orange (they don't).
In languages like C#, the types of both the left hand and the right hand of an assignment must be the same.
Simply put
type variable = value; --> Works only if type from the LHS = type from the RHS
Your second error is happening is because you're trying to create a Vector3 with the wrong set of values. The process of creating a type is done through calling the Constructor of the class you're trying to create.
Now, look at the Constructor for a Vector3. One constructor takes 3 parameters of type float and the other takes 2 parameters, again of type float.
You're trying to call the constructor of a Vector3 using a float (from Mathf.PingPong) and a Vector3 (xAxis).
Now that we have that out of the way, try this code.
using UnityEngine;
using System;
[System.Serializable]
public class Boundary
{
public float xMin, xMax;
}
public class BadGuyMovement : MonoBehaviour
{
public Transform transformx;
private Vector3 xAxis;
private float secondsForOneLength = 1f;
public Boundary boundary; //Assuming that you're assigning this value in the inspector
void Start()
{
//xAxis = Boundary; //This won't work. Apples != Oranges.
}
void Update()
{
//transform.position = new Vector3(Mathf.PingPong(Time.time, 3), xAxis); //Won't work, incorrect constructor for a Vector3
//The correct constructor will take either 2 float (x & y), or 3 floats (x, y & z). Let's ping pong the guy along the X-axis, i.e. change the value of X, and keep the values of Y & Z constant.
transform.position = new Vector3( Mathf.PingPong(boundary.xMin, boundary.xMax), transform.position.y, transform.position.z );
}
void OnTriggerEnter(Collider Player)
{
Destroy(Player.gameObject);
}
}
I'm creating a game in unity 2D and in this game a GameObject called Moving_Truck is required to smoothly move into the scene from that left side. as this will be required later, I tried to make the method run from an other code on another object, the object is called scene control and the script is called opening scene.
the problem is when I push the space button the Moving_Truck game object does not move. I am fairly new to C# and have tried a few solutions such as Vector2.MoveTowards and Vector2.Lerp. I have also modified my code multiple times trying to get this to work. here is the most recent version of the codes:
CharacterBase
using UnityEngine;
using System.Collections;
public class CharacterBase : MonoBehaviour {
private float SprSize, HalfSprSize, Distance;
public int run = 1;
public void CharMove(int Dir, string Char, float speed, string BackgroundName)
{
var CharOBJ = GameObject.Find(Char);
var BGOBJ = GameObject.Find(BackgroundName);
SprSize = CharOBJ.GetComponent<Renderer>().bounds.size.x;
HalfSprSize = SprSize / 2;
Vector2 EndPos = new Vector2(BGOBJ.transform.position.x, CharOBJ.transform.position.y);
Debug.Log(EndPos);
CharOBJ.transform.position = Vector2.MoveTowards(CharOBJ.transform.position, EndPos, speed * Time.deltaTime);
}
}
OpeningScene
using UnityEngine;
using System.Collections;
public class OpeningScene : CharacterBase {
int Advance = 0, Run = 0;
void Start ()
{
}
void FixedUpdate()
{
if (Input.GetKeyUp("space"))
{
Run = 1;
Debug.Log("Space Pressed");
}
if (Run == 1)
{
Run = 0;
Advance += 1;
switch (Advance)
{
case 1:
CharMove(-1, "Moving_Truck", 0.05f, "House_Front");
break;
case 2:
CharMove(1, "Moving_Truck", 0.05f, "House_Front");
break;
}
}
}
}
This is driving me nuts, I've been trying to fix it for about an hour or Two now, can someone please help, also sorry for the long question, just comment if you need more info. also please ignore the Dir Argument for now.
Thanks.
Unity's Input.GetKeyUp only returns true on the frame when you release the spacebar. Because of this, CharMove will only be called that one frame you press the spacebar, and then only move 0.05f * timeDelta, which is probably going to be less than a pixel.
Also, this is unrelated, but you don't want to call GameObject.Find(string) every time you move the character. Instead, call it once in the Start() method and then store the result to a field.
Have you tried
GetComponent<Rigidbody2D> ().velocity = new Vector2 (moveSpeed, GetComponent<Rigidbody2D> ().velocity.y);