I'm trying to get my enemies to follow the player and stop within 20 pixels, I have tried a number of algorithms including the Vector2.Lerp(); method to try and fix this but it keeps breaking the build. Any help would be greatly appreciated. The code is below.
public void Update(GameTime gameTime)
{
if (this.IsAlive)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
double distanceToPlayer = Math.Sqrt(Math.Pow(Level.Player.Position.X - this.Position.X, 2) + Math.Pow(Level.Player.Position.Y - this.Position.Y, 2));
// Calculate tile position based on the side we are walking towards.
float posX = Position.X + localBounds.Width / 2 * (int)direction;
int tileX = (int)Math.Floor(posX / Tile.Width) - (int)direction;
int tileY = (int)Math.Floor(Position.Y / Tile.Height);
if (waitTime > 0)
{
// Wait for some amount of time.
waitTime = Math.Max(0.0f, waitTime - (float)gameTime.ElapsedGameTime.TotalSeconds);
if (waitTime <= 0.0f)
{
// Then turn around.
direction = (FaceDirection)(-(int)direction);
}
}
else
{
// If we are about to run into a wall or off a cliff, start waiting.
if (Level.GetCollision(tileX + (int)direction, tileY - 1) == TileCollision.Impassable || Level.GetCollision(tileX + (int)direction, tileY) == TileCollision.Passable) //is the enemy is close and is not attacking, attack and turn!
{
waitTime = MaxWaitTime;
}
else
{
// Move in the current direction.
Vector2 velocity = new Vector2((int)direction * MoveSpeed * elapsed, 0.0f);
position = position + velocity;
}
}
dtAttack += gameTime.ElapsedGameTime;
AttackPlayer();
}
else
{
dt += gameTime.ElapsedGameTime;
if (dt.TotalSeconds > (sprite.Animation.FrameCount * sprite.Animation.FrameTime))
this.Remove = true;
}
}
Does it have to be 20 pixels on the screen? Seems strange.
You could try to calculate the euclidean distance between the player and the enemy, using the Vector2.Distance method. If the distance is 20 or lower, stop the enemy. If not, keep following the player.
Related
I have a problem with moving from one place to another in Unity over time. I would like my character to move from current position to current + 1 on Y. Unfortunately, looks like it does not get the current position properly or something, since if I debug what I wrote, it says that the magnitude is always 1, so the point is moving with me. Shouldn't it just check the current position and add 1 to Y, move to that position and then check again? I have no idea what's wrong with this code, or if it's strictly connected with how Unity checks positions and things in real time?
public bool moving = false;
private Vector3 dir;
void FrontMovement()
{
Vector3 movement = new Vector3(-Mathf.Sin(transform.eulerAngles.z * Mathf.PI / 180), Mathf.Cos(transform.eulerAngles.z * Mathf.PI / 180), 0f); // always moving front, even after rotation
if (moving == false)
{
dir = movement - transform.position;
moving = true;
return;
}
transform.Translate(dir.normalized * Time.deltaTime);
if(dir.magnitude <= Time.deltaTime)
{
Debug.Log("Finished movement");
moving = false;
}
}
void FixedUpdate()
{
Debug.Log(dir.magnitude);
FrontMovement();
}
I would also like to know how to do rotations over time.
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
lerp also works for rotations
// Movement speed in units per second.
public float speed = 1.0F;
// Time when the movement started.
private float startTime;
// Total distance between the markers.
private float journeyLength;
void StartMoving() {
// Keep a note of the time the movement started.
startTime = Time.time;
Vector3 modifiedPosition = transform.position;
transform.position.y += 1.0f;
// Calculate the journey length.
journeyLength = Vector3.Distance(transform.position, modifiedPosition.position);
moving = true;
}
// Move to the target end position.
void Update()
{
if (moving) {
// Distance moved equals elapsed time times speed..
float distCovered = (Time.time - startTime) * speed;
// Fraction of journey completed equals current distance divided by total distance.
float fractionOfJourney = distCovered / journeyLength;
// Set our position as a fraction of the distance between the markers.
transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fractionOfJourney);
if (fractionOfJourney >= 1.0f) {
moving = false;
}
}
}
You could use Coroutine + Vector3.Lerp to move with a specified amount of time:
public IEnumerator MoveToPosition(Transform transform, Vector3 position, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while(t <= 1f)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, position, t);
yield return null;
}
transform.position = position;
}
you call the coroutine in Start Method
StartCoroutine(MoveToPosition(transform, newposition, timeToMove))
You could use the same logic for Rotating, with Quaternion.Lerp or Slerp and Quaternion.LookRotation, of course you have lot of sample with rotation over time on WEB!! google is your friend...
public IEnumerator RotateToDirection(Transform transform, Vector3 position, float timeToRotate)
{
var startRotation = transform.rotation;
var direction = position - transform.position;
var finalRotation = Quaternion.LookRotation(direction);
var t = 0f;
while (t <= 1f)
{
t += Time.deltaTime / timeToRotate;
transform.rotation = Quaternion.Lerp(startRotation, finalRotation, t);
yield return null;
}
transform.rotation = finalRotation;
}
I am trying to make my camera smoothly zoom in and out whenever I use a mouse scroll wheel, but for some reason it zooms instantly and not smoothly.
This is how I update the zoom:
[SerializeField, Range(10, 100)] float scrollSpeed = 10f;
[SerializeField] Vector2 zoomAmount = Vector2.zero;
private float ScrollWheel
{
get { return Input.GetAxis("Mouse ScrollWheel"); }
}
private new Camera camera = null;
void Update()
{
if (camera == null) return;
UpdateZoom();
}
void UpdateZoom()
{
pos = camera.transform.position;
pos = Vector3.Lerp(pos, new Vector3(pos.x, pos.y - scrollSpeed * 10 * ScrollWheel, pos.z), Time.deltaTime * 2);
pos.y = Mathf.Clamp(pos.y, zoomAmount.x, zoomAmount.y);
camera.transform.position = pos;
}
I think you're a little confused about Lerp (I was confused in the same way too when I started using it). When you pass in a 0 for the time argument (the third argument), it will return your "starting" vector. When you pass a value of 1 or more for the time argument, it will return your "ending" vector. However, if you're passing Time.deltaTime * 2, then you'll be returning approximately the same interpolated vector every frame. Lerp doesn't "track" how far it's already interpolated, so by passing in the same time value every frame, Lerp will never actually return your ending vector. So rather than passing Time.deltaTime * 2, you'd need to do something like this
float interpolatedTime = 0;
void Update()
{
var myVector = Vector3.Lerp(vector1, vector2, this.interpolatedTime);
this.interpolatedTime += Time.deltaTime;
}
How about something like this for your camera:
float zoomTime;
float zoomTarget;
float lastScrollWheelDirection;
void Update()
{
// If this camera is currently zooming in and the player started zooming
// out (or vice versa), reset the amount that is remaining to be zoomed
if ((this.lastScrollWheelDirection > 0 && this.ScrollWheel < 0) ||
(this.lastScrollWheelDirection < 0 && this.ScrollWheel > 0))
{
this.zoomTarget = 0;
}
if (this.ScrollWheel != 0)
{
this.lastScrollWheelDirection = this.ScrollWheel;
}
// zoomTarget is the total distance that is remaining to be zoomed.
// Each frame that the scroll wheel is moved, we'll add a little more
// to the distance that we want to zoom
zoomTarget += this.ScrollWheel * this.scrollSpeed;
// zoomTime is used to do linear interpolation to create a smooth zoom.
// Each time the player moves the mouse wheel, we reset zoomTime so that
// we restart our linear interpolation
if (this.ScrollWheel != 0)
{
this.zoomTime = 0;
}
if (this.zoomTarget != 0)
{
this.zoomTime += Time.deltaTime;
// Calculate how much our camera will be moved this frame using linear
// interpolation. You can adjust how fast the camera zooms by
// changing the divisor for zoomTime
var translation = Vector3.Lerp(
new Vector3(0, 0, 0),
new Vector3(0, this.zoomTarget, 0),
zoomTime / 4f); // see comment above
// Zoom the camera by the amount that we calculated for this frame
this.transform.position -= translation;
// Decrease the amount that's remaining to be zoomed by the amount
// that we zoomed this frame
this.zoomTarget -= translation.y;
}
}
I am attempting to create a minigolf game but am having trouble solving how to calculate the direction to hit the ball. I am thinking that it is easiest to hit the ball in the forward direction that the camera is facing, but I get unexpected results after the first hit as the ball will not calculate the correct direction after hitting the first time. How can I set the direction for hitting the ball and then apply the force I am calculating?
This is my script attached to my ball object at the moment. Sorry for the messiness.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Golfball : MonoBehaviour {
public GameObject ball = null;
public GameObject hole = null;
public GameObject cam = null;
public Text distance;
public Text score;
public Slider powerbar;
private int strokes = 0;
private bool isMoving = false;
private bool increasing = true;
private float distanceToHole;
public float minHitPower = 40.0f;
public float maxHitPower = 270.0f;
private float hitPower = 0;
private float powerIncrement = 5.0f;
private float powerMultiplier = 10;
private float ballRollTime = 0;
private Vector3 ballDir;
// Use this for initialization
void Start() {
distance.GetComponent<Text>().text = "Distance To Hole:" + distanceToHole;
ball.GetComponent<Rigidbody> ();
score.GetComponent<Text>().text = "Strokes:" + strokes;
}
// Update is called once per frame
void Update () {
//Allow the ball to be hit if the ball is not null, not currently moving, and the left mouse button is clicked.
if (ball != null) {
if (Input.GetButton("Fire1") && !isMoving) {
calculatePower ();
}
//Hit ball using power level and set ball to moving.
if (Input.GetButtonUp("Fire1"))
{/**********************************************
//Calculate direction to hit ball
ballDir = cam.transform.forward.normalized;
hitBall(hitPower);
isMoving = true;
}**********************************************/
//Detect when the ball stops
if (isMoving) {
ballRollTime += Time.deltaTime;
if (ballRollTime > 1 && GetComponent<Rigidbody> ().velocity.magnitude <= 0.5) {
GetComponent<Rigidbody> ().velocity = Vector3.zero;
GetComponent<Rigidbody> ().angularVelocity = Vector3.zero;
isMoving = false;
}
} else {
ballRollTime = 0;
}
}
//Calculate distance to hole
distanceToHole = Mathf.Round((Vector3.Distance(ball.transform.position, hole.transform.position) * 100f) / 100f);
distance.GetComponent<Text>().text = "Distance To Hole: " + distanceToHole;
Debug.DrawLine(ball.transform.position, ballDir, Color.red, Mathf.Infinity);
}
void calculatePower(){
//Increase power if it is less than the max power.
if (increasing)
{
if (hitPower < maxHitPower)
{
hitPower += powerIncrement * powerMultiplier;
increasing = true;
}
else if (hitPower >= maxHitPower)
{
increasing = false;
}
}
//Decrease power if power level is not increasing until the power hits the minimum level.
if(!increasing) {
//Debug.Log ("Not Increasing");
if (hitPower > minHitPower) {
//Debug.Log ("HitPower: " + hitPower);
hitPower -= powerIncrement * powerMultiplier;
} else if (hitPower <= minHitPower) {
increasing = true;
}
}
//Update the slider
powerbar.value = hitPower / powerMultiplier;
}
void hitBall (float power){
//Add force to the ball
//ball.GetComponent<Rigidbody>().AddForce(new Vector3(0, 0, power));
//Camera.main.transform.forward
ball.GetComponent<Rigidbody>().AddRelativeForce(ballDir * power);
//Increase stroke count
strokes++;
updateScore(strokes);
//Reset the power and power bar level to minimum default after hitting ball
hitPower = minHitPower;
powerbar.value = hitPower / powerMultiplier;
Debug.Log("HitPower Reset: " + hitPower);
}
void updateScore(int stroke)
{
score.GetComponent<Text>().text = "Strokes:" + stroke;
}
}
You'll have to apply the force like that:
ball.GetComponent<Rigidbody>().AddForce(ballDir * power, ForceMode.Impulse);
But you'll probably have to play with your 'power' variable.
Also note that you want to use impulse because you are hitting the ball and not applying force for some period of time.
The ballDir * power multiples the ballDir vector by your float number. You probably already know how it works, but here is the rundown:
scalarValue * new Vector(x0, x1, ..., xn) == new Vector(scalarValue * x0, scalarValue * x1, ... scalarValue * xn);]
You might also want to get rid of direction's y component so that your ball doesn't fly into the air (you are playing minigolf, right?)
ballDir.y = 0;
I'm creating a 2D game based on asteroids. And In that game I need to thrust the ship in a direction.
I'm able draw the ship, so that it turns around. But when it comes to moving it forward my problem occours.
I don't seem to be able to get my head around this. (This whole making games thing is new to me ^^)
The player.cs
protected Vector2 sVelocity;
protected Vector2 sPosition = Vector2.Zero;
protected float sRotation;
private int speed;
public Player(Vector2 sPosition)
: base(sPosition)
{
speed = 100;
}
public override void Update(GameTime gameTime)
{
attackCooldown += (float)gameTime.ElapsedGameTime.TotalSeconds;
// Reset the velocity to zero after each update to prevent unwanted behavior
sVelocity = Vector2.Zero;
// Handle user input
HandleInput(Keyboard.GetState(), gameTime);
if (sPosition.X <= 0)
{
sPosition.X = 10;
}
if (sPosition.X >= Screen.Instance.Width)
{
sPosition.X = 10;
}
if(sPosition.Y <= 0)
{
sPosition.Y = 10;
}
if (sPosition.Y >= Screen.Instance.Height)
{
sPosition.Y = 10;
}
// Applies our speed to velocity
sVelocity *= speed;
// Seconds passed since iteration of update
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Multiplies our movement framerate independent by multiplying with deltaTime
sPosition += (sVelocity * deltaTime);
base.Update(gameTime);
}
private void HandleInput(KeyboardState KeyState, GameTime gameTime)
{
if (KeyState.IsKeyDown(Keys.W))
{
//Speed up
speed += 10;
sVelocity.X = sRotation; // I know this is wrong
sVelocity.Y = sRotation; // I know this is wrong
}
else
{
//Speed down
speed += speed / 2;
}
if (KeyState.IsKeyDown(Keys.A))
{
//Turn left
sRotation -= 0.2F;
if (sRotation < 0)
{
sRotation = sRotation + 360;
}
}
if (KeyState.IsKeyDown(Keys.D))
{
//Turn right
sRotation += 0.2F;
if (sRotation > 360)
{
sRotation = sRotation - 360;
}
}
}
Am I close, or seriously far from right?
sRotation is an angle, sVelocity is a velocity. You need trigonometry.
for instance, you could use something like that (I didn't test the signs for correctness):
if (KeyState.IsKeyDown(Keys.W))
{
//Speed up
speed += 10;
sVelocity.X = Math.cos(sRotation * 2 * Math.PI / 360);
sVelocity.Y = -Math.sin(sRotation * 2 * Math.PI / 360);
}
Would that solve your problem?
EDIT: your "speed down" formula is wrong. You are currently adding speed/2 with speed, you should have something along:
speed = speed / 2; // note the "=", not "+="
Also, it would probably be preferable to use something like:
if (speed > 0) {
speed -= 5;
} else {
speed = 0;
}
I am trying to implement basic (for now) collision detection into my platformer. I have tiles that are each 16 x 16 in size. The character is 32 x 32 pixels in size and has its own bounding box. Now, in my Tile class, I have a bool, isSolid. Each of these tiles in my array also have a rect for their respective bounding boxes.
I am checking to see if there's an intersection between the player and tiles by doing:
if (player.GetBoundingBox().Intersects(map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].bounds) && map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].isSolid)
{
...
}
Now, my problem is that this is extremely inaccurate as I'm rounding off the position. I'm tired as heck right now and for the life of me I can't figure out how to properly do this. What is the best way to approach this issue?
Well this might not be exactly "basic", It works very nicely and dosen't have any problems because it calculates the X axis and Y axis seperatley, this collision structure will help you later on. (I switched to this from the old Platformer Starter kit code, which was very glitchy)
Assuming you already have methods for gravity, lets get started.
This should be after your falling and velocity logic, It will see what axises need to be checked.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; //If you havent already, get the elapsed time
if (velocity.X != 0f)
{
Position += velocity.X * Vector2.UnitX * elapsed;
HandleCollisions(CollisionDirection.Horizontal);
}
if (velocity.Y != 0f)
{
Position += velocity.Y * Vector2.UnitY * elapsed;
HandleCollisions(CollisionDirection.Vertical);
}
Now for the very important HandleCollisons method
private void HandleCollisions(CollisionDirection direction)
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = player.GetBoundingBox();
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
Rectangle tileBounds = Level.GetBounds(x, y);
// If this tile is collidable,
bool IsSolid = map.tiles[x,y].IsSolid;
Vector2 depth;
if (isSolid && TileIntersectsPlayer(BoundingRectangle, tileBounds, direction, out depth))
{
if ((collision == ItemCollision.Platform && movement.Y > 0))
continue;
isOnGround = true;
if (isSolid || isOnGround)
{
if (direction == CollisionDirection.Horizontal)
{
position.X += depth.X;
}
else
{
isOnGround = true;
position.Y += depth.Y;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
public static bool TileIntersectsPlayer(Rectangle player, Rectangle block, CollisionDirection direction, out Vector2 depth)
{
depth = direction == CollisionDirection.Vertical ? new Vector2(0, player.GetVerticalIntersectionDepth(block)) : new Vector2(player.GetHorizontalIntersectionDepth(block), 0);
return depth.Y != 0 || depth.X != 0;
}
Thats it for that! It will detect collisons, but we need to allow it to figure out how much to push the player back up once it collides! You will need these two extension methods.
public static float GetHorizontalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = rectA.Width / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
// Calculate centers.
float centerA = rectA.Left + halfWidthA;
float centerB = rectB.Left + halfWidthB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA - centerB;
float minDistanceX = halfWidthA + halfWidthB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceX) >= minDistanceX)
return 0f;
// Calculate and return intersection depths.
return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}
public static float GetVerticalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfHeightA = rectA.Height / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
// Calculate centers.
float centerA = rectA.Top + halfHeightA;
float centerB = rectB.Top + halfHeightB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceY = centerA - centerB;
float minDistanceY = halfHeightA + halfHeightB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceY) >= minDistanceY)
return 0f;
// Calculate and return intersection depths.
return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}
Note you may need to modify this a bit, as the players position is the BOTTOM left. Also a collision enum is needed, for vertical and horizontal. Please tell me if anything seems missing in this.