I am making my first foray into AI, specifically AI that will follow the player.
I am using the A* Path finding project scripts, but used the Brackeys tutorial for the code
https://www.youtube.com/watch?v=jvtFUfJ6CP8
Source in case needed.
Here's the code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class EnemyAI : MonoBehaviour
{
public Transform Target;
public float speed = 200f;
public float NextWayPointDistance = 3f;
Path path;
int currentWaypoint = 0;
bool ReachedEndOfpath = false;
Seeker seeker;
Rigidbody2D rb;
public Transform EnemyGraphics;
// Start is called before the first frame update
void Start()
{
seeker = GetComponent<Seeker>();
rb = GetComponent<Rigidbody2D>();
InvokeRepeating("UpdatePath", 0f, 1f);
}
void UpdatePath()
{
if (seeker.IsDone())
seeker.StartPath(rb.position, Target.position, OnPathComplete);
}
void OnPathComplete(Path p)
{
if (!p.error)
{
path = p;
currentWaypoint = 0;
}
}
// Update is called once per frame
void fixedUpdate()
{
if(path == null)
return;
if(currentWaypoint >= path.vectorPath.Count)
{
ReachedEndOfpath = true;
return;
}
else
{
ReachedEndOfpath = false;
}
Vector2 Direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
Vector2 Force = Direction * speed * Time.fixedDeltaTime;
rb.AddForce(Force);
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
if(distance < NextWayPointDistance)
{
currentWaypoint++;
}
if(rb.velocity.x >= 0.01f)
{
EnemyGraphics.localScale = new Vector3(-1f, 1f, 1f);
}else if(rb.velocity.x <= 0.01f)
{
EnemyGraphics.localScale = new Vector3(1f, 1f, 1f);
}
}
}
How I tried to fix the issue:
I thought it could've been a problem with the speed so I increased it to 10000000, and still nothing
Next I thought it was a problem with the Rigidbody2d so I check there, found the gravity scale was set at 0, so I increased it to 1. It made my enemy fall to the ground but still no movement.
I thought it could've been a problem with the mass and drag, so I set Linear drag and Angular drag to 0, and also set mass to 1. Still nothing.
I set the body type to kinematic, pressed run, nothing. Set the body type to static, pressed run, nothing. Set the body type to Dynamic, pressed run, still nothing.
I tried to make a new target for the enemy to follow, dragged the empty game object i nto the target, pressed run and still didn't move.
I am at a loss on how to fix this.
Please help?
Looks like maybe a typo? You have:
// Update is called once per frame
void fixedUpdate()
{
but the method is called FixedUpdate(), with a big F in front. You have fixedUpdate, which is NOT the same.
Related
When my player is moving, the animation switches between idle and walk when I am holding down D. I played the animation and it looks fine but when I play the transition it starts idle and switches to walk. I have tried creating a new project and doing it again but it does the same thing. Does anyone know how to fix this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
float moveForce = 10f;
[SerializeField]
float jumpForce = 11f;
float movementX;
Rigidbody2D myBody;
Animator Anim;
private SpriteRenderer sr;
string WALK_ANIMATION = "Walk";
private void Update()
{
playerMoveKeyboard();
animatePlayer();
}
private void Awake()
{
myBody = GetComponent<Rigidbody2D>();
Anim = myBody.GetComponent<Animator>();
sr = GetComponent<SpriteRenderer>();
}
void playerMoveKeyboard()
{
movementX = Input.GetAxisRaw("Horizontal");
transform.position += new Vector3(movementX, 0f, 0f) * moveForce * Time.deltaTime;
}
void animatePlayer()
{
if(movementX > 0)
{
Anim.SetBool(WALK_ANIMATION, true);
sr.flipX = false;
}
else if (movementX < 0)
{
Anim.SetBool(WALK_ANIMATION, true);
sr.flipX = true;
}
else
{
Anim.SetBool(WALK_ANIMATION, false);
}
}
}
Not sure if I am seeing the problem correctly from the gif, but can't you just turn off "Has exit time" and change the transition duration to 0?
Not sure but using a float instead of a bool to decide when to play the animation might solve this. That is how I've been doing it and never had any problems.
Do this by creating a new parameter in the animator called "Speed". Change the condition from idle to walk to Speed greater 0.01 and walk to idle to Speed less 0.01 or some other small number.
In the code you can change "Speed" to the player speed by adding:
anim.setFloat("Speed", Mathf.Abs(movementX))
to the update function inside the player movement script.
You should also remove the Anim.SetBool() from the funciton animatePlayer().
I have a 3D object, broken into 5 parts. When a user clicks on the object, it opens and shows internals. This works fine using OnMouseDown(). What I'm trying to do is make these objects slowly move when clicked, as opposed to just jumping to the finish point. Below is the code. What am I doing wrong here? I haven't really worked on cleaning up the code as I'm trying to just get the functionality to work.
I've uploaded the code to GitHub as well so you can see all the scripts that are in the project.
GothardJ2/BatteryBreakdown: Code for the battery breakdown (github.com)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OpenBattery : MonoBehaviour
{
[SerializeField] float moveSpeed = .05f;
[SerializeField] GameObject top;
[SerializeField] GameObject board;
[SerializeField] GameObject cells;
[SerializeField] GameObject bottom;
public bool isOpen = false;
public ExtendCells extendCells;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void OnMouseDown()
{
if (isOpen == false)
{
OpenBatteryCase();
}
else if (isOpen && extendCells.isExtended == false)
{
CloseBatteryCase();
}
}
private void CloseBatteryCase()
{
Vector3 topXMovement = new Vector3(-0.99f, 0f, 0f);
Vector3 boardXMovement = new Vector3(-0.68f, 0f, 0f);
Vector3 bottomXMovement = new Vector3(0.79f, 0f, 0f);
top.transform.Translate(topXMovement);
board.transform.Translate(boardXMovement);
bottom.transform.Translate(bottomXMovement);
isOpen = false;
}
private void OpenBatteryCase()
{
Debug.Log("Close");
Vector3 topXMovement = new Vector3(0.99f, 0f, 0f);
Vector3 boardXMovement = new Vector3(0.68f, 0f, 0f);
Vector3 bottomXMovement = new Vector3(-0.79f, 0f, 0f);
top.transform.Translate(topXMovement * moveSpeed * Time.deltaTime);
board.transform.Translate(boardXMovement);
bottom.transform.Translate(bottomXMovement);
isOpen = true;
}
}
There is a concept of Coroutines in Unity for making things like these. Coroutines are different than void methods. It's simply a way to make waits or move object in time etc.
public IEnumerator enumerator()
{
for (int i = 0; i < 100; i++)
{
transform.position = new Vector3(transform.position.x+1, transform.position.y,transform.position.z);
yield return new WaitForSeconds(0.1f);
}
}
For example in the above code, transform's position is changing with the intervals of 0.1 seconds in every for loop is looping.
And to start this Coroutine you can't just simply use
enumerator();
instead it starts like this:
StartCoroutine(enumerator());
Just don't use a Coroutine inside the Update method. It's a beginner error.
And to stop it:
StopAllCoroutines();
This stops not just this Coroutine, but all working Coroutines.
For further information on Coroutines check this:
https://docs.unity3d.com/ScriptReference/Coroutine.html
So, this code just isn't responding for whatever reason. I have an enemy that I'm trying to get to face the player at all times (The enemy swoops over the player's head back and forth periodically). But other than making a flip happen whenever a timer hits 0, which doesn't really work all that well, I can't get the Flip function to work. I know the Flipper function is fine; I already tested it out and everything. I'm just not sure how to tell the enemy that when the player is to the left of it, to turn, and vice versa.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class dragoonDetection : MonoBehaviour {
private Rigidbody2D rb;
private Animator anim;
public Transform Player;
private bool facingRight = true;
void Start ()
{
rb = GetComponent<Rigidbody2D> ();
anim = GetComponent<Animator> ();
}
void Update()
{
Flip();
}
void Flip()
{
if (Player.transform.localScale.x > 0) {
transform.localScale = new Vector3 (1.69f, 1.54f, 1f);
}
if (Player.transform.localScale.x < 0) {
transform.localScale = new Vector3 (-1.69f, 1.54f, 1f);
}
}
void Flipper()
{
facingRight = !facingRight;
Vector2 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
Got any ideas? I'd rather avoid using FindGameObject because it's not actually looking for the player script. It's looking for a child transform with no script attached to the player. And because I have two different player GameObjects that you can switch to anytime in the game, it wouldn't really work for me in that regard.
You will need to perform a check of some sort against the players position with the bird position if you want it to face the player at all times. A barebones method would just be to compare the x-positions of the two objects and change the scale accordingly.
void Update()
{
transform.localScale = new Vector3(getDir()*1.69f, 1.54f, 1);
}
private int getDir()
{
if (player.transform.position.x < transform.position.x)
return -1;
else
return 1;
}
You should throw some additional checks in here to keep it from updating the scale every frame when there is no change.
I am trying to allow the player, in a pong game, to control both paddles. However, for some reason only one paddle is controllable while the other simply does nothing. Here is an image of the paddle property bar.
And here is the code to the right most paddle that should be controlled with arrow keys.
using UnityEngine;
using System.Collections;
public class PaddleRight : MonoBehaviour
{
public Vector3 playerPosR;
public float paddleSpeed = 1F;
public float yClamp;
void Start()
{
}
// Update is alled once per frame
void Update()
{
float yPos = gameObject.transform.position.y + (Input.GetAxis("Vertical") * paddleSpeed);
if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.UpArrow)) {
playerPosR = new Vector3(gameObject.transform.position.x, Mathf.Clamp(yPos, -yClamp, yClamp), 0);
print("right padddle trying to move");
}
gameObject.transform.position = playerPosR;
}
}
I can't seem to figure out anywhere why it won't move.. Please, any help would be awesome because I have checked everywhere at this point. Thanks!
I recreated the problem in a project and found that the only problem with it might be you forgetting that the yClamp is public and its set to 0 in the inspector. Make sure you set yClamp to whatever it should be instead of 0.
I would suggest moving the yPos assignment as well as setting the position inside the if statement as you arent changing those if the player isnt moving.
You can also change gameObject.transform.position to just plain transform.position
here is the refined code:
public Vector3 playerPosR;
public float paddleSpeed = 1F;
public float yClamp; // make sure it's not 0 in the inspector!
// Update is alled once per frame
void Update()
{
if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.UpArrow))
{
float yPos = transform.position.y + (Input.GetAxis("Vertical") * paddleSpeed);
playerPosR = new Vector3(transform.position.x, Mathf.Clamp(yPos, -yClamp, yClamp), 0);
transform.position = playerPosR;
}
}
what i't trying to achieve is have my turrent rotate and follow an "Enemy".
At the moment it detects the enemy but it is not rotating and I don't know why.
The bullet it a prefab i drag in, there has to be a better way to do this? Anyone have any suggestions please?
At the moment the bullet never triggers, but the log Shoot and does...and the rotate doesn't work.
Here is what i have
using UnityEngine;
using System.Collections;
public class TurretController : MonoBehaviour {
public Rigidbody bulletPrefab;
private Transform target;
private GameObject bullet;
private float nextFire;
private Quaternion targetPos;
void OnTriggerEnter(Collider otherCollider) {
if (otherCollider.CompareTag("Enemy"))
{
Debug.Log ("in");
target = otherCollider.transform;
StartCoroutine ("Fire");
}
}
void OnTriggerExit(Collider otherCollider) {
if (otherCollider.CompareTag("Enemy"))
{
Debug.Log ("out");
target = null;
StopCoroutine("Fire"); // aborts the currently running Fire() coroutine
}
}
IEnumerator Fire()
{
while (target != null)
{
nextFire = Time.time + 0.5f;
while (Time.time < nextFire)
{
// smooth the moving of the turret
targetPos = Quaternion.LookRotation (target.position);
transform.rotation = Quaternion.Slerp(transform.rotation, targetPos, Time.deltaTime * 5);
yield return new WaitForEndOfFrame();
}
// fire!
Debug.Log ("shoot");
bullet = Instantiate(bulletPrefab, transform.position, transform.rotation) as GameObject;
//bullet.rigidbody.velocity = transform.forward * bulletSpeed;
}
}
}
I tried to change the instantiate part by using this instead
bullet = (GameObject)Instantiate(bulletPrefab, transform.position, transform.rotation);
bullet.GetComponent<Bullet>().target = target.transform;
But then i just get errors like "InvalidCastException: Cannot cast from source type to destination type.
TurretController+c__Iterator0.MoveNext () (at Assets/Scripts/TurretController.cs:44)"
BTW, here's the turret rotation code I used in my project (shared with permission):
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Turret : MonoBehaviour
{
[SerializeField]
private float turnRateRadians = 2 * Mathf.PI;
[SerializeField]
private Transform turretTop; // the gun part that rotates
[SerializeField]
private Transform bulletSpawnPoint;
private Enemy target;
void Update()
{
TargetEnemy();
}
void TargetEnemy()
{
if (target == null || target.health <= 0)
target = Enemy.GetClosestEnemy(turretTop, filter: enemy => enemy.health > 0);
if (target != null)
{
Vector3 targetDir = target.transform.position - transform.position;
// Rotating in 2D Plane...
targetDir.y = 0.0f;
targetDir = targetDir.normalized;
Vector3 currentDir = turretTop.forward;
currentDir = Vector3.RotateTowards(currentDir, targetDir, turnRateRadians*Time.deltaTime, 1.0f);
Quaternion qDir = new Quaternion();
qDir.SetLookRotation(currentDir, Vector3.up);
turretTop.rotation = qDir;
}
}
}
class Enemy : MonoBehaviour
{
public float health = 0;
private static HashSet<Enemy> allEnemies = new HashSet<Enemy>();
void Awake()
{
allEnemies.Add(this);
}
void OnDestroy()
{
allEnemies.Remove(this);
}
/// <summary>
/// Get the closest enemy to some transform, optionally filtering
/// (for example, enemies that aren't dead, or enemies of a certain type).
/// </summary>
public static Enemy GetClosestEnemy(Transform referenceTransform, System.Predicate<Enemy> filter=null)
{
// Left as an exercise for the reader.
// Remember not to use Vector3.Distance in a loop if you don't need it. ;-)
// return allEnemies[0];
}
}
First problem: the bullet prefab. The variable type is RigidBody. If you want to treat it as a game object, the variable must be a game object. Or you can instantiate it, cast to RigidBody, then use the .gameObject accessor. Like this:
((RigidBody)Instantiate(theRigidbody)).gameObject
Second problem: start simple with the rotation. If it's not working, don't get fancy yet. Start with something like this (an instant rotation toward the target):
Vector3 targetDirection = target.transform.position - transform.position;
targetDirection.y = 0; // optional: don't look up
transform.forward = targetDirection;
If it works, then add small pieces of additional complexity until it does exactly what you want. And if you don't get things figured out, give me a shout (a comment) on Monday. I've written turret-aiming code (including a maximum rotation speed), and I don't think my boss would mind if I upload it.