Using Switch for Simple Unity3D Patrol Movement - c#

I'm using a switch statement to create two movement types on an enemy: Forward, and Backwards. The enemy has three patrol points. When he begins, I want him moving from the first patrol point and adding 1 to the current point when he hits his second patrol point (empty 3D gameobject), and so on. Then I'll have him reverse direction when he hits the final point.
switch (moveType)
{
case MoveType.Forward:
if (transform.position == patrolPoints[currentPoint].position)
{
currentPoint ++;
}
break;
case MoveType.Backwards:
if (transform.position == patrolPoints[patrolPointsLength].position)
{
currentPoint --;
}
break;
}
The problem is, I can't figure out a way to "trigger" the two MoveTypes. How do I code this so that when the enemy hits his final patrol point, he switches to MoveType.Backwards? I'm sure I'm making this way harder than it needs to be. Thanks for the help!

This is how I would do it if I really wanted to use the switch statement:
float someSmallValue = 0.5f; // Adjust this as per your needs
if (Vector3.Distance(transform.position, patrolPoints[currentPoint].position) < someSmallValue)
{
switch (moveType)
{
case MoveType.Forward:
currentPoint++;
if (currentPoint > patrolPoints.Length - 1)
{
currentPoint -= 1;
moveType = MoveType.Backwards;
}
break;
case MoveType.Backwards:
currentPoint--;
if (currentPoint < 0)
{
currentPoint = 1;
moveType = MoveType.Forward;
}
break;
}
}
I think renaming currentPoint to targetPoint would make the variable name more clear for this chunk of code.
EDIT: I forgot to decrement currentPoint inside the Backwards case block. Updated my answer.

The solution I have here adds some things like a pause time. It also ensures that fast moving entities won't overshoot the destination and not turn around or something.
// Movement speed normalized. So 1 is instantaneous travel and zero is no movement.
public float movementSpeed = 0.025;
// Let's add a 'pause' time where the unit waits a while at the turning point.
public float waitTime = 1;
// Lets say the list has 5 points.
public List<Vector3> patrolPoints ...
// We keep track of our starting and ending points. Here we start at zero and move to position 1.
public Vector2 currentLeg = new Vector2(0,1);
// We use a coroutine
IEnumerator Patrol()
{
// This keeps track of where we are. 0 is at the starting point and 1 is at the destination.
float progress = 0;
while(true)
{
Vector3 startingPoint = patrolPoints[currentLeg.x];
Vector3 destination = patrolPoints[currentLeg.y];
// Please note this won't compile. It's for brevity. You must lerp x,y,z indiviualy in C#.
transform.position = Mathf.Lerp(startingPoint, destination, progress);
progress+= movementSpeed;
// If we are at our destination
if(progress >=1 )
{
// Reset our progress so wa can start over.
progress = 0;
// Our current point is now what our destination was before.
currentLeg.x = currentLeg.y;
switch(moveType)
{
case MoveType.Forward:
{
// If this condition is true then it means we are at the final point (4 in this case). So we invert the movement direction and set the current point to 3.
if(currentLeg.y == patrolPoints.Count()-1)
{
currentLeg.y -= 1;
moveType = MoveType.Backwards;
// We wait 1 seconds and then do everything again.
yield return new WaitForSeconds(waitTime);
}
else
currentLeg.y++;
}
break;
case MoveType.Backward:
{
// If this condition is true then it means we are at the starting point (0). So we invert the movement direction and set the current point to 1.
if(currentLeg.y == 0)
{
currentLeg.y += 1;
moveType = MoveType.Forward;
// We wait 1 seconds and then do everything again.
yield return new WaitForSeconds(waitTime);
}
else
currentLeg.y--;
}
break;
}
}
}
}
For a quick fix Jayson's answer will be better. But it might fail if the unit moves fast or the unit will stop too early if it moves too slow.
EDIT: The wait time was in the wrong place.

So you want to use a switch to patrol because only 1 enum can be selected at a time?
Why not use a public Transform target; variable and have your AI always follow whats in that variable? Then just make an empty game object and stick it into that variable and he will follow it wherever it goes. He wont be limited to "Forward" and "Backward", and you wont have to recode or be confused, when you want to recycle the code for an AI moving "Left" to "Right"
This will speed up execution, clean up your code, get rid of tons of lines of code as well, and you can still use your switch statement too, because this actually will open your code up to more dynamic abilities such as:
public enum ActionType
{
Patrol,
Veer,
Stop
}
Lets say hes patrolling and halfway to the target he spots an enemy, ActionType curAction; Can be set to ActionType.Veer , now he is veering off the patrol line to attack the enemy, and after you can set it back to ActionType.Patrol and he continues to the target.
So for patrolling you can set up a public List<Vector3> wayPoints; and just add a bunch of Vector3's in there. When he reaches the target, he can stop for a sec or two, then have the empty game object target jump to the next vector3 in the list.

Related

How to extend the curve created using quadratic interpolation in C# & Unity?

I am attempting to use quadratic interpolation in order to throw a rock at the player's position. The rock simply follows the curve. However, if the player moves, the rock begins to loop and starts back at its original position. I have stopped the rock following the curve once it reaches the end of the curve and add force to the rock's rigidbody but it only works in specific examples. I was wondering if there was a way to extend the curve so the rock hits the ground and destroys itself using the code I already have. The code I am using is below. Thanks in advance. The code is below.
Quadratic Interpolation Code (it is run every frame)
private void FollowQuadraticPath()
{
interpolateAmount = (interpolateAmount + Time.deltaTime) % 1f;
pointAB.transform.position = Vector3.Lerp(transformerPosition, throwHeightPosition, interpolateAmount);
pointBC.transform.position = Vector3.Lerp(throwHeightPosition, playerPosition, interpolateAmount);
transform.position = Vector3.Lerp(pointAB.transform.position, pointBC.transform.position, interpolateAmount);
}
Damage
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.GetComponent<PlayerHealth>())
{
Destroy(pointABInstance);
Destroy(pointBCInstance);
collision.gameObject.GetComponent<PlayerHealth>().DealDamage(damage);
Destroy(gameObject);
}
else
{
Destroy(pointABInstance);
Destroy(pointBCInstance);
Destroy(gameObject);
}
}
This is what I want to happen. Pretending the player had moved, the rock would continue and hit the ground past the player point.
Instead, once again pretending the player wasn't there, the object is destroyed before it reaches the ground
Edit: Check comments of the answer for the solution I used
I guess you are using FollowQuadraticPath() within Update so what happens is you always use it with the current updated position of the player.
Instead I would use a Coroutine. A Coroutine is somewhat like a temporary Update method so it has a clear start and end and you can have local variables that persist through all frames the routine is running and do something like e.g.
private IEnumerator FollowQuadraticPath()
{
// Cash the target positions ONCE so they are not updated later
var startAB = TransformerPosition;
var targetAB = throwHeightPosition;
var targetBC = playerPosition;
// Iterates until 1 second has passed
// linearly increasing interpolateAmount from 0 to 1
for(var interpolateAmount = 0f; interpolateAmount < 1f; interpolateAmount += Time.deltaTime)
{
pointAB.transform.position = Vector3.Lerp(startAB, targetAB, interpolateAmount);
pointBC.transform.position = Vector3.Lerp(targetAB, targetBC, interpolateAmount);
transform.position = Vector3.Lerp(pointAB.transform.position, pointBC.transform.position, interpolateAmount);
// Tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
}
// Destroy if it reaches the original player position
Destroy (gameObject);
}
You run this ONCE e.g. in
private void Start ()
{
StartCorouine(FollowQuadraticPath());
}
or Start can also be the routine itself like
private IEnumerator Start()
{
var startAB = TransformerPosition;
var targetAB = throwHeightPosition;
.....
}
you don't have to worry about terminating the routine because it either reaches the original player position and is then destroyed or it hits the ground before and is destroyed. In either case the coroutine is automatically gone as well.
What I don't really get though is why you have 2 additional transforms for this. Why not simply only calculate with Vector3 itself like
var ab = Vector3.Lerp(startAB, targetAB, interpolateAmount);
var bc = Vector3.Lerp(targetAB, targetBC, interpolateAmount);
transform.position = Vector3.Lerp(ab, bc, interpolateAmount);

OnGUI and Coroutine moving an object: really strange behavior

In the title it is not easy to summarize this behavior in the best way.
I have an Editor and a Monobehaviour executed in [ExecuteInEditMode].
I need to move objects by selecting them with the mouse. I thought I didn't encounter any difficulties as I perform this operation with both a character and sprite. But I was wrong. When I select a 3D object (not the character) I can pick and drag it but if I hold the mouse button down without moving it it tends to slip away as if it were falling but on the Y and Z axes. If I deselect its collider the issue does not happen but obviously the collider is necessary for my operations.
I have removed all the unnecessary lines from the code, hoping to solve the problem, but it remains.
void OnGUI() {
if (moveChar) {
StartCoroutine("WaitChar");
} else if (moveSpot) {
StartCoroutine("WaitSpot");
}
Event e = Event.current;
Vector3 mousePosition = e.mousePosition;
mousePosition.y = Screen.height - mousePosition.y;
Ray ray = cam.ScreenPointToRay(mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
mousePositionWorld = hitInfo.point;
if (e.rawType == EventType.MouseDown) {
switch (e.button) {
case 0:
if (isSelectedSphere) {
moveSpot = true;
}
break;
case 1:
if (isSelectedChar) {
moveChar = true;
}
break;
}
} else if (e.rawType == EventType.MouseUp) {
switch (e.button) {
case 0:
if (moveSpot) {
moveSpot = false;
}
break;
case 1:
if (moveChar) {
moveChar = false;
}
break;
}
}
}
}
public IEnumerator WaitChar() {
character.transform.position = mousePositionWorld;
yield return new WaitForSeconds(0.1f);
}
public IEnumerator WaitSpot() {
MoveSpot.transform.position = mousePositionWorld;
MoveSpot.transform.localScale = Vector3.one * ((new Plane(cam.transform.forward,
cam.transform.position).GetDistanceToPoint(
MoveSpot.transform.position)) / radiusPoint);
yield return new WaitForSeconds(0.1f);
}
I have simplified everything with two public variables to choose the object to move.
What I do is very simple, I don't understand why I get this behavior. With the character I have no problem (not even with the sprites, which for the moment I removed from the code). When I click with the right mouse button I run the coroutine that moves the character to the cursor coordinates and continues like this until I release the button. No problem, the character moves along with the cursor without problem.
However, if I execute the same operation (left button) with a 3D object like Sphere, Cylinder, Cube it moves following the mouse but if I don't move the cursor and don't release the button the object slides away with the Y and the Z axes that increase in negative. As I said, if I deactivate the collider the object remains at the cursor position.
The problem is that the spheres cannot be selected through public variables but directly from the script so I need the collider.
I probably don't know Unity well enough to know the reason for this behavior.
Can you clarify this for me?
OnGui is bad (let me explain)
First, OnGui is part of Unity's IMGui system (stands for IMmediate Gui), and has been heavily depreciated in favor of uGui, which is a lot easier to work with for a number of reason. And one of those reasons is....
OnGui is called more than once per frame
Because OnGui is called multiple times per frame, that means that Event.current hasn't changed, meaning moveChar hasn't changed, meaning your coroutine gets started more than once.
And there's no real way around this. Its going to be almost impossible to do what you want using OnGui. It was never meant to handle this sort of logic.
Everything you have should be done inside Update() instead and replacing things like e.rawType == EventType.MouseDown with their Input relatives, eg. Input.GetMouseDown(0).

Transform EulerAngles Error

I have a condition that makes a check. Like this:
if (moveDown)
{
Debug.Log("hit1");
if (RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z <= -40f)
{
moveDown = false;
moveUp = true;
}
else
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex++];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z - angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
}
if (moveUp)
{
Debug.Log("hit2");
if (RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z >= 40f)
{
moveDown = true;
moveUp = false;
}
else
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex--];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z + angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
}
The problem is that when the object is rotated to -40 degrees, and the condition is checked it does not deactivate moveDown and activate moveUp. He further rotates the object with angle index.
I do this rotation when I click on a button. He must, when he reaches -40 degrees, deactivate me moveDown. Why not disable it?
First things first, create some intermediate fields, your code is really heavy, and you're actually accessing the data each time because of all the nested calls you are making, and it's really making it unclear, especially for anyone outside of your project.
Secondly, I don't see the point of having 2 booleans which are the exact opposite value of each other.. you'd get the same result having just one since they are inversed.
That being said, I believe (from what I got from your snippet) that you're probably not in the degree range you might think ? I don't know if you have verified that but, your character could look totally normal, exactly like you have place him originally, but he might have a 360, 720 etc... degree rotation applied(can be negative too), so if you are not looking at the rotation between initial position and desired, you might not get the right values, maybe do a RotationDifference() func, or if (rotation == 360 or -360) rotation = 0 I'm just throwing out ideas, you really have many ways of doing it.
Lastly, your code seems quite heavy for a seemingly mundane task, you should look into that. Try to abstract your tasks, you are trying to do everything at once, instead of having sturdy systems taking care of it.
(new answer after comments)
The logic you want is not appearing "clearly" while reading the code, this makes difficult to debug and understand what is wrong !
As per your comment, I understand that you need move back and forth from -40 to 40.
First, we need to clearly separate the tasks
The two separate tasks are :
Decide in which direction tower is moving next step.
Move the tower up or down. This is basically the two 3-lines blocks of code. Let's call these blocks "MoveUp()" and "MoveDown()"
Now, write some pseudo code for it
// pseudo code
// First, decide in which direction to go
if current angle is 40° (or more),
set direction to down for next move.
else if current angle is -40 (or less),
set direction to up for next move.
else
just continue in the current direction
// Second, move the tower
if direction is down, move down
else move up.
Let's translate this to real C# code
// suppose you have a private bool variable in your class
// called "isMovingDown"
private bool isMovingDown = false; // inital value, could be true if you want
// call this in your Update or FixedUpdate logic
private void MoveYourTowerLogic() {
// First : decide in which direction to go :
var currentAngle = RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z;
if (currentAngle >= 40f)
{
isMovingDown = false;
}
else if (currentAngle <= -40f)
{
isMovingDown = true;
}
// else current angle is between 40 and -40
// we simply need to o nothing, because we want to keep the previous direction set.
// remember, the variable movingDown is declared at the class level,
// so it 'remembers' the last value set from last call
// Second : Move the tower
if (isMovingDown)
{
MoveDown();
}
else // tower is moving up
{
MoveUp();
}
// NOTE : I would simply create one function Move(int angle)
// to simplify this much further and avoid to write all this mostly useless code before
// but I want to keep this simple to understand for now
}
private void MoveDown()
{
// logic to move tower down
}
private void MoveUp()
{
// logic to move tower up
}
Notice how reading this is much more "human-friendly" now, you can now, I hope, read this more easily, and that will help you modify it if you want to do more complex things later.
I strongly suggest you to avoid writing the same code several time. You can notice how I simplified and wrote only once the logic to get the angle into a variable named exactly like what I was thinking in my pseudo code.
Also, to write the methods "MoveUp" and "MoveDown", you can simply copy paste your actual code into them. But then after, you could even refactor them to avoid code repetition.
Like this :
(Edit)
Let's refactor this further
Basically, the moving logic is a completely separate task, and the only difference between moving up and down is the + or - angle.
Also, maybe you want to move your towers with a different angle later. It make sense to say that the rotation of tower depends only on the angle.
So let's create a method for that has only the angle as parameter.
With something like, instead of MoveUp() and MoveDown() :
private void MoveTower(int angle)
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex++];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z + angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
The second part will be greatly simpler :
// Second, move the tower
MoveTower(isMovingDown ? angle : -angle);

Moving towards WayPoints [C#]

I've got this problem: I need that enemies move towards a target position, but my problem is that i wrote this inside the Update function, so the "foreach" statement is done in just one frame. Also, the "waypoint is reached" sentence is never shown. How can I solve this?
Here is the code: `public class WayPointMovement : MonoBehaviour {
public EnemyDataSpawn enemy;
private Vector3 lastPos;
void Start ()
{
lastPos = gameObject.transform.position;
}
void Update ()
{
gameObject.transform.LookAt (LevelManager.current.playerObject.transform);
//WaypointData is a scriptable object that contains the definition of "x" and "z" coordinates.
foreach (WaypointData waypoints in enemy.enemy.waypoint) {
Debug.Log (waypoints.x);
gameObject.transform.position = Vector3.MoveTowards (gameObject.transform.position, new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z), Time.deltaTime * enemy.enemy.easySpeedMovement);
if (gameObject.transform.position.x == lastPos.x + waypoints.x && gameObject.transform.position.z == lastPos.z + waypoints.z) {
Debug.Log("waypoint reached");
lastPos = gameObject.transform.position;
}
}
}
}`
You need to rework Update so that it only does one frame's worth of work. That means that you have to remember a) which waypoint you're going toward, and b) how far you were through going to it. You only move on to the next waypoint when you actually get to it.
There are two ways to track the progress through waypoints:
1. You can simply delete waypoints once they are reached. Then, the waypoint you're trying to get to is always the first one in the list.
2. If you want to keep the waypoints, then you need an extra variable to track which waypoint you're currently moving toward. This could be something like an index into an array or list, or it could be an enumerator (IEnumerator<WaypointData>) on which you call MoveNext each time you finish a waypoint.
You've indicated (in comments on this answer) that you don't want to delete waypoints as you reach them. The most flexible arrangement, allowing you to add new waypoints as you go, is to use an index variable:
int waypointIndex;
void Start()
{
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
update position;
if (reached waypoint)
{
waypointIndex++;
}
}
}
Then, in your Update method, instead of writing a loop, you update your game object's position exactly once, check if it has reached the waypoint just once, and if it has, move on to the next waypoint before returning. If multiple moves are needed to get to a waypoint, you allow those to be multiple calls to Update; each Update is only one step of the movement.
You will need better logic for determining when you have reached a waypoint. As other commenters have indicated, with floating-point values, simply checking if they are exactly equal is not likely to work. Instead, you can check whether the distance to the waypoint is less than Time.deltaTime * enemy.enemy.easySpeedMovement, and if it is, then instead of using Vector3.MoveTowards, just set the position to the waypoint exactly, and treat that step as having reached the waypoint.
Here's the rough logic (in pseudocode) (but not as pseudocode as when I first wrote it):
Vector3 lastPos;
int waypointIndex;
void Start()
{
lastPos = gameObject.transform.position;
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
waypointPosition = new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z);
if (Vector3.Distance(gameObject.transform.position, waypointPosition) > length of one step)
{
gameObject.transform.position = Vector3.MoveToward(gameObject.transform.position, waypointPosition);
}
else
{
gameObject.transform.position = waypointPosition;
log("Waypoint reached");
waypointIndex++;
}
}
}
Use Vector3.sqrMagnitude for comparing closeness. You can define how "close" is close enough. I'm going to use a waypoint as a Vector3 in this example. You can determine the waypoint Vector3 however you like.
// define this globally
public float closeEnough = 5f;
// Within your compare method
if((gameObject.transform.position - waypoint).sqrMagnitude < closeEnough)
{
Debug.Log("waypoint reached");
}
Note: The result of the Vector3 - Vector3, is another Vector3.

How to work out what direction an object is moving

SO I currently have an object which moves in the Y axis (up and down). How would I be able to program it so the program knows whether the object is moving up or down?
I understand I'd need an if statement like the following:
if (object is moving up)
{
//set direction to 1
}
else
{
//object must be going down, set direction to 2
}
I just don't understand what syntax I'd need to use. This would be easy if I was holding down the key and it was moving up or down however that's not the case. The object is a bouncing ball and therefore when you set a power the ball jumps, and bounces so it is constantly changing.
Thanks for your help, let me know if this wasn't described well and you need more info.
You need to save the current coordinates so that you can check them after the update method is called. Then you can check the differences between the previous and current location:
Declare it in Game1() constructor:
Point previous = new Point();
previous.X = myObject.InitialX;
previous.Y = myObject.InitialY;
In Update:
int deltaX = object.X - previous.X;
int deltaY = object.Y - previous.Y;
if (deltaX < 0)
{
object is moving upwards
}
else
{
object is moving downwards
}
if (deltaY < 0)
{
object is moving to the left
}
else
{
object is moving to the right
}
//update the previous state
previous.X = myObject.X;
previous.Y = myObject.Y;
When you update the position of the ball you have one position and you calculate the position for the next frame -> You can calculate the distance vector.
With the sign of the y-value of this distance vector you can decide if it's moving upwards, downwards or isn't moving (=0).

Categories