I have been following a tutorial on a unity 2d game. The player and the enemy inherit from a base class known as MovingObject. Everything in the game works fine except that the player and the enemy can't move. Here are the scripts. The movement happens in a gridlike tile system.
This is the original tutorial series. Try going over the moving object, player, and enemy tutorials.
https://www.youtube.com/playlist?list=PLX2vGYjWbI0SKsNH5Rkpxvxr1dPE0Lw8F
Base MovingObject class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class MovingObject : MonoBehaviour {
public float moveTime = 0.1f;
public LayerMask blockinglayer;
private BoxCollider2D boxCollider;
private Rigidbody2D rb2D;
private float inverseMoveTime;
// Use this for initialization
protected virtual void Start () {
boxCollider = GetComponent<BoxCollider2D>();
rb2D = GetComponent<Rigidbody2D>();
inverseMoveTime = 1f / moveTime;
}
protected bool Move(int xDir, int yDir, out RaycastHit2D hit)
{
Vector2 start = transform.position;
Vector2 end = start + new Vector2(xDir, yDir);
boxCollider.enabled = false;
hit = Physics2D.Linecast(start, end, blockinglayer);
boxCollider.enabled = true;
if (hit.transform == null)
{
StartCoroutine(SmoothMovement(end));
return true;
}
return false;
}
protected IEnumerator SmoothMovement(Vector3 end)
{
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
while (sqrRemainingDistance > float.Epsilon)
{
Vector3 newPosition = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
rb2D.MovePosition(newPosition);
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
yield return null;
}
}
protected virtual void AttemptMove<T>(int xDir, int yDir)
where T : Component
{
RaycastHit2D hit;
bool canMove= Move(xDir, yDir, out hit);
if (hit.transform == null)
{
return;
}
T hitComponent = hit.transform.GetComponent<T>();
if(!canMove && hitComponent != null)
{
OnCantMove(hitComponent);
}
}
protected abstract void OnCantMove<T>(T component)
where T : Component;
// Update is called once per frame
}
Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MovingObject {
public int wallDamage = 1;
public int pointsPerFood = 10;
public int pointsPerSoda = 20;
public float restartLevelDelay = 1f;
private Animator animator;
private int food;
// Use this for initialization
protected override void Start () {
animator = GetComponent<Animator>();
food = GameManager.instance.playerFoodPoints;
base.Start();
}
private void OnDisable()
{
GameManager.instance.playerFoodPoints = food;
}
// Update is called once per frame
void Update () {
if (GameManager.instance.playersTurn)
{
return;
}
int horizontal = 0;
int vertical = 0;
horizontal = (int)Input.GetAxisRaw("Horizontal");
vertical = (int)Input.GetAxisRaw("Vertical");
if (horizontal != 0)
{
vertical = 0;
}
if(horizontal!=0 || vertical != 0)
{
AttemptMove<Wall>(horizontal, vertical);
}
}
protected override void OnCantMove<T>(T component)
{
Wall hitwall = component as Wall;
hitwall.damageWall(wallDamage);
animator.SetTrigger("playerChop");
}
protected override void AttemptMove<T>(int xDir, int yDir)
{
food--;
base.AttemptMove<T>(xDir, yDir);
RaycastHit2D hit;
CheckIfGameOver();
GameManager.instance.playersTurn = false;
}
public void LoseFood(int loss)
{
animator.SetTrigger("playerHit");
food -= loss;
CheckIfGameOver();
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Exit")
{
Invoke("Restart", restartLevelDelay);
enabled = false;
}
else if (other.tag == "Food")
{
food += pointsPerFood;
other.gameObject.SetActive(false);
}
else if (other.tag == "Soda")
{
food += pointsPerSoda;
other.gameObject.SetActive(false);
}
}
private void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
private void CheckIfGameOver()
{
if (food <= 0)
{
GameManager.instance.GameOver();
}
}
}
Enemy Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MovingObject {
public int playerDamage;
private Animator animator;
private Transform target;
private bool skipmove;
// Use this for initialization
protected override void Start () {
GameManager.instance.AddEnemyToList(this);
animator = GetComponent<Animator>();
target = GameObject.FindGameObjectWithTag("Player").transform;
base.Start();
}
protected override void AttemptMove<T>(int xDir, int yDir)
{
if (skipmove)
{
skipmove = false;
return;
}
base.AttemptMove<T>(xDir, yDir);
}
public void MoveEnemy()
{
int xDir = 0;
int yDir = 0;
if (Mathf.Abs(target.position.x - transform.position.x) < float.Epsilon)
{
yDir = target.position.y > transform.position.y ? 1 : -1;
}
else
{
xDir = target.position.x > transform.position.x ? 1 : -1;
AttemptMove<Player>(xDir,yDir);
}
}
protected override void OnCantMove<T>(T component)
{
Player hitplayer = component as Player;
animator.SetTrigger("enemyAttack");
hitplayer.LoseFood(playerDamage);
}
}
I'm not sure which whether the player or enemy have an issue of the base script. Can anyone help.
A few Observations (Hopefully it helps):
MovingObject.cs - Inside of Update(), should it not be this?
if (!GameManager.instance.playersTurn)
Note I added a "!" before GameManager
Player.cs - in AttemptMove(), you need to call Move()... like this
Move (xDir, yDir, out hit);
Enemy.cs
1) At the end of AttemptMove(), there should be:
skipMove = true;
2) In MoveEnemy(), at the end, this code should NOT be in the else, but after the else:
AttemptMove<Player>(xDir,yDir);
Related
I am having 3 errors with 2 errors being NullReferenceException and 1 with ReceiveDamage has no receiver! error.
This is the enemy script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : Mover
{
// Experience
public int xpValue = 1;
// Logic
public float triggerLength = 1;
public float chaseLength = 5;
private bool chasing;
private bool collidingWithPlayer;
private Transform playerTransform;
private Vector3 startingPosition;
// Hitbox
public ContactFilter2D filter;
private BoxCollider2D hitbox;
private Collider2D[] hits = new Collider2D[10];
protected override void Start()
{
base.Start();
playerTransform = GameManager.instance.player.transform;
startingPosition = transform.position;
hitbox = transform.GetChild(0).GetComponent<BoxCollider2D>();
}
private void FixedUpdate()
{
// Is the player in range?
if (Vector3.Distance(playerTransform.position, startingPosition) < chaseLength)
{
if (Vector3.Distance(playerTransform.position, startingPosition) < triggerLength)
chasing = true;
if (chasing)
{
if (!collidingWithPlayer)
{
UpdateMotor((playerTransform.position - transform.position).normalized);
}
}
else
{
UpdateMotor(startingPosition - transform.position);
}
}
else
{
UpdateMotor(startingPosition - transform.position);
chasing = false;
}
// Check for overlaps
collidingWithPlayer = false;
boxCollider.OverlapCollider(filter, hits);
for (int i = 0; i < hits.Length; i++)
{
if (hits[i] == null)
continue;
if (hits[i].tag == "Fighter" && hits[i].name == "Player")
{
collidingWithPlayer = true;
}
// The array is not cleaned up, so we do it ourself
hits[i] = null;
}
}
protected override void Death()
{
Destroy(gameObject);
GameManager.instance.GrantXp(xpValue);
GameManager.instance.ShowText("+" + xpValue + " xp", 30, Color.magenta, transform.position, Vector3.up * 40, 1.0f);
}
}
I have two errors for the enemy script: 1. NullReferenceException: Object reference not set to an instance of an object Enemy.Start () (at Assets/Scripts/Enemy.cs:26), 2. NullReferenceException: Object reference not set to an instance of an object Enemy.FixedUpdate () (at Assets/Scripts/Enemy.cs:34)
The last error occurs in the collidable script. Collidable script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Collidable : MonoBehaviour
{
public ContactFilter2D filter;
private BoxCollider2D boxCollider;
private Collider2D[] hits = new Collider2D[10];
protected virtual void Start()
{
boxCollider = GetComponent<BoxCollider2D>();
}
protected virtual void Update()
{
// Collision work
boxCollider.OverlapCollider(filter, hits);
for (int i = 0; i < hits.Length; i++)
{
if (hits[i] == null)
continue;
OnCollide(hits[i]);
// The array is not cleaned up, so we do it ourself
hits[i] = null;
}
}
protected virtual void OnCollide(Collider2D coll)
{
Debug.Log("OnCollide was not implemented in " + this.name);
}
}
In this script there is one error: SendMessage ReceiveDamage has no receiver!
UnityEngine.GameObject:SendMessage (string,object) EnemyHitbox:OnCollide (UnityEngine.Collider2D) (at Assets/Scripts/EnemyHitbox.cs:23) Collidable:Update () (at Assets/Scripts/Collidable.cs:25)
I've been trying to make my enemy fall when it's health reaches zero.
So I made a damage variable. The damage will subtract to the health and when it reaches zero or lower, he's supposed to turn into a ragdoll. But its seems as he's not dying in the first place..
In the gun script:
using UnityEngine;
public class gun : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera Cam;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Fire();
}
void Fire()
{
RaycastHit hit;
if(Physics.Raycast(Cam.transform.position, Cam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Enemy enemy = hit.transform.GetComponent<Enemy>();
if (enemy != null)
{
enemy.damage(damage);
}
}
}
}
}
Now in the enemy script:
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
public class Enemy : MonoBehaviour
{
Animator myAnim;
List<Rigidbody> ragdollRigids;
public float health = 100f;
public void damage (float amount)
{
health -= amount;
if(health <= 0)
{
Death();
}
}
void Death()
{
activateRagdoll();
}
//Update
void Start()
{
myAnim = GetComponent<Animator>();
ragdollRigids = new List<Rigidbody>(transform.GetComponentsInChildren<Rigidbody>());
ragdollRigids.Remove(GetComponent<Rigidbody>());
DeactivateRagdoll();
}
void Update()
{
}
//Ragdoll dependencies.
public void activateRagdoll()
{
myAnim.enabled = false;
for (int i = 0; i < ragdollRigids.Count; i++)
{
ragdollRigids[i].useGravity = true;
ragdollRigids[i].isKinematic = false;
}
}
public void DeactivateRagdoll()
{
myAnim.enabled = true;
for (int i = 0; i < ragdollRigids.Count; i++)
{
ragdollRigids[i].useGravity = false;
ragdollRigids[i].isKinematic = true;
}
}
}
Thank you in advance. :)
Solution
Your variables (damage and amount) are named differently but are used for the same purpose. I have edited your code. There should be no errors. If there are, contact me my commenting on my answer.
Code
Gun:
using UnityEngine;
public class gun : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera Cam;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Fire();
}
void Fire()
{
RaycastHit hit;
if(Physics.Raycast(Cam.transform.position, Cam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Enemy enemy = hit.transform.GetComponent<Enemy>();
if (enemy != null)
{
enemy.damage(damage);
}
}
}
}
}
Enemy:
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
public class Enemy : MonoBehaviour
{
Animator myAnim;
List<Rigidbody> ragdollRigids;
public float health = 100f;
public void damage (float damage)
{
health -= damage;
if(health <= 0)
{
Death();
}
}
void Death()
{
activateRagdoll();
}
//Update
void Start()
{
myAnim = GetComponent<Animator>();
ragdollRigids = new List<Rigidbody>(transform.GetComponentsInChildren<Rigidbody>());
ragdollRigids.Remove(GetComponent<Rigidbody>());
DeactivateRagdoll();
}
void Update()
{
}
//Ragdoll dependencies.
public void activateRagdoll()
{
myAnim.enabled = false;
for (int i = 0; i < ragdollRigids.Count; i++)
{
ragdollRigids[i].useGravity = true;
ragdollRigids[i].isKinematic = false;
}
}
public void DeactivateRagdoll()
{
myAnim.enabled = true;
for (int i = 0; i < ragdollRigids.Count; i++)
{
ragdollRigids[i].useGravity = false;
ragdollRigids[i].isKinematic = true;
}
}
}
So my current code removes 5 from the curhealth variable in my enemys once the bullet collides with it. But the issue is, if there is no matter which enemy i shoot and hit, it removes from every single instantiated enemy. I only want to remove health from the enemy who gets hit, how can i fix this?
Enemy.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
using UnityStandardAssets.Characters.FirstPerson;
public interface IDamagable
{
void TakeDamage(int damage);
}
public class Enemy : MonoBehaviour, IDamagable
{
//Health
public float maxHealth = 100f;
static public float curHealth = 100f;
//Health Bar
public Image healthBar;
public Transform goal;
// AI Pathfinding Variables
NavMeshAgent agent;
// Enemy Collision Variables
public float damageCooldown = 1f;
// Start is called before the first frame update
void Start()
{
curHealth = maxHealth;
}
// Update is called once per frame
void Update()
{
// Pathfinding code(?)
agent = GetComponent<NavMeshAgent>();
agent.destination = goal.position;
//Health Bar Image
healthBar.fillAmount = curHealth / maxHealth;
//Death Function
if (curHealth < 1)
{
Destroy(gameObject);
ZombieSpawner.zombieCount--;
}
}
private void OnTriggerEnter(Collider other)
{
curHealth -= Weapon.damage;
}
private void OnCollisionStay(Collision collision)
{
if (damageCooldown > 0f)
{
damageCooldown = damageCooldown - 0.1f;
}
if (collision.rigidbody.name == "Player" && damageCooldown <= 0f)
{
RigidbodyFirstPersonController.playerCurHealth = RigidbodyFirstPersonController.playerCurHealth - 10;
damageCooldown = 1f;
}
}
public void TakeDamage(int damage)
{
curHealth -= damage;
}
private void Awake()
{
//agent = GetComponent<NavMeshAgent>();
}
}
Weapon.cs
using UnityEngine;
using UnityEngine.UI;
public class GunHit
{
public float damage;
public RaycastHit raycastHit;
}
public class Weapon : MonoBehaviour
{
//Weapon Model
public GameObject weaponModel;
public LayerMask mask;
//UI Variables
public Text ammoUI;
// Weapon Variables
static public int damage = 5;
public int ammo = 12;
public int MaxAmmo = 12;
public int reserveAmmo = 90;
public AudioSource fire;
public AudioSource empty;
public bool isAiming = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
ammoUI.text = ammo + "/" + reserveAmmo;
weaponFiring();
weaponReload();
}
void weaponAiming()
{
if (Input.GetKeyDown(KeyCode.Mouse1))
{
//weaponModel.transform.position = 0, 0, 0;
}
}
// Default Position = x0, y0, z0.5
public GameObject projectilePrefab;
public GameObject gunBarrel;
void weaponFiring()
{
if (Input.GetKeyDown(KeyCode.Mouse0) && ammo > 0)
{
fire.Play();
Instantiate(projectilePrefab, gunBarrel.transform.position, gunBarrel.transform.rotation);
ammo--;
}
if (Input.GetKeyDown(KeyCode.Mouse0) && ammo == 0)
{
empty.Play();
}
}
void weaponReload()
{
if (Input.GetKeyDown(KeyCode.R))
{
if (ammo == 0)
{
if (reserveAmmo >= MaxAmmo)
{
ammo = MaxAmmo;
reserveAmmo = reserveAmmo - ammo;
}
else
{
ammo = reserveAmmo;
reserveAmmo = 0;
}
}
else if (ammo < MaxAmmo)
{
//Tiddies code
reserveAmmo += ammo;
ammo = 0;
reserveAmmo -= ammo = (reserveAmmo < MaxAmmo ? (reserveAmmo) : (MaxAmmo));
/*
* My code
* ammo = MaxAmmo - ammo;
reserveAmmo = reserveAmmo - ammo;
if (reserveAmmo > MaxAmmo)
{
ammo = MaxAmmo;
}
else if (reserveAmmo < MaxAmmo)
{
ammo = reserveAmmo;
reserveAmmo = 0;
}*/
}
}
}
}
The issue is that your current health variable is static, that means that each enemy is using the same variable for health.
Static makes the varaible global to everybody.
remove the static modifier and the variable becomes personal to each enemy ;)
I'm having trouble keeping this method running. It runs for 1 frame them shuts itself off. I am having trouble working out the logic to keep it running. Yes, the enemy does detect if the player is within a field of view, and it does stop when it collides with walls, it also stops detecting the player when they leave the fov.
I've tried changing around if statements, but it's overall very important that the code runs based on the player existing in the FOV collider. The problem is pretty specific, so I don't know what research to do.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(AudioSource))]
public class EnemyAI : MonoBehaviour
{
public float patrolSpeed, chaseSpeed, chaseWaitTime, patrolWaitTime, castRadius;
public Transform[] wayPoints, wayPOI;
public Transform player;
public Animation flinch, die;
public AudioClip grunt, callOut, death;
public LayerMask mask;
public PlayerCheck check;
private float chaseTimer, patrolTimer, wanderTimer;
private int destIndex, destInit, destStart, health;
private bool playerIn;
protected string aggro, resting, warned, sawPlayer;
protected bool patroling;
public bool aggress;
private NavMeshAgent agent;
private Transform playerLP;
private Vector3 startPos;
private Animator anim;
private AudioSource aud;
MeshCollider fieldOfView;
void Awake()
{
//patroling = true;
startPos = transform.position;
destInit = destIndex;
agent = GetComponent<NavMeshAgent>();
agent.autoRepath = true;
agent.autoBraking = true;
fieldOfView = transform.GetChild(0).GetComponent<MeshCollider>();
if (fieldOfView == null)
{
Debug.LogError("The first object MUST be FOV, otherwise the script will not work correctly. 1");
}
check = GameObject.FindObjectOfType<PlayerCheck>();
anim = GetComponent<Animator>();
}
void FixedUpdate()
{
if (check == null)
{
check = GameObject.FindObjectOfType<PlayerCheck>();
}
playerIn = check.playerIn;
RaycastHit hit;
if (Physics.Linecast(transform.position, player.position, out hit, ~mask))
{
if (!playerIn)
{
Debug.DrawLine(transform.position, hit.point, Color.red);
}
else if (hit.collider.gameObject != null)
{
if (!hit.collider.CompareTag("Player"))
{
Debug.DrawLine(transform.position, hit.point, Color.blue);
return;
}
else
{
Debug.DrawLine(transform.position, hit.point, Color.green);
aggress = true;
}
}
}
if (aggress)
{
Aggro(sawPlayer);
}
if (patroling)
{
GoToNext();
}
}
void GoToNext()
{
patroling = true;
aggress = false;
if (wayPoints.Length == 0)
return;
if (playerIn)
return;
if (agent.remainingDistance < .7f)
{
agent.SetDestination(wayPoints[destIndex].position);
destIndex = (destIndex + 1) % wayPoints.Length;
}
}
void Aggro(string condition)
{
if (condition == sawPlayer)
{
Chase(player);
}
}
void Chase(Transform transform)
{
patroling = false;
if (playerIn)
{
agent.SetDestination(transform.position);
print("Chasing");
}
else
{
**Wander(sawPlayer, 15);**
print("Wander, please");
}
}
**void Wander(string condition, float time)**
{
patroling = false;
print(time);
wanderTimer += Time.deltaTime;
print("Wandering");
//this is where I'm having the most trouble, it will print wandering for 1 //frame then stop and go back to patroling
//once the player leaves the fov. I'm trying to figure out where the method //stops and why.
if (condition == sawPlayer)
{
if (wanderTimer >= time)
{
wanderTimer = 0;
if (!agent.pathPending && agent.remainingDistance < .5f)
{
GoToNext();
}
}
else
{
Vector3 vec;
vec = new Vector3(Random.Range(agent.destination.x - 10, agent.destination.x), Random.Range(agent.destination.y - 10, agent.destination.y), Random.Range(agent.destination.z - 10, agent.destination.z));
agent.destination = transform.InverseTransformDirection(vec);
if (agent.remainingDistance < .3f || wanderTimer > time)
{
GoToNext();
}
}
}
}
}
I have the following script
public class SpeedUp : MonoBehaviour {
public Rigidbody2D ball;
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "Ball") {
ball.AddForce(Vector2.left * 1000f);
Debug.Log("ABC");
}
}
}
and every time I run my game, the Debug.Log("ABC") prints ABC in the console, but the Rigidbody doesn't move, it stays as it is. Can someone explain me why, because I don't understand why does the console print work and the Rigidbody doesn't move
This is the code for the Ball
public class Ball : MonoBehaviour {
public Rigidbody2D rb;
public Rigidbody2D hook;
public float releaseTime = 0.15f;
private bool isPressed = false;
void Update()
{
if (isPressed)
{
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Vector3.Distance(mousePos, hook.position) > 2.5f)
{
rb.position = hook.position + (mousePos - hook.position).normalized * 2.5f;
}
else
{
rb.position = mousePos;
}
}
}
void OnMouseDown()
{
isPressed = true;
rb.isKinematic = true;
}
void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(Release());
}
IEnumerator Release()
{
yield return new WaitForSeconds(releaseTime);
GetComponent<SpringJoint2D>().enabled = false;
this.enabled = false;
}
}
The Rigidbody doesn't move may be it's need to getComponenrt()
So, add void Start() method in your the script
public class SpeedUp : MonoBehaviour {
public Rigidbody2D ball;
void Start()
{
ball = ball.GetComponent<Rigidbody2D>();
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "Ball") {
ball.AddForce(Vector2.left * 1000f);
Debug.Log("ABC");
}
}
}