I am trying to spawn a set number of cubes within an ever changing area (a plane, ARKit) and NOT have them overlap. Pretty simple I'd think, and I have this working in Unity editor like so:
My problem is deploy to device (iPhone) and everything is different. Several things aren't working, and I don't know why - it's a relatively simple script. First I thought CheckSphere wasn't working, something with scale being different - but this is how I try to get an empty space:
public Vector3 CheckForEmptySpace (Bounds bounds)
{
float sphereRadius = tierDist;
Vector3 startingPos = new Vector3 (UnityEngine.Random.Range(bounds.min.x, bounds.max.x), bounds.min.y, UnityEngine.Random.Range(bounds.min.z, bounds.max.z));
// Loop, until empty adjacent space is found
var spawnPos = startingPos;
while ( true )
{
if (!(Physics.CheckSphere(spawnPos, sphereRadius, 1 << 0)) ) // Check if area is empty
return spawnPos; // Return location
else
{
// Not empty, so gradually move position down. If we hit the boundary edge, move and start again from the opposite edge.
var shiftAmount = 0.5f;
spawnPos.z -= shiftAmount;
if ( spawnPos.z < bounds.min.z )
{
spawnPos.z = bounds.max.z;
spawnPos.x += shiftAmount;
if ( spawnPos.x > bounds.max.x )
spawnPos.x = bounds.min.x;
}
// If we reach back to a close radius of the starting point, then we didn't find any empty spots
var proximity = (spawnPos - startingPos).sqrMagnitude;
var range = shiftAmount-0.1; // Slight 0.1 buffer so it ignores our initial proximity to the start point
if ( proximity < range*range ) // Square the range
{
Debug.Log( "An empty location could not be found" );
return new Vector3 (200, 200, 200);
}
}
}
}
This again, works perfect in editor. This is the code Im running on my device (without check sphere)
public void spawnAllTiers(int maxNum)
{
if(GameController.trackingReady && !hasTriedSpawn)
{
hasTriedSpawn = true;
int numTimesTried = 0;
BoxCollider bounds = GetGrid ();
if (bounds != null) {
while (tiersSpawned.Length < maxNum && numTimesTried < 70) { //still has space
Tier t = getNextTier ();
Vector3 newPos = new Vector3 (UnityEngine.Random.Range(GetGrid ().bounds.min.x, GetGrid ().bounds.max.x), GetGrid ().bounds.min.y, UnityEngine.Random.Range(GetGrid ().bounds.min.z, GetGrid ().bounds.max.z));
//Vector3 newPos = CheckForEmptySpace (bounds.bounds);
if(GetGrid ().bounds.Contains(newPos)) //meaning not 200 so it is there
{
spawnTier (newPos, t);
}
numTimesTried++;
platformsSpawned = GameObject.FindObjectsOfType<Platform> ();
tiersSpawned = GameObject.FindObjectsOfType<Tier> ();
}
if(tiersSpawned.Length < maxNum)
{
print ("DIDNT REACH - maxed at "+tiersSpawned.Length);
}
}
}
//maybe check for num times trying, or if size of all spawned tiers is greater than area approx
}
//SPAWN NEXT TIER
public void spawnTier(Vector3 position, Tier t) //if run out of plats THEN we spawn up like tree house
{
print ("SUCCESS - spawn "+position+"SPHERE: "+Physics.CheckSphere(position, tierDist, 1 << 0));
// Vector3 pos = currentTier.transform.position; //LATER UNCOMMENT - would be the current tier spawning from
//TO TEST comment to this line ---------------------------------------------------------------------------
#if UNITY_EDITOR
Instantiate (t, position, Quaternion.identity);
anchorManager.AddAnchor(t.gameObject);
#else
//------------------------------------------------------------------------------------------
Instantiate (t, position, Quaternion.identity);
anchorManager.AddAnchor(t.gameObject);
#endif
}
This doesnt crash the device but spawns ALL in the same place. I cant understand why. If I do this, CHECKING for overlap:
public void spawnAllTiers(int maxNum)
{
if(GameController.trackingReady && !hasTriedSpawn)
{
hasTriedSpawn = true;
int numTimesTried = 0;
BoxCollider bounds = GetGrid ();
if (bounds != null) {
while (tiersSpawned.Length < maxNum && numTimesTried < 70) { //still has space
Tier t = getNextTier ();
//Vector3 newPos = new Vector3 (UnityEngine.Random.Range(GetGrid ().bounds.min.x, GetGrid ().bounds.max.x), GetGrid ().bounds.min.y, UnityEngine.Random.Range(GetGrid ().bounds.min.z, GetGrid ().bounds.max.z));
Vector3 newPos = CheckForEmptySpace (GetGrid ().bounds);
if(GetGrid ().bounds.Contains(newPos) && t) //meaning not 200 so it is there
{
spawnTier (newPos, t);
}
numTimesTried++;
platformsSpawned = GameObject.FindObjectsOfType<Platform> ();
tiersSpawned = GameObject.FindObjectsOfType<Tier> ();
}
if(tiersSpawned.Length < maxNum)
{
print ("DIDNT REACH - maxed at "+tiersSpawned.Length);
}
}
}
//maybe check for num times trying, or if size of all spawned tiers is greater than area approx
}
Works great in editor again, but completely freezes the device. Logs are not helpful, as I just get this every time, even though they aren't spawning in those positions:
SUCCESS - spawn (0.2, -0.9, -0.9)SPHERE: False
SUCCESS - spawn (-0.4, -0.9, 0.2)SPHERE: False
SUCCESS - spawn (0.8, -0.9, 0.2)SPHERE: False
SUCCESS - spawn (-0.4, -0.9, -0.8)SPHERE: False
SUCCESS - spawn (0.9, -0.9, -0.8)SPHERE: False
What the hell is happening - why would it freeze only on device like this?
Summary:
it sounds like you needed a short gap between each spawn.
(BTW a useful trick is, learn how to wait until the next frame - check out many articles on it.)
All-time classic answer for this
https://stackoverflow.com/a/35228592/294884
get in to "chunking" for random algorthims
observe the handy line of code at "How to get sets of unique random numbers."
Enjoy
Unrelated issue -
Could it be you need to basically wait a small moment between spawning each cube?
For a time in unity it's very simply Invoke - your code pattern would look something like this:
Currently ...
for 1 to 100 .. spawn a cube
To have a pause between each ...
In Start ...
Call Invoke("_spawn", 1f)
and then
func _spawn() {
if count > 70 .. break
spawn a cube
Invoke("_spawn", 1f)
}
Similar example code - https://stackoverflow.com/a/36736807/294884
Even simpler - https://stackoverflow.com/a/35807346/294884
Enjoy
Related
So my movement isn't instant. You accelerate and decelerate. I use two floats starting at 0, going to 1, to calculate my movement speed. Here it is:
private void Update()
{
float moveTowardsX = 0;
float moveTowardsY = 0;
// Adjust thrust to be below max Thrust
if(thrust >= maxThrust)
{
thrust = maxThrust;
}
// Calculates movement speed based on all variables
float changeRatePerSecond = 1 / timeFromZeroToMax * thrust * Time.deltaTime;
changeRatePerSecond /= weight / 1.5f;
if(onMobile == true)
{
// Checks for Input. If Input is detected, values get changed [MOBILE]
if(isMovingLeft == true && isMovingRight == false)
{
moveTowardsX = -1.0f;
}
else if(isMovingRight == true && isMovingLeft == false)
{
moveTowardsX = 1.0f;
}
if(isMovingUp == true && isMovingDown == false)
{
moveTowardsY = 1.0f;
}
else if(isMovingDown == true && isMovingUp == false)
{
moveTowardsY = -1.0f;
}
if(isRotatingLeft == true && isRotatingRight == false)
{
rotationsPerSecond++;
}
else if(isRotatingRight == true && isRotatingLeft == false)
{
rotationsPerSecond--;
}
}
Then I parse these values into my movement code and smooth them out:
void FixedUpdate()
{
// Makes values change smoothly
valueX = Mathf.MoveTowards(valueX, moveTowardsX, changeRatePerSecond);
valueY = Mathf.MoveTowards(valueY, moveTowardsY, changeRatePerSecond);
// Turn numbers into movement
rb.velocity = new Vector2(valueX * runSpeed, valueY * runSpeed);
}
The Problem is that when I run into a wall, the numbers don't decrease. And if I run into a wall standing still, the numbers increase. Here is a video linking to my problem too (Look at the top right): https://youtu.be/XRE9p0yo4GA
What could I implement to fix this?
I have 2 suggestions, either one should work:
Do a ground check. Platformers often perform ground checks to check that the player is on the ground. By doing a box cast, you can check if your character is adjacent to a wall in the direction of their movement, and if they are, do not let them move (or gain speed) in that direction.
Here is a video for how to do a ground check. I would recommend the box cast method here, since it would give you the most accurate results:
https://www.youtube.com/watch?v=c3iEl5AwUF8
Use the rb.velocity of the object in a direction divided by your character's maximum speed in place of your valueX and valueY. So, use:
rb.velocity.x / runSpeed
instead of
valueX
and the same for y. This would take into account anything that changes the velocity of the object outside this script, such as crashing into a wall, but also if your character is thrown by some effect you create later on.
So i'm currently making a game, and i've recently added a level editor, but the placing tool does not work how i wanted it to.
https://youtu.be/MuUvnVTL6eg
If you've watched this video, you've probably realized that the block placing works pretty much how placing rectangles in ms pain with alt does, and i want it to work like placing rectangles in ms pain without alt xd.
I'm using this code to place the block:
if (Input.GetKeyDown(KeyCode.Mouse0)){
startDrawPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
tmpObj = spawnObject(blocks[selected].gameObject, startDrawPos);
drawing = true;
}
if (Input.GetKey(KeyCode.Mouse0)){
Vector2 mPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 tmpScale = new Vector2(startDrawPos.x - mPos.x, startDrawPos.y - mPos.y);
tmpObj.transform.localScale = tmpScale;
}
if (Input.GetKeyUp(KeyCode.Mouse0))
{
drawing = false;
var scale = tmpObj.transform.localScale;
//Code below destroys the object if it's too small to avoid accidental placements
if (scale.x <= 0.1 && scale.x > -0.1 || scale.y <= 0.1 && scale.y > -0.1)
{
Destroy(tmpObj);
}
}
(All of this code is in the Update() function)
(spawnObject function just instantiates the object prefab)
There is a bit more code but it has nothing to do with the position of the block, it just detect which block is selected and decides if it can be resized or not.
I solved this problem. But because your complete script is not in question, I rebuilt the code with IEnumerator, Here, by pressing the left mouse button, IEnumerator is activated and all commands are grouped in one method to make the code more efficient.
private void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0)) StartCoroutine(DrawRect());
}
How does the Desktop Rect formula work?
By running IEnumerator, the code first records the starting point of the mouse. It also makes a simple cube because I do not have access to your objects. Now until the mouse is pressed. Resize Rect to the difference between current and recorded points. The only thing is that to avoid ALT control, you have to place it between the current and initial points. The reason for adding the camera forward is to be seen in the camera.
cubeObject.transform.position = (startDrawPos + currentDrawPos) / 2;
The final structure of the DrawRect is as follows:
public IEnumerator DrawRect()
{
drawing = true;
var scale = Vector2.zero;
var startDrawPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var cubeObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
while (Input.GetKey(KeyCode.Mouse0))
{
var currentDrawPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
cubeObject.transform.position = (startDrawPos + currentDrawPos) / 2 + Camera.main.transform.forward * 10;
scale = new Vector2(startDrawPos.x - currentDrawPos.x, startDrawPos.y - currentDrawPos.y);
cubeObject.transform.localScale = scale;
yield return new WaitForEndOfFrame();
}
if (scale.x <= 0.1 && scale.x > -0.1 || scale.y <= 0.1 && scale.y > -0.1) Destroy(cubeObject);
drawing = false;
}
So, I want to implement dodging which is something like a dash in a direction. It works pretty simple - if you move in a direction and press the button the character dodges there. The velocity should not matter for this, so that the dodging speed is always the same, no matter if you are running or standing and then pressing a direction + dodging. I've got something that seems to work but there are still problems left. It seems like that diagonal dodging is a bit too far in comparison to horizontal/vertical dodging and I tried to fix this. But I'm still not sure if it worked or not. In practice the character dodges and stuff but something still seems off, like it works pretty good with a controller but sometimes it's still wacky when using keyboard after standing still and then using dodge. The thing is - I'm not sure if the code I have is the best I could have or if there are still problems.
A good example of how it should work would be the dodging in Rune Factory 4.
Edit: If the problem lies in the Input Settings I could show them.
if (IsDodging == false)
{
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (input.sqrMagnitude > 1) input.Normalize();
if (Input.GetButtonDown("Jump"))
{
// Jumping stuff I blended out
}
else if (Input.GetButtonDown("Block/Dodge") && input.x != 0 || Input.GetButtonDown("Block/Dodge") && input.z != 0) // To activate dodging
{
if (Time.time > Time_Start + DodgeCooldown) // Checks the cooldown
{
Time_Start = Time.time;
IsDodging = true;
dodge_direction = input;
if (dodge_direction.x > 0) { dodge_direction.x = 1; } // To fixate the distance traveled when dodging while ignoring how fast you are
if (dodge_direction.z > 0) { dodge_direction.z = 1; }
if (dodge_direction.x > 0 && dodge_direction.z > 0) { dodge_direction.Normalize(); } // To prevent diagonal dodging distance from becoming too big
}
}
else
{
// Walk.
transform.position += input * run_speed * Time.deltaTime;
}
}
else
{
transform.position += dodge_direction * dodge_distance;
distance_traveled += Vector3.Distance(transform.position, dodge_direction * dodge_distance) * Time.deltaTime;
if (distance_traveled > dodge_distance)
{
IsDodging = false;
dodge_direction = Vector3.zero;
distance_traveled = 0;
}
}
Ok, I got a series of cards that will move 1 by 1 to the center of the camera each and every time the user clicks.
StageContainer is the parent of all cards. This is the one that will move making it look like the cards are moving instead.
First I this is my code without smoothdamp
// Update is called once per frame
void Update () {
if(Input.GetMouseButtonDown(0)){
StartCoroutine ( ProcessFocus() );
frames++;
}
}
IEnumerator ProcessFocus() {
curPos = StageContainer.transform.localPosition;
nextPos = curPos;
nextPosX = nextPos.x - 400;
nextPos.x = nextPosX;
StageContainer.transform.localPosition = nextPos;
yield break;
}
The code above gives me the instant change of cards on the center of the camera. No transitions, animation whatsoever.. the point is it works.
Now when I change this :
StageContainer.transform.localPosition = nextPos;
to this :
float smoothTime = 0.3F;
Vector3 velocity = Vector3.zero;
StageContainer.transform.localPosition = Vector3.SmoothDamp(curPos, nextPos, ref velocity, smoothTime);
I assumed it will transition from current X point to next X point,
but every time I mouse click, it just move bit by bit like 10~20 X points
I have no clue why it behave like that. Please help.
Your coroutine needs to keep running until your movement is completed.
IEnumerator ProcessFocus() {
curPos = StageContainer.transform.localPosition;
nextPos = curPos;
nextPosX = nextPos.x - 400;
nextPos.x = nextPosX;
float smoothTime = 0.3F;
Vector3 velocity = Vector3.zero;
while(this.transform.position != nextPos) {
StageContainer.transform.localPosition = Vector3.SmoothDamp(curPos, nextPos, ref velocity, smoothTime);
yield return null;
}
}
That's because your code runs once and then exits the coroutine. To run the coroutine for some amount of time, you have to yield return some YieldInstruction instances. In this case, I suspect that you want to use WaitForEndOfFrame.
I am newbie in Unity platform. I have 2D game that contains 10 boxes vertically following each other in chain. When a box goes off screen, I change its position to above of the box at the top. So the chain turns infinitely, like repeating Parallax Scrolling Background.
But I check if a box goes off screen by comparing its position with a specified float value. I am sharing my code below.
void Update () {
offSet = currentSquareLine.transform.position;
currentSquareLine.transform.position = new Vector2 (0f, -2f) + offSet;
Vector2 vectorOne = currentSquareLine.transform.position;
Vector2 vectorTwo = new Vector2 (0f, -54f);
if(vectorOne.y < vectorTwo.y) {
string name = currentSquareLine.name;
int squareLineNumber = int.Parse(name.Substring (11)) ;
if(squareLineNumber < 10) {
squareLineNumber++;
} else {
squareLineNumber = 1;
}
GameObject squareLineAbove = GameObject.Find ("Square_Line" + squareLineNumber);
offSet = (Vector2) squareLineAbove.transform.position + new Vector2(0f, 1.1f);
currentSquareLine.transform.position = offSet;
}
}
As you can see, when I compare vectorOne.y and vectorTwo.y, things get ugly. Some boxes lengthen and some boxes shorten the distance between each other even I give the exact vector values in the code above.
I've searched for a solution for a week, and tried lots of codes like Mathf.Approximate, Mathf.Round, but none of them managed to compare float values properly. If unity never compares float values in the way I expect, I think I need to change my way.
I am waiting for your godlike advices, thanks!
EDIT
Here is my screen. I have 10 box lines vertically goes downwards.
When Square_Line10 goes off screen. I update its position to above of Square_Line1, but the distance between them increases unexpectedly.
Okay, I found a solution that works like a charm.
I need to use an array and check them in two for loops. First one moves the boxes and second one check if a box went off screen like below
public GameObject[] box;
float boundary = -5.5f;
float boxDistance = 1.1f;
float speed = -0.1f;
// Update is called once per frame
void Update () {
for (int i = 0; i < box.Length; i++) {
box[i].transform.position = box[i].transform.position + new Vector3(0, speed, 0);
}
for (int i = 0; i < box.Length; i++)
{
if(box[i].transform.position.y < boundary)
{
int topIndex = (i+1) % box.Length;
box[i].transform.position = new Vector3(box[i].transform.position.x, box[topIndex].transform.position.y + boxDistance, box[i].transform.position.z);
break;
}
}
}
I attached it to MainCamera.
Try this solution:
bool IsApproximately(float a, float b, float tolerance = 0.01f) {
return Mathf.Abs(a - b) < tolerance;
}
The reason being that the tolerances in the internal compare aren't good to use. Change the tolerance value in a function call to be lower if you need more precision.