Video example: https://imgur.com/a/d7MRjVG
Hello, my enemy object is getting stuck to my player object and dragged along. Normally, the enemy has a much lower movement speed than my player does. But when stuck to the player, the enemy essentially inherits my player's movement. I should note that this ONLY happens when the enemy is higher than my player ie has a greater Y position
Player is a rigidbody with Kinematic type
Enemy is a rigidbody with Dynamic type (I wanted this so I can push enemies aside)
Enemy is scripted not to attempt movement if too close to the player as well
The enemy has a pretty basic movement script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AI_Common : MonoBehaviour
{
public float moveSpeed; // Movement speed
public float contactDamageDistance; // offset for contact damage and when to stop walking
public ContactFilter2D movementFilter; // Filter of object types to collide with
Vector2 proposedMovement; // movement to check for collisions before executing
List<RaycastHit2D> castCollisions = new List<RaycastHit2D>(); // Collision list
float myX; // Used for incrementing movement
float myY; // Used for incrementing movement
GameObject player;
Rigidbody2D rBody;
Animator animator;
SpriteRenderer spriteRenderer;
// Start is called before the first frame update
void Start()
{
player = GameObject.FindWithTag("Player");
rBody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
}
// Once per physics frame
private void FixedUpdate()
{
animator.SetBool("IsWalking", false);
// Attempt to move toward player if far enough away
if (Vector2.Distance(player.transform.position, rBody.transform.position) < contactDamageDistance)
return;
bool moveSuccess = false;
myX = 0;
myY = 0;
if (player.transform.position.x > rBody.position.x)
{
myX = moveSpeed * Time.fixedDeltaTime;
} else if (player.transform.position.x < rBody.position.x)
{
myX = -moveSpeed * Time.fixedDeltaTime;
}
// Flip X if needed
if(player.transform.position.x - rBody.position.x < -contactDamageDistance)
{
spriteRenderer.flipX = true;
}
else if (player.transform.position.x - rBody.position.x < contactDamageDistance)
{
spriteRenderer.flipX = false;
}
if (player.transform.position.y > rBody.position.y)
{
myY = moveSpeed * Time.fixedDeltaTime;
}
else if (player.transform.position.y < rBody.position.y)
{
myY = -moveSpeed * Time.fixedDeltaTime;
}
print($"MyX:{rBody.transform.position.x}, MyY:{rBody.transform.position.y}, PlayerX{player.transform.position.x}, playerY{player.transform.position.y}");
print($"Distance:{Vector2.Distance(player.transform.position, rBody.transform.position)}");
moveSuccess = TryMove(new Vector2(myX, myY));
// If move failed, try X only
if (!moveSuccess && myX != 0)
{
moveSuccess = TryMove(new Vector2(myX, 0));
}
// If move failed, try Y only
if (!moveSuccess && myY != 0)
{
moveSuccess = TryMove(new Vector2(0, myY));
}
animator.SetBool("IsWalking", moveSuccess);
}
private bool TryMove(Vector2 moveDirection)
{
// Check for collisions
int count = rBody.Cast(
moveDirection, // X and Y values -1 to 1
movementFilter, // Specifies which objects are considered for collision
castCollisions, // List of collisions detected by cast
moveSpeed * Time.fixedDeltaTime + contactDamageDistance); // Length of cast equal to movement plus offset
// If no collisions found, move
if (count == 0)
{
rBody.MovePosition(rBody.position + moveDirection);
return true;
}
return false;
}
}
Related
I just finished a BreakOut style game but there is a bug where sometimes the ball gets stuck to the edges of the map with no direction or speed as shown in the screenshot
What I see is that it happens when the ball completely loses trajectory or speed, but could not solve the error
enter image description here
my code
public class Ball : MonoBehaviour
{
[SerializeField] Rigidbody2D rigidbody2D;
Vector2 moveDirection;
Vector2 currentVelocity;
float velocity=10;
//GameManager gameManager;
Transform paddle;
[SerializeField] AudioController audioController;
[SerializeField] AudioClip bounceSfx;
[SerializeField] AudioClip dieSfx;
public bool superBall;
[SerializeField] float superBallTime=10;
[SerializeField]float yMinSpeed = 10;
[SerializeField]TrailRenderer trailRenderer;
public bool SuperBall
{
get=> superBall;
set{
superBall=value;
if(superBall)
StartCoroutine(ResetSuperBall());
}
}
// Start is called before the first frame update
void Start()
{
//rigidbody2D = GetComponent<Rigidbody2D>();
//rigidbody2D.velocity = Vector2.up*velocity*Time.deltaTime;
GameManager.Instance = FindObjectOfType<GameManager>();
paddle = transform.parent;
}
// Update is called once per frame
void Update()
{
currentVelocity = rigidbody2D.velocity;
if (Mathf.Abs(currentVelocity.y) < 3 && Mathf.Abs(currentVelocity.y) < 3 && GameManager.Instance.ballOnGame)
{
velocity = 10;
rigidbody2D.velocity = Vector2.up * velocity ;
}
if (Mathf.Abs(currentVelocity.y) + Mathf.Abs(currentVelocity.y) < 10 && GameManager.Instance.ballOnGame)
{
velocity = 10;
rigidbody2D.velocity = Vector2.up * velocity ;
}
if (velocity <10 && GameManager.Instance.ballOnGame)
{
velocity = 10;
rigidbody2D.velocity = Vector2.up * velocity ;
}
if ((Input.GetKey(KeyCode.W) && GameManager.Instance.ballOnGame == false)||(Input.GetKey(KeyCode.Space) && GameManager.Instance.ballOnGame == false))
{
rigidbody2D.velocity = Vector2.up * velocity ;
transform.parent = null;
GameManager.Instance.ballOnGame = true;
rigidbody2D.isKinematic = false;
rigidbody2D.AddForce(new Vector3(velocity, velocity, 0));
if (!GameManager.Instance.GameStarted)
{
GameManager.Instance.GameStarted = true;
}
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.transform.CompareTag("Brick") && superBall)
{
rigidbody2D.velocity = currentVelocity;
return;
}
moveDirection=Vector2.Reflect(currentVelocity,collision.GetContact(0).normal);
if (Mathf.Abs(moveDirection.y) < yMinSpeed)
{
//permitir velocidad minima
moveDirection.y = yMinSpeed*Mathf.Sign(moveDirection.y);
}
rigidbody2D.velocity=moveDirection;
audioController.PlaySfx(bounceSfx);
if (collision.transform.CompareTag("BottomLimit"))
{
if(GameManager.Instance != null)
{
GameManager.Instance.PlayerLives--;
audioController.PlayDie(dieSfx);
if (GameManager.Instance.PlayerLives > 0)
{
rigidbody2D.velocity = Vector2.zero;
transform.SetParent(paddle);
transform.localPosition = new Vector2(0, 0.65f);
GameManager.Instance.ballOnGame = false;
}
}
}
}
IEnumerator ResetSuperBall()
{
trailRenderer.enabled = true;
yield return new WaitForSeconds(superBallTime);
trailRenderer.enabled = false;
GameManager.Instance.powerIsActive = false;
superBall = false;
}
}
This is a common issue with Rigidbodies at low velocities. The physics engine implements a Physics2D.velocityThreshold that is designed to dampen slow bounces and calm down a pile of Rigidbodies.
The default value for velocityThreshold is 1, meaning that the x or y component of the velocity that is slower will be floored to 0.
To resolve this issue you can simply lower the value to 0.001. You can do it in the Physcs2d tab located at Edit->ProjectSettings->Physics2d
The enemy must look at the player if the player comes into view. Below is the code how I did it. The problem is that the enemy sees the player through the wall. How can I fix the code so that the enemy does not see the player through obstacles?
I came up with this solution - to filter out all hits to the player, and then additionally filter out those that do not pass through the barrier. However, I don't know how to implement it.
RaycastHit[] hitsInfo = Physics.SphereCastAll(head.position, sightDistance, transform.forward, sightDistance);
for (int i = 0; i < hitsInfo.Length; i++)
if (hitsInfo[i].transform.gameObject.tag == "Player") {
transform.LookAt(hitsInfo[i].transform.position);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
Debug.DrawLine(head.position, hitsInfo[i].transform.position, Color.red, 0.05f, true);
break;
}
Below is an illustration: the player is white, the enemy is blue, the red is a raycast visualization.
You can use math to check whether the player in sight or not and than use raycast to filter all obstacles.
Here is an example:
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private Transform _objectOfInterest;
[SerializeField] private float _sightAngle = 60f;
[SerializeField] private float _maxDistance = 10f;
private void Update()
{
if (InSight(_objectOfInterest))
{
Debug.Log("InSight");
if (BehindObstacle((_objectOfInterest)))
{
Debug.Log("BehindObstacle");
}
}
}
private bool InSight(Transform objectOfInterest)
{
var forwardDirection = transform.forward;
var directionToTarget = objectOfInterest.position - transform.position;
var angle = Mathf.Acos(Vector3.Dot(forwardDirection.normalized, directionToTarget.normalized));
if (angle * 100 <= _sightAngle) return true;
return false;
}
private bool BehindObstacle(Transform objectOfInterest)
{
var direction = objectOfInterest.position - transform.position;
var raycastDistance = direction.magnitude < _maxDistance ? direction.magnitude : _maxDistance;
var ray = new Ray(transform.position, direction.normalized);
var hits = new RaycastHit[16];
var hitsAmount = Physics.RaycastNonAlloc(ray, hits, raycastDistance);
Debug.DrawRay(transform.position, direction.normalized * raycastDistance);
if (hitsAmount == 0) return true;
foreach (var hit in hits)
{
if (hit.transform.TryGetComponent<Player>(out Player player) == false) // Player is empty MonoBehaviour in my case
{
return true;
}
return false;
}
return false;
}
}
All the math are in InSight method. forwardDirection is the direction where an enemy is looking. When we use "targetposition" - "myposition" in directionToTarget we getting vector that pointing from our position to target. Than we use simple function to get the angle at which the player is in relation to the enemy. The function is - angle = Acos(Dot(A, B)) where A and B are normalized vectors.
In BehindObstacle we just making raycast to player position and if there are any obstacle returning true. You can set sight angle and max distance on your opinion.
And don't use this in Update method (i'm using it only for test) or very often, it can cause performance issues.
I'm able to get my sprite to jump using a Axis.RawInput. This input also serves as a parameter to trigger the jumping animation when the RawInput is greater than 0. This issue with this is when you release the key, the sprite instantly falls back down. How can I perform a fixed jump when the key is pressed once or held down and then have the sprite fall at a fixed rate while also having the animations trigger?
This is what I have in my PlayerMover script now.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMover : MonoBehaviour
{
public Animator anim;
public Vector3 velocity = Vector3.zero;
public float runSpeed = 0f;
public float jumpSpeed = 0f;
private SpriteRenderer sp;
public float maxJump = 4f;
private void Awake()
{
sp = GetComponent<SpriteRenderer>();
}
void Update()
{
runSpeed = Input.GetAxisRaw("Horizontal");
jumpSpeed = Input.GetAxisRaw("Vertical");
anim.SetFloat("Jump", jumpSpeed);
anim.SetFloat("Speed", Mathf.Abs(runSpeed));
}
private void FixedUpdate()
{
Move(runSpeed, jumpSpeed*4);
}
void Move(float horizontal, float vertical)
{
if(horizontal > 0 || horizontal > 0 && vertical >0)
{
anim.SetBool("Idle", false);
sp.flipX = false;
}
else if(horizontal < 0 || horizontal <0 && vertical >0)
{
anim.SetBool("Idle", false);
sp.flipX = true;
}
else
anim.SetBool("Idle", true);
velocity.x = horizontal;
velocity.y = vertical;
transform.position += velocity * Time.fixedDeltaTime;
}
}
I've read about using something like
if(Input.GetKeyDown(GetKeyCode("Space"){
rigidbody2D.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}
In order to move but it allows additional jumps whenever Space is pressed.
If you want to use something like:
if(Input.GetKeyDown(GetKeyCode("Space"))){
rigidbody2D.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}
but need to stop the player from jumping when they're already in the air, just only allow them to do this when they're already on the floor. So:
if(Input.GetKeyDown(GetKeyCode("Space")) && isOnFloor()){
rigidbody2D.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}
Where isOnFloor() is some function that checks if your character is on the floor. Depending on your game implementation this could be done several ways, but the most common one is to check if the player character is colliding with anything, and if so, if that collision object is below them. This stackexchange thread gives some code samples for how to achieve this.
In order to use this
if (Input.GetKeyDown(GetKeyCode("Space"))
{
rigidbody2D.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}
you need to check if your player is colliding with the ground, so instead you would have a bool like bool isGrounded to check if you are touching the ground, to do this you can do it in the OnCollisionStay() to confirm it is true when you are colliding with the ground. You can use tags to check if the collider you are colliding with is the ground. Then when you jump, you need to say isGrounded = false; then it will not be true untill you land on the ground again
if (Input.GetKeyDown(GetKeyCode("Space") && isGrounded == true)
{
rigidbody2D.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
isGrounded = false;
}
My gameobjects are moving oddly.
On the enemy, I have this script :
public float speed = 1.0f;
private Transform target;
public void Start()
{
var player = GameObject.FindWithTag("Player");
target = player.transform;
}
void Update()
{
// Move our position a step closer to the target.
float step = speed * Time.deltaTime; // calculate distance to move
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
// Check if the position of the cube and sphere are approximately equal.
if (Vector3.Distance(transform.position, target.position) < 0.001f)
{
// Swap the position of the cylinder.
target.position *= -1.0f;
}
}
After I hit the enemy with a projectile, it starts moving slower. The script behind the projectile is this :
if (coll.gameObject.tag != "Player")
{
Destroy(gameObject);
if ((coll.collider.GetComponent("Damageable") as Damageable) != null)
{
var d = coll.collider.GetComponent<Damageable>();
d.Damage(1);
}
}
I added this script in, as a Damageable component, however, this behavior was there even before this script was active, so I don't think it's related :
public void Damage(int damageAmount)
{
print("Damage : " + Health + ":" + damageAmount);
Health -= damageAmount;
if (Health <= 0)
{
Destroy(gameObject);
}
}
Any recommendations on what is wrong?
coll
I guess this is short for collision, collision happens on 2 rigidbodies (or another static collider, but unrelated to the question), so when the bullet hit the enemy, it also block the enemy's path, even if you destroy it immediately the enemy will still stop moving for 1 frame.
So make the collider on the bullet to be a trigger, a trigger won't block other rigidbodies.
Use OnTriggerEnter2D (or OnTriggerEnter(Collider) for 3D game) to receive touch event.
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag != "Player")
{
Destroy(gameObject);
var d = other.GetComponent<Damageable>();
if(d != null)
d.Damage(1);
}
}
I'm trying to program AI for a pong game and i'm trying to make the paddle game object follow the y co-ordinate movement of the ball game object.
The problem is that the paddle ends up just moving up and down and not actually following the ball.
This is the script i used to make it follow.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AIMove : MonoBehaviour {
public int speed;
public bool validUp = true;
public bool validDown = true;
void Awake()
{
}
void Update()
{
GameObject sphere = GameObject.Find("Sphere");
Transform spherePosition = sphere.GetComponent<Transform>();
float spherePos = spherePosition.position.y;
if (spherePos < (0) && validUp == (true))
{
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
else if (spherePos > (0) && validDown == (true))
{
transform.Translate(Vector3.down * speed * Time.deltaTime);
}
if (transform.position.y >= (2.3F))
{
validUp = false;
}
else if (transform.position.y <= (-2.3F))
{
validDown = false;
}
else
{
validUp = true;
validDown = true;
}
}
}
I think you do not fully understand how your code works. Let's take a look inside your Update method.
if (spherePos < (0) && validUp == (true))
{
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
What you are saying here is that if the position of the sphere on the Y axis is below 0, the AI paddle should move up. This contains no condition on whether the sphere is moving upwards or downwards. So, the sphere could be moving downwards, and you would still be moving the AI paddle upwards.
else if (spherePos > (0) && validDown == (true))
{
transform.Translate(Vector3.down * speed * Time.deltaTime);
}
This contains the same kind of problems.
Instead, you could do something like this.
[SerializeField]
private float speed;
private Transform sphere;
private void Start()
{
// In general, I'd recommend avoiding
// GameObject.Find, but we'll use it for now.
sphere = GameObject.Find("Sphere").transform;
}
private void Update()
{
if(sphere.position.y >= transform.position.y)
{
transform.Translate(Vector.up * speed * Time.deltaTime);
}
else if(sphere.position.y <= transform.position.y)
{
transform.Translate(Vector.down * speed * Time.deltatime);
}
}
This would basically compare the paddle's position on the Y axis to the sphere's position on the Y axis and move the paddle accordingly, to the correct direction. Then you just have to adjust the speed value as you want.
Note that this only moves the paddle based on the sphere's position on the Y axis alone. It does not take into account how the sphere is going to bounce off the walls or anything like that.