How to properly use raycasting for interacting with objects

I am currently working on a first person shooter example, but the code for the bullet is this:

var Bullet = pc.createScript('bullet');

// TAG REQUIRED FOR THE OBJECT TO DESTROY. USEFUL IF YOU WANT TO HAVE SPECIFIC GUNS REQUIRED FOR SPECIFIC ENEMIES
Bullet.attributes.add("enemytag", {
type: "string",
default: "enemy"
});

Bullet.prototype.initialize = function() {
    this.entity.collision.on('triggerenter', this.onTriggerEnter, this);
};

Bullet.prototype.onTriggerEnter = function(entity) {
    if(!entity.tags.has('player')) {
        if(entity.tags.has(this.enemytag)) {
            entity.sound.play("sound");
            entity.translateLocal(0, -50000, 0);
            Grams += 1;
        }
    }
};

(Ignore what the code is doing with the translations and stuff, focus on how it is doing it)

As you can clearly tell, it is basically just using a very long collision and checking for rigidbodies. Now, this works just fine. The problem is obviously that it goes right through walls and stuff and is generally quite jank. I was wondering if anyone could help me fix this with some proper raycasting, as I currently have basically no knowledge of how raycasts work.

Here is the project so you can see how everything currently works, if you was curious.

ALUCARDS LETHAL MUNITIONS EXPO | Editor

ALUCARDS LETHAL MUNITIONS EXPO | Launch

(I know, sick af name)

Ok so my solution involves removing the bullet entity and instead relying on raycasting using the gun scripts.
Here is the single auto:

var Singleauto = pc.createScript('singleauto');

// THE DELAY BETWEEN SHOTS BEFORE THE PLAYER CAN FIRE AGAIN IN SECONDS, IN BURST MODE THIS IS THE TIME BETWEEN BURSTS
Singleauto.attributes.add("thedelay", {
    type: "number",
    default: 1
});

// INHERENT INACCURACY WHEN NOT AIMING
Singleauto.attributes.add("inaccuracy", {
    type: "number",
    default: 10
});

// ACCURACY WHEN AIMING, USUALLY LEAVE THIS AT ZERO UNLESS YOU ARE DOING WEIRD STUFF
Singleauto.attributes.add("accuracy", {
    type: "number",
    default: 0
});

// CHECKS IF THE GUN IS BURST FIRE
Singleauto.attributes.add("isburst", {
    type: "boolean",
});

// TIME BETWEEN BULLETS IN SINGLE BURST IN SECONDS. ONLY ACTIVE IF ISBURST IS TRUE
Singleauto.attributes.add("burstdelayS", {
    type: "number",
});

// TIME BETWEEN BULLETS IN SINGLE BURST IN RPM. ONLY ACTIVE IF ISBURST IS TRUE
Singleauto.attributes.add("burstdelayRPM", {
    type: "number",
});

// NUMBER OF BULLETS IN BURST. ONLY ACTIVE IF ISBURST IS TRUE
Singleauto.attributes.add("burstnumber", {
    type: "number",
});

// ENEMY TAG IT DAMAGES
Singleauto.attributes.add("enemyTag", {
    type: "string",
    default: "enemy"
});

// BULLET MAX RANGE
Singleauto.attributes.add("range", {
    type: "number",
    default: 10000
});

Singleauto.prototype.initialize = function() {
    this.timer = 0;
    this.currentburst = 1;
    this.canburst = false;
};

Singleauto.prototype.update = function(dt) {

    // ONLY USED FOR BURST. REMEMBER THAT SECONDS TAKES PRIORITY OVER RPM, SO IF RPM IS TO BE USED SECONDS MUST BE AT ZERO. WHAT THIS FUNCTION ACTUALLY DOES HERE IS CONVERT THE RPM TO SECONDS, WHICH IS WHAT THE SCRIPT ACTUALLY USES
    if (this.burstdelayS === 0) {
        Realrate = (1 / (this.burstdelayRPM / 60))
    } else {
        Realrate = this.burstdelayS
    }

    // THREE DEBUG FUNCTIONS BELOW CAN BE REMOVED. MESSED UP BY BURST MODE BUT THIS IS ONLY DEBUG INFO SO WHO CARES.
    FirerateinS = this.thedelay
    FirerateinRPM = Math.round(60 / this.thedelay)
    Firemode = "SINGLE AUTO"

    // CHECKS IF PLAYER IS AIMING AND ACCURACY IS ADJUSTED
    if (this.app.mouse.isPressed(pc.MOUSEBUTTON_RIGHT)) {
        this.entity.setLocalEulerAngles((Math.random() * this.accuracy) - (this.accuracy / 2), (Math.random() * this.accuracy) - (this.accuracy / 2), (Math.random() * this.accuracy) - (this.accuracy / 2));
    } else {
        this.entity.setLocalEulerAngles((Math.random() * this.inaccuracy) - (this.inaccuracy / 2), (Math.random() * this.inaccuracy) - (this.inaccuracy / 2), (Math.random() * this.inaccuracy) - (this.inaccuracy / 2));
    }

    this.timer += dt;

    // CHECKS TO MAKE SURE BULLETS IS MORE THAN ZERO, THAT LEFT CLICK IS CLICKED, AND IF THE TIMER IS GREATER THAN THE DELAY
    if (Bullets > 0) {
        if (this.app.mouse.wasPressed(pc.MOUSEBUTTON_LEFT)) {
            if (this.timer > this.thedelay) {
                anglePos = this.entity.getLocalEulerAngles().clone();
                anglePos.mulScalar(this.range);
                from = this.entity.getPosition();
		result = this.app.systems.rigidbody.raycastFirst(from,anglePos);
		if(result){
			if(result.entity.tags.has(this.enemyTag) {
				result.entity.translateLocal(0,-50000);
				result.sound.play("sound");
				Grams += 1;
			};
		};
                this.entity.sound.play("sound");
                this.timer = 0;
                Bullets -= 1;
                Missing += 1;
                Recoilburst = 1;
                this.canburst = true;
                //this.currentburst += 1;
            }
        }

        // BURSTS GUN ONLY IF ISBURST IS TRUE
        if (this.isburst === true) {
            if (this.currentburst < this.burstnumber) {
                if (this.canburst === true) {
                    if (this.timer > Realrate) {
                        anglePos = this.entity.getLocalEulerAngles().clone();
                	anglePos.mulScalar(this.range);
                	from = this.entity.getPosition();
			result = this.app.systems.rigidbody.raycastFirst(from,anglePos);
			if(result){
				if(result.entity.tags.has(this.enemyTag) {
					result.entity.translateLocal(0,-50000);
					result.sound.play("sound");
					Grams += 1;
				};
			};
                        this.entity.sound.play("sound");
                        this.timer = 0;
                        Bullets -= 1;
                        Missing += 1;
                        Recoilburst = 1;
                        this.currentburst += 1;
                    }
                }
            }
        }

    }

    if (this.currentburst === this.burstnumber) {
        this.currentburst = 1;
        this.canburst = false;
    } else if (Bullets === 0) {
        this.currentburst = 1;
        this.canburst = false;
    }

};

And the full auto

var Fullauto = pc.createScript('fullauto');
// REMEMBER THAT THIS SCRIPT IS ATTACHED TO THE BULLET PARENT ENTITY

// THIS IS THE FIRE RATE IN SECONDS. LEAVE AT ZERO IF YOU WANT TO USE RPM
Fullauto.attributes.add("firerateS", {
    type: "number",
    default: 0
});

// THIS IS THE FIRE RATE IN ROUNDS PER MINUTE
Fullauto.attributes.add("firerateRPM", {
    type: "number",
    default: 0.1
});

// INHERENT INACCURACY WHEN NOT AIMING
Fullauto.attributes.add("inaccuracy", {
    type: "number",
    default: 10
});

// ACCURACY WHEN AIMING, USUALLY LEAVE THIS AT ZERO UNLESS YOU ARE DOING WEIRD STUFF
Fullauto.attributes.add("accuracy", {
    type: "number",
    default: 0
});

// TAG OF THE ENEMY
Fullauto.attributes.add("enemyTag", {
	type:"string",
	default:"enemy"
});

// BULLET MAX RANGE
Fullauto.attributes.add("range", {
	type:"number",
	default:"10000"
});

Fullauto.prototype.initialize = function() {
    this.timer = 0;
};

Fullauto.prototype.update = function(dt) {

    // REMEMBER THAT SECONDS TAKES PRIORITY OVER RPM, SO IF RPM IS TO BE USED SECONDS MUST BE AT ZERO. WHAT THIS FUNCTION ACTUALLY DOES HERE IS CONVERT THE RPM TO SECONDS, WHICH IS WHAT THE SCRIPT ACTUALLY USES
    if (this.firerateS === 0) {
        Realrate = (1 / (this.firerateRPM / 60))
    } else {
        Realrate = this.firerateS
    }

    // THREE DEBUG FUNCTIONS BELOW CAN BE REMOVED
    FirerateinS = Realrate
    FirerateinRPM = Math.round(60 / Realrate)
    Firemode = "FULL AUTO"

    // CHECKS IF PLAYER IS AIMING AND ACCURACY IS ADJUSTED
    if (this.app.mouse.isPressed(pc.MOUSEBUTTON_RIGHT)) {
        this.entity.setLocalEulerAngles((Math.random() * this.accuracy) - (this.accuracy / 2), (Math.random() * this.accuracy) - (this.accuracy / 2), (Math.random() * this.accuracy) - (this.accuracy / 2));
    } else {
        this.entity.setLocalEulerAngles((Math.random() * this.inaccuracy) - (this.inaccuracy / 2), (Math.random() * this.inaccuracy) - (this.inaccuracy / 2), (Math.random() * this.inaccuracy) - (this.inaccuracy / 2));
    }

    this.timer += dt;

    // CHECKS TO MAKE SURE BULLETS IS MORE THAN ZERO, THAT LEFT CLICK IS CLICKED, AND IF THE TIMER IS GREATER THAN THE FIRERATE REQUIREMENT
    if (Bullets > 0) {
        if (this.app.mouse.isPressed(pc.MOUSEBUTTON_LEFT)) {
            if (this.timer > Realrate) {
                anglePos = this.entity.getLocalEulerAngles().clone();
                anglePos.mulScalar(this.range);
                from = this.entity.getPosition();
		result = this.app.systems.rigidbody.raycastFirst(from,anglePos);
		if(result){
			if(result.entity.tags.has(this.enemyTag) {
				result.entity.translateLocal(0,-50000);
				result.sound.play("sound");
				Grams += 1;
			};
		};
                this.entity.sound.play("sound");
                this.timer = 0;
                Bullets -= 1;
                Missing += 1;
                Recoilburst = 1;
            }
        }
    }
};

These scripts should work, but are untested. If you get any errors please inform me.

  • Forgot a parentheses on line 117 for the first and 76 on the second
  • Got an error that said “range default value needs to be a number” because the full auto number is a string
  • After those fixes the script gives no errors but does not seem to actually do anything

Oh sorry lol, I was writing on my phone.

I fixed the errors but the script still does not work.

I know, working on it

This example seems to have what I need, but I am not sure how to turn this into a directional ray from the entity.

Physics raycasting by tag | Editor

Ok so I solved the main issue of it not working, it needs to be

anglePos = this.entity.forward.clone();
anglePos.scale(this.distance);
from = this.entity.getPosition();
result = this.app.systems.rigidbody.raycastFirst(from,anglePos.add(from));

instead of what I had previously put. Unfortunately, this leads to the next problem of it breaking every 6-9 shots due to it not returning a result.

I am currently working on a fork of the project to try and fix it.

Ok got singleauto finished, gonna work on fullauto now

I sort of fixed the problem, but it only lets me shoot 2 things until it just refuses to give an entity result.

yeah i got it fixed.

So what do the solush be?

Not sure, I am working on implementing full auto still. I will send you finished results when I am done

I mean the full auto script solution should be basically the same as the single auto fix, as it does the exact same thing just repeatedly.

Yes, an issue came up as for some reason all of the aim pos and not aim pos entities got deleted so i wrote a quick patch to have global defaults.

Of course I have been fiddling around with stuff so perhaps you forked it mid fiddle? Or were they already there then just disappeared?

I forked it mid fiddle.

I was replacing the 6 location values to replace them with 2 entities so it can get lerpy.

ah okay. I just ported it to a vec3 and it works all the same.