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
Related
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScaleRotate : MonoBehaviour
{
public Vector3 minScale;
public Vector3 maxScale;
public float duration;
private bool scaling = true;
private void Start()
{
StartCoroutine(ScaleOverSeconds(maxScale, duration));
}
public IEnumerator ScaleOverSeconds(Vector3 scaleTo, float seconds)
{
float elapsedTime = 0;
Vector3 startingScale = transform.localScale;
while (elapsedTime < seconds)
{
transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
transform.localScale = scaleTo;
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.S))
{
if(scaling == true)
{
StartCoroutine(ScaleOverSeconds(minScale, duration));
scaling = false;
}
}
}
}
Now it scales to max and when S is pressed to min.
But I want when I press on S it will scale to max/min each time pressing the S key.
And also I want when the object scales that it will rotate by 180 degrees according to the scaling duration.
I don't fully get the rotation part but as I understand you rather want to toggle the scaling so something like e.g.
// flag for the scale direction
private bool scaleToMax;
// flag for checking if already scaling to avoid concurrent routines
private bool alreadyScaling;
private void Start()
{
// initial scale to max
ToggleScale();
}
private void Update()
{
// first check if another routine is already running
// -> ignore/lock input
if(!alreadyScaling && Input.GetKeyDown(KeyCode.S))
{
ToggleScale();
}
}
// Invert the scale direction and start aroutine
private void ToggleScale()
{
if(alreadyScaling)
{
Debug.LogWarning("Already scaling -> Ignored!", this);
return;
}
// invert the scale direction
scaleToMax = !scaleToMax;
StartCoroutine(ScaleOverSeconds(scaleToMax ? maxScale : minScale, duration));
// This is basically a shortcut for doing something like
//Vector3 targetScale;
//if(scaleToMax)
//{
// targetScale = maxScale;
//}
//else
//{
// targetScale = minScale;
//}
//StartCoroutine(ScaleOverSeconds(targetScale, duration));
}
private IEnumerator ScaleOverSeconds(Vector3 scaleTo, float seconds)
{
// block concurrent routines
if(alreadyScaling) yield break;
alreadyScaling = true;
var startingScale = transform.localScale;
var initialRotation = transform.rotation;
// Here it is a bit unclear which axis you want to rotate on but you could do e.g.
var targetRotation = initialRotation * Quaternion.Euler(0, 180, 0);
var elapsedTime = 0f;
while (elapsedTime < seconds)
{
var factor = elapsedTime / seconds;
// Just fyi here you can also add some smoothing at the beginning and end e.g.
//factor = SmoothStep(0, 1, factor);
transform.localScale = Vector3.Lerp(startingScale, scaleTo, factor);
transform.rotation = Quaternion.Lerp(initialRotation, targetRotation, factor);
elapsedTime += Time.deltaTime;
// Unless you explicitly need the end of frame yielding null is just fine
yield return null;
}
transform.localScale = scaleTo;
transform.rotation = targetRotation;
// allow the next routine to start
alreadyScaling = false;
}
to make this you can just use else operator in your void Update()
And for rotation Unity uses Quaternions, which are math abstractions with 3 imaginary units and one real unit. If you want, you can read about it more
https://en.wikipedia.org/wiki/Quaternion
https://docs.unity3d.com/ScriptReference/Quaternion.html
but it's really not easy to understand so Unity provides localEulerAngles for easier rotation. It works just as Vector3 (you can see it in inspector in Transform.rotation field)
So, what will do our changed code:
if object has max scale (scaling == true), it will change to min scale and rotate to 180 degrees (clockwise) by Y-axis
And if object has min scale (scaling == false), it will change to max scale and rotate to 180 degrees (also clockwise) by Y-axis
If you want to change second rotation to counterclockwise, you can change second rotation from 180 degrees to -180 degrees.
Also it's good idea to not change size if size is changing right now. For example if object is becoming bigger right now and we press 'S' to make it smaller, it will be at the same time trying to become bigger and smaller and the result could be very strange!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScaleRotate : MonoBehaviour
{
//It's better to use private fields. If you want to see it in inspector, use SerializeField
[SerializeField]
private Vector3 minScale;
[SerializeField]
private Vector3 maxScale;
[SerializeField]
private float duration;
private bool scaling = true;
//Variable to set angle to rotate your object (in degrees)
[SerializeField]
private float rotationAngle = 180.0f;
//Bool which says: if object is changing it's size right now
private bool isInProcess = false;
private void Start()
{
StartCoroutine(ScaleOverSeconds(maxScale, duration));
}
public IEnumerator ScaleOverSeconds(Vector3 scaleTo, Vector3 rotateTo, float seconds)
{
isInProcess = true;
float elapsedTime = 0;
Vector3 startingScale = transform.localScale;
Vector3 startingRotation = transform.localEulerAngles;
//If you want, you can change axis of rotation or angle or everything you want. But what I do - I rotate by Y-axis for 180 degrees
while (elapsedTime < seconds)
{
transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
transform.localEulerAngles = Vector3.lerp(startingRotation, rotateTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
//You can use yield return null instead of yield return new WaitForEndOfFrame() - this will do the same, but it's easier to write
yield return null;
}
transform.localScale = scaleTo;
transform.localEulerAngles = rotateTo;
isInProcess = false;
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.S))
{
//Check if object is not changing it's scale right now
if(!isInProcess){
//Use if(scaling) instead of if(scaling == true) - this means the same, but it's more readable
if(scaling)
{
Vector3 rotateTo = transform.localEulerAngles + new Vector3(0, rotationAngle, 0);
StartCoroutine(ScaleOverSeconds(minScale, rotateTo, duration));
//Remove scaling = false (we will paste it later)
}
//Add there else operator
else {
//If you want to change rotation to counterclockwise, change '+' to '-'
Vector3 rotateTo = transform.localEulerAngles + new Vector3(0, rotationAngle, 0);
StartCoroutine(ScaleOverSeconds(maxScale, rotateTo, duration));
}
//Change scaling value. If you want, you can move this line into ScaleOverSeconds coroutine
scaling = !scaling;
}
}
}
}
(Once it reaches point B, it goes to point A and back to point B in a smooth and orderly fashion). For some reason, the platform refuses to move and stays put. I have tried many things such as using vector3.movetowards and much more but nothing makes it move.
Here is the code. (Point A and Point B are empty game objects that are not parented to the platform)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveTwoTransforms : MonoBehaviour
{
public Transform pointA;
public Transform pointB;
bool HeadingtowardsB;
bool HeadingtowardsA;
public float speed = 10;
// Start is called before the first frame update
void Start()
{
transform.position = pointA.position;
HeadingtowardsB = true;
HeadingtowardsA = false;
GlideAround();
}
// Update is called once per frame
void Update()
{
}
public IEnumerator GlideAround()
{
while (true)
{
while ((Mathf.Abs((pointB.position.x - transform.position.x) + (pointB.position.y - transform.position.y)) > 0.05f) && HeadingtowardsB == true && HeadingtowardsA==false )
{
yield return new WaitForEndOfFrame();
transform.position = Vector2.Lerp(transform.position, pointB.position, speed * Time.deltaTime);
if(Mathf.Abs((pointB.position.x - transform.position.x) + (pointB.position.y - transform.position.y)) > 0.05f)
{
HeadingtowardsB = false;
HeadingtowardsA = true;
}
}
HeadingtowardsB = false;
HeadingtowardsA = true;
while (Mathf.Abs((pointA.position.x - transform.position.x) + (pointA.position.y - transform.position.y)) > 0.05f && HeadingtowardsA==true && HeadingtowardsB==false)
{
yield return new WaitForEndOfFrame();
transform.position=transform.position=Vector3.Lerp(transform.position, pointA.position, speed*Time.deltaTime);
}
}
}
}
There are no error messages, the platform won't move. The platform is still colliding and it seems to behave like a normal platform.
GlideAround() is an IEnumerator and can not be called like a method. You have to start it using StartCoroutine
StartCoroutine(GlideAround());
Also note that speed * Time.deltaTime makes little sense for usage in Lerp. You usually would want a constant value between 0-1 in your case (since you re-use the current position as first parameter).
E.g. a value of 0.5 means: Every frame set the new position to the center between the current and the target position.
Since you catch it using a threashold of 0.05f this should be fine but in general I wouldn't use Lerp like this ... with very small values you might never really reach the target position.
I would therefore prefer to either control the constant speed and use
bool isHeadingA = true;
while(true)
{
// if it was intended you can ofourse also again use
// Vector2.Distance(transform.position, isHeadingA ? pointA.position : pointB.position) <= 0.05f)
while (transform.position != (isHeadingA ? pointA.position : pointB.position))
{
yield return new WaitForEndOfFrame();
transform.position = Vector2.MoveTowards(transform.position, isHeadingA ? pointA.position : pointB.position, speed * Time.deltaTime);
}
// flip the direction
isHeadingA = !isHeadingA;
}
!= has a precision of 0.00001 and is fine here since MoveTowards avoids overshooting so at some point it will surely reach the position if speed != 0.
Or alternatively you can use Lerp if you rather want to control the duration of the movement with a smoothed in and out speed using e.g. Mathf.PingPong as factor and Mathf.SmoothStep for easing in and out like
while(true)
{
yield return new WaitForEndOfFrame();
// linear pingpong between 0 and 1
var factor = Mathf.PingPong(Time.time, 1);
// add easing at the ends
factor = Mathf.SmoothStep(0, 1, factor);
// optionally add even more easing ;)
//factor = Mathf.SmoothStep(0, 1, factor);
transform.position = Vector2.Lerp(pointA.position, pointB.position, factor);
}
Try this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveTwoTransforms : MonoBehaviour
{
public Transform pointA;
public Transform pointB;
bool HeadingtowardsB;
bool HeadingtowardsA;
public float speed = 10;
// Start is called before the first frame update
void Start()
{
transform.position = pointA.position;
HeadingtowardsB = true;
HeadingtowardsA = false;
StartCoroutine(GlideAround());
}
// Update is called once per frame
void Update()
{
}
public IEnumerator GlideAround()
{
//Because we want a specific speed, the % between the two points
//that we should be at will be equal to (time * speed) / distance
//with an adjustment for going backwards.
float distance = Vector3.Distance(pointA, pointB) * 2;
float lapTime = distance / speed;
float startTime = Time.time;
Debug.Log("The platform speed is: " + speed.ToString());
Debug.Log("The distance for one full lap is: " + distance.ToString());
Debug.Log("One lap will take: " + lapTime.ToString() + " seconds");
while (true)
{
yield return new WaitForEndOfFrame();
float elapsedTime = (Time.time - startTime) % lapTime;
float progress = elapsedTime / (lapTime / 2);
if (progress > 1){
progress = 2 - progress;
}
Debug.Log("The platform speed is currently: " + progress.ToString() + "% between pointA and pointB");
transform.position = Vector2.Lerp(pointA.position, pointB.position, progress);
}
}
As I mentioned in the comments, you Lerp uses a percentage between two points as the return value. You need to give it a percent, not a speed.
Here is an implementation that uses speed, like you wanted, but #derHugo's answer with PingPong is much simpler!
The main issue is that you are not using coroutines properly, as pointed out in derHugo's answer.
However, I'll provide my own answer, seeing that you are making the rookie mistake of way over-engineering this problem.
I think teaching by example might be the most appropriate in this case, so here it is:
If the points dictating the platform's movement are static, you should do this with animation. I won't explain it here. Tutorials like this one can easily be found all over the unity tutorials, unity forums, other StackOverflow Q&As, and youtube.
If your points are dynamic, this is more than enough:
public class MoveTwoTransforms : MonoBehaviour {
public Transform pointA;
public Transform pointB;
public float speed = 10;
void Start() {
transform.position = pointA.position;
StartCoroutine(GlideAround());
}
private IEnumerator MoveTowards(Vector3 targetPosition) {
while (transform.position != targetPosition) {
transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
yield return null;
}
}
private IEnumerator GlideAround() {
while(true) {
yield return StartCoroutine(MoveTowards(pointA));
yield return StartCoroutine(MoveTowards(pointB));
}
}
}
Just a final note:
If the platform should have physics or a collider, it is preferable to add a Rigidbody, set it to be kinematic, and to the movement by setting the Rigidbody.position instead of the transform. This is because that is updated on the physics loop (FixedUpdate) rather than the frame loop (Update), and avoids some bugs related to asyncrony between the physics system and moving objects through transform's position.
rigidbody.position = Vector3.MoveTowards(rigidbody.position, //...
Here is the code that worked.
public IEnumerator GlideAround()
{
while (true)
{
while (HasReachedA == false)
{
yield return new WaitForEndOfFrame();
transform.position = Vector2.Lerp(transform.position, pointA.position, 0.01f);
if ((Mathf.Abs(Vector2.Distance(pointA.position, transform.position)) < 0.01f))
{
HasReacedB = false;
HasReachedA = true;
}
}
while (HasReacedB == false)
{
yield return new WaitForEndOfFrame();
transform.position = Vector2.Lerp(transform.position, pointB.position, 0.01f);
if ((Mathf.Abs(Vector2.Distance(pointB.position, transform.position)) < 0.01f))
{
HasReacedB = true;
HasReachedA = false;
}
}
}
}
So I edited my question because I have forgotten to show you a camera script. Maybe the problem is somewhere there. I just believe that even after editing the old question there will absolutely no people who read edited article. So I've created a new question.
So I have such a problem. I try to make a tram moving. To do it I downloaded a script from Asset Store "Bezier Path Creator": https://assetstore.unity.com/packages/tools/utilities/b-zier-path-creator-136082. It works nice. Before the need of creating third person camera. After writing a short script I saw that vehicle is twitching after getting some speed. I sat a couple of days trying to solve this problem and I saw one thing. If to remove * Time.deltaTime there are no twitching anymore but there is dependence on FPS (what was predictable). Also if to click Pause button and look frame by frame there are no twitching. What can I do about it?
There are some pieces of code I have problems with:
switch (acceleration) {
case -1:
acc = -12F;
break;
case -2:
acc = -15F;
break;
case -3:
acc = -18F;
break;
case 0:
acc = 0.000f;
break;
case 1:
acc = 9f;
break;
case 2:
acc = 12f;
break;
case 3:
acc = 17.1f;
break;
}
if (Input.GetKeyDown ("e")) {
acceleration++;
}
if (Input.GetKeyDown ("q")) {
acceleration--;
}
if (acceleration < -3) {
acceleration = -3;
}
if (acceleration > 3) {
acceleration = 3;
}
if (speed > 40.0f) {
speed = 40.0f;
saveSpeed = speed;
speed = saveSpeed;
} else if (speed >= 0.0f && speed <= 0.03f && acceleration == 0) {
speed = 0.0f;
} else if (speed <= 0.0f && speed >= -0.03f && acceleration == 0) {
speed = 0.0f;
} else if (speed <= 40.0f) {
speed += ((Time.time - startTime)/1000)*(-acc);
}
// Taking a piece of Asset Store code
transform.rotation = pathCreator.path.GetRotationAtDistance(distanceTravelled);
transform.position = pathCreator.path.GetPointAtDistance(distanceTravelled); // I guess the problem is somewhere there
So there are GetRotationAtDistance and GetPointAtDistance methods from Asset Store script:
public Vector3 GetPointAtDistance(float dst, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.Loop)
{
float t = dst / length;
return GetPoint(t, endOfPathInstruction);
}
public Vector3 GetPoint(float t, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.Loop)
{
var data = CalculatePercentOnPathData(t, endOfPathInstruction);
return Vector3.Lerp(vertices[data.previousIndex], vertices[data.nextIndex], data.percentBetweenIndices);
}
public Quaternion GetRotationAtDistance(float dst, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.Loop)
{
float t = dst / length;
return GetRotation(t, endOfPathInstruction);
}
public Quaternion GetRotation(float t, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.Loop)
{
var data = CalculatePercentOnPathData(t, endOfPathInstruction);
Vector3 direction = Vector3.Lerp(tangents[data.previousIndex], tangents[data.nextIndex], data.percentBetweenIndices);
Vector3 normal = Vector3.Lerp(normals[data.previousIndex], normals[data.nextIndex], data.percentBetweenIndices);
return Quaternion.LookRotation(direction, normal);
}
Script of camera:
using UnityEngine;
using System.Collections;
public class CameraRotateAround : MonoBehaviour {
public GameObject target;
public Vector3 offset;
public float sensitivity = 3; // чувствительность мышки
public float limit = 80; // ограничение вращения по Y
public float zoom = 0.25f; // чувствительность при увеличении, колесиком мышки
public float zoomMax = 10; // макс. увеличение
public float zoomMin = 3; // мин. увеличение
private float X, Y;
void Start ()
{
limit = Mathf.Abs(limit);
if(limit > 90) limit = 90;
offset = new Vector3(offset.x, offset.y, -Mathf.Abs(zoomMax)/2);
transform.position = target.transform.position + offset;
offset.z -= zoom + 3;
}
void Update ()
{
if(Input.GetAxis("Mouse ScrollWheel") > 0) offset.z += zoom;
else if(Input.GetAxis("Mouse ScrollWheel") < 0) offset.z -= zoom;
offset.z = Mathf.Clamp(offset.z, -Mathf.Abs(zoomMax), -Mathf.Abs(zoomMin));
X = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivity;
Y += Input.GetAxis("Mouse Y") * sensitivity;
Y = Mathf.Clamp (Y, -limit, 0);
transform.localEulerAngles = new Vector3(-Y, X, 0);
transform.localPosition = transform.localRotation * offset + target.transform.position;
}
}
Hope somebody'll help me this time because I really don't know what can I do to fix this bag.
I once had a similar problem and This link Helped me out a ton.
The problem you are experiencing is due to The camera being 3rd person and not parented to the object it is following.
The problem is that the target object when moved, moves before the camera, thus causing the stutter, as the camera moves just after the object. Try putting the camera script in LateUpdate, but if that does not work, then I suggest checking the link I have linked above.
The link talks about interpolation, which, without increasing your physics timestep, moves the camera to a position which the provided asset calculates the camera will be at, thus removing the stutter and achieving smooth motion. Check the link for the full info
The story is that I have this research project in which I have to simulate a tracking experiment.
I have a ball in a box (which we hope can be implemented as a VR room in the future), and the ball moves to random coordinates.
I also plan on adding another object that the user can click on (or in the case of VR, use a joystick) to move and follow the ball.
The coordinates of each movement of the ball, and the coordinates of each movement of the object must be outputted to a file.
Right now, I am having difficulty sending the ball to random coordinates.
The ball is named "Player" and the file is named "PlayerController." The two issues are that while utilizing the Unity version of Random, I consistently attain the same coordinates on each run and the ball does not stop moving.
My code is attached below. Also, if any of you readers have an idea on how to implement the rest of my project, suggestions are definitely appreciated!
Thank you so much!
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class PlayerController : MonoBehaviour {
private float movementDuration = 2.0f;
private float waitBeforeMoving = 2.0f;
private bool hasArrived = false;
private Coroutine moveCoroutine = null;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (!hasArrived)
{
hasArrived = true;
Vector3[] v = new Vector3[20];
for (int i=0; i<v.Length; i++)
{
Random.InitState(System.DateTime.Now.Millisecond);
v[i] = new Vector3(Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f));
moveCoroutine = StartCoroutine(MoveToPoint(v[i]));
}
}
if (Input.GetMouseButtonDown(0))
StopMovement();
}
private IEnumerator MoveToPoint(Vector3 targetPos)
{
float timer = 0.0f;
Vector3 startPos = transform.position;
while (timer < movementDuration)
{
timer += Time.deltaTime;
float t = timer / movementDuration;
t = t * t * t * (t * (6f * t - 15f) + 10f);
transform.position = Vector3.Lerp(startPos, targetPos, t);
yield return null;
}
yield return new WaitForSeconds(waitBeforeMoving);
hasArrived = false;
}
private void StopMovement()
{
if (moveCoroutine != null)
StopCoroutine(moveCoroutine);
}
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("sphereTag"))
StopMovement();
}
The ball doesn't stop moving because you set hasArrived to false at the end of your coroutine so it just fires again every 2 seconds.
The coordinates were different for me every time - not sure what issue you are facing there. May be something with Random.InitState()
The way you have your loop set up, every vector in the array is getting fed into its own coroutine, but this is all happening essentially instantaneously since there is no delay before going into the next iteration of your for-loop. If you are attempting to loop through all 20 of the points you should instead call a single coroutine, with the for-loop and embedded delay inside:
bool hasArrived;
private float movementDuration = 2.0f;
private float waitBeforeMoving = 2.0f;
void Update()
{
if (!hasArrived)
{
hasArrived = true;
StartCoroutine(MoveToPoints());
}
}
private IEnumerator MoveToPoints()
{
Vector3[] v = new Vector3[20];
for (int i = 0; i < v.Length; i++)
{
float timer = 0.0f;
Vector3 startPos = transform.position;
v[i] = new Vector3(Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f));
while (timer < movementDuration)
{
timer += Time.deltaTime;
float t = timer / movementDuration;
t = t * t * t * (t * (6f * t - 15f) + 10f);
transform.position = Vector3.Lerp(startPos, v[i], t);
yield return null;
}
yield return new WaitForSeconds(waitBeforeMoving);
}
}
If the waypoints are small size for example cubes at size 0.1 or 1 it's fine.
When i change the cubes size to 20-30 if there is a situation that there are two waypoints on the way but the enemy should get to the second waypoint he will stuck on the wall of the first waypoint will shake/stutter and will try to go to one of the sides and then in the end he will pass this waypoint and continue to the waypoint he should get the target.
It happen only when the waypoints(cubes) are very big and if a waypoint block the waypoint the enemy should get.
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityStandardAssets.Characters.ThirdPerson;
public class WayPoints : MonoBehaviour {
public GameObject[] waypoints;
public Transform target;
public float moveSpeed = 10f;
public float moveSpeed1 = 10f;
public float slowDownSpeed = 3f;
public float reverseSlowDownSpeed = 3f;
public float rotationSpeed = 1f;
private Transform myTransform;
private int targetsIndex = 0;
private Vector3 originalPosition;
private GameObject[] robots;
public Transform reverseTarget;
private int reverseTargetsIndex = 0;
private Vector3 reverseOriginalPosition;
public bool random = false;
void Awake()
{
myTransform = transform;
}
// Use this for initialization
void Start()
{
waypoints = GameObject.FindGameObjectsWithTag("ClonedObject");
robots = GameObject.FindGameObjectsWithTag("Robots");
AddColliderToWaypoints();
originalPosition = robots[0].transform.position;
reverseOriginalPosition = robots[1].transform.position;
}
// Update is called once per frame
void Update()
{
if (MyCommands.walkbetweenwaypoints == true)
{
WayPointsAI();
ReverseWayPointsAI();
}
DrawLinesInScene();
}
private void WayPointsAI()
{
if (targetsIndex == waypoints.Length)
targetsIndex = 0;
target = waypoints[targetsIndex].transform;
float distance = Vector3.Distance(robots[0].transform.position, target.transform.position);
robots[0].transform.rotation = Quaternion.Slerp(robots[0].transform.rotation, Quaternion.LookRotation(target.position - robots[0].transform.position), rotationSpeed * Time.deltaTime);
//move towards the player
if (distance < 30)
{
robots[0].transform.position += robots[0].transform.forward * slowDownSpeed * Time.deltaTime;
}
else
{
robots[0].transform.position += robots[0].transform.forward * moveSpeed * Time.deltaTime;
}
if (distance < target.transform.localScale.magnitude)
{
targetsIndex++;
}
}
private void ReverseWayPointsAI()
{
if (reverseTargetsIndex == 0)
reverseTargetsIndex = waypoints.Length -1;
reverseTarget = waypoints[reverseTargetsIndex].transform;
float distance = Vector3.Distance(robots[1].transform.position, reverseTarget.transform.position);
robots[1].transform.rotation = Quaternion.Slerp(robots[1].transform.rotation, Quaternion.LookRotation(reverseTarget.position - robots[1].transform.position), rotationSpeed * Time.deltaTime);
//move towards the player
if (distance < 30)
{
robots[1].transform.position += robots[1].transform.forward * reverseSlowDownSpeed * Time.deltaTime;
}
else
{
robots[1].transform.position += robots[1].transform.forward * moveSpeed1 * Time.deltaTime;
}
if (distance < reverseTarget.transform.localScale.magnitude)
{
reverseTargetsIndex--;
}
}
void RandomWayPointsAI()
{
if (random == true)
{
int index = Random.Range(0, waypoints.Length);
target = waypoints[index].transform;
}
}
void DrawLinesInScene()
{
// draw lines between each checkpoint //
for (int i = 0; i < waypoints.Length - 1; i++)
{
Debug.DrawLine(waypoints[i].transform.position, waypoints[i + 1].transform.position, Color.blue);
}
// draw a line between the original transform start position
// and the current transform position //
Debug.DrawLine(originalPosition, robots[0].transform.position, Color.red);
Debug.DrawLine(reverseOriginalPosition, robots[1].transform.position, Color.red);
// draw a line between current transform position and the next waypoint target
// each time reached a waypoint.
if (target != null)
Debug.DrawLine(target.transform.position, robots[0].transform.position, Color.green);
if (reverseTarget != null)
Debug.DrawLine(reverseTarget.transform.position, robots[1].transform.position, Color.green);
}
void AddColliderToWaypoints()
{
foreach (GameObject go in waypoints)
{
SphereCollider sc = go.AddComponent<SphereCollider>() as SphereCollider;
sc.isTrigger = true;
}
}
}
I tried to make
if (distance < reverseTarget.transform.localScale.magnitude)
I tried before it to make
if (distance < reverseTarget.tranform.localscale.x)
Or
if (distance < reverseTarget.tranform.localscale.x / 2)
Same with target and the first AI function.
But nothing is working. Maybe i should find the waypoints radius ?
Not sure how to solve it.
What OP seems to have wanted was a way to give the AI pathfinding so that when the AI wanted to go from (dynamic/code generated) point A to B and there was an obstacle between, the AI wouldn't get stuck in the obstacle.
Unity offers NavMesh and NavMesh Obstacle, a simple pathfinding tool Unity gives us to achieve a smart AI with little effort.