I know this is a commonly-asked question, but nothing I’ve read has clued me in to why I can’t get this thing working.
What I’m doing is simple enough: I have a player (I will have at least two) who can throw bombs. By default, each player can only throw one bomb at a time.
I will eventually be working in 360 degree aiming with a Dualshock3 controller, but for now, aiming works like this: Hold down the “Throw” key to “cook” a bomb (start it’s detonation timer), and release the “Throw” key to throw the bomb in whichever direction you’re facing. You can also hold down the down arrow key to drop the bomb at your feet.
While cooking a bomb, you can move around with it. To allow for this, I’ve made the player object have a bomb object as its child in the hierarchy. By default, the bomb’s sprite and collider are disabled, and its rigidbody is kinematic. Once you hold down “Throw”, the sprite becomes enabled, and when you throw, the collider and rigidbody switch states as well. There isn’t any collision between the player and the bomb. In fact, the player’s collider is a trigger-- all player collisions are handled with raycasting.
I just want to create a new bomb object (and have it set as a child of the player) when the “Throw” key is pressed if the bomb object isn’t destroyed. As you’d expect, that’s what happens in the last line of the bomb’s Explode() function.
This is the PlayerController script. Sorry it’s messy-- I have a lot of work to do on it still.
using UnityEngine;
using System.Collections;
// collision code is based on tutorial by Sebastian Lague on YouTube
public class PlayerController : MonoBehaviour
{
float speed = 5;
float playerAcceleration = 30;
public LayerMask collisionMask;
public bool grounded;
public float jumpRate;
public bool canThrow = false;
public bool canCook = true;
private int coolDown; // time delay after throwing a bomb before you can throw another. Default is equal to bomb.timeToDetonate
private bool stuck = false; // true if stuck to a wall (clinging)
private int throwPower = 10; // how hard can this player throw a bomb
private bool hittingWall = false;
public bool jumping;
private bool hitCeiling = false;
public float jumpLimit = 15;
public float jumpCounter = 0;
public float gravity = 40;
private float currentSpeed;
private float targetSpeed;
private float skin = 0.005f;
private Vector2 amountToMove;
private BoxCollider2D box;
private Vector2 size;
private Vector2 centre;
private Ray2D ray;
private RaycastHit2D hit;
private BombScript bomb;
private BombScript bombClone;
// Use this for initialization
void Start ()
{
box = GetComponent<BoxCollider2D>();
size = box.size;
centre = box.center;
bomb = GetComponentInChildren<BombScript>();
bombClone = bomb;
coolDown = bomb.getTimeToDetonate();
jumpRate = 8;
jumping = false;
}
void Update ()
{
if (hittingWall)
{
targetSpeed = 0;
currentSpeed = 0;
}
targetSpeed = Input.GetAxis("Horizontal") * speed;
currentSpeed = IncrementTowards(currentSpeed, targetSpeed, playerAcceleration);
if (currentSpeed > 0 && transform.localScale.x < 0)
Flip();
else if (currentSpeed < 0 && transform.localScale.x > 0)
Flip();
if (grounded)
{
jumping = false;
jumpCounter = 0;
if (Input.GetButtonDown("Jump"))
jumping = true;
}
// don't move up if you hit a ceiling
if (hitCeiling)
{
amountToMove.y = 0;
}
if (jumping)
{
// jump
if (Input.GetButton("Jump") && (jumpCounter < jumpLimit) && !hitCeiling)
{
//jumping = true;
amountToMove.y = jumpRate;
jumpCounter++;
}
else
{
jumping = false;
}
}
amountToMove.x = currentSpeed;
// only apply gravity if not already grounded
if (!grounded)
amountToMove.y -= gravity * Time.deltaTime;
Move (amountToMove * Time.deltaTime);
// Cook a bomb: Hold down the Throw button
if (Input.GetButtonDown ("Throw") && canCook)
{
bombClone = (BombScript) Instantiate (bomb, transform.position, transform.rotation);
bombClone.transform.parent = this.transform;
bombClone.Cook();
canCook = false;
canThrow = true;
}
// Throw a bomb: Release the Throw button
if (Input.GetButtonUp ("Throw") && canThrow)
{
// if you're holding the down key, drop the bomb
if (Input.GetAxis("Vertical") < 0)
bombClone.Throw(new Vector2 (transform.localScale.x, 0.5f), 0);
else
bombClone.Throw(new Vector2 (transform.localScale.x, 0.5f), throwPower);
canThrow = false;
canCook = true; // temp: canCook should be made true after the coolDown timer expires, but that isn't worked in yet
}
}
// increment towards target by speed
private float IncrementTowards(float n, float target, float acc)
{
if (n == target)
return n;
else
{
float dir = Mathf.Sign(target - n);
n += acc * Time.deltaTime * dir;
return (dir == Mathf.Sign(target - n)? n: target);
}
}
private void Move(Vector2 moveAmount)
{
float deltaY = moveAmount.y;
float deltaX = moveAmount.x;
Vector2 position = transform.position;
// above and below collisions
grounded = false;
hitCeiling = false;
for (int i = 0; i < 3; i++)
{
float dir = Mathf.Sign (deltaY);
float x = (position.x + centre.x - (size.x / 2)) + (size.x / 2) * i;
float y = position.y + centre.y + size.y / 2 * dir;
ray = new Ray2D(new Vector2(x,y + (skin * Mathf.Sign (dir))), new Vector2(0, dir));
Debug.DrawRay(ray.origin, ray.direction);
hit = Physics2D.Raycast(ray.origin, new Vector2(0, dir), Mathf.Abs(deltaY) + skin, collisionMask);
if (hit)
{
float dst = Vector2.Distance(ray.origin, hit.point);
// Stop player's downward movement after coming within skin width of a collider
if (dst > skin)
{
deltaY = dst * dir + skin * dir * 0.5f;
}
else
{
deltaY = 0;
}
if (dir > 0)
hitCeiling = true;
else
grounded = true;
break;
}
}
// left and right collisions
hittingWall = false;
for (int i = 0; i < 3; i++)
{
float dir = Mathf.Sign (deltaX);
float x = position.x + centre.x + size.x / 2 * dir;
float y = position.y + centre.y - size.y / 2 + size.y / 2 * i;
ray = new Ray2D(new Vector2(x,y), new Vector2(dir, 0));
Debug.DrawRay(ray.origin, ray.direction);
hit = Physics2D.Raycast(ray.origin, new Vector2(dir, 0), Mathf.Abs(deltaX) + skin, collisionMask);
if (hit)
{
float dst = Vector2.Distance(ray.origin, hit.point);
// Stop player's downward movement after coming within skin width of a collider
if (dst > skin)
{
deltaX = dst * dir - skin * dir;
}
else
{
deltaX = 0;
}
hittingWall = true;
break;
}
}
Vector2 finalTransform = new Vector2(deltaX, deltaY);
transform.Translate(finalTransform);
} // END OF- MOVE
private void Flip()
{
transform.localScale = new Vector3(-transform.localScale.x, transform.localScale.y, transform.localScale.z);
}
}
When I test this, the hierarchy shows new bomb(Clone) objects, but they aren’t visible and they don’t seem to have their colliders or rigidbodies enabled. It seems to be creating clones of the destroyed object, but as you can see, it’s the clone that I’m destroying (by calling Cook() and Throw()). Also, I get an exception:
NullReferenceException: Object reference not set to an instance of an object
BombScript.Cook () (at Assets/Scripts/BombScript.cs:81)
PlayerController.Update () (at Assets/Scripts/PlayerController.cs:115)
The line this is pointing to looks like this:
sprite.enabled = true;
In the BombScript, sprite is a SpriteRenderer object that, in the Start() function is set to GetComponent().
Does anybody know what I’m doing wrong here? Thanks!