I have some pretty cool first person movement code, which plays footsteps, has a model with animation etc, the issue is: it results in strange shadows to occur in the scene.
first person movement with playcanvas’ script result above.
result of my script.
If you notice: shadows are all wrong, not in places they should be. The post effects are the same for both cameras, in fact they are the same camera since I thought the camera could be the issue, or the model, so I removed the head model, same result. It has to be the code or a side effect of it. Please see my script to help me fix this issue if anyone has the time.
(script is as follows)
var FirstPersonMovement = pc.createScript('firstPersonMovement');
FirstPersonMovement.attributes.add('head', {
type: 'entity',
description: 'Assign the head_1 entity for rotation'
});
// Add an attribute to reference the sound entity
FirstPersonMovement.attributes.add('footSteps', {
type: 'entity',
description: 'The entity with the walking sound component (footsteps)'
});
FirstPersonMovement.attributes.add('bodyParts', {
type: 'entity',
array: true,
description: 'Assign the body parts that should follow the cameras Y rotation'
});
FirstPersonMovement.attributes.add('power', {
type: 'number',
default: 2500,
description: 'Adjusts the speed of player movement'
});
FirstPersonMovement.attributes.add('lookSpeed', {
type: 'number',
default: 0.25,
description: 'Adjusts the sensitivity of looking'
});
FirstPersonMovement.attributes.add('jumpPower', {
type: 'number',
default: 350,
description: 'Adjusts the jump force'
});
FirstPersonMovement.attributes.add('sprintMultiplier', {
type: 'number',
default: 1.5,
description: 'Multiplier for sprint speed'
});
FirstPersonMovement.prototype.initialize = function () {
this.force = new pc.Vec3();
this.eulers = new pc.Vec3();
this.canJump = false;
var app = this.app;
app.mouse.on("mousemove", this._onMouseMove, this);
app.mouse.on("mousedown", function () { app.mouse.enablePointerLock(); }, this);
this.entity.rigidbody.fixedRotation = true;
this.animComponent = this.entity.anim;
if (!this.animComponent) console.error("Missing 'anim' component");
// Sound references
this.footStepsSound = this.footSteps?.sound?.slot('footsteps') || null;
this.runStepsSound = this.footSteps?.sound?.slot('runsteps') || null;
// Movement states
this.isMoving = false;
this.isRunning = false;
this.isJumping = false;
// Collision events
this.entity.collision.on("collisionstart", this.onCollisionStart, this);
this.entity.collision.on("collisionend", this.onCollisionEnd, this);
// Listen for jump input
app.keyboard.on(pc.EVENT_KEYDOWN, this._onKeyDown, this);
};
FirstPersonMovement.prototype.update = function (dt) {
var force = this.force;
var app = this.app;
var forward = this.head.forward;
var right = this.head.right;
var x = 0;
var z = 0;
// Movement input
if (app.keyboard.isPressed(pc.KEY_D)) { x -= right.x; z -= right.z; }
if (app.keyboard.isPressed(pc.KEY_A)) { x += right.x; z += right.z; }
if (app.keyboard.isPressed(pc.KEY_S)) { x += forward.x; z += forward.z; }
if (app.keyboard.isPressed(pc.KEY_W)) { x -= forward.x; z -= forward.z; }
var isSprinting = app.keyboard.isPressed(pc.KEY_SHIFT) && (x !== 0 || z !== 0);
var speedMultiplier = isSprinting ? this.sprintMultiplier : 1;
// Apply movement force
if (x !== 0 || z !== 0) {
force.set(x, 0, z).normalize().scale(this.power * speedMultiplier);
this.entity.rigidbody.applyForce(force);
if (!this.isJumping) {
this._setWalkState(!isSprinting);
this._setRunState(isSprinting);
this._playFootsteps(isSprinting);
}
} else {
this._setWalkState(false);
this._setRunState(false);
this._stopFootsteps();
}
// Mouse look
this.bodyParts.forEach(function (part) {
part.setLocalEulerAngles(0, this.eulers.x, 0);
}, this);
};
FirstPersonMovement.prototype._resumeFootsteps = function () {
var app = this.app;
var forward = this.head.forward;
var right = this.head.right;
var x = 0;
var z = 0;
// Movement detection for all keys: WASD
if (app.keyboard.isPressed(pc.KEY_D)) { x -= right.x; z -= right.z; }
if (app.keyboard.isPressed(pc.KEY_A)) { x += right.x; z += right.z; }
if (app.keyboard.isPressed(pc.KEY_S)) { x += forward.x; z += forward.z; }
if (app.keyboard.isPressed(pc.KEY_W)) { x -= forward.x; z -= forward.z; }
// Check for sprinting (Shift key held while moving)
var isSprinting = app.keyboard.isPressed(pc.KEY_SHIFT) && (x !== 0 || z !== 0);
// If the player is moving, play the correct footstep sounds
if (x !== 0 || z !== 0) {
this._playFootsteps(isSprinting);
} else {
this._stopFootsteps();
}
};
FirstPersonMovement.prototype._playFootsteps = function (isRunning) {
if (this.isJumping) {
this._stopFootsteps();
return;
}
// Play the appropriate sound based on whether the player is running or walking
if (isRunning && this.runStepsSound && !this.runStepsSound.isPlaying) {
this._stopFootsteps();
this.runStepsSound.play();
} else if (!isRunning && this.footStepsSound && !this.footStepsSound.isPlaying) {
this._stopFootsteps();
this.footStepsSound.play();
}
};
// Stop all footsteps
FirstPersonMovement.prototype._stopFootsteps = function () {
if (this.footStepsSound && this.footStepsSound.isPlaying) this.footStepsSound.stop();
if (this.runStepsSound && this.runStepsSound.isPlaying) this.runStepsSound.stop();
};
// Handle mouse movement
FirstPersonMovement.prototype._onMouseMove = function (e) {
if (pc.Mouse.isPointerLocked() || e.buttons[0]) {
this.eulers.x -= this.lookSpeed * e.dx;
this.eulers.y += this.lookSpeed * e.dy; // Fixed inverted movement
this.eulers.y = pc.math.clamp(this.eulers.y, -90, 70); // Clamp vertical rotation
this.head.setLocalEulerAngles(this.eulers.y, this.eulers.x, 0);
}
};
// Jump input
FirstPersonMovement.prototype._onKeyDown = function (e) {
if (e.key === pc.KEY_SPACE && this.canJump) {
this.entity.rigidbody.applyImpulse(0, this.jumpPower, 0);
this.canJump = false;
this._setJumpState(true);
this._stopFootsteps(); // Stop sounds immediately on jump
}
};
// Jump input
FirstPersonMovement.prototype._onKeyDown = function (e) {
if (e.key === pc.KEY_SPACE && this.canJump) {
this.entity.rigidbody.applyImpulse(0, this.jumpPower, 0);
this.canJump = false;
this._setJumpState(true);
this._stopFootsteps(); // Stop sounds immediately on jump
}
};
FirstPersonMovement.prototype.onCollisionStart = function (result) {
if (result.other && result.other.rigidbody) {
this.canJump = true;
// Set jump state to false when landing
this._setJumpState(false);
// Recheck movement inputs immediately
this._checkMovementAfterLanding();
// Resume footsteps if movement keys are held
this._resumeFootsteps();
}
};
// Re-enable footsteps when the player lands
FirstPersonMovement.prototype._checkMovementAfterLanding = function () {
var app = this.app;
var forward = this.head.forward;
var right = this.head.right;
var x = 0;
var z = 0;
// Movement detection
if (app.keyboard.isPressed(pc.KEY_D)) { x -= right.x; z -= right.z; }
if (app.keyboard.isPressed(pc.KEY_A)) { x += right.x; z += right.z; }
if (app.keyboard.isPressed(pc.KEY_S)) { x += forward.x; z += forward.z; }
if (app.keyboard.isPressed(pc.KEY_W)) { x -= forward.x; z -= forward.z; }
// Check if the player is sprinting or walking
var isSprinting = app.keyboard.isPressed(pc.KEY_SHIFT) && (x !== 0 || z !== 0);
var isMoving = x !== 0 || z !== 0;
// Update animation and sound state based on movement
if (isMoving) {
this._setWalkState(!isSprinting);
this._setRunState(isSprinting);
this._playFootsteps(isSprinting);
} else {
this._setWalkState(false);
this._setRunState(false);
this._stopFootsteps();
}
};
FirstPersonMovement.prototype.onCollisionEnd = function (result) {
if (result.other && result.other.rigidbody) {
this.canJump = false;
}
};
// State management functions
FirstPersonMovement.prototype._setWalkState = function (isWalking) {
if (this.animComponent) {
this.animComponent.setBoolean('isWalk', isWalking);
this.isMoving = isWalking;
}
};
FirstPersonMovement.prototype._setRunState = function (isRunning) {
if (this.animComponent) {
this.animComponent.setBoolean('isRun', isRunning);
this.isRunning = isRunning;
}
};
FirstPersonMovement.prototype._setJumpState = function (isJumping) {
if (this.animComponent) {
this.animComponent.setBoolean('isJump', isJumping);
this.isJumping = isJumping;
// Stop all movement sounds when jumping
if (isJumping) {
this._stopFootsteps();
}
}
};