FPS game - Shooting into center of screen from front of gun postion

Hi all,

I am trying to shoot/raycast from gun front to the center of the screen.

I’ve got it working, however there are a couple problems.

  1. Since it’s shooting from the front of the gun, it’s slightly offset to the middle of the screen when hitting the ground. (The further away you shoot, the closer to the middle it gets, which is expected) How do I always shoot in the center of the screen, no matter what? Do I use the camera position instead of gunFront position? Do I need to always rotate the gun to lookAt the center of the screen position? etc

  2. When rotating my first person player, it changes the direction of the raycast a tiny bit. For example, if you look left, the raycast result is a tiny bit on the right of middle of screen, and vice versa. This could be fixed if the gun is always pointing towards center of screen.

Hope this makes sense!

Here is my current raycast code.

    var from = this.entity.findByName('GunFront').getPosition();
    // var from = this.camera.getPosition();
    var to = this.camera.camera.screenToWorld(screenPosition.x, screenPosition.y, this.camera.camera.farClip);
    var result = this.app.systems.rigidbody.raycastFirst(from, to);

Thanks

1 Like

There are a few ways of doing this. Two that I know of are:

  1. Ignore the gun. Do the raycasts from the camera and just use the gun for VFX (eg muzzle flash)
  2. Do a raycast from the camera and aim the gun to what it intersects with (See golden eye on the N64)

Okay cool. Thanks. My other problem is the Rotation issue. When I rotate the camera/player, objects attached to the camera act a little weird. They sort of do an orbiting motion.

Example:

All help is appreciated!

Is the gun a child of the camera?

Yes.

Is there any code currently that affects the rotation or position of the gun?

No, as it’s attached to the camera.

I would double check to see if there is anything that could be affecting the position and rotation. A child attached directly to the camera shouldn’t move in relation to the camera.

It almost looks like it is trying to match the rotation of the camera but the pivot point is slightly off.

I think it’s possible that it’s something to do with the camera. Or my firstpersonmovement script. I’ve added a box primitive to the camera, and does the same thing.

Here is my movement script. Let me know if you can spot anything obvious! Thanks.

var FirstPersonMovement = pc.createScript('playerMovement');
var width = window.innerWidth;
var height = window.innerHeight;

// optional, assign a camera entity, otherwise one is created
FirstPersonMovement.attributes.add('camera', {
    type: 'entity'
});

FirstPersonMovement.attributes.add('playerModel', {
    type: 'entity'
});

FirstPersonMovement.attributes.add('gun', {
    type: 'entity',
    description: "Selected entity requires the GunBehavior script"
});

FirstPersonMovement.attributes.add('power', {
    type: 'number',
    default: 2500
});

FirstPersonMovement.attributes.add('lookSpeed', {
    type: 'number'
    // default: 360 / screen.width // move cursor from left to right on your screen == 1 rotation ingame
});

// initialize code called once per entity
FirstPersonMovement.prototype.initialize = function() {
    // this.camera       = null
    this.force        = new pc.Vec3();
    this.eulers       = new pc.Vec3();
    this.groundRay    = new pc.Vec3();
    this.lastGroundCollision = 0;
    this.newJumpAllowedAt    = 0;
    var app = this.app;

    // Listen for mouse move events
    app.mouse.on("mousemove", this._onMouseMove, this);

    // when the mouse is clicked hide the cursor
    app.mouse.on("mousedown", function (e) {
        if (e.event.isOverlayEvent === true)
            return;
        app.mouse.enablePointerLock();
    }, this);            

    // Check for required components
    if (!this.entity.collision) {
        console.error("First Person Movement script needs to have a 'collision' component");
    }

    if (!this.entity.rigidbody || this.entity.rigidbody.type !== pc.BODYTYPE_DYNAMIC) {
        console.error("First Person Movement script needs to have a DYNAMIC 'rigidbody' component");
    }
    
    if(this.gun) {
        if(!this.gun.script.gunBehaviour) {
            console.error("First Person Movement option script Gun Entity required the GunBehaviour script component");
        }
        
        this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.mouseDown, this);    
        this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    }
    
    // If a camera isn't assigned from the Editor, create one
    if ( ! this.camera) {
        this._createCamera();
    }
    
    this.camera.setLocalEulerAngles(0,0,0);
    
    this.entity.collision.on("contact", this.onContact, this);  
    
   
};

FirstPersonMovement.prototype.mouseDown = function (e) {
    if (event.button === pc.MOUSEBUTTON_LEFT) {
        this.gun.script.gunBehaviour.isShooting = true;
    }
};

FirstPersonMovement.prototype.onMouseUp = function (event) {
    if (event.button === pc.MOUSEBUTTON_LEFT) {
        this.gun.script.gunBehaviour.isShooting = false;
    }
};

/*
Example result for standing on rigidbody floor:
    contactResult.contacts[0]
    [ContactPoint]
    localPoint     : Vec3 {x: -0.002215355634689331    , y: -0.9999810457229614, z:  0.0017574280500411987   }
    localPointOther: Vec3 {x:  0.16426098346710205     , y:  0.5               , z: -0.125589519739151       }
    normal         : Vec3 {x: -0.0000018488642581360182, y: -1                 , z:  0.0000068243434725445695}
    point          : Vec3 {x:  0.16426098346710205     , y:  0.500004768371582 , z: -0.125589519739151       }
    pointOther     : Vec3 {x:  0.16426098346710205     , y:  0.5               , z: -0.125589519739151       }
*/

FirstPersonMovement.prototype.onContact = function(contactResult) {
    var contacts = contactResult.contacts;
    var n = contacts.length;
    var foundGroundCollision = false;
    for (var i = 0; i < n; i++) {
        var contact = contacts[i];
        // special case for the infinite plane
        // the contact.normal will be [0,1,0]
        // however, standing on the ridigbody floor will be [0,-1,0]'ish
        // and to make it even more complex, a gltf physics model "floor" is [0,1,0]'ish
        // trying to allow a certain slope only for jumping is kinda hard with these conditions...
        // so currently i just do: if there is a contact, allow jumping
        if (contact.normal.equals(pc.Vec3.UP)) {
            foundGroundCollision = true;
            break;
        }
        //viewer.anim_info.innerHTML = contact.normal.dot( pc.Vec3.DOWN );
        //viewer.anim_info.innerHTML = contact.normal;
        // always true for `n > 0`
        // until the collision normals are fixed for a dot()>slope check
        foundGroundCollision = true;
    }
    if (foundGroundCollision) {
        this.lastGroundCollision = pc.now();
    }
    //console.log(arguments);
    //

};

// update code called every frame
FirstPersonMovement.prototype.update = function(dt) {
    var force = this.force;
    var app = this.app;

    // Get camera directions to determine movement directions
    var forward = this.camera.forward;
    var right = this.camera.right;
       
    //viewer.anim_info.innerHTML = pc.now() - this.lastGroundCollision + "ms";
       
    // allow jump if touched a ground in last 100ms
    if (pc.now() - this.lastGroundCollision < 100) {
        this.isGrounded = true;
    } else {
        this.isGrounded = false;
    }
    
    // movement
    var x = 0;
    var z = 0;

    // Use W-A-S-D keys to move player
    // Check for key presses
    if (app.keyboard.isPressed(pc.KEY_A)) {
        x -= right.x;
        z -= right.z;
    }

    if (app.keyboard.isPressed(pc.KEY_D)) {
        x += right.x;
        z += right.z;
    }

    if (app.keyboard.isPressed(pc.KEY_W)) {
        x += forward.x;
        z += forward.z;
    }

    if (app.keyboard.isPressed(pc.KEY_S)) {
        x -= forward.x;
        z -= forward.z;
    }

    // use direction from keypresses to apply a force to the character
    if (x !== 0 && z !== 0) {
        force.set(x, 0, z).normalize();
        force.scale(this.power);
        
    if (app.keyboard.isPressed(pc.KEY_SHIFT)) { // run feature
        this.entity.rigidbody.applyForce(force);
    }
        
        var speed = this.entity.rigidbody.linearVelocity.length();
        //viewer.anim_info.innerHTML = speed;
        if (speed < 10)
            this.entity.rigidbody.applyForce(force);
        //this.entity.rigidbody.applyImpulse(force);
        //this.entity.rigidbody.linearVelocity = force;
    }

    // max 1 jump per 200ms
    var canJump = pc.now() - this.newJumpAllowedAt > 200; 
    
    if (canJump && this.isGrounded && app.keyboard.isPressed(pc.KEY_SPACE)) {
        this.lastGroundCollision = 0; // reset timer
        this.newJumpAllowedAt = pc.now() + 200;
        force.set(0, 600, 0);
        this.entity.rigidbody.applyImpulse(force);
    }
    
    // update camera angle from mouse events
    this.camera.setLocalEulerAngles(this.eulers.y, this.eulers.x, 0);
    
    if(this.playerModel) { // still allow this code to work even if a PlayerModel doesn't exist
        this.playerModel.setLocalEulerAngles(0, this.eulers.x - 180, 0);  // set playermodel to follow camera direction
        //this.playerModel.setLocalEulerAngles(this.eulers.y, this.eulers.x, 0);  // set playermodel to follow camera direction
    }
};

FirstPersonMovement.prototype._onMouseMove = function (e) {
    if (e.event.isOverlayEvent === true)
        return;
    // If pointer is disabled
    // If the left mouse button is down update the camera from mouse movement
    if (pc.Mouse.isPointerLocked() || e.buttons[0]) {
        this.eulers.x -= this.lookSpeed * e.dx;
        this.eulers.y -= this.lookSpeed * e.dy;
        this.eulers.y = pc.math.clamp(this.eulers.y, -90, 90);
    }  
    
};

FirstPersonMovement.prototype._createCamera = function () {
    // If user hasn't assigned a camera, create a new one
    this.camera = new pc.Entity();
    this.camera.setName("First Person Camera");
    this.camera.addComponent("camera", {
        fov: 100
    });
    this.entity.addChild(this.camera);
    this.camera.translateLocal(0, 0.5, 0);
};

image

Having my camera be the parent of the player fixes the rotation issue on the gun, for some reason? But the player sort of needs to be the parent as thats what is used by the firstpersonmovement script.

I have to keep it like this:

image

Just done it myself and it looks fine to me? https://playcanvas.com/editor/scene/805637

Could you use the same firstPersonMovement script as me and try again? (the script I sent)

Thanks

Must be something wrong with my Camera or scene or something. Used the same code as you and didn’t seem to work… Hmmm

cant wait to see ur game go public

1 Like

why is your reticle like that in so used to seeng it in the middle of the screen