when my enemy ship moves towards the player i find the distance between them with
private float FindDistance(Vector2 heroCenter, Vector2 spritePos)
{
var deltaX = Math.Pow((spritePos.X - heroCenter.X), 2); // the 2 is the power value (into the power of 2)
var deltaY = Math.Pow((spritePos.Y - heroCenter.Y), 2);
float distance = (float)Math.Sqrt(deltaX + deltaY);
return distance; // returns the distance between the two objects
}
then I calculate the enemy ship angle of rotation with this code
distanceFromHero = FindDistance(Constants.heroCenter, spritePos);
if (distanceFromHero < Constants.HeroWithinRange)
{
heroClose = true;
VelocityX *= .985f; // enemy reduces vel X
VelocityY *= .985f; // enemy reduces vel Y
//atan2 for angle
var radians = Math.Atan2((spritePos.Y - Constants.heroCenter.Y), (spritePos.X - Constants.heroCenter.X));
//radians into degrees
rotationAngle = (float)(radians * (180 / Math.PI));
}
else
{
heroClose = false;
}
but strangely the enemy though moving towards the player does not lock on and stay steady but does a pendulum like movement and when they at the same point the enemy ship rotate endlessly. Some help with code would help.
The perfect answer to my question i found in an XBox forum which couldn't loaded into Visual Studio 2017 so I made a new solution. Hope others can benefit from this excellent tutorial made by Microsoft. The Solution is here.
Related
I've been trying to realistically reflect a 3d sphere on the walls of a box for a while now in Unity. For some reason, the reflection is generally correct, but when the ball hits a wall in certain directions, the reflection is incorrect.
To illustrate what happens to the ball upon hitting a wall: T = top wall, R = right wall, L = left wall, and B = bottom wall. Let r = the ball comes/goes to the right, l = for the left, and s = the ball stops/slows down significantly. The instructions below take this format: Xyz, where X = the wall the ball is about to hit, y = the ball's initial direction, z = the reflection. The game has a top-down perspective, and the instructions are based on the wall's perspective. I'm also new to C#, so the code is potentially eye burning.
Instructions: Tll, Trl; Bll, Brl; Rls or after hitting another wall Rlr, Rrl; Lls or after hitting another wall Llr, Lrl
Generally, when the ball stops, it jumps in the air. I wonder if this is because the angle reflects along the wrong axis, but why would this only sometimes happen? Also, when only one key is held, the ball bounces back and forth until it leaves the arena. I know about discrete and continuous hit detection, and the setting is on discrete, but the walls generally contain the ball well enough, with this case being the exception.
What I tried:
Figuring out how to use Vector3.Reflect. I do not understand what variables this function should contain and how to apply this to my code. I did look at the Unity Documentation page for help, but it did not answer my question.
Changing the negative signs, as the angle has to be a reflection on the y-axis, and this does change how the reflections work, but does not solve the problem. The current way the negatives are ordered are the most ideal I found.
Giving the ball a physics material for bounciness.
Adding a small number to the denominator in the arctan equation to help prevent a division by zero. This did not help at all.
Creating different equations (basically changing the negatives) for varying combinations of positive and negative accelerations. Since there is a certain positive or negative acceleration associated with each button press (see Movement script), and there seems to be an issue with said signs, I wondered if associating each acceleration with its own set of equations could solve the problem. It did not work.
Checked if the walls were at different angles.
Deleting the variables xA and yA, or placing them in different spots.
Experimented with finding the speed of the object, but had no idea how to implement it.
The code for the Movement script for the player named Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 && yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration + 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z + (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 && yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) + 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
This is the code for the collider and reflection:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
The picture below is the game setup. I apologize that it is a link; I do not have enough Reputation to merit a loaded image on this page. Also, if anything is unclear, please message me.
https://i.stack.imgur.com/VREV4.png
I would really appreciate the help. Thanks!
One very important note here: As soon as there is any Rigidbody involved you do not want to set any values through the .transform - This breaks the physics and collision detection!
Your Movement should rather alter the behavior of the Rigidbody e.g. by simply changing its Rigibody.velocity
I would then also place the collision check directly into the balls's component and check whether you hit a wall ("Boundary")
Then another note: Your code is currently frame-rate dependent. It means that if your target device runs with only 30 frames per second you will add 0.3 per second to the acceleration. If you run however on a more powerful device that manages to run with 200 frames per second then you add 2 per second.
You should rather define the de/increase per second and multiply it by Time.deltaTime
All together maybe something like this
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)
{
velocity.z += _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
{
velocity.x += _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
And then finally simply add a PhysicsMaterial with desired settings to the walls and ball.
I used Friction = 0f and Bounciness = 0.7f for ball and walls. For slow movements you also might want/have to adjust the Bounce Threshold in the Project's Physics Settings otherwise there will be no bouncing if the velocity is smaller then 2 by default.
This depends a bit on your definition of "realistic". I disabled gravity so the ball also has no rotation and angular friction:
I'm making a 2D game in Unity and one feature I'm trying to implement is flipping the player if the mouse position is over 90 meaning he is always facing the direction of the mouse.
Vector3 mousePosition = UtilsClass.GetMouseWorldPosition();
Vector3 aimDirection = (mousePosition - transform.position).normalized;
float angle = Mathf.Atan2(aimDirection.y, aimDirection.x) * Mathf.Rad2Deg;
Vector3 aimLocalScale = Vector3.one;
if (angle > 90 || angle < -90)
{
rotate = true;
}
else
{
rotate = false;
}
gameObject.transform.localScale = aimLocalScale;
if (rotate == true)
{
transform.Rotate(0f, 180f, 0f);
}
else
{
transform.Rotate(0f, 0f, 0f);
}
However, one problem I am having is that one, it's very finicky and if you go too fast the player is looking in the wrong direction, and two, if you look straight up, he doesn't know exactly where to look and keeps snapping left and right really fast.
Does anyone know a way to fix this allowing for a smoother more functional flipping?
Try flipping the sprite by adjusting its scale from 1 to -1. Then implement that into a FlipSprite() method and just call it when you want to flip it
So if you want to flip and sprite one approach could be to inverse the localScale of the sprite, like this:
private void FlipSprite()
{
// Multiply the player's x local scale by -1.
Vector3 flippedScale = transform.localScale;
flippedScale.x *= -1;
transform.localScale = flippedScale;
}
I suggest you to keep a boolean variable like isFacingRight to keep tracking the direction the sprite is looking at!
The issue is you use the same rotate threshold for when the character is facing right and left, so when you straddle the threshold he flips a lot and its bad UX for the player.
Solution : make the rotate threshold dynamic so each time the player rotates the threshold gets pushed down.
float thresholdAngle = 90.0f;
change angle > 90 || angle < -90 to
if (angle > thresholdAngle || angle < -1.0f * thresholdAngle)
Then add in
if (rotate == true)
{
transform.Rotate(0f, 180f, 0f);
thresholdAngle = 110.0f;
}
else
{
transform.Rotate(0f, 0f, 0f);
thresholdAngle = 80.0f;
}
I'm not sure which way your character faces first so you may have to swap the thresholdAngle = 110.0f with the threshold = 80.0f;
Unity using C#
In an top down game where the camera rotation is locked, I have made a character controller which is restricted to 8 axis and will only rotate in 45 degree increments. Think Links awakening. Everything works except when moving in a diagonal direction(using WASD).
When I let go of W & D for example, whichever key I let go of last even if by a fraction of a second, the player will end up facing in that direction rather than the intended up and to the right diagonal direction.
I am looking to modify the following code to add some sort of buffer so that the player remains facing the intended direction when I let go of the 2 keys. I'd like to keep it as simple as possible.
public float defaultSpeed = 6f;
CharacterController controller;
private Vector3 playerMovement;
Vector2 input;
float angle;
Quaternion targetRotation;
Transform cam;
// Start is called before the first frame update
void Start()
{
controller = GetComponent<CharacterController>();
cam = Camera.main.transform;
}
// Update is called once per frame
void Update()
{
Movement();
}
public void Movement()
{
//Get inputs
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
//Only update if keys are pressed
if (Mathf.Abs(input.x) < deadZone && Mathf.Abs(input.y) < deadZone) return;
//Get camera angle: input to radians - radians to degrees - degrees plus the camera angle - limit angle to 45 degree increments
angle = Mathf.Atan2(input.x, input.y);
angle = Mathf.Rad2Deg * angle;
angle += cam.eulerAngles.y;
angle = Mathf.Round(angle / 45) * 45;
//Rotate Character
targetRotation = Quaternion.Euler(0, angle, 0 );
transform.rotation = targetRotation;
//Move Character
transform.position += transform.forward * defaultSpeed * Time.deltaTime;
}
Make a courotine that checks if a key is released, and waits, say 0.5 seconds for another key to be released. If another key is released, then stay in that direction. Hope it works. Good luck.
Got an issue where the enemy will fire at the player, but always seems to go high or to the side of the player even when the player is stationary and isn't moving. Am I doing something wrong in my code which creates this wild issue or is it just a random annoying bug?
Using the same script for the player albeit it under a different name works, which leads me to believe the issue lies within the fire point. Under the player's script I fire like so:
// Get the place the player has clicked
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(ray, out hitInfo))
{
if(hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
Whereas in the enemy script it is just done through the player's position.
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(player.transform.position,transform.forward, out hitInfo))
{
Is this underlying issue in the Physics.Raycast call then?
Rest of code for reference:
//More above this but doesn't influence the firing
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
{
if (hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
}
}
private void FireAtPoint(Vector3 point)
{
// Get the velocity to fire out at
var velocity = FiringVelocity(point, angle);
Rigidbody rg = Instantiate(bulletPrefab.gameObject, firePoint.position, firePoint.rotation).GetComponent<Rigidbody>();
EnemyBulletController newProjectile = rg.GetComponent<EnemyBulletController>();
newProjectile.speed = velocity;
}
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
// Get the direction of the mouse click from the player, then get the height differential.
Vector3 direction = destination - transform.position;
float height = direction.y;
height = 0;
// Get the distance in a float of the vector3
float distance = direction.magnitude;
// Turn the firing angle into radians for calculations, then work out any height differential
float AngleRadians = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(AngleRadians);
distance += height / Mathf.Tan(AngleRadians);
// Calculate the velocity magnitude
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * AngleRadians));
// Return the normalized vector to fire at.
return velocity * direction.normalized;
}
Picture for reference:
Your equation for computing the velocity looks doubtful. Let's re-derive it:
The equations of free-fall motion under constant gravity are:
After rearranging by substituting the first into the second, we find an expression for the firing velocity:
This is different to what you have, as you are missing the h/d term; said term also gives a constraint on the allowed values of θ:
(Basically means that if you fire directly at the target the bullet would never reach due to gravity)
There are many other problems with your code; just to list three:
Why set height to zero?
Why add a correction to distance? The correction has no physical interpretation.
The fix suggested by #BasillePerrnoud
Amended code:
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
Vector3 direction = destination - transform.position;
float height = direction.y;
float distance = Mathf.Sqrt(direction.x * direction.x + direction.z * direction.z); // *horizontal* distance
float radians = angle * Mathf.Deg2Rad;
float hOverd = height / distance;
float tanAngle = Mathf.Tan(radians);
if (tanAngle <= hOverd)
// throw an exception or return an error code, because no solution exists for v
float cosAngle = Mathf.Cos(radians);
direction.Y = distance / cosAngle;
float velocity = Mathf.Sqrt((distance * Physics.gravity.magnitude) /
(2 * cosAngle * cosAngle * (tanAngle - hOverd)));
return velocity * direction.normalized;
}
I think you use Raycast wrongly. According to the doc, the second argument is the direction, not the destination:
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
Should be
if (Physics.Raycast(transform.position, player.transform.position -
transform.position, out hitInfo))
That would explain why it is not firing at the right moment and why the direction is not accurate (since hitInfo is wrong)
I wrote some code for a projectile class in my game that makes it track targets if it can:
if (_target != null && !_target.IsDead)
{
Vector2 currentDirectionVector = this.Body.LinearVelocity;
currentDirectionVector.Normalize();
float currentDirection = (float)Math.Atan2(currentDirectionVector.Y, currentDirectionVector.X);
Vector2 targetDirectionVector = this._target.Position - this.Position;
targetDirectionVector.Normalize();
float targetDirection = (float)Math.Atan2(targetDirectionVector.Y, targetDirectionVector.X);
float targetDirectionDelta = targetDirection - currentDirection;
if (MathFunctions.IsInRange(targetDirectionDelta, -(Info.TrackingRate * deltaTime), Info.TrackingRate * deltaTime))
{
Body.LinearVelocity = targetDirectionVector * Info.FiringVelocity;
}
else if (targetDirectionDelta > 0)
{
float newDirection = currentDirection + Info.TrackingRate * deltaTime;
Body.LinearVelocity = new Vector2(
(float)Math.Cos(newDirection),
(float)Math.Sin(newDirection)) * Info.FiringVelocity;
}
else if (targetDirectionDelta < 0)
{
float newDirection = currentDirection - Info.TrackingRate * deltaTime;
Body.LinearVelocity = new Vector2(
(float)Math.Cos(newDirection),
(float)Math.Sin(newDirection)) * Info.FiringVelocity;
}
}
This works sometimes, but depending on the relative angle to the target projectiles turn away from the target instead. I'm stumped; can someone point out the flaw in my code?
Update: thinking about it and trying stuff has led me to the conclusion that it has something to do with when the direction (being in radians) is below 0 and the current projectile angle is above 0.
The variable targetDirectionDelta is not always the shortest direction to the target. If the absolute value of targetDirectionDelta is greater than PI radians, it will appear the projectile is turning away from the target. Turning in the other direction is shorter and expected.
Example:
currentDirection = 2
targetDirection = -2
The projectile can turn -4 radians (in the negative direction), or 2*(PI-2) radians (about 2.2 radians) (in the positive direction).
For this case, your code always calculates the longer direction, but you are expecting the projectile to turn towards the shorter direction:
targetDirectionDelta = targetDirection - currentDirection