Im making a wave survival game where you can build up defenses and I want to make it so the enemies will attack the objects you placed down to break them and come attack you. I want to know if theres a way to find if the enemy is not moving at a certain speed, therefore they are stuck behind a wall trying to get to the player. And use that to make them attack the nearest wall if they are stuck.
using UnityEngine;
using System.Collections;
namespace CompleteProject
{
public class EnemyMovement : MonoBehaviour
{
// Reference to the player's position.
Transform player;
Transform wall;
// Reference to the player's health.
PlayerHealth playerHealth;
// Reference to this enemy's health.
EnemyHealth enemyHealth;
// Reference to the nav mesh agent.
UnityEngine.AI.NavMeshAgent nav;
void Awake ()
{
// Set up the references.
player = GameObject.FindGameObjectWithTag ("Player").transform;
playerHealth = player.GetComponent <PlayerHealth> ();
enemyHealth = GetComponent <EnemyHealth> ();
nav = GetComponent <UnityEngine.AI.NavMeshAgent> ();
wall = GameObject.FindGameObjectWithTag("Wall").transform;
}
void Update ()
{
// If the enemy and the player have health left...
if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)
{
// ... set the destination of the nav mesh agent to the player.
nav.SetDestination(player.position);
}
// Otherwise...
else
{
// ... disable the nav mesh agent.
nav.SetDestination(player.position);
}
}
}
}
I added the health script to the walls used on the player as well as a rigidbody
Assuming you have rigidbodies attached to your enemies, you can use the magnitude of the velocity component of the rigidbody to determine its speed. With this you can do something like...
Rigidbody2D rbody;
void Start()
{
rbody = GetComponent<Rigidybody2D>();
}
void Update()
{
if (rbody.velocity.magnitude < 1f)
{
// change target to wall or do some logic here
}
else
{
// target is player,
}
}
Related
In my game on unity 2d I have spaceship and a planet. The planet is orbiting a star so I made a script that parents the planet to the player when I get within a range so the planet doesn't fly past or into the player. This script makes the player move with the planet so they land on it and fly around it easily.
Here is the script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ParentPlayer : MonoBehaviour
{
[SerializeField] GameObject Player;
private float Dist;
[SerializeField] float Threshold;
private CircleCollider2D ParentTrigger;
// Start is called before the first frame update
void Start()
{
ParentTrigger = GetComponents<CircleCollider2D>()[1];
ParentTrigger.isTrigger = true;
ParentTrigger.radius = Threshold / transform.localScale.x;
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject == Player)
{
collider.gameObject.transform.SetParent(transform);
}
}
private void OnTriggerExit2D(Collider2D collider)
{
if(collider.gameObject == Player)
{
collider.gameObject.transform.SetParent(null);
}
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, Threshold);
}
}
The problem is that as the planet rotates it ends up moving the player that has been parented to it. How can I make the planet's rotation not affect the position and rotation of the player, but still make the planets position affect the position of the player?
This might not be what you're looking for but I'm going to add it reguardless. Given that the Child-object will follow the parent, I would suggest putting the planet and spaceship as a child of the empty gameobject. Then you could rotate the the planet object, and if planets are in movement, you could add the movement to the parent object.
How about instead of parenting the player, write a script to set its position to the planets + some offset, and then the rotation wouldn't be an issue. Alternatively, if the player doesn't rotate at all, add constraints to its Rigidbody. Maybe something like this:
player.transform.position = planet.transform.position + offset;
Hope that works!
I added a particle system to my unity project. But I can't make it attach to a certain game object or tag. How can I make a Particle System sit on the center of a cube, while it's getting played once if the player triggers with this cube?
Code used for the trigger to work:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Collision_Player_grower : MonoBehaviour
{
public GameObject Player_grower;
public ParticleSystem CollisionGrower;
// Start is called before the first frame update
void Start()
{
times_player_grower = 0;
CollisionGrower.transform.position = Player_grower.transform.position;
}
// Update is called once per frame
void OnTriggerEnter(Collider collision)
{
if (collision.gameObject.tag == "Player")
{
print("we hit an playergrower");
CollisionGrower.Play();
Destroy(Player_grower);
}
}
}
Note: It does work if I manually place the particle system in the heart of the cube, but I assume this can be done in an easier way.
From what I understand, you want the position of the particle system to be in the center of another object.
1)
You could do that simply by attaching a particle system game object as a child of the cube you were talking about.
2)
You could do this with code instead. In case you want the particle system to do something extra, you could just adjust the code.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Collision_Player_grower : MonoBehaviour
{
public GameObject ParticleSystemObject;
public GameObject Cube;
public GameObject Player_grower;
public ParticleSystem CollisionGrower;
// Start is called before the first frame update
void Start()
{
times_player_grower = 0;
CollisionGrower.transform.position = Player_grower.transform.position;
}
// Update is called once per frame
void Update()
{
ParticleSystemObject.transform.position = Cube.transform.position;
}
void OnTriggerEnter(Collider collision)
{
if (collision.gameObject.tag == "Player")
{
print("we hit an playergrower");
CollisionGrower.Play();
Destroy(Player_grower);
}
}
}
I don’t fully understand what the rest of the variables are for, and what they are, so I may have done some un-needed things.
First, I added two GameObject variables. One of them is the cube object you want the particle system to go to, and the other is the particle system as a game object. I set the position of the particle system to the position of the cube.
Important: if you have this script attached to the cube, remove the Cube variable.
Instead of using
... = Cube.transform.position;
Use
... = transform.position;
I make game with isometric view. When player comes into the house the roof of it hides and player can interact with NPCs, items, etc. But now it can interact with it even when roof is visible. How to detect that item is hidden by house roof or wall or another object?
void Update() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
if (Input.GetMouseButtonDown(0)) {
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
}
//Interact ...
}
}
}
You can simply check the distance between the hit wall/roof and NPC, from the ray-cast origin (camera). Like so:
private Camera cameraRef;
private void Awake() {
// P.S: Cache the 'Camera.main', calls to it can be expensive.
cameraRef = Camera.main;
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = cameraRef.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
} else if (RaycastHitRoofOrWallFirst(hits, hit.collider.gameObject)) {
// This NPC is hidden behind a roof/wall.
continue;
}
// Interaction...
}
}
}
/// <summary>
/// Check if a target object is being hidden behind a roof/wall.
/// </summary>
/// <param name="hits">The hits that the raycast gotten.</param>
/// <param name="targetObject">The gameobject to check against.</param>
/// <returns>Return true if the target object is hidden, false if not.</returns>
private bool RaycastHitRoofOrWallFirst(RaycastHit[] hits, GameObject targetObject) {
foreach (RaycastHit hit in hits) {
if (hit.collider.CompareTag("roof") || hit.collider.CompareTag("wall")) {
float distanceFromCameraToObstacle = Vector3.Distance(cameraRef.transform.position, hit.collider.transform.position);
float distanceFromCameraToNPC = Vector3.Distance(cameraRef.transform.position, targetObject.transform.position);
// Check if the NPC is closer to the camera (raycast origin)
// compared to the roof or wall.
if (distanceFromCameraToObstacle < distanceFromCameraToNPC) {
// The roof/wall is closer to the camera (raycast origin)
// compared to the NPC, hence the NPC is blocked by the roof/wall
return true;
}
}
}
return false;
}
Here is a small visual diagram of what it should check for:
Or just use simple raycast...
If possible depending on the context, instead of using Physics.RaycastAll, you can use Physics.Raycast.
It returns the first object that the ray-cast hits.
Adding to this answer an alternative could maybe also be using OnBecameVisible
OnBecameVisible is called when the object became visible by any Camera.
This message is sent to all scripts attached to the Renderer.
and OnBecameInvisible
OnBecameInvisible is called when the Renderer is no longer visible by any Camera.
This message is sent to all scripts attached to the Renderer.
OnBecameVisible and OnBecameInvisible are useful to avoid computations that are only necessary when the object is visible.
For activating and deactivating the according NPC's colliders so the Raycast anyway will only work on visible objects in the first place.
Like on the NPCs have a script
public class InteractableController : MonoBehaviour
{
// you can also reference them via the Inspector
public Collider[] colliders;
private void Awake()
{
// pass in true to also get inactive components
if(colliders.Length = 0) colliders = GetComponentsInChildren<Collider>(true);
}
private void OnBecameInvisible()
{
foreach(var collider in colliders)
{
collider.enabled = false;
}
}
private void OnBecameVisible()
{
foreach(var collider in colliders)
{
collider.enabled = true;
}
}
}
However
Note that object is considered visible when it needs to be rendered in the Scene. It might not be actually visible by any camera, but still need to be rendered for shadows for example. Also, when running in the editor, the Scene view cameras will also cause this function to be called.
In my survival shooter tutorial, I just finished adding the ScoreManager. I copied the scripts exactly as is, but the ScoreManager will never recognize that the score has increased. I've added debugging on the ScoreManager and EnemyHealth scripts to see if the score is actually increasing, and believe me, it isn't.
Here's the EnemyHealth script:
using UnityEngine;
using UnityEngine.AI;
public class EnemyHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health thee nemy starts the game with.
public int currentHealth; // The current health the enemy has.
public float sinkSpeed = 2.5f; // The speed at which the enemy sinks through the floor when dead.
public int scoreValue = 10; // The amount added to the player's score when the enemy dies.
public AudioClip deathClip; // The sound to play when the enemy dies.
Animator anim; // Reference to the animator.
AudioSource enemyAudio; // Reference to the audio source.
ParticleSystem hitParticles; // Reference to the particle system that plays when the enemy is damaged.
CapsuleCollider capsuleCollider; // Reference to the capsule collider.
bool isDead; // Whether the enemy is dead.
bool isSinking; // Whether the enemy has started sinking through the floor.
void Awake()
{
// Setting up the references.
anim = GetComponent<Animator>();
enemyAudio = GetComponent<AudioSource>();
hitParticles = GetComponentInChildren<ParticleSystem>();
capsuleCollider = GetComponent<CapsuleCollider>();
// Setting the current health when the enemy first spawns.
currentHealth = startingHealth;
}
void Update()
{
// If the enemy should be sinking...
if (isSinking)
{
// ... move the enemy down by the sinkSpeed per second.
transform.Translate(-Vector3.up * sinkSpeed * Time.deltaTime);
}
}
public void TakeDamage(int amount, Vector3 hitPoint)
{
// If the enemy is dead...
if (isDead)
// ... no need to take damage so exit the function.
return;
// Play the hurt sound effect.
enemyAudio.Play();
// Reduce the current health by the amount of damage sustained.
currentHealth -= amount;
// Set the position of the particle system to where the hit was sustained.
hitParticles.transform.position = hitPoint;
// And play the particles.
hitParticles.Play();
// If the current health is less than or equal to zero...
if (currentHealth <= 0)
{
// ... the enemy is dead.
Death();
}
}
void Death()
{
// The enemy is dead.
isDead = true;
// Turn the collider into a trigger so shots can pass through it.
capsuleCollider.isTrigger = true;
// Tell the animator that the enemy is dead.
anim.SetTrigger("Dead");
// Change the audio clip of the audio source to the death clip and play it (this will stop the hurt clip playing).
enemyAudio.clip = deathClip;
enemyAudio.Play();
}
public void StartSinking()
{
// Find and disable the Nav Mesh Agent.
GetComponent<NavMeshAgent>().enabled = false;
// Find the rigidbody component and make it kinematic (since we use Translate to sink the enemy).
GetComponent<Rigidbody>().isKinematic = true;
// The enemy should no sink.
isSinking = true;
// Increase the score by the enemy's score value.
ScoreManager.score += scoreValue;
// After 2 seconds destory the enemy.
Destroy(gameObject, 2f);
}
}
Here is the ScoreManager:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace CompleteProject
{
public class ScoreManager : MonoBehaviour
{
public static int score; // The player's score.
Text text; // Reference to the Text component.
void Awake()
{
// Set up the reference.
text = GetComponent<Text>();
// Reset the score.
score = 0;
Debug.Log("score reset");
}
void Update ()
{
// Set the displayed text to be the word "Score" followed by the score value.
Debug.Log(score);
text.text = "Score: " + score;
if (Input.GetKeyDown(KeyCode.V))
{
score += 10;
}
}
}
}
The Score should increase when enemy dies, but for some reason it wont. I know this because in the ScoreManager I've added Debug.Log to print out the value of score every frame. I can press V (added for debug) and my score value increases, but normally it does not.
I have a small car game, and when I move up/down and left/right, the sprite becomes different. But the physicsbody remains the same. How do I adjust physicsbody? I added a screenshot of my sprite. At the moment I have Polygon physics body as on the right one.
Here is the code that adjusts animation states:
void FixedUpdate()
{
if (Input.GetKey(KeyCode.W)) {
rb2d.AddForce(Vector2.up * physicsConstant);
animator.CrossFade("CarUpIdle", 0);
} else if (Input.GetKey(KeyCode.S)) {
rb2d.AddForce(-Vector2.up * physicsConstant);
animator.CrossFade("CarDownIdle", 0);
} else if (Input.GetKey(KeyCode.D)) {
rb2d.AddForce(Vector2.right * physicsConstant);
animator.CrossFade("CarRightIdle", 0);
} else if (Input.GetKey(KeyCode.A)) {
rb2d.AddForce(-Vector2.right * physicsConstant);
animator.CrossFade("CarLeftIdle", 0);
}
}
To Change polygon based on sprite first you will need to have Serializefield variables to keep track of all the respective colliders. In this script basically what I do is I am keeping all the polygon colliders in array and iterate the array and enable it depending upon the sprite.
In the script im putting the required sprites along with the respective collider for sprite in sequence. So when I request the sprite to change I enable the respective collider and disables the other colliders. You will require something similer like this :
[SerializeField]
private Sprite[] Sprites;
[SerializeField]
private PolygonCollider2D[] Colliders;
private int index = 0;
private SpriteRenderer sp;
void Start () {
sp = GetComponent<SpriteRenderer>();
sp.sprite = Value[index];
}
void OnGUI() {
if(GUI.Button(new Rect(0,0, 80,35), "ChangeSprite")) {
colliders[index].enabled = false;
index ++;
if(index > Value.Length -1) {
index = 0;
}
sp.sprite = Sprites[index];
colliders[index].enabled = true;
}
}
Also in this tutorial it has been explained how to tackle this kind of problem
Unity Game Tutorial
Another way to proceed is to remove the ploygon collider and recreate it
Destroy(GetComponent<PolygonCollider2D>());
gameObject.AddComponent<PolygonCollider2D>();
though this is very bad programming considering I will be creating collider on every sprite change which is heavy on game.