Unity Follow The Leader Behavior - c#

I asked this question two years ago. Not ever having success, I abandoned the idea until recently.
I have since been able to semi-fix / replicate the mechanic. However, all the objects seem to jump to their next position, with some duplicating their "leader's" position.
The orange is the head, with the body parts being green.
As you can see from the commented out code below, I have tried multiple permutations to get the children to follow their leader smoothly with the distance between each body-part just being the circle colliders radius.
My thought was, if the "leader" has moved the distance of the radius, then the follower can move towards the leaders old position. This give the leader time to move.
But the only one that seems to semi work, is the un-commented one.
Can anyone see the problem?
FollowTheLeader.cs
public class FollowTheLeader : MonoBehaviour
{
[Header("Head")]
public GameObject bodyPart;
public int bodyLength = 6;
[Header("Move Speed")]
[Range(0.25f, 2.0f)] public float moveMin = 0.5f;
[Range(0.25f, 2.0f)] public float moveMax = 2.0f;
[Header("Change Directions")]
[Range(0.25f, 2.0f)] public float changeMin = 0.5f;
[Range(0.25f, 2.0f)] public float changeMax = 2.0f;
[SerializeField]
private Vector2 oldPosition;
public Vector2 OldPosition { get => oldPosition; set => oldPosition = value; }
[SerializeField]
private Vector2 moveDirection = new Vector2(0, -1);
public Vector2 MoveDirection { get => moveDirection; set => moveDirection = value; }
[Header("Child")]
public int index;
public bool isChild;
public FollowTheLeader leader;
public float leaderDistance;
private CircleCollider2D m_collider2D;
private Rigidbody2D body2d;
private float moveSpeed;
private float moveTimePassed;
private float changeDirInterval;
private void Awake()
{
m_collider2D = GetComponent<CircleCollider2D>();
body2d = GetComponent<Rigidbody2D>();
AddBodyParts();
DefineDirection(moveDirection);
}
private void AddBodyParts()
{
if (isChild || bodyPart == null)
return;
//The head will generate its body parts. Each body part will have reference to the one before it.
FollowTheLeader temp = this;
for (int i = 1; i <= bodyLength; i++)
{
GameObject bp = Instantiate(bodyPart, transform);
bp.transform.SetParent(null);
//bp.transform.position = transform.position;
bp.transform.position = new Vector2(i * m_collider2D.radius, 0);
bp.name = $"Body {i}";
FollowTheLeader c = bp.AddComponent<FollowTheLeader>();
c.isChild = true;
c.index = i;
c.OldPosition = bp.transform.position;
c.leader = temp;
// cache the parent for the next body part
temp = c;
}
}
private void Start()
{
OnNewDirection();
}
private void FixedUpdate()
{
//Store the old postion for the next child
OldPosition = body2d.position;
// If child
if (isChild)
{
// Calculate the leaders distance
leaderDistance = Vector2.Distance(OldPosition, leader.OldPosition);
// We only want to move if the parent is as far away as the m_collider2D.radius.
if (leaderDistance < m_collider2D.radius)
return;
// BARELY ANY MOVEMENT
//body2d.MovePosition(leader.OldPosition.normalized);
//body2d.MovePosition(leader.OldPosition.normalized * moveSpeed);
//body2d.MovePosition(leader.OldPosition.normalized * moveSpeed * Time.deltaTime);
//body2d.MovePosition(leader.OldPosition.normalized * parentDistance * moveSpeed * Time.deltaTime);
//body2d.MovePosition(leader.OldPosition.normalized * m_collider2D.radius * parentDistance * moveSpeed * Time.deltaTime);
//FLYS ALL OVER THE PLACE
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized);
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized * moveSpeed);
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized * parentDistance * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized * m_collider2D.radius * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition.normalized * m_collider2D.radius * parentDistance * moveSpeed * Time.deltaTime);
// BARELY ANY MOVEMENT
//body2d.MovePosition(leader.OldPosition * moveSpeed);
//body2d.MovePosition(leader.OldPosition * moveSpeed * Time.deltaTime);
//body2d.MovePosition(leader.OldPosition * parentDistance * moveSpeed * Time.deltaTime);
//body2d.MovePosition(leader.OldPosition * m_collider2D.radius * parentDistance * moveSpeed * Time.deltaTime);
//FLYS ALL OVER THE PLACE
//body2d.MovePosition(body2d.position + leader.OldPosition);
//body2d.MovePosition(body2d.position + leader.OldPosition * moveSpeed);
//body2d.MovePosition(body2d.position + leader.OldPosition * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition * parentDistance * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition * m_collider2D.radius * moveSpeed * Time.deltaTime);
//body2d.MovePosition(body2d.position + leader.OldPosition * m_collider2D.radius * parentDistance * moveSpeed * Time.deltaTime);
// KINDA FOLLOWS BUT ALL SEEM TO JUMP INTO THE SAME POSITION AS SEEN IN THE GIF
body2d.MovePosition(leader.OldPosition);
return;
}
// HEAD ONLY
// Countdown to next direction change
moveTimePassed += Time.deltaTime;
if (moveTimePassed >= changeDirInterval)
{
OnNewDirection();
}
// Calculate the next position
body2d.MovePosition(body2d.position + MoveDirection.normalized * moveSpeed * Time.deltaTime);
}
public void OnNewDirection()
{
moveTimePassed = 0;
moveSpeed = Random.Range(moveMin, moveMax);
changeDirInterval = Random.Range(changeMin, changeMax);
RandomDirection();
}
private void RandomDirection()
{
switch (Random.Range(0, 4))
{
case 0:
DefineDirection(Vector2.up);
break;
case 1:
DefineDirection(Vector2.right);
break;
case 2:
DefineDirection(Vector2.down);
break;
case 3:
DefineDirection(Vector2.left);
break;
default:
DefineDirection(Vector2.down);
break;
}
}
public void DefineDirection(Vector2 direction)
{
if (direction.Equals(Vector2.up))
{
MoveDirection = Vector2.up;
}
if (direction.Equals(Vector2.down))
{
MoveDirection = Vector2.down;
}
if (direction.Equals(Vector2.left))
{
MoveDirection = Vector2.left;
}
if (direction.Equals(Vector2.right))
{
MoveDirection = Vector2.right;
}
}
}

Many different ways you can approach it but let me show you one way.
Snake - moves the leader forward, creates new points in the path, manages minions
Path - ring buffer with of all the points
Minion - follow the path based on the distance from the leader
Here's an example with gizmos showing:
Green is the leader
Red is the head of the path
Blue is the tail of the path
The snake is where the main logic is at.
The snake moves forward automatically. When the distance between the leader and the last point is greater than RADIUS we create a new point. We then move all of the minions along the path of points.
public class Snake : MonoBehaviour
{
public const float RADIUS = 1f; // distance between minions
public const float MOVE_SPEED = 1f; // movement speed
public Vector2 dir = Vector2.up; // movement direction
public float headDist = 0f; // distance from path 'head' to leader (used for lerp-ing between points)
public Path path = new Path(1); // path points
public List<Minion> minions = new List<Minion>(); // all minions
public Minion Leader => minions[0];
void Awake()
{
path.Add(this.transform.position);
AddMinion(new Knight());
}
void AddMinion(Minion minion)
{
// Initialize a minion and give it an index (0,1,2) which is used as offset later on
minion.Init(minions.Count);
minions.Add(minion);
minion.MoveOnPath(path, 0f);
// Resize the capacity of the path if there are more minions in the snake than the path
if (path.Capacity <= minions.Count) path.Resize();
}
void FixedUpdate()
{
MoveLeader();
MoveMinions();
}
void MoveLeader()
{
// Move the first minion (leader) towards the 'dir'
Leader.transform.position += ((Vector3)dir) * MOVE_SPEED * Time.deltaTime;
// Measure the distance between the leader and the 'head' of that path
Vector2 headToLeader = ((Vector2)Leader.transform.position) - path.Head().pos;
// Cache the precise distance so we can reuse it when we offset each minion
headDist = headToLeader.magnitude;
// When the distance between the leader and the 'head' of the path hits the threshold, spawn a new point in the path
if (headDist >= RADIUS)
{
// In case leader overshot, let's make sure all points are spaced exactly with 'RADIUS'
float leaderOvershoot = headDist - RADIUS;
Vector2 pushDir = headToLeader.normalized * leaderOvershoot;
path.Add(((Vector2)Leader.transform.position) - pushDir);
// Update head distance as there is a new point we have to measure from now
headDist = (((Vector2)Leader.transform.position) - path.Head().pos).sqrMagnitude;
}
}
void MoveMinions()
{
float headDistUnit = headDist / RADIUS;
for (int i = 1; i < minions.Count; i++)
{
Minion minion = minions[i];
// Move minion on the path
minion.MoveOnPath(path, headDistUnit);
// Extra push to avoid minions stepping on each other
Vector2 prevToNext = minions[i - 1].transform.position - minion.transform.position;
float distance = prevToNext.magnitude;
if (distance < RADIUS)
{
float intersection = RADIUS - distance;
minion.Push(-prevToNext.normalized * RADIUS * intersection);
}
}
}
}
Path is a ring buffer, Head() gives you the newest point that was added, you can use Head(index) to get the head and offset it in a direction(+/-). Minions use it to fetch points that are just behind the head: path.Head(-1).
public class Path
{
public Vector2[] Points { get; private set; }
public int Capacity => Points.Length;
int head;
public Path(int capacity)
{
head = 0;
Points = new Vector2[capacity];
}
public void Resize()
{
Vector2[] temp = new Vector2[Capacity * 2];
for (int i = 0; i < temp.Length; i++)
{
temp[i] = i < Capacity ? Head(i + 1) : Tail();
}
head = Capacity - 1;
Points = temp;
}
public void Add(Vector2 pos)
{
int prev = Mod(head, Capacity);
Next();
int next = Mod(head, Capacity);
Points[next].pos = pos;
}
public Vector2 Head()
{
return Points[head];
}
public Vector2 Head(int index)
{
return Points[Mod(head + index, Capacity)];
}
public Vector2 Tail()
{
return Points[Mod(head + 1, Capacity)];
}
public Vector2 Tail(int index)
{
return Points[Mod(head + 1 + index, Capacity)];
}
void Next()
{
head++;
head %= Capacity;
}
int Mod(int x, int m)
{
return (x % m + m) % m;
}
}
A minion contains an index, which tells us the placement of the minion within the snake (first, second, third). We use this index to get the two points needed for interpolation. path.Head(-0) will give us the leader's point. path.Head(-1) will give us the first minion's point.
public class Minion : MonoBehaviour
{
int index;
public Init(int index)
{
this.index = index;
}
// Move the minion along the path
public void MoveOnPath(Path path, float dist)
{
Vector2 prev = path.Head(-index);
Vector2 next = path.Head(-index + 1);
// Interpolate the position of the minion between the previous and the next point within the path. 'dist' is the distance between the 'head' of the path and the leader
this.transform.position = Vector2.Lerp(prev.pos, next.pos, dist);
}
// Push the minion to avoid minions stepping on each other
public void Push(Vector2 dir)
{
this.transform.position += (Vector3)dir;
}
}
I've stripped out a lot of code to make the example simpler. I hope you get the basic idea and will be able to implement your own solution.

I tried recreating this scenario in 3D and got somewhat the same behaviour as yours.
First, you want to use twice the radius, as each child would otherwise overlap half of the parent since it is the center of the circle that is being moved.
I used a different approach to move the children than you did. The result is a smooth, snake-like motion:
The code is simple enough:
First I rotate the child to point its forward axis at the leader's position
Second the length from the current position to the desired position is calculated. I subtract twice the radius, as the new position would otherwise result in overlapping spheres
I use the translate function to move the GameObject in the forward direction using the calculated magnitude.
// KINDA FOLLOWS BUT ALL SEEM TO JUMP INTO THE SAME POSITION AS SEEN IN THE GIF
//body2d.MovePosition(leader.OldPosition);
transform.LookAt(leader.transform);
float length = leaderDistance - (m_collider2D.radius * 2);
transform.Translate(transform.forward * length, Space.World);
return;
The result is a smooth, predictable motion. You can even turn off kinematic on the rigidbody to enable collisions.
Hope that helped you.

Related

How to rotate around an object without using unity's built-in functions?

i want to rotate a cube around a 1x1 pipe with arrow keys. (left and right).
The problem is i cannot use built-in functions which sets transform's position and location directly. (Such as transform.lookAt, transform.Rotate or transform.RotateAround). Because I need the vector values of rotation's euler and position for multiple stuff before i modify the value of the transform i want to rotate.
I tried different techniques but no luck so far.
I tried using sin-cos for rotating but could not figure out how to make it work for both rotation and position.
_timer += Time.deltaTime * _larvaSpeed;
float x = -Mathf.Cos(_timer) * distanceBetweenCenter;
float y = Mathf.Sin(_timer) * distanceBetweenCenter;
Here is what i want to achieve. By pressing right or left, move and rotate the object around the pipe.
The result i want. (If i pressed right arrow key a litte bit).
I would appreciate any help. Thank you!
here is the solution using circle mathematics and I strongly recommended not use it, it's just to understand the circular move using circle equation as #FaTaLL ask in the comments
Circle equation...
(x1 - x2)^2 + (y1 - y2)^2 = r^2
x1, y1 is the cube position
x2, y2 is the pipe position
r is the distance between cube and pipe;
using UnityEngine;
public class Rotating : MonoBehaviour
{
public GameObject pipe;
public float Delta;
Vector3 nextpos;
bool compareY;
bool next;
int switchx;
float storeVarAxis;
float x, y, r;
private void Start()
{
next = true;
switchx = 1;
compareY = true;
x = transform.position.x - pipe.transform.position.x;
y = transform.position.y - pipe.transform.position.y;
storeVarAxis = y;
r = Mathf.Sqrt(x * x + y * y);
}
private void Update()
{
if (next)
{
if (compareY == true)
{
y -= Delta * Time.deltaTime;
if (y <= -storeVarAxis)
{
y = -storeVarAxis;
compareY = false;
switchx = -1;
}
}
else
{
y += Delta * Time.deltaTime;
if (y >= storeVarAxis)
{
y = storeVarAxis;
compareY = true;
switchx = 1;
}
}
float v = r * r - y * y;
x = Mathf.Sqrt(Mathf.Abs(v));
nextpos = new Vector3(pipe.transform.position.x + x * switchx, pipe.transform.position.y + y, transform.position.z);
next = false;
}
transform.position = Vector3.MoveTowards(transform.position, nextpos, 1f * Time.deltaTime);
if(Vector3.Distance(transform.position, nextpos) < .05) transform.position = nextpos;
if (transform.position.x.Equals(nextpos.x) && transform.position.y.Equals(nextpos.y)) next = true;
}
}
well, the recommended way is using this simple script
using UnityEngine;
public class Rotating : MonoBehaviour
{
public float speed;
public GameObject pipe;
float r, angle;
Vector3 startpos;
private void Start()
{
r = Mathf.Abs(transform.position.y - pipe.transform.position.y);
angle = 0;
transform.position = pipe.transform.position;
startpos = transform.position;
}
void Update()
{
angle = angle + speed * Time.deltaTime;
transform.rotation = Quaternion.EulerAngles(0,0, angle);
transform.position = startpos + (transform.rotation * new Vector3(r, 0, 0));
}
}
I think Quaternion * Vector3 is what you are looking for. Luckily the box's rotation in its own local coordinates is the same rotation you need to apply to the box's position.
public float speed; //how fast to rotate
public float radius; //radius of the cylinder
public float angle; //angle around it
void Update()
{
if (Input.GetKey(KeyCode.LeftArrow))
{
angle = angle + speed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.RightArrow))
{
angle = angle - speed * Time.deltaTime;
}
//figure out the rotation (from euler angles i guess??)
var quat = Quaternion.EulerAngles(new Vector3(0, angle, 0));
//ok uh what is the box position?? lets just multiply
var unrotated_position = new Vector3(radius, 0, 0);
var rotated_position = quat * unrotated_position;
this.transform.position = rotated_position;
//oh yea and also rotate the box in its own local coordinates
this.transform.rotation = quat;
}

How can I add equal gap between gameobjects?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DropDown : MonoBehaviour
{
public GameObject dropdownPrefab;
public int numberOfObjects;
public float speed = 1.5f;
public float duration = 5f;
public Vector3 startPos;
public Vector3 endPos;
public float distanceToMove = 2f;
public float gap;
private List<GameObject> dropDownObjects = new List<GameObject>();
private List<Vector3> endPositions = new List<Vector3>();
private void Start()
{
for (int i = 0; i < numberOfObjects; i++)
{
dropDownObjects.Add(Instantiate(dropdownPrefab, transform.parent));
endPositions.Add(new Vector3(dropDownObjects[i].transform.position.x,
dropDownObjects[i].transform.position.y + i + 2,
dropDownObjects[i].transform.position.z) - Vector3.up * distanceToMove);
}
startPos = dropDownObjects[0].transform.position;
}
private void OnMouseDown()
{
StartCoroutine(StartDropObjects());
}
private IEnumerator StartDropObjects()
{
for (int i = 0; i < dropDownObjects.Count; i++)
{
// Random wait period before rotation starts
if (i == 0)
{
//yield return new WaitForSeconds(0);
}
else
{
//yield return new WaitForSeconds(Random.Range(0, 2f));
}
yield return new WaitForSeconds(2f);
StartCoroutine(Drop(dropDownObjects[i].transform, duration, endPositions[i]));
}
}
private IEnumerator Drop(Transform objectToDrop, float duration, Vector3 endpos)
{
float t = 0.0f;
while (t < duration)
{
t += Time.deltaTime;
objectToDrop.transform.position = Vector3.MoveTowards(objectToDrop.transform.position, endpos, speed * Time.deltaTime);
yield return null;
}
}
}
The objects should be moving down but when I'm adding the gap in this case the gap is i + 2 for example if I = 0 then the gap is 2 then I = 1 so the gap will be 3 then I = 2 the gap is 4.
endPositions.Add(new Vector3(dropDownObjects[i].transform.position.x,
dropDownObjects[i].transform.position.y + i + 2,
dropDownObjects[i].transform.position.z) - Vector3.up * distanceToMove);
But I want to have equal gaps between the objects. For example if I will set the gap value to 1 there will be equal spaces/gaps between the objects and if the gap value is 5 then equal 5 between each two objects.
The problem now is that I getting higher each time by 1 so the gaps are not equal.
And also the objects are moving up and not down when I'm doing: + i + 2
In the screenshot the objects moved up instead down and the top two objects seems too close to each other. And the gaps are not equal.
But the idea is when the objects are moving to the target destination endPositions that each one will move to his on endPosition depending on the gap.
So in the end the objects should be like this :
The first one is the one on the most bottom. The last one is the one on the top.
The first dropDownObjects[0] object moved to his endPosition the farest endPosition. In this case distanceToMove is set to 2.
Then dropDownObjects1 is moving to his distanceToMove - the gap. And so on. This is the logic that in the end after all objects move they will have equal gaps.
And the first moved object is the one that should get to the distnaceToMove.
Problem is not here:
endPositions.Add(new Vector3(dropDownObjects[i].transform.position.x,
dropDownObjects[i].transform.position.y + i + 2,
dropDownObjects[i].transform.position.z) - Vector3.up * distanceToMove);
But here:
private IEnumerator Drop(Transform objectToDrop, float duration, Vector3 endpos)
{
float t = 0.0f;
while (t < duration)
{
t += Time.deltaTime;
objectToDrop.transform.position = Vector3.MoveTowards(objectToDrop.transform.position, endpos, speed * Time.deltaTime);
yield return null;
}
}
Because you are using the same duration for all objects, but they have a different distance. The last objects just don't have time to finish their movement.
You can check this by changing your method Drop(...) to this code:
private IEnumerator Drop(Transform objectToDrop, float duration, Vector3 endpos)
{
while (objectToDrop.transform.position != endpos)
{
objectToDrop.transform.position = Vector3.MoveTowards(objectToDrop.transform.position, endpos, speed * Time.deltaTime);
yield return null;
}
}
I would change how you're determining your endPositions to make it easier to configure.
I would define a starting distance and a max distance and you can calculate the gap programmatically by interpolating between the starting distance and minDistanceToMove maxDistanceToMove using Mathf.Lerp with a t of (float)i/(numberOfObjects-1):
public float minDistanceToMove = 0.5f;
public float maxDistanceToMove = 2f;
private void Start()
{
for (int i = 0; i < numberOfObjects; i++)
{
dropDownObjects.Add(Instantiate(dropdownPrefab, transform.parent));
float t = (float)i/(numberOfObjects-1);
float distDown = Mathf.Lerp(minDistanceToMove,maxDistanceToMove,t);
endPositions.Add(new Vector3(dropDownObjects[i].transform.position.x,
dropDownObjects[i].transform.position.y,
dropDownObjects[i].transform.position.z) - Vector3.up * distDown);
}
startPos = dropDownObjects[0].transform.position;
}
Alternatively, you can set only a minDistanceToMove and a gap size and then calculate distDown that way:
public float minDistanceToMove = 0.5f;
public float gapSize = 0.5f;
private void Start()
{
for (int i = 0; i < numberOfObjects; i++)
{
dropDownObjects.Add(Instantiate(dropdownPrefab, transform.parent));
float distDown = minDistanceToMove + gapSize * i;
endPositions.Add(new Vector3(dropDownObjects[i].transform.position.x,
dropDownObjects[i].transform.position.y,
dropDownObjects[i].transform.position.z) - Vector3.up * distDown);
}
startPos = dropDownObjects[0].transform.position;
}
Either way, you then need to change Drop to last for however long it takes for the panel to drop to its destination:
private IEnumerator Drop(Transform objectToDrop, float duration, Vector3 endpos)
{
// duration is unused. It is in the method signature here to avoid
// confusion when swapping with the alternative below
while (objectToDrop.transform.position != endPos)
{
objectToDrop.transform.position = Vector3.MoveTowards(
objectToDrop.transform.position, endpos, speed * Time.deltaTime);
yield return null;
}
}
Alternatively, you can make each panel take the given duration and calculate the speed necessary to traverse the distance:
private IEnumerator Drop(Transform objectToDrop, float duration, Vector3 endpos)
{
float mySpeed = Vector3.Distance(
objectToDrop.transform.position, endpos) / duration;
while (objectToDrop.transform.position != endPos)
{
objectToDrop.transform.position = Vector3.MoveTowards(
objectToDrop.transform.position, endpos, mySpeed * Time.deltaTime);
yield return null;
}
}

Multiple GameObjects Instantiated Between Two Angles

I am trying to spawn n GameObjects between angles equally spaced out.
Ideally, I'd like to be able to adjust the "cone" to so that the enemy can shoot in any direction, in any density.
Can someone see what I have done wrong?
These are enemy projectiles. That I am trying "scatter shot". Think of the dragon from Level 1 in NES Zelda:
Though, I am not entirely sure what is happening with my implementation.
Projectile.cs
public Vector2 moveDirection = Vector2.zero;
public float moveSpeed = 4.0f;
private void FixedUpdate()
{
_body.MovePosition(transform.position + (new Vector3(moveDirection.x, moveDirection.y, 0).normalized) * (moveSpeed * Time.deltaTime));
}
MultiShooter.cs
public GameObject projectileObject;
public Transform projectileEmitter;
[Range(2, 10)] public int numToShoot = 3;
[Space]
[Range(0, 360)] public int angle = 30;
[Range(1, 50)] public float rayRange = 10.0f;
[Range(0, 360)] public float coneDirection = 180;
public void OnStartShooting()
{
for (int i = 1; i <= numToShoot; i++)
{
var projectile = Instantiate(projectileObject);
projectile.transform.position = projectileEmitter.position;
var projectileScript = projectile.GetComponent<Projectile>();
projectileScript.moveDirection = DirFromAngle(((angle / i) + coneDirection)* pointDistance, rayRange);
projectile.SetActive(true);
}
}
public Vector3 DirFromAngle(float angleInDegrees, float range)
{
return Quaternion.AngleAxis(angleInDegrees, Vector3.forward) * transform.up * range;
}
Editor script to show the lines.
private void OnSceneGUI()
{
MultiShooter fow = (MultiShooter)target;
Handles.color = Color.magenta;
Vector3 upDirection = fow.DirFromAngle((-fow.angle / 2.0f) + fow.coneDirection, fow.rayRange);
Vector3 dwDirection = fow.DirFromAngle((fow.angle / 2.0f) + fow.coneDirection, fow.rayRange);
Handles.DrawLine(fow.projectileEmitter.position, upDirection);
Handles.DrawLine(fow.projectileEmitter.position, dwDirection);
}
For the ith object, the fraction of angular distance from one side of the range to the other can be expressed with the formula i/(numToShoot-1) for values ofnumToShoot > 1. If numToShoot == 1, you can just have the percentage be 50% to shoot right in the middle of the range.
Your drawing method seems to work with coneDirection ± angle/2, so we can subtract .5 from this angular percentage to express it in terms of angular distance from the center of the range.
Then we can use the same math as the drawing method with coneDirection + angle percentage * angle range:
public void OnStartShooting()
{
for (int i = 0; i < numToShoot; i++)
{
var projectile = Instantiate(projectileObject);
projectile.transform.position = projectileEmitter.position;
var projectileScript = projectile.GetComponent<Projectile>();
float anglePercentage;
if (numToShoot == 1)
anglePercentage = 0f;
else
anglePercentage = (float)i/(numToShoot-1f) - .5f;
projectileScript.moveDirection = DirFromAngle(
coneDirection
+ anglePercentage * angle, rayRange);
projectile.SetActive(true);
}
}

How to calculate the trajectory of bullet in Unity2D?

I'm doing the 2D game with bullets moving in parabola, but before I shoot the projectile I want to be able to calculate its trajectory.
float velocity = Mathf.Min(Mathf.Max(distance, 1), 2.5f);
float cos = Mathf.Cos( (gameObject.transform.localEulerAngles.z + 90) * Mathf.Deg2Rad);
float sin = Mathf.Sin( (gameObject.transform.localEulerAngles.z + 90) * Mathf.Deg2Rad);
newGO.GetComponent<Rigidbody2D>().AddForce(velocity * new Vector3(cos, sin))
This code adds the rigidbody2d to my bullet (newGO). I try to count a point after 1 sec of flying using it:
Debug.Log(new Vector3(source.transform.position.x + velocity * 1 * cos, source.transform.position.y + velocity * sin * 1 + Physics2D.gravity.y * 0.5f * 1));
Unfortunately it doesn't return the correct result. What should I do?
Thanks for help and sorry for my English.
I think being a projectile you should use Rigidbody2D.velocity to set the initial velocity of the projectile rather than Rigidbody2D.AddForce. (Although the documentation does not recommend)
This simple script predicts where the attached object will be in a time determined by the variable "timeLimit" and moves a "target" gameobject to that point.
public class Velocity : MonoBehaviour {
public GameObject target;
private float HoriInitialSpeed = 5;
private float VertInitialSpeed = 5;
private float timeLimit = 2;
private float timeElapsed=0;
void Start ()
{
float HoriPredict = HoriInitialSpeed * timeLimit;
float VertiPredict = VertInitialSpeed* timeLimit + (0.5f * Physics2D.gravity.y * timeLimit * timeLimit);
target.transform.Translate(HoriPredict, VertiPredict,0);
GetComponent<Rigidbody2D>().velocity = new Vector2(HoriInitialSpeed, VertInitialSpeed);
}
void Update () {
timeElapsed += Time.deltaTime;
if(timeElapsed >= timeLimit)
{
//stop the bullet
GetComponent<Rigidbody2D>().velocity = new Vector2(0, 0);
GetComponent<Rigidbody2D>().gravityScale = 0;
}
}
}
Note: the bullet and the target must be in the same point inicially

How do I jump a fix distance in Unity 3d?

I have a Controller which moves my object diagonally. The left arrow should move the player forward and to the left 45 degrees, and the right arrow the same to the right. I would like to move the player relatively to its current position. Right now it moves relatively to the point(0,0,0).
My code:
public class JollyJumper : MonoBehaviour {
protected CharacterController control;
public float fTime = 1.5f; // Hop time
public float fRange = 5.0f; // Max dist from origin
public float fHopHeight = 2.0f; // Height of hop
private Vector3 v3Dest;
private Vector3 v3Last;
private float fTimer = 0.0f;
private bool moving = false;
private int number= 0;
private Vector2 direction;
public virtual void Start () {
control = GetComponent<CharacterController>();
if(!control){
Debug.LogError("No Character Controller");
enabled=false;
}
}
void Update () {
if (fTimer >= fTime&& moving) {
var playerObject = GameObject.Find("Player");
v3Last = playerObject.transform.position;
Debug.Log(v3Last);
v3Dest = direction *fRange;
//v3Dest = newVector* fRange + v3Last;
v3Dest.z = v3Dest.y;
v3Dest.y = 0.0f;
fTimer = 0.0f;
moving = false;
}
if(Input.GetKeyDown(KeyCode.LeftArrow)){
moving = true;
direction = new Vector2(1.0f, 1.0f);
number++;
}
if(Input.GetKeyDown(KeyCode.RightArrow)){
moving = true;
direction = new Vector2(-1.0f, 1.0f);
number++;
}
if(moving){
Vector3 v3T = Vector3.Lerp (v3Last, v3Dest, fTimer / fTime);
v3T.y = Mathf.Sin (fTimer/fTime * Mathf.PI) * fHopHeight;
control.transform.position = v3T;
fTimer += Time.deltaTime;
}
}
}
How can resolve this? Any ideas? Thanks a lot!
The short answer is: you hard-coded two locations you want to jump to: points (1, 1) and (-1, 1). You should create new Vector each time you start jumping. Replace each
direction = new Vector2(1.0f, 1.0f);
with this line:
v3Dest = transform.position + new Vector3(1.0f, 0, 1) * fRange;
and it should work.
While I'm on it, there are some other things I want to point:
There is a lot of floating point error after each jump. Notice that in your code v3T will never be equal to v3Dest (you never actually reach your destination), because you switch the moving flag earlier. You should explicitly set your position to v3Dest when the jump is over.
You are checking jump timers etc. every frame. A more elegent solution is to start a coroutine.
You use a sinusoid as your jump curve, which looks ok, but using a parabola would be conceptually more correct.
Right now it is possible to start next jump mid-air (I'm not sure whether it is intended or not)
Here is some code you may use that avoids those problems:
using System.Collections;
using UnityEngine;
public class Jumper : MonoBehaviour
{
#region Set in editor;
public float jumpDuration = 0.5f;
public float jumpDistance = 3;
#endregion Set in editor;
private bool jumping = false;
private float jumpStartVelocityY;
private void Start()
{
// For a given distance and jump duration
// there is only one possible movement curve.
// We are executing Y axis movement separately,
// so we need to know a starting velocity.
jumpStartVelocityY = -jumpDuration * Physics.gravity.y / 2;
}
private void Update()
{
if (jumping)
{
return;
}
else if (Input.GetKeyDown(KeyCode.LeftArrow))
{
// Warning: this will actually move jumpDistance forward
// and jumpDistance to the side.
// If you want to move jumpDistance diagonally, use:
// Vector3 forwardAndLeft = (transform.forward - transform.right).normalized * jumpDistance;
Vector3 forwardAndLeft = (transform.forward - transform.right) * jumpDistance;
StartCoroutine(Jump(forwardAndLeft));
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
Vector3 forwardAndRight = (transform.forward + transform.right) * jumpDistance;
StartCoroutine(Jump(forwardAndRight));
}
}
private IEnumerator Jump(Vector3 direction)
{
jumping = true;
Vector3 startPoint = transform.position;
Vector3 targetPoint = startPoint + direction;
float time = 0;
float jumpProgress = 0;
float velocityY = jumpStartVelocityY;
float height = startPoint.y;
while (jumping)
{
jumpProgress = time / jumpDuration;
if (jumpProgress > 1)
{
jumping = false;
jumpProgress = 1;
}
Vector3 currentPos = Vector3.Lerp(startPoint, targetPoint, jumpProgress);
currentPos.y = height;
transform.position = currentPos;
//Wait until next frame.
yield return null;
height += velocityY * Time.deltaTime;
velocityY += Time.deltaTime * Physics.gravity.y;
time += Time.deltaTime;
}
transform.position = targetPoint;
yield break;
}
}

Categories