Line Renderer with Collision in 2D game

Been digging and can’t seem to find a great solution for adding collision detection for a Line Renderer that moves depending on whether/where it’s hitting a target, check the video out below. So I want to add a Collider to this Line Renderer but I’m guessing it would have to redraw the Collider each time because the shape of the Renderer is changing, which doesn’t sound right.

electricpassionateargusfish

That’s what physics queries are for; they allow you to dynamically perform collision detection.

Made some good progress using Raycasts, had to first figure out how to only apply it to 2 of my LayerMasks, then noticed that it actually takes that as a 4th argument!

So now I’ve tied the laser eyeball weapon (sprite/animation for it not started yet!) to my player character with one script, which controls it’s activation and how long it stays on the player, until it’s destroyed, then it’ll pop back up again after 3 seconds.

In another script I control the functionality of the laser itself and the way in which it damages the enemies.

I still have 2 remaining issues, that are illustrated in the video below:

cornysophisticatedaustraliancattledog

1.) When the Ray is cast but it’s not hitting any collider, it falls into an else block for the LineRenderer to be drawn to it’s full distance. If it is hitting something, the Line Renderer will stop at the point of collision. However when it’s not hitting any collider and my player is moving, the line is not going straight horizontally in the correct direction, it starts moving up or down depending on my players movement. I believe it’s something perhaps at line 50 of the code below that’s causing the issue

2.) When I’ve hit an enemy, the enemy turns red. However I want to put the enemy back to it’s normal color when it’s no longer being hit. I’ve tried to put some code in for this at line 52 of the code below, however the code I’ve got is saying “If we have a spriteRenderer stored from the last hit, but there’s no hit, then return that enemy back to normal (not red)”, however if I move the lazer from one enemy to another straight away, the last enemy will remain red. It’s like I need to compare the old collision to the new one and see if they are different, if they are, return the old enemy back to normal color, not quite sure how to achieve this though.

Below is the code I’m using for the laser logic:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class EyeballLogic : MonoBehaviour
{
[SerializeField] private float defDistanceRay;
public LineRenderer lineRenderer;
public Transform EyeballFirePoint;
Transform EyeballTransform;
[SerializeField] private LayerMask EnemyHitboxLayerMask;
[SerializeField] private LayerMask EnemyPusherLayerMask;
[SerializeField] private int damage;
[SerializeField] private Transform pfDamageIndicator;

private SpriteRenderer spriteOfenemy;
private PlayerMovement player;
private Vector3 facingRight;
private Vector3 facingLeft;
float timer = 0f;

private void Start() {
    player = GetComponentInParent<PlayerMovement>();
    facingRight = new Vector3(1,0,0);
    facingLeft = new Vector3(-1,0,0);
}

private void Update() {
    Vector3 directionToFire = player.facingRight ? facingRight : facingLeft;
    ShootLaser(directionToFire);
}

void ShootLaser(Vector3 directionToFire) {

    if (Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 100, EnemyHitboxLayerMask | EnemyPusherLayerMask)) {

        RaycastHit2D hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 100, EnemyHitboxLayerMask | EnemyPusherLayerMask);
        Draw2DRay(EyeballFirePoint.position, hit.point);

        if (timer > 0f) {
            timer -= Time.deltaTime;
        }

        if (timer <= 0f) {
            DamageEnemy(hit);
        }
    }
    else {
        Draw2DRay(EyeballFirePoint.position, directionToFire * 100);

        if (spriteOfenemy) {
            ChangeEnemyBackToNormalColor();
        }
 
    }

}

void Draw2DRay(Vector2 startPos, Vector2 endPos) {

    lineRenderer.SetPosition(0, startPos);
    lineRenderer.SetPosition(1, endPos);
}

void DamageEnemy(RaycastHit2D hit) {

    if (hit.collider) {
       StandardEnemyMovement enemyLogic = hit.collider.GetComponentInParent<StandardEnemyMovement>();

        enemyLogic.health = enemyLogic.health - damage;

        TextMeshPro textMesh = pfDamageIndicator.GetComponent<TextMeshPro>();

        textMesh.SetText(damage.ToString());

        Instantiate(pfDamageIndicator, hit.transform.position, Quaternion.identity);
 
        spriteOfenemy = hit.collider.GetComponentInParent<SpriteRenderer>();

        spriteOfenemy.color = new Color(184, 0, 0, 255);

        timer = 0.3f;
    }
}

void ChangeEnemyBackToNormalColor() {
 
    spriteOfenemy.color = new Color(255, 255, 255, 255);
    spriteOfenemy = null;
}

}

EDIT: Issue 2.) has now been fixed :slight_smile: I did:

void DamageEnemy(RaycastHit2D hit) {

    if (hit.collider) {
       StandardEnemyMovement enemyLogic = hit.collider.GetComponentInParent<StandardEnemyMovement>();

        enemyLogic.health = enemyLogic.health - damage;

        TextMeshPro textMesh = pfDamageIndicator.GetComponent<TextMeshPro>();

        textMesh.SetText(damage.ToString());

        Instantiate(pfDamageIndicator, hit.transform.position, Quaternion.identity);

        if (spriteOfEnemy != null && spriteOfEnemy != hit.collider.GetComponentInParent<SpriteRenderer>()) {
            spriteOfEnemy.color = new Color(255, 255, 255, 255);
        }
  
        spriteOfEnemy = hit.collider.GetComponentInParent<SpriteRenderer>();

        spriteOfEnemy.color = new Color(184, 0, 0, 255);

        timer = 0.3f;
    }
}

So, if we have a sprite of the enemy and the new one being hit is not the same as the old one, put it’s color back to normal from red.

EDIT: Issue 1.) has now been fixed :sunglasses: I did:

void ShootLaster(Vector3 directionToFire) {

    if (Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, EnemyHitboxLayerMask | EnemyPusherLayerMask)) {

        RaycastHit2D hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, EnemyHitboxLayerMask | EnemyPusherLayerMask);
        Draw2DRay(EyeballFirePoint.position, hit.point);

        if (timer > 0f) {
            timer -= Time.deltaTime;
        }

        if (timer <= 0f) {
            DamageEnemy(hit);
        }
    }
    else {
        Draw2DRay(EyeballFirePoint.position, new Vector2(player.facingRight ? EyeballFirePoint.position.x + 50 : EyeballFirePoint.position.x -50, EyeballFirePoint.position.y));

        if (spriteOfEnemy) {
            ChangeEnemyBackToNormalColor();
        }
      
    }

}

void Draw2DRay(Vector2 startPos, Vector2 endPos) {

    Debug.Log(endPos);

    lineRenderer.SetPosition(0, startPos);
    lineRenderer.SetPosition(1, endPos);
}

Had to tinker around with the Vector2 I passed as the end position to the Draw2Ray function. Had to take a break from it, as i was just trying to force stuff to work and not actually thinking about the problem.

If anyone is interested in seeing it working! Gotta do the sprite/animation for the actually demon eyeball shooting the laser now :slight_smile:

unfortunatedarkivorybackedwoodswallow

EnemyHitboxLayerMask
EnemyPusherLayerMask

Is there a reason why you’re using two layer masks? A LayerMask is obviously a selection of layers so you can define this as one.

Also, you perform a raycast but nowhere do you check to see if it hits anything, you just assume it does.

You should do “if (hit) { hit is valid! }”.

Finally, Letting Unity implicitly convert Vector3 to Vector2 can be a source of subtle bugs. I would try to use Vector2 where you can. Passing around Vector3s which are only ever going to be used in 2D things like 2D Physics can lead to subtle bugs such as if you were to normalise them but they have a non-zero value in Z.

Hope this help.

I found if I did it this way, it gives you access to a dropdown in the inspector and I can just grab the layers I wanted the raycast to hit, just thought it was a nice easy way to do it.

Does the first if block not do this?

if (Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, EnemyHitboxLayerMask | EnemyPusherLayerMask)) {

        RaycastHit2D hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, EnemyHitboxLayerMask | EnemyPusherLayerMask);
        Draw2DRay(EyeballFirePoint.position, hit.point);

        if (timer > 0f) {
            timer -= Time.deltaTime;
        }

        if (timer <= 0f) {
            DamageEnemy(hit);
        }
    }
    else {
        Draw2DRay(EyeballFirePoint.position, new Vector2(player.facingRight ? EyeballFirePoint.position.x + 50 : EyeballFirePoint.position.x -50, EyeballFirePoint.position.y));

        if (spriteOfEnemy) {
            ChangeEnemyBackToNormalColor();
        }
     
    }

as the else to this will just then draw the line in full.

Would I need to do:

if (hit.point) {
   Draw2DRay(EyeballFirePoint.position, hit.point)
}

Ah thank you, makes sense actually, not sure why I was using Vector3’s in some places, might be from when I was watching tutorials and not converting them to Vector2’s

Maybe our wires are crossed here but my point was, a single LayerMask is designed to allow you to select multiple layers so Enemy hit layer and pusher layer. You can select multiple layers in the inspector, they are bit flags. Then you use that single layermask in all your queries. You don’t need two so each can have a single layer then have merge them.

Well yes but why are you doing it twice, that makes no sense and costs you twice as much time.

var hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, EnemyHitboxLayerMask | EnemyPusherLayerMask);
if (hit)
{
    // Hit!
}
else
{
    // Not hit.
}

No. As I mentioned above, use “if (hit) { hit is valid! }”. Don’t access any of the members if the hit isn’t valid. A point is a Vector2, not a bool value.

1 Like

Ahhh you can assign multiple in the dropdown lol ah man, I thought you could only select one… I thought that was strange, being that a LayerMask is a number made up of the different layers. :stuck_out_tongue: oversight on my part there bud haha

Ah yes! I think I did that because you can’t do something like this like I would normally do in PHP

    if (var hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, LayerMask))

So assigning the variable within the if statement. So I just ended up calling it twice, which yeah…isn’t great.

So now I’ve got:

void ShootLaser(Vector2 directionToFire) {

    var hit = Physics2D.Raycast(EyeballFirePoint.position, directionToFire, 50, LayerMask);

    if (hit) {

        Draw2DRay(EyeballFirePoint.position, hit.point);

        if (timer > 0f) {
            timer -= Time.deltaTime;
        }

        if (timer <= 0f) {
            DamageEnemy(hit);
        }
    }
    else {
        Draw2DRay(EyeballFirePoint.position, new Vector2(player.facingRight ? EyeballFirePoint.position.x + 50 : EyeballFirePoint.position.x -50, EyeballFirePoint.position.y));

        if (spriteOfEnemy) {
            ChangeEnemyBackToNormalColor();
        }
      
    }

}

Thank you yet again for your guidance! :slight_smile:

1 Like

Looks good to me! :slight_smile:

If the “EyeballFirePoint” isn’t moving then this isn’t a problem but if this here or in any other project has a Rigidbody2D then refer to the current “Rigidbody2D.position” and NOT the Transform which won’t necessarily be the same if you’re interpolating.

The rules is, if you’re using 2D physics and something is moving, always read the Rigidbody2D pose, not the Transform otherwise you’ll be behind where the body is (interpolation is historic).

Gotcha, in this case the EyeballFirePoint isn’t moving itself, instead the Player is moving, the EyeballFirePoint is a child of an EyeballContainer, the EyeballContainer is then attached to the Player via a Joint (the reason I didn’t tie the EyeballContainer simply as a child is to do with the child then inheriting the layer of the player, which I didn’t want).

1 Like

Just a follow up, made the little sprite if ya interested in seeing :slight_smile: Might add in some particle effects for the laser beam too, near the eyeball.

impartialglumkakarikis

1 Like