I am generating small spheres between two given points. However it always generates in a straight line. Is it possible that the spheres can start generating as a curve from Point A to Point B?
I am defining how many spheres should be generated between two points by defining NumberOfSegments.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GeneratePoints : MonoBehaviour
{
public Transform PointA;
public Transform PointB;
public float NumberOfSegments = 3;
public float AlongThePath = .25f;
// Update is called once per frame
void Start()
{
StartCoroutine(StartSpheringOut());
}
IEnumerator StartSpheringOut()
{
NumberOfSegments += 1;// since we are skipping 1st placement since its the same as starting point we increase the number by 1
AlongThePath = 1 / (NumberOfSegments);//% along the path
for (int i = 1; i < NumberOfSegments; i++)
{
yield return new WaitForSeconds(0.05f);
Vector3 CreatPosition = PointA.position + (PointB.position - PointA.position) * (AlongThePath * i);
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = CreatPosition;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
}
}
This actually almost exists already in the Unity Manual:
using UnityEngine;
public class CircleFormation : MonoBehaviour
{
// Instantiates prefabs in a circle formation
public GameObject prefab;
public int numberOfObjects = 20;
public float radius = 5f;
void Start()
{
for (int i = 0; i < numberOfObjects; i++)
{
float angle = i * Mathf.PI * 2 / numberOfObjects;
float x = Mathf.Cos(angle) * radius;
float z = Mathf.Sin(angle) * radius;
Vector3 pos = transform.position + new Vector3(x, 0, z);
float angleDegrees = -angle*Mathf.Rad2Deg;
Quaternion rot = Quaternion.Euler(0, angleDegrees, 0);
Instantiate(prefab, pos, rot);
}
}
}
You can use this for starters and then adjust it to your needs in order to make it work in general. So since you only want the half of the circle all you have to do is basically to devide the angle by 2 and add the pointA and pointB in order to calculate the center position. Also if both positions are not in the same XZ-plane you would have to rotate the entire circle:
public GameObject A;
public GameObject B;
public int amount;
[ContextMenu("PlaceSpheres()")]
privtae void DebugPlace()
{
PlaceSpheres(A.transform.position, B.transform.position, amount);
}
public void PlaceSpheres(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
for (var i = 0; i < numberOfObjects; i++)
{
// Max angle is 180° (= Mathf.PI in rad) not 360° (= Mathf.PI * 2 in rad)
// |
// | don't place the first object exactly on posA
// | but start already with an offset
// | (remove the +1 if you want to start at posA instead)
// | |
// | | don't place the last object on posB
// | | but end one offset before
// | | (remove the +1 if you want to end
// | | exactly a posB instead)
// | | |
// V V V
var angle = Mathf.PI * (i + 1) / (numberOfObjects + 1f);
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = centerPos + pos;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
}
Related
Hello there everybody!
I'm trying to create a variable where you can put the number of points and the radius of a circle, and it will divide those points uniformly around the circle.
I'm trying to not use the Euler angles to set rotation or the Rotate or RotateAroundmethods.
But I am not having success...
These are how my code looks until the moment
public class PowerUps : MonoBehaviour
{
public GameObject PowerUpPrefab;
public GameObject Player;
public int PowerUpCount = 3;
public float PowerUpRadius = 1;
public Vector3 newPowerupSpace;
public GameObject[] SpawnPowerUps()
{
//get player positin
Vector3 anchorPoint = Player.transform.position;
GameObject[] SpawnPowerUps = new GameObject[PowerUpCount];
float angleStep = Mathf.HalfToFloat((ushort)(360.0 / PowerUpCount));
for (int i = 0; i < PowerUpCount; i++)
{
float theta = i * angleStep;
newPowerupSpace.x = anchorPoint.x + (PowerUpRadius * Mathf.Cos(theta));
newPowerupSpace.y = anchorPoint.y + (PowerUpRadius * Mathf.Sin(theta));
SpawnPowerUps[i] = (GameObject)Instantiate(PowerUpPrefab, newPowerupSpace, Quaternion.identity);
}
return null;
}
}
Any suggestions?
Update:
I changed the
float angleStep = Mathf.HalfToFloat((ushort)(360.0 / PowerUpCount));
to
float angleStep = ((ushort)((360.0 / PowerUpCount) * Mathf.Deg2Rad));
and now is working.
I feel kind of stupid....
Update 2:
After doing some tests, I notice that some numbers don't divide uniformly across the circle. That's because I was converting de circle degrees to radium in the wrong part.
Here's how the code looks like now:
public GameObject[] SpawnPowerUps()
{
Vector3 anchorPoint = Player.transform.position;
GameObject[] SpawnPowerUps = new GameObject[PowerUpCount];
float angleStep = ((ushort)(360.0 / PowerUpCount));
for (int i = 0; i < PowerUpCount; i++)
{
float theta = i * angleStep;
newPowerupSpace.x = anchorPoint.x + (PowerUpRadius * Mathf.Cos(theta * Mathf.Deg2Rad));
newPowerupSpace.y = anchorPoint.y + (PowerUpRadius * Mathf.Sin(theta * Mathf.Deg2Rad));
SpawnPowerUps[i] = (GameObject)Instantiate(PowerUpPrefab, newPowerupSpace, Quaternion.identity);
}
return null;
}
}
To describe a circle in 3D it should have three parameters, Position, Radius and Normal. The normal can be used to create a transform from the unit circle to the 3D space. This example uses instance methods instead of static methods for cross/dot/normalization, but it should be trivial to translate to the unity3D conventions.
First we create an arbitrary vector that is orthogonal to the normal:
public static Vector3 GetOrthogonal(this Vector3 self)
{
self = self.Normalized();
if (Math.Abs(self.Dot(Vector3.UnitX)) > 0.9)
{
return self.Cross(Vector3.UnitY).Normalized();
}
return self.Cross(Vector3.UnitX).Normalized();
}
We can then create two vectors that are orthogonal to the normal, and to each other:
var xAxis = normal.GetOrthogonal();
var yAxis = xAxis.Cross(normal).Normalized();
Getting the points is then simply multiplying each coordinate with the corresponding axis:
var x = xAxis * (PowerUpRadius * Mathf.Cos(theta * Mathf.Deg2Rad));
var y = yAxis * (PowerUpRadius * Mathf.Sin(theta * Mathf.Deg2Rad));
newPowerupSpace = x + y + anchorPoint;
You could do the same by producing a transform matrix and transforming the 2D points. But this is fairly simple to do.
In relation to the question asked here (How to place spheres in a half circle shape between 2 points) that generates spheres between two points A and B.
How do I create just one sphere that moves from Point A to Point B and then back from Point B to Point A in a loop cycle? How do I use Lerp in this context?
I have tried making the sphere move in the angle (half circle) described in the below code but it always moves in a straight line.
The below code generates spheres between two points.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
[ContextMenu("PlaceSpheres()")]
public void Start()
{
PlaceSpheres(A.transform.position, B.transform.position, amount);
}
public void PlaceSpheres(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i+1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = centerPos + pos;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
}
}
The below script I had created that makes an object move between two points in a loop but only in a straight line. How do I make it move in a curve (180 degrees)?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RunInLoop : MonoBehaviour
{
public float speed = 0.25f;
public Transform PointA;
public Transform PointB;
private Vector3 origin;
private bool backToOrigin;
void Start()
{
transform.position = PointA.transform.position;
origin = transform.position;
}
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, backToOrigin ? origin : PointB.transform.position, speed * Time.deltaTime);
// if one of the two positions is reached invert the flag
if (transform.position == PointB.transform.position || transform.position == origin)
{
backToOrigin = !backToOrigin;
}
}
}
Solution using your code
As I told you in my last answer that provided your first code you should store them in a list and then make the object move between them:
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
public float moveSpeed;
private List<Vector3> positions = new List<Vector3>();
private Transform sphere;
private int currentIndex = 0;
private bool movingForward = true;
private void Start()
{
sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
GeneratePositions(A.transform.position, B.transform.position, amount);
sphere.position = positions[0];
}
private void GeneratePositions(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i + 1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
// store them in a list this time
positions.Add(centerPos + pos);
}
}
private void Update()
{
if (positions == null || positions.Count == 0) return;
// == for Vectors works with precision of 0.00001
// if you need a better precision instead use
//if(!Mathf.Approximately(Vector3.Distance(sphere.position, positions[currentIndex]), 0f))
if (sphere.position != positions[currentIndex])
{
sphere.position = Vector3.MoveTowards(sphere.transform.position, positions[currentIndex], moveSpeed * Time.deltaTime);
return;
}
// once the position is reached select the next index
if (movingForward)
{
if (currentIndex + 1 < positions.Count)
{
currentIndex++;
}
else if (currentIndex + 1 >= positions.Count)
{
currentIndex--;
movingForward = false;
}
}
else
{
if (currentIndex - 1 >= 0)
{
currentIndex--;
}
else
{
currentIndex++;
movingForward = true;
}
}
}
}
If you want to stick to Single-Responsibility-Principles you could also seperate the movement from the list generation like e.g.
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
public float moveSpeed;
private void Start()
{
GeneratePositions(A.transform.position, B.transform.position, amount);
}
private void GeneratePositions(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
List<Vector3> positions = new List<Vector3>();
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i + 1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
// store them in a list this time
positions.Add(centerPos + pos);
}
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
var movement = sphere.AddComponent<MoveBetweenPoints>();
movement.positions = positions;
movement.moveSpeed = moveSpeed;
}
and in a seperate script
public class MoveBetweenPoints : MonoBehaviour
{
public List<Vector3> positions = new List<Vector3>();
public float moveSpeed;
privtae bool movingForward = true;
private int currentIndex = 0;
private void Update()
{
if (positions == null || positions.Count == 0) return;
// == for Vectors works with precision of 0.00001
// if you need a better precision instead use
//if(!Mathf.Approximately(Vector3.Distance(sphere.position, positions[currentIndex]), 0f))
if (sphere.position != positions[currentIndex])
{
transform.position = Vector3.MoveTowards(transform.position, positions[currentIndex], moveSpeed * Time.deltaTime);
return;
}
// once the position is reached select the next index
if (movingForward)
{
if (currentIndex + 1 < positions.Count)
{
currentIndex++;
}
else if (currentIndex + 1 >= positions.Count)
{
currentIndex--;
movingForward = false;
}
}
else
{
if (currentIndex - 1 >= 0)
{
currentIndex--;
}
else
{
currentIndex++;
movingForward = true;
}
}
}
}
Actual Solution
However, if you want a smooth movement on a circle curve ... why even reduce that circle curcve to a certain amount of positions? You could directly moove according to the angle between 0° and 180° like this:
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
// now in Angles per second
public float moveSpeed;
private Transform sphere;
private bool movingForward = true;
private float angle;
private void Start()
{
sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
private void Update()
{
if (movingForward)
{
angle += moveSpeed * Time.deltaTime;
}
else
{
angle -= moveSpeed * Time.deltaTime;
}
if (angle < 0)
{
angle = 0;
movingForward = true;
}
else if (angle > 180)
{
angle = 180;
movingForward = false;
}
// get circle center and radius
var radius = Vector3.Distance(A.transform.position, B.transform.position) / 2f;
var centerPos = (A.transform.position + B.transform.position) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((B.transform.position - A.transform.position).normalized);
var x = Mathf.Sin(angle * Mathf.Deg2Rad) * radius;
var z = Mathf.Cos(angle * Mathf.Deg2Rad) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
sphere.position = centerPos + pos;
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
[RequireComponent(typeof(LineRenderer))]
public class DrawCircle : MonoBehaviour
{
[Range(0, 50)]
public int segments = 50;
public bool circle = true;
[Range(0, 50)]
public float xradius = 15;
[Range(0, 50)]
public float yradius = 15;
LineRenderer line;
public float drawSpeed = 0.3f;
void Start()
{
line = gameObject.GetComponent<LineRenderer>();
line.positionCount = (segments + 1);
line.useWorldSpace = false;
StartCoroutine(CreatePoints());
}
IEnumerator CreatePoints()
{
if (circle == true)
yradius = xradius;
float x;
float z;
float change = 2 * (float)Math.PI / segments;
float angle = change;
x = Mathf.Sin(angle) * xradius;
line.SetPosition(0, new Vector3(x, 0.5f, 0));
for (int i = 1; i < (segments); i++)
{
x = Mathf.Sin(angle) * xradius;
z = Mathf.Cos(angle) * yradius;
yield return new WaitForSeconds(drawSpeed);
line.SetPosition((int)i, new Vector3(x, 0.5f, z));
angle += change;
}
}
}
When running the game I want it will start with one line at index 0 drawn already from the center of the circle. So I added this two lines before the loop:
x = Mathf.Sin(angle) * xradius;
line.SetPosition(0, new Vector3(x, 0.5f, 0));
Before adding this two lines the for loop was:
for (int i = 0; i < (segments + 1); i++)
But now I want to start the loop from index 1 so I tried:
for (int i = 1; i < (segments); i++)
But it's not making a complete round:
The circle drawing is moving in the clockwise direction.
You're not starting at 0
float angle = change; //change is > 0
I'll assume change is 10 degrees for the purposes of explanation.
Because of this, your first segment goes from the center, to a point 10 degrees around the circle.
You should do it like this:
float angle = 0;
x = Mathf.Sin(angle) * xradius;
line.SetPosition(0, new Vector3(x, 0.5f, 0));
So I have this function attached to a sphere and in inverse one attached to another. They are rigged to emit a trail and the game object they are orbiting around is a sphere which is locked at 0,0,0. This is my code so far :
float t = 0;
float speed;
float width;
float height;
int cat = 0;
public GameObject orbit; //the object to orbit
// Use this for initialization
void Start()
{
speed = 2;
width = 5;
height = 2;
InvokeRepeating("test", .001f, .009f);
}
void test()
{
t += Time.deltaTime * speed;
Vector3 orbitv = orbit.transform.position;
float inc = .0000000001f;
inc += inc + t;
float angmom = .00001f;
angmom = angmom + t;
float x = orbitv.x + Mathf.Cos(t);
float z = orbitv.z + inc; //+ (Mathf.Cos(t)*Mathf.Sin(t));
float y = orbitv.y + Mathf.Sin(t);
transform.position = new Vector3(x, y, z);
}}
Which does this:
Instead of moving in the z direction exclusively, I'd like them to continue their current rotation, but in a circle around the sphere at 0,0,0 while staying at the same y level. Anyone have any ideas how I can do this?
Edit: This is what I'm trying to do:
Here's some code I cooked up for you.
All the movement is achieved with basic trigonometric functions and some easy vector math.
To tackle problems like these, try to break things down like I did with dividing the movement into circular/up-down & sideways. This lets you create the ring effect.
Adding more intertwined waves should be trivial by changing the phase of the oscillations and adding more trails.
public class Orbit : MonoBehaviour {
float t = 0;
public float speed;
public float width;
public float height;
public float radius;
int cat = 0;
public GameObject center; //the object to orbit
public Vector3 offset;
void Update()
{
t = Time.time * speed;
OrbitAround();
AddSideways();
}
void OrbitAround()
{
float x = radius * Mathf.Cos(t);
float y = Mathf.Sin(offset.y * t);
float z = radius * Mathf.Sin(t);
transform.position = new Vector3(x, y, z);
}
void AddSideways()
{
float x = Mathf.Cos(offset.x * t);
Vector3 dir = transform.position - center.transform.position;
transform.position += dir.normalized * x;
}
}
It should give you a trail like this:
You can play around with the Vec3 offset which changes the frequency of the oscillations and essentially lets you choose the number of rings.
When it's square formation i can set the space fine.
But i'm not sure how to do it with the circle formation.
Inside the FormationSquare method i'm using the space variable for the square formation. Now i need to do the same idea for the circle formation too.
Maybe inside the RandomCircle to change something and using the space variable ?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SquadFormation : MonoBehaviour
{
enum Formation
{
Square, Circle
}
public Transform squadMemeber;
public int columns = 4;
public int space = 10;
public int numObjects = 20;
public float yOffset = 1;
private Formation formation;
// Use this for initialization
void Start()
{
formation = Formation.Square;
ChangeFormation();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
GameObject[] objects = GameObject.FindGameObjectsWithTag("Squad Member");
if (objects.Length > 0)
{
foreach (GameObject obj in objects)
Destroy(obj);
}
ChangeFormation();
}
}
private void ChangeFormation()
{
switch (formation)
{
case Formation.Square:
for (int i = 0; i < 23; i++)
{
Transform go = Instantiate(squadMemeber);
Vector3 pos = FormationSquare(i);
go.position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
go.Rotate(new Vector3(0, -90, 0));
go.tag = "Squad Member";
}
formation = Formation.Circle;
break;
case Formation.Circle:
Vector3 center = transform.position;
for (int i = 0; i < numObjects; i++)
{
Vector3 pos = RandomCircle(center, 5.0f);
var rot = Quaternion.LookRotation(center - pos);
pos.y = Terrain.activeTerrain.SampleHeight(pos);
pos.y = pos.y + yOffset;
Transform insObj = Instantiate(squadMemeber, pos, rot);
insObj.rotation = rot;
insObj.tag = "Squad Member";
}
formation = Formation.Square;
break;
}
}
Vector2 FormationSquare(int index) // call this func for all your objects
{
float posX = (index % columns) * space;
float posY = (index / columns) * space;
return new Vector2(posX, posY);
}
Vector3 RandomCircle(Vector3 center, float radius)
{
float ang = Random.value * 360;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.z = center.z + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.y = center.y;
return pos;
}
}
The issue here is that your RandomCircle() method is...well, random. You're just arbitrarily grabbing an angle around the circle, without any regard for where other squad members have been placed, meaning they'll practically never be evenly distributed.
Consider calculating the angle that will separate each squad member (if distributed evenly) beforehand, then perform a similar approach to FormationSquare() where you iterate through the squad to distribute them.
This may be a bit closer to what you want (basing this on the assumption that RandomCircle() already works as designed):
Vector3 FormationCircle(Vector3 center, float radius, int index, float angleIncrement)
{
float ang = index * angleIncrement;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.z = center.z + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.y = center.y;
return pos;
}
To call it, you'd do:
Vector3 center = transform.position;
float radius = (float)space / 2;
float angleIncrement = 360 / (float)numObjects;
for (int i = 0; i < numObjects; i++)
{
Vector3 pos = FormationCircle(center, radius, i, angleIncrement);
// ...
}
Hope this helps! Let me know if you have any questions.