I have written a script that detects when something collides with my game object. When something collides with it, the collision may last 1 - 2 seconds but I only want one game object to spawn. When I run the scene and collide with it hundreds of game objects spawn instantly and everything crashes.
I have tried using Thread.Sleep() and IEnumerator waitor() but no luck so far.
Any ideas would be appreciated, I will attach the code below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class ColliderHit : MonoBehaviour
{
public GameObject topLevelMenu;
public GameObject sublevel;
public GameObject topMenuItem;
public GameObject menuItemTouched;
public GameObject itemToSpawn;
public bool topLevelItem;
void OnTriggerEnter(Collider col)
{
if (col.gameObject.name != "" || col.gameObject.name == "RightHandVisual" || col.gameObject.name == "OculusHand_R" || col.gameObject.name == "HandGrabInteractor" || col.gameObject.name == "OVRHandPrefab")
{
if (topLevelItem)
{
topLevelMenu.SetActive(false);
sublevel.SetActive(true);
sublevel.transform.position = topMenuItem.transform.position;
sublevel.transform.rotation = topMenuItem.transform.rotation;
}
else
{
StartCoroutine(waiter());
var itemsPos = menuItemTouched.transform.position;
var itemsRot = menuItemTouched.transform.rotation;
var itemsSca = menuItemTouched.transform.localScale;
GameObject spawned = Instantiate(itemToSpawn);
spawned.transform.rotation = itemsRot;
spawned.transform.localScale = itemsSca;
var zpos = itemsPos.z - (0.1f);
spawned.transform.position = new Vector3(itemsPos.x, itemsPos.y, zpos);
}
}
}
IEnumerator waiter()
{
//Wait for 4 seconds
yield return new WaitForSeconds(4);;
}
}
Solution to fix your code as is
That's not how Co-routines work. The code that needs to be await needs to be within the coroutine:
bool isOnCollideCooldown = false;
void OnTriggerEnter(Collider col)
{
if (col.gameObject.name != "" || col.gameObject.name == "RightHandVisual" || col.gameObject.name == "OculusHand_R" || col.gameObject.name == "HandGrabInteractor" || col.gameObject.name == "OVRHandPrefab")
{
if (topLevelItem)
{
topLevelMenu.SetActive(false);
sublevel.SetActive(true);
sublevel.transform.position = topMenuItem.transform.position;
sublevel.transform.rotation = topMenuItem.transform.rotation;
}
else
{
// Return if it is instantiated
if (isOnCollideCooldown) return;
// Otherwise spawn and wait
StartCoroutine(waiter());
}
}
}
IEnumerator waiter()
{
// Go onto cooldown
isOnCollideCooldown = true;
// Spawn the object
var itemsPos = menuItemTouched.transform.position;
var itemsRot = menuItemTouched.transform.rotation;
var itemsSca = menuItemTouched.transform.localScale;
GameObject spawned = Instantiate(itemToSpawn);
spawned.transform.rotation = itemsRot;
spawned.transform.localScale = itemsSca;
var zpos = itemsPos.z - (0.1f);
spawned.transform.position = new Vector3(itemsPos.x, itemsPos.y, zpos);
//Wait for 4 seconds
yield return new WaitForSeconds(4);
// Go off cooldown
isOnCollideCooldown = false;
}
Additional Solution
The issue is that when you spawn the object, it instantly collides with the other objects. So on the script with the object that spawns you can use the following:
Collider col;
public float noCollisionTime = 1;
void Awake() {
col.enabled = false;
StartCoroutine(EnableCollider());
}
IEnumerator EnableCollider()
{
yield return new WaitForSeconds(noCollisionTime);
col.enabled = true;
}
You could try declaring a
bool isInstantiated = false;
and a method like this:
void ResetInstantiation()
{
isInstantiated = false;
}
then, check if it's already been instantiated:
else
{
StartCoroutine(waiter());
var itemsPos = menuItemTouched.transform.position;
var itemsRot = menuItemTouched.transform.rotation;
var itemsSca = menuItemTouched.transform.localScale;
if (isInstantiated) return;
GameObject spawned = Instantiate(itemToSpawn);
spawned.transform.rotation = itemsRot;
spawned.transform.localScale = itemsSca;
var zpos = itemsPos.z - (0.1f);
spawned.transform.position = new Vector3(itemsPos.x, itemsPos.y, zpos);
isInstatiated = true;
Invoke("ResetInstantiation", 4.0f);
}
This way, you set the value to true with stops the instantiation and reset the flag after 4 seconds. (or however many you wish)
Related
Im using a Grab Object script that grabs a object that is inside the trigger collider.
But when i try to grab a object with more than 1 object inside the collider it grabs all the objects at the same time (example image with two bricks grabbed at the same time).
I need a rule to grab only the nearest object.
two brick grabbed
im using this script to grab the objects.
Found it on a YT tutorial and tweaked it
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickUp : MonoBehaviour {
public float throwForce = 100;
public bool canHold = true;
public GameObject item;
public GameObject tempParent;
public GameObject poof;
public GameObject vanish;
public GameObject poofParent;
public Transform guide;
public bool isHolding = false;
float distance;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
distance = Vector3.Distance(item.transform.position, guide.transform.position);
if (Input.GetKeyDown("space"))
if (distance <= 6f)
{
isHolding = true;
item.transform.position = tempParent.transform.position;
//Poof
GameObject myPoof = Instantiate(poof, Vector3.zero, Quaternion.identity) as GameObject;
myPoof.transform.parent = poofParent.transform;
myPoof.transform.position = poofParent.transform.position;
Destroy (myPoof, 2);
//Particles
GameObject myVanish = Instantiate(vanish, Vector3.zero, Quaternion.identity) as GameObject;
myVanish.transform.parent = tempParent.transform;
myVanish.transform.position = tempParent.transform.position;
Destroy (myVanish, 4);
}
if (isHolding==true)
{
item.GetComponent<Rigidbody>().useGravity = false;
item.GetComponent<Rigidbody>().detectCollisions = true;
item.GetComponent<Rigidbody>().isKinematic = false;
item.transform.parent = tempParent.transform;
item.transform.position = tempParent.transform.position;
if (Input.GetKeyUp("space"))
{
Debug.Log("Trying to throw");
item.GetComponent<Rigidbody>().AddForce(guide.transform.forward * throwForce);
isHolding = false;
}
}
else
{
item.GetComponent<Rigidbody>().useGravity = true;
item.GetComponent<Rigidbody>().isKinematic = false;
item.transform.parent = null;
}
}
}
There is a sphere cast implemented in Unity's physics system, however it exists for a different purpose. Instead you're looking for Physics.OverlapSphere
Assuming that your guide object should be the center of the sphere you can determine the closest object (closestSelectedObject) in a specified selection radius.
float radiusOfSphere;
float smallesDistance;
Transform closestSelectedObject;
/***/
void Update(){
if(Input.GetKeyDown("space")){
Collider[] hitColliders = Physics.OverlapSphere(guide.transform.position, radiusOfSphere);
if(hitColliders.length > 0){
smallesDistance = radiusOfSphere;
foreach(Collider obj in hitColliders){
float tempDistance = Vector3.Distance(obj.transform.position, guide.transform.position);
if(tempDistance <= smallesDistance){
smallesDistance = tempDistance;
closestSelectedObject = obj;
}
}
/*other stuff that your code does*/
}
}
}
my final functional (Update) code here
Cant make it without your help #derHugo, thank you!
void Update () {
Collider[] hitColliders = Physics.OverlapSphere(guide.transform.position, radiusOfSphere);
var closestSelectedObject = hitColliders.OrderBy(obj => (obj.transform.position - guide.transform.position). sqrMagnitude).FirstOrDefault();
GameObject grabbed = GameObject.Find("grabbedObject");
if (grabbed != null)
{
Debug.Log("existe");
if (Input.GetKeyDown(KeyCode.Space) && !spaceDisabled) {
spaceDisabled = true;
}
}
else
{
if (Input.GetKeyDown("space"))
{
// targetObject does not exist in the scene
Debug.Log("NAOexiste");
if (closestSelectedObject.tag.Contains("CanPickup"))
{
closestSelectedObject.transform.position = tempParent.transform.position;
closestSelectedObject.transform.parent = tempParent.transform;
closestSelectedObject.GetComponent<Rigidbody>().detectCollisions = false;
closestSelectedObject.GetComponent<Rigidbody>().isKinematic = true;
closestSelectedObject.GetComponent<Rigidbody>().useGravity = true;
originalName = closestSelectedObject.name;
closestSelectedObject.name = "grabbedObject";
//Poof
GameObject myPoof = Instantiate(poof, Vector3.zero, Quaternion.identity) as GameObject;
myPoof.transform.parent = poofParent.transform;
myPoof.transform.position = poofParent.transform.position;
Destroy (myPoof, 2);
//Particles
GameObject myVanish = Instantiate(vanish, Vector3.zero, Quaternion.identity) as GameObject;
myVanish.transform.parent = tempParent.transform;
myVanish.transform.position = tempParent.transform.position;
Destroy (myVanish, 4);
}
}
}
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("Q key was pressed.");
grabbed.transform.parent = null;
grabbed.GetComponent<Rigidbody>().detectCollisions = true;
grabbed.GetComponent<Rigidbody>().isKinematic = false;
grabbed.GetComponent<Rigidbody>().useGravity = true;
grabbed.GetComponent<Rigidbody>().AddForce(guide.transform.forward * throwForce);
Input.ResetInputAxes();
grabbed.name = originalName;
}
}
I have a 3D board game in Unity. I would like to move my character without having to press a key, but most importantly I would like to show a dynamic panel in canvas for whatever square the character lands on. So far I have the dice rolling and the character moving (after pressing a key) the correct amount of squares, but I am unable to figure out how to activate the panel based on the square color. Any help would be appreciated.
Here is my CharacterScript:
public class CharacterScript : MonoBehaviour
{
public Path currentPath;
public int squarePosition;
public int squares;
bool isMoving;
public GameObject PinkSquarePanel = GameObject.FindGameObjectWithTag("PinkSquare");
public GameObject CyanSquarePanel = GameObject.FindGameObjectWithTag("CyanSquare");
public GameObject WhiteSquarePanel = GameObject.FindGameObjectWithTag("WhiteSquare");
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape) && !isMoving)
{
squares = DiceNumberTextScript.diceNumber;
}
StartCoroutine(Move());
if (squares == 0)
{
ActivateSquarePanel();
}
}
IEnumerator Move()
{
if (isMoving)
{
yield break;
}
isMoving = true;
while (squares > 0)
{
Vector3 nextPosition = currentPath.squareList[squarePosition + 1].position;
while (MoveToNextSquare(nextPosition))
{
yield return null;
}
yield return new WaitForSeconds(0.1f);
squares--;
squarePosition++;
}
isMoving = false;
}
bool MoveToNextSquare(Vector3 goal)
{
return goal != (transform.position = Vector3.MoveTowards(transform.position, goal, 4f * Time.deltaTime));
}
void ActivateSquarePanel()
{
if (squarePosition.Equals(PinkSquarePanel))
{
PinkSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(CyanSquarePanel))
{
CyanSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(WhiteSquarePanel))
{
WhiteSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
}
}
And here is my PathScript:
public class Path : MonoBehaviour
{
Transform[] squareObjects;
public List<Transform> squareList = new List<Transform>();
GameObject[] PinkSquares = GameObject.FindGameObjectsWithTag("PinkSquare");
PinkSquare[] pinkList = new PinkSquare[1];
GameObject[] CyanSquares = GameObject.FindGameObjectsWithTag("CyanSquare");
CyanSquare[] cyanList = new CyanSquare[1];
GameObject[] WhiteSquares = GameObject.FindGameObjectsWithTag("WhiteSquare");
WhiteSquare[] whiteList = new WhiteSquare[1];
private void OnDrawGizmos()
{
Gizmos.color = Color.black;
FillSquares();
for (int i = 0; i < squareList.Count; i++)
{
Vector3 currentPosition = squareList[i].position;
if (i > 0)
{
Vector3 previousPosition = squareList[i - 1].position;
Gizmos.DrawLine(previousPosition, currentPosition);
if(currentPosition.Equals(PinkSquares))
{
pinkList[i] = new PinkSquare();
}
else if (currentPosition.Equals(CyanSquares))
{
cyanList[i] = new CyanSquare();
}
else if (currentPosition.Equals(WhiteSquares))
{
whiteList[i] = new WhiteSquare();
}
}
}
}
void FillSquares()
{
squareList.Clear();
squareObjects = GetComponentsInChildren<Transform>();
foreach (Transform square in squareObjects)
{
if (square != this.transform)
{
squareList.Add(square);
}
}
}
}
I believe your issue is in your comparisons, you are trying to use an Equals to compare your currentPosition which is a Vector3 type to a GameObject[] which is an array of gameObjects. As I mentioned in my comments, this comparison will always fail as an array of gameObjects can not be equal to a vector.
Instead of using these lines, try this line:
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
The full snippet of if else would look like
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
{
pinkList[i] = new PinkSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("CyanSquare")
{
cyanList[i] = new CyanSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("WhiteSquare")
{
whiteList[i] = new WhiteSquare();
}
Your CharacterScript is going to need to get the gameObject or Transform from the Path script as it is only keeping track of indexes. Your issue in this script is you are comparing an integer to a GameObject which would never be true. I would also recommend not using OnDrawGizmos() as it is an editor only script and should only be used to render editor debugging tools. The only reference to a Gizmo I see in the function is Gizmos.color = Color.black; which does nothing as you are not rendering a gizmo anywhere. I would move this code to a different function and call it from your CharacterScript. Have the return type be GameObject or Transform of the square you are on, so the CharacterSCript can check which color it lands on. Using an Integer nor Vector3 to compare to a GameObject[] will never work.
I am not sure if there are issues elsewhere in the code, but as this comparison would always fail, none of these statements would get broken into. What this means is your panels would never have the chance to get their alpha set nor get created.
I'm making a block breaker game that the player can get extra balls and shoot those balls with the original one.
Does anyone knows what is wrong? If there is not enough information, just tell me and I can provide it here.
Thanks
=========== CHANGED THE CODE TO BE MORE CLEAN ===========
Created my own pool logic to get more control and created a ExtraBallManager.cs to handle only functions related to that.
Here is how it is now (also I figured out that I didn't have to use coroutine):
public class ExtraBallManager : MonoBehaviour
{
private Ball ballController;
private GameManager gameManager;
public float ballWaitTime;
private float ballWaitTimeSeconds;
public int numberOfExtraBalls;
public int numberOfBallsToFire;
public ObjectPool objectPool;
//public TextAlignment numberOfBallsText;
// Start is called before the first frame update
void Start()
{
ballController = FindObjectOfType<Ball>();
gameManager = FindObjectOfType<GameManager>();
ballWaitTimeSeconds = ballWaitTime;
numberOfExtraBalls = 0;
numberOfBallsToFire = 0;
}
void Update() {
if(ballController.currentBallState == Ball.ballState.fire || ballController.currentBallState == Ball.ballState.wait){
if(numberOfBallsToFire > 0){
ballWaitTimeSeconds -= Time.deltaTime;
if(ballWaitTimeSeconds <= 0){
GameObject tempBall = objectPool.GetPooledObject("Temp Ball");
if(tempBall != null){
tempBall.transform.position = ballController.ballLaunchPosition;
tempBall.SetActive(true);
//It seems the error occurs in here
//sometimes it just don't add this tempBall to the
//list
gameManager.ballsInScene.Add(tempBall);
tempBall.GetComponent<Rigidbody2D>().velocity = ballController.initialBallVelocity;
ballWaitTimeSeconds = ballWaitTime;
numberOfBallsToFire--;
}
ballWaitTimeSeconds = ballWaitTime;
}
}
}
if(ballController.currentBallState == Ball.ballState.endShot){
numberOfBallsToFire = numberOfExtraBalls;
}
}
}
My Ball.cs part where switch the Ball State:
void Start()
{
arrow.SetActive(false);
currentBallState = ballState.aim;
rigidBody = GetComponent<Rigidbody2D>();
ballTransform = GetComponent<Transform>();
lineRenderer = arrow.GetComponent<LineRenderer>();
gameManager = FindObjectOfType<GameManager>();
stoppedPosition = transform.position;
currentBallState = Ball.ballState.aim;
gameManager.ballsInScene.Add(this.gameObject);
}
// Update is called once per frame
void Update()
{
switch(currentBallState) {
case ballState.aim:
if (Input.GetMouseButtonDown(0) && gameObject.tag == "Ball"){
MouseClicked();
}
if(Input.GetMouseButton(0) && gameObject.tag == "Ball"){
MouseDragged();
}
if(Input.GetMouseButtonUp(0) && gameObject.tag == "Ball"){
ReleaseMouse();
}
break;
case ballState.fire:
break;
case ballState.wait:
stoppedPosition = transform.position;
/*
if(gameManager.IsThereAnyExtraBallOnScene()){
currentBallState = ballState.endShot;
}*/
if(gameManager.ballsInScene.Count == 1){
currentBallState = Ball.ballState.endShot;
}
break;
case ballState.endShot:
for(int i = 0; i < gameManager.blocksInScene.Count; i++){
if(gameManager.blocksInScene[i]){
gameManager.blocksInScene[i].GetComponent<BlockMovementController>().currentState = BlockMovementController.blockState.move;
}
}
gameManager.level += 1;
gameManager.SpawnBlocks();
currentBallState = ballState.aim;
break;
default:
break;
}
}
I have several objects with the same tag, and I want to check if they are moving or not, to call a function when they're not moving. So I used the code bellow, but the aremoving is always false even when some objects still moving! Do you know what's wrong in my code?
Script:
bool aremoving;
void LateUpdate()
{
GameObject[] Cubes = GameObject.FindGameObjectsWithTag("Cube");
foreach (GameObject Cube in Cubes)
{
if (Cube.GetComponent<Rigidbody>() == null)
{
continue;
}
if (Cube.GetComponent<Rigidbody>().velocity.magnitude > 0.01)
{
aremoving = true;
}
if (Cube.GetComponent<Rigidbody>().velocity.magnitude <= 0.01)
{
aremoving = false;
}
}
Debug.Log("Cubes moving: " + aremoving);
}
Write the code like this
bool aremoving;
void LateUpdate()
{
GameObject[] Cubes = GameObject.FindGameObjectsWithTag("Cube");
foreach (GameObject Cube in Cubes)
{
if (Cube.GetComponent<Rigidbody>() == null)
{
continue;
}
if (Cube.GetComponent<Rigidbody>().velocity.magnitude > 0.01f)
{
aremoving = true;
}
Debug.Log("Cubes moving: " + aremoving);
aremoving = false;
}
My game:
Each block has a class that doesn't run anything on update, and there is a 200 x 10 span of blocks. I'm implementing random terrain generation later, and this is what I want to be done.
BLOCK CODE: (in every single block, should I optimize? or do it differently?)
using UnityEngine;
using System.Collections;
public class scr_blockMechanics : MonoBehaviour {
//#EDITABLE VARS
public string block_blockType;
private SpriteRenderer spriteRenderer;
public bool isMineable { get; set; }
public bool isRandomlyGenerated { get; set; }
public bool isWall { get; set; }
//#STATIC VARS
GameObject obj_blockItem;
//#START
void Start () {
obj_blockItem = GameObject.Find("main_blockItem");
spriteRenderer = GetComponent<SpriteRenderer>();
if(isRandomlyGenerated == false){
isMineable = false;
}
//#CREATING WALLS
/*if(isRandomlyGenerated == true && isWall == false){
//#TODO Create walls!
GameObject obj_blockWall = (GameObject)Instantiate(gameObject, transform.localPosition, Quaternion.identity);
obj_blockWall.name = obj_blockWall.name + " WALL";
obj_blockWall.transform.localPosition = new Vector3(
obj_blockWall.transform.localPosition.x,
obj_blockWall.transform.localPosition.y,
5);
Collider2D obj_blockWall_collider = obj_blockWall.GetComponent<Collider2D>();
obj_blockWall_collider.enabled = false;
Color obj_blockWall_color = Color.grey;//new Vector4(64, 64, 64, 1);
SpriteRenderer spriteRenderer_wall = obj_blockWall.GetComponent<SpriteRenderer>();
spriteRenderer_wall.color = obj_blockWall_color;
scr_blockMechanics blockMechanics_wall = obj_blockWall.GetComponent<scr_blockMechanics>();
blockMechanics_wall.isRandomlyGenerated = true;
blockMechanics_wall.isMineable = false; //#TODO: not yet mineable, because it's a wall.
blockMechanics_wall.isWall = true;
obj_blockWall.name = blockMechanics_wall.block_blockType+" WALL";
}*/
}
//#CREATE ITEM
void createItem(int uid){
GameObject obj_blockItemNew = (GameObject)Instantiate(obj_blockItem, transform.localPosition, Quaternion.identity);
obj_blockItemNew.rigidbody2D.isKinematic = false;
obj_blockItemNew.name = gameObject.name + " ITEM";
}
//#DESTROY BLOCK
void destroyBlock(){
//print ("'"+transform.name+"' has been destroyed!");
//spriteRenderer.color = Color.red;
createItem(0);
Destroy(transform.gameObject);
}
//#UPDATE
void Update () {
if(renderer.IsVisibleFrom(Camera.main) == true){
renderer.enabled = true;//gameObject.SetActive(true);
}
if(renderer.IsVisibleFrom(Camera.main) == false){
renderer.enabled = false;
}
}
}
'RANDOM' TERRAIN GENERATION
using UnityEngine;
using System.Collections;
public class scr_randomGen : MonoBehaviour {
private SpriteRenderer spriteRenderer;
private string[] block_blockTypes = new string[]{
"DIRT", //#UID 0
"STONE", //#UID 1
"STONE_BRICK", //#UID 2
};
void GENERATE(){
//#DEFAULTS
GameObject obj_block = GameObject.Find("main_block");
int pos_startingPosX = -100;
int pos_endingPosX = 100;
int pos_startingPosY = 0;
int pos_endingPosY = 10;
float X = 0;
float Y = 0;
//#TERRAIN GENERATION
for(X=pos_startingPosX;X<pos_endingPosX;X=X+1){
GameObject obj_blockNew = (GameObject)Instantiate(obj_block, new Vector3(0 + (1*X),0,0), Quaternion.identity);
scr_blockMechanics blockMechanics = obj_blockNew.GetComponent<scr_blockMechanics>();
blockMechanics.isMineable = true;
blockMechanics.isRandomlyGenerated = true;
blockMechanics.block_blockType = block_blockTypes[2];
obj_blockNew.name = blockMechanics.block_blockType+" "+X+", "+Y;
for(Y=pos_startingPosY;Y<pos_endingPosY;Y=Y+1){
obj_blockNew = (GameObject)Instantiate(obj_block, new Vector3(X,1 + (1*Y),0), Quaternion.identity);
blockMechanics = obj_blockNew.GetComponent<scr_blockMechanics>();
blockMechanics.isMineable = true;
blockMechanics.isRandomlyGenerated = true;
blockMechanics.block_blockType = block_blockTypes[2];
obj_blockNew.name = blockMechanics.block_blockType+" "+X+", "+Y;
}
}
}
void Start () {
GENERATE();
}
void Update () {
}
}
I have it so that if the camera isn't looking at blocks it disables their sprite renderer so it's not rendering as much.
Should I just disable them as a whole? I get 6fps upon running the game.
When I only have, say, 10 - 20 blocks instead of 2000 I get 130 FPS (which is still low in my opinion)
What's the best way to optimize this? make it so I get atleast 70 fps? I wanted to use Java but I don't feel comfortable with the editor and I already put 2 hours of work into this.
I have looked at people's projects in Java and it's a lot smoother and they don't have to worry about the lag. And I have looked at peoples posts saying that it is possible to create a Terarria-like game in Unity but it's always going to be laggy.
Anybody got a good idea on how to fix the lag issues?