Transform EulerAngles Error - c#

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);

Related

Is there a way to create a curve when launching a gameobject to the player?

This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.

Moving an enemy back and forth in Monogame / XNA

I am trying to achieve a very basic 2D game right now where an enemy is on the screen and it bounces back and forth between set points (50 and 500) kind of like a space invaders sort of thing. My issue is I can only get it to go right, but then not come back towards the left and repeat.
I was messing around with coding it myself before bothering to look into it and actually figure it out but I thought I had something that would work, but well it doesn't, my issue is I don' get why.
My code is supposed to work like a switch, two if statements within the Update loop, one comes on the other goes off, one moves it right the other moves it left, I thought that was fine but it doesn't do that. It moves it right just fine but then the left part just doesn't work.
So, why does the following code not work?
namespace _2D_game_num1
{
class Enemy
{
int health;
Vector2 enemy_location = new Vector2(50, 50);
Vector2 enemy_speed = new Vector2(1, 1);
Player player = new Player("dummy");
public Enemy()
{
health = 100;
}
public void UpdateLocation()
{
//Vector2 player_pos = player.GetLocation();
//if (player_pos.X < 200)
// Using the players location to figure out where the enemy should move
bool right = true;
bool left = false;
if (right)
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X == 500)
{
right = false;
left = true;
}
}
if (left)
{
enemy_location.X -= enemy_speed.X;
if (enemy_location.X == 50)
{
right = true;
left = false;
}
}
}
public Vector2 GetLocation()
{
return enemy_location;
}
}
}
And then in the main Game class I have it so enemy1.UpdateLocation(); is within the Update section correctly (along with my players movement which works fine).
Try this:
public void UpdateLocation()
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X <= 50 || enemy_location.X >= 500)
{
enemy_speed.X = new Vector2(-enemy_speed.X, enemy_speed.Y);
}
}
What we are doing here is moving the enemy based on it's current speed. Then if the location is on the left or right of the screen, change direction.
Sometimes it pays to keep things simple. Get rid of the left and right flags, as they are just confusing things. When you're dealing with Update methods you're typically changing the state of something. The way you had it before, the left and right state was getting reset every time UpdateLocation is called.
Btw, you might want to consider passing in GameTime to your Update methods. Typically, when you're moving things around in real-time you'll want to multiply movement by some kind of deltaTime to keep things smooth on all devices. You're probably getting away with it because by default it'll be a fixed frame rate, but that may not always be the case, so it's a good habit to get into.

Make GameObject “attach” properly?

This script makes a cube "stick" to whatever it collided with. The problem is that when it's going at relatively high or medium speeds (or when the device itself is slow), the cube tends to "get a bit inside" what it collided with and then stick to it. What changes do I have to make to fix this?
In order for this script to work, one GameObject must have bool _sticksToObjects = true; and the other bool _sticksToObjects = false;
I have tried turning the Rigidbody's Collision Detection mode to either Continuous or Continuous Dynamic
I think my script depends on frame rate. That may be where the problem lies.
Normal "Attach":
Abnormal "Attach":
Rigidbody _rigidBody;
Transform _meshTransform;
bool _sticksToObjects = true;
public Transform _stuckTo = null;
protected Vector3 _offset = Vector3.zero;
void Awake()
{
GameObject CubeMesh = GameObject.FindWithTag ("CubeMesh");
GameObject Cube = GameObject.FindWithTag ("Cube");
_rigidBody = Cube.GetComponent<Rigidbody> ();
_meshTransform = CubeMesh.GetComponent<Transform> ();
}
void Update()
{
if (_stuckTo != null)
{
transform.position = _stuckTo.position - _offset;
}
}
void OnCollisionEnter(Collision collision)
{
if (!_sticksToObjects) {
return;
}
_rigidBody.isKinematic = true;
// Get the approximate collision point and normal, as there
// may be multipled collision points
Vector3 contactPoint = Vector3.zero;
Vector3 contactNormal = Vector3.zero;
for (int i = 0; i < collision.contacts.Length; i++) {
contactPoint += collision.contacts [i].point;
contactNormal += collision.contacts [i].normal;
}
// Get the final, approximate, point and normal of collision
contactPoint /= collision.contacts.Length;
contactNormal /= collision.contacts.Length;
// Move object to the collision point
// This acts as setting the pivot point of the cube mesh to the collision point
transform.position = contactPoint;
// Adjust the local position of the cube so it is flush with the pivot point
Vector3 meshLocalPosition = Vector3.zero;
// Move the child so the side is at the collision point.
// A x local position of 0 means the child is centered on the parent,
// a value of 0.5 means it's to the right, and a value of -0.5 means it to the left
meshLocalPosition.x = (0.5f * contactNormal.x);
_meshTransform.localPosition = meshLocalPosition;
if (_stuckTo == null || _stuckTo != collision.gameObject.transform) {
_offset = collision.gameObject.transform.position - transform.position;
}
_stuckTo = collision.gameObject.transform;
}
Here are some screenshots of the Unity editor:
This is a well-known category of problem in game engineering and you'll be pleased to know the solution is relatively simple. You'll be pleased to hear there are similar, but much more complicated, problems that are actually solved in the same way. I'll try to explain.
Now here's the thing. It's quite often that the following question comes up...
So I'm working on GTA. I have a humanoid, H, running around. She approaches vehicle V. She opens the door and gets in and drives off. After that everything goes to hell in Mecanim and all the code stops working. What to do?
Surprisingly, the way that is done in games is:
Surprisingly: you actually swap to totally different models at that point!!!!!
You have H and V in the game. But then you have an animation (say) for H climbing in to V. But then, you literally destroy the game objects of H and V, and you Instantiate (or just awake) a new, totally different, game object, which is D ("a car being driven around by a lady").
(If you think about it, you can see that when you do this, you carefully adjust all the stuff in D, so that it matches what was "just then happening" in the frame, in relation to both H and V. So for example, literally, you copy the transform, twist etc of the car V, to the new car-inside-D, if lady H has the SmearedMakeupEffect, you put the same SmearedMakeupEffect on the lady-within-D, you position all the bones identically, and so on.)
Another simple example of this is, you often get people asking, "my character C gets killed and I want it to become a ragdoll, how to?" In fact you just swap to a totally new game object you have all set up for that passage of the game. Indeed, if you have a character A ("Arnie") in a game, it's normal that you have 4 or 5 "different As" sitting offside the stage, so, there's "ragdoll A", "A who can dance" "A with weapon". And indeed many of these are combos, you know "A on the horse" "A in the car" and so on.
So interestingly, the "real" solution here is,
once they become a new connected thing, destroy them both and swap to a new game object altogether!
if you have made games "until you are blue in the face" from making games, this is just what you would do as a matter of course. Even though its' a simple situation, it's just easier in the long run. After all, consider all the stuff you have to do when this happens:
make hitting object child of the other
turn off physics on the child
change the way your physics works for the whole thing
turn off or change the collider on the hitting object, perhaps making it part of the overall object
you'll likely have some sort of new "separation" physics where it can be knocked-off - you'd have to turn all that on
likely change minor issues like sound effects, colors etc
As you can see it's a huge chore doing all this stuff, and indeed it's one of those things it's just "easier to do properly" and change to a new model.
All that being said, I know you want a Quick Script Solution you can Paste In :) Here it is...
Step 0, You'll create "YourScript" which goes on the "main" cube. it will "catch" another cube moving around.
YourScript will look basically like this ...
[System.NonSerialized] public bool isConnectedNow;
void OnCollisionEnter(Collision collision)
GameObject theThingWeCaught = collision.gameObject
Debug.Log("We caught this thing .. " + theThingWeCaught.name)
// make it a child of us......
theThingWeCaught.transform.parent = transform
theThingWeCaught ... set kinematic
theThingWeCaught ... probably disable the rigidbody
theThingWeCaught ... probably disable the collider
isConnectedNow = true;
That's really all you have to do.
Step 1, YOUR script must have a public bool like this
[System.NonSerialized] public bool isConnectedNow;
Step 2, Here's MyScript which goes on the hitting cube, first we'll unit-test that your isConnectedNow bool is working
public Class MyScript:MonoBehaviour // attach to the "child" cube
{
public float correctXDistance;
public float correctYDistance;
public Transform bigCube;
public YourScript yourScript;
void Update()
{
string message = yourScript.isConnectedNow ? "free" : "stuck";
Debug.Log("I am " + message);
}
}
attach, debug, and run. Make the little cube stick and unstick from the big cube .. Watch the console. it works? So add this to MyScript
private void DistanceCorrectionX()
{
float xDistance = bigCube.position.x - transform.position.x;
float xSign = Mathf.Sign(xDistance);
float xDelta = Mathf.Abs(xDistance);
float closenessPercentage = (xDelta/correctXDistance)*100f;
if ( closenessPercentage<90f || closenessPercentage>110f)
{
// they are not close enough to quantize on this axis
// this comes in to play when you have multiple axes
return; // do nothing.
}
float xShouldBe = bigCube.position.x + xSign * correctXDistance;
Vector3 p = transform;
p.x = xShouldBe; // be careful it's .y, .z etc for other axes
transform.position = p;
}
for now call that in Update() in MyScript like this
void Update()
{
Debug.Log("I am " yourScript.isConnectedNow ? "free" : "stuck");
if (yourScript.isConnectedNow) DistanceCorrectionX();
}
Now actually Play and make it stick. Now, since it's running in Update simply while Play look at the Inspector for MyScript and adjust the value of correctXDistance to get the exact look you want. When yo have decided on a value, unPlay and put that in as the final value you wish.
Next, in DistanceCorrectionX simply duplicate all the code and do it again for the Y axis DistanceCorrectionX. If you also do Z, do that.
Finally. Note you will have a lot of messy code, like this...
void Update()
{
// handle all the DistanceCorrectionX etc as seen above.
if (yourScript.isConnectedNow)
{
.. turn off the collider on me
}
else
{
.. turn on the collider on me
}
}
and so on, there's "many little things" you'll need to do.
Don't forget also, overwhelmingly you may want to make the hitting object a child of the big object, depending on your situation. (Then of course they would move around together as a unit.)
Note that in the positioning code above I just showed it as position, not local position, for pedagogic clarity. If you want to do them flinging around, and spinning and so on, you'd make the hitting object a child of the other and you would use localPosition in the same way. Enjoy.
One possible way that comes to my mind is:
Inside of the "collision enter" check the distance between these objects and move the one that should stick to the other one a bit away.
As you see in the picture the distance between A and B should be equal to the sum of the widths divided by 2 (with a small threshold of course).
If the distance is less than the sum of the widths / 2 then you have an abnormal "attach" and you have to move one of the objects away. Its not really difficult to accomplish that.

Using Switch for Simple Unity3D Patrol Movement

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.

How to return an enemy to original position after losing aggro [issues[

So basically I have an enemy that, once you get close enough, will chase you around and try to attack you. Once you get far enough away, it will run back to where it was originally.
The issue I'm having is that once it runs back to where its starting point is, but doesn't quite hit the point, and keeps falling over and trying to get to that exact point..
Here's what I have for code for that section:
if (inAggroRange()) {
//In aggro range
if (!inAttackRange()) {
chase();
} else {
animation.CrossFade(animationAttack.name);
attack();
if (animation[animationAttack.name].time > 0.9 * animation[animationAttack.name].length) {
impacted = false;
}
}
} else if (!inAggroRange()){
//Go back to home position
if (!(transform.position == enemyHomePosition)) {
animation.CrossFade(animationRun.name);
transform.LookAt(enemyHomePosition);
controller.SimpleMove(transform.forward * speed);
} else {
//Enemy is at home position
animation.CrossFade(animationIdle.name);
}
}
just use
private readonly float maxDistanceToHomePosition = 0.3f; // change this value as you like
...
if((transform.position - enemyHomePosition).magnitude > maxDistanceToHomePosition)
{
...
}
...
the way you are doing it, the enemy is trying to reach one specific point but without a megaton of luck it will always be just a bit over, then turn around and try again. Much like a golf player trying to hit the whole but alyways hitting to hard.
edit: also think about using != for 'not equal', in my opinion thats a bit easier to read

Categories