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;
}
}
Related
I'm currently trying to make an object curve a little and not just move straight to the destination how would I do that with Vector3.MoveTowards?
Right now I only have this object moving towards another object called "gotocoin"; it just moves straight any help is appreciated thank you!
void Update()
{
if (going == true)
{
transform.position = Vector3.MoveTowards(transform.position, gotocoin.transform.position, 3 * Time.deltaTime);
}
}
As mentioned in the comments, you can use Bezier Curve to solve your problem. I have written a simple code to demonstrate. It first calculates all the points between an initial position and the target position and then moves the object, which the script is attached to, to each of them. Changing control1 and control2 will change the shape of the curve. The Gizmos method is for debugging and can be removed.
[SerializeField] private Transform target;//the destination
[SerializeField] private Vector3 offset = new Vector3(4.58f, 4.52f, 0);//determines arch of the curve
private const float DistanceToTarget = 1;
private Vector3 _initialPosition;
private List<Vector3> _allPositions;
private int _counter;
private void Start()
{
_initialPosition = transform.position;
_allPositions = new List<Vector3>(100);
for (var i = 0; i < 100; i++)
{
var newPosition = CubicCurve(_initialPosition, _initialPosition + offset, _initialPosition + offset,
target.position, (float)i / 100);
_allPositions.Add(newPosition);
}
}
private void Update()
{
if (_counter < _allPositions.Count)
{
transform.position = Vector3.MoveTowards(transform.position, _allPositions[_counter], Time.deltaTime);
if (Vector3.Distance(transform.position, _allPositions[_counter]) < DistanceToTarget) _counter++;
}
}
private Vector3 CubicCurve(Vector3 start, Vector3 control1, Vector3 control2, Vector3 end, float t)
{
return (((-start + 3 * (control1 - control2) + end) * t + (3 * (start + control2) - 6 * control1)) * t +
3 * (control1 - start)) * t + start;
}
//since _initialPosition is set on start, the drawn curve is from (0,0,0) if the code is not executed
private void OnDrawGizmos()
{
for (var i = 0; i < 100; i++)
{
var newPosition = CubicCurve(_initialPosition, _initialPosition + offset, _initialPosition + offset,
target.position, (float)i / 100);
Gizmos.DrawSphere(newPosition, 1f);
}
}
Try using animation curves in unity's animation system. Hope this helps😀
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);
}
}
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.
I want my shots to follow a specific pattern (I also need the arc and gap between the shots to be adjustable). Right now I've got my shooting script down but the shots go in a straight line which is not what I want (don't want a straight line now but I'll need it later when designing other weapons).
Here's a screenshot with example of said saidpatters:
I don't know much about quaternions and angles so all I tried is modifying the angles after x time and the velocity after x time but none worked (it might be the solution but I have 0 clue how to use angles in unity so I couldn't get it to work).
Another thing please provide an explanation along with your answer because I want to learn why something works the way it does so I don't have to ask again later.
Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Player_Shooting : MonoBehaviour
{
[SerializeField]
private Transform shootingPoint;
[SerializeField]
private GameObject shot; //this is what I'm shooting, shot also has a script but all it does is apply velocity upwards and do damage to enemy if it hits
private bool shootAgain = true;
private int dexterity = Player_Stats.GetDexterity();
private int numberofshots = 2; //amount of shots
private int shotGap = 5; //how many degrees between the shots
void Update()
{
Vector3 mousepos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = new Vector2(mousepos.x - transform.position.x, mousepos.y - transform.position.y);
transform.up = direction;
if (Input.GetButton("Fire1") && shootAgain == true)
{
shootAgain = false;
StartCoroutine(RateOfFire(dexterity));
}
}
private void Shoot()
{
Vector3 temp = transform.rotation.eulerAngles;
Quaternion angle = Quaternion.Euler(temp.x, temp.y, temp.z);
for (int i = 0; i < numberofshots; i++)
{
int multiplier = i + 1;
if (numberofshots % 2 == 1)
{
Instantiate(shot, shootingPoint.position, angle);
if (i % 2 == 0)
{
temp.z -= shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
else
{
temp.z += shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
}
else if (numberofshots % 2 == 0)
{
if (i % 2 == 0)
{
temp.z -= shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
else
{
temp.z += shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
Instantiate(shot, shootingPoint.position, angle);
}
}
}
IEnumerator RateOfFire(int dex)
{
Shoot();
float time = dex / 75;
time *= 6.5f;
time += 1.5f;
yield return new WaitForSeconds(1 / time);
shootAgain = true;
}
}
This is what i came up with after a few hours.
it can be improved upon for your needs but it works and with less code.
i used a separate script on another gameObject to Instantiate the projectiles. The bullet script is attached to a sprite with a trail
it should be easy to manipulate the firing sequence from there.
comments explain what most things do.
i added a bool function to fire in opposing angles.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bullet : MonoBehaviour{
public float turnLength = 0.5f; // how long it turns for 0.0+
public float turnSpeed = 5f; // how fast the projectile turns 0.0+
public float anglePauseTime = 0.2f; // Optional wave form variable. coupled with high turnrate + curve speed = higher frequency sine wave.
public float shotAngle = -12f; // the angle the shot is taken as an offset (usually nagative value) 0- or turnspeed*2.25 for straight shots
public float projectileSpeed = 50; // obvious
public bool opositeAngles = false;
// Start is called before the first frame update
void Start(){
if(opositeAngles){
transform.Rotate(0, 0, -shotAngle);
}
else{
transform.Rotate(0, 0, shotAngle);
}
StartCoroutine(WaveForm(turnLength, turnSpeed, anglePauseTime, opositeAngles));
}
// Update is called once per frame
void Update(){
transform.position += transform.right * Time.deltaTime * projectileSpeed;
}
IEnumerator WaveForm(float seconds, float aglSpeed, float pause, bool reverse){
// multiplier correlates to waitForSeconds(seconds)
// faster update time = smoother curves for fast projectiles
// less cycles = shorter Corutine time.
//10, 0.1 100cycles/second (shallow waves, jagged on higher frequency waves, doesnt last long)
//10, 0.05 200cycles/second (probably best)
//100, 0.02 500cycles/second (smooth curves all around. requires smaller adjustment numbers)
// i had to up it for the waveform to last longer.
float newSeconds = seconds * 10;
for (int i = 0; i < newSeconds; i++) {
for (int j = 0; j < newSeconds; j++) {
yield return new WaitForSeconds(0.05f); // controls update time in fractions of a second.
if(reverse){
transform.Rotate(0, 0, -aglSpeed, Space.Self);
}
else {
transform.Rotate(0, 0, aglSpeed, Space.Self);
}
}
yield return new WaitForSeconds(pause);
aglSpeed = -aglSpeed;
}
}
}
Example image
I have a script for waypoints, which I find here. It works fine for objects, which move on the horizontal axes. But it don't move objects up and down. Can't understand, why.
Elevator are just decoration and it will randomly move from one floor to another.
public Transform[] waypoint; // The amount of Waypoint you want
float patrolSpeed = 3.0f; // The walking speed between Waypoints
float dampingLook = 6.0f; // How slowly to turn
public float pauseDuration; // How long to pause at a Waypoint
float curTime;
int currentWaypoint;
CharacterController character;
void Start()
{
character = GetComponent<CharacterController>();
}
void Update()
{
if (currentWaypoint < waypoint.Length)
Patrol();
else
currentWaypoint = 0;
}
void Patrol()
{
Vector3 target = waypoint[currentWaypoint].position;
target.y = transform.position.y; // Keep waypoint at character's height
Vector3 moveDirection = target - transform.position;
if(moveDirection.magnitude < 0.5)
{
if (curTime == 0)
curTime = Time.time; // Pause over the Waypoint
if ((Time.time - curTime) >= pauseDuration)
{
currentWaypoint = Random.Range(0, waypoint.Length);
curTime = 0;
}
}
else
{
var rotation = Quaternion.LookRotation(target - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * dampingLook);
character.Move(moveDirection.normalized * patrolSpeed * Time.deltaTime);
}
}
Edit1:
Here is the video how it works.
Try this, without a rigidbody:
using UnityEngine;
using System.Collections;
public class Elevator : MonoBehaviour {
public Transform[] waypoint; // The amount of Waypoint you want
float patrolSpeed = 3.0f; // The walking speed between Waypoints
float dampingLook = 6.0f; // How slowly to turn
public float pauseDuration; // How long to pause at a Waypoint
float curTime;
int currentWaypoint;
void Update()
{
if (currentWaypoint < waypoint.Length)
Patrol();
else
currentWaypoint = 0;
}
void Patrol()
{
Vector3 target = waypoint[currentWaypoint].position;
Vector3 moveDirection = target - transform.position;
if(moveDirection.magnitude < 0.5)
{
if (curTime == 0)
curTime = Time.time; // Pause over the Waypoint
if ((Time.time - curTime) >= pauseDuration)
{
currentWaypoint = Random.Range(0, waypoint.Length);
curTime = 0;
}
}
else
{
transform.Translate(moveDirection.normalized * patrolSpeed * Time.deltaTime);
}
}
}
Edit: Correct the script by removing two rotation lines.