Third person camera controller

My project involved third person controller. Here is the script i have been using til now, plus some changes I tried to make to avoid the trouble of seing through solids objects, but nothing seems to work properly when i am too close from a wall, I can see through the wall… The script I based my work is the playcanvas classic one, there is a RayCastEndPoint entity, which is not used in the script??? I wonder why it is there? Is there a way to avoid this see through annoying problem, beause I am thinking right now th create a second camera, in front of the player to switch to one person style when such collision are detected.

Actual script : var CameraMovement = pc.createScript(‘cameraMovement’);

// ---------- Paramètres exposés ----------
CameraMovement.attributes.add(‘mouseSpeed’, {
type: ‘number’,
default: 1.4,
description: ‘Sensibilité souris’
});

CameraMovement.attributes.add(‘distance’, {
type: ‘number’,
default: 4,
description: ‘Recul derrière le joueur (m)’
});

CameraMovement.attributes.add(‘height’, {
type: ‘number’,
default: 1.6,
description: ‘Hauteur au-dessus du pivot (m)’
});

// “épaisseur” virtuelle de la caméra
CameraMovement.attributes.add(‘cameraRadius’, {
type: ‘number’,
default: 0.7,
description: ‘Rayon de collision caméra’
});

/* ---------- Initialisation ---------- */
CameraMovement.prototype.initialize = function () {
this.eulers = new pc.Vec3(); // (x = yaw, y = pitch)
this.touchCoords = new pc.Vec2();

var app = this.app;
app.mouse.on('mousemove', this.onMouseMove, this);
app.mouse.on('mousewheel', this.onMouseWheel, this);

this.camera = this.entity.camera;
this.initialFov = this.camera.fov;
this.minFov = this.initialFov - 20;
this.maxFov = this.initialFov + 20;

this.on('destroy', function () {
    app.mouse.off('mousemove', this.onMouseMove, this);
    app.mouse.off('mousewheel', this.onMouseWheel, this);
}, this);

this.rayEnd = app.root.findByName('RaycastEndPoint');

};

/* ---------- Variation FOV avec molette ---------- */
CameraMovement.prototype.onMouseWheel = function (event) {
if (!this.camera) return;

this.camera.fov = pc.math.clamp(
    this.camera.fov - event.wheel * 1,
    this.minFov,
    this.maxFov
);

};

/* ---------- Mise à jour après simulation ---------- */
CameraMovement.prototype.postUpdate = function (dt) {
var pivot = this.entity.parent;

var targetY = this.eulers.x + 180;
var targetX = this.eulers.y;
pivot.setEulerAngles(-targetX, targetY, 0);

this.entity.setPosition(this.getWorldPoint());
this.entity.lookAt(
    pivot.getPosition().x,
    pivot.getPosition().y + this.height,
    pivot.getPosition().z
);

};

/* ---------- Déplacement souris ---------- */
CameraMovement.prototype.onMouseMove = function (e) {
if (pc.Mouse.isPointerLocked()) {
this.eulers.x -= (this.mouseSpeed * e.dx) / 60;
this.eulers.y += (this.mouseSpeed * e.dy) / 60;

    this.eulers.x = (this.eulers.x + 360) % 360;
    this.eulers.y = pc.math.clamp(this.eulers.y, -60, 45);
}

};

/* ---------- Calcule la position finale de la caméra (avec “épaisseur”) ---------- */
CameraMovement.prototype.getWorldPoint = function () {
var pivot = this.entity.parent;

// Position de la tête du joueur
var pivotPos = pivot.getPosition();
var start = pivotPos.clone().add(new pc.Vec3(0, this.height, 0));

// Direction derrière le joueur
var backDir = pivot.forward.clone().scale(-1).normalize();

// Position souhaitée sans collision
var desiredPos = start.clone().add(backDir.clone().scale(this.distance));

var rbSystem = this.app.systems.rigidbody;
if (!rbSystem) {
    // sécurité : si pas de physique, on rend la position brute
    return desiredPos;
}

// Raycast tête -> caméra
var hit = rbSystem.raycastFirst(start, desiredPos);

if (!hit) {
    // pas de mur entre tête et caméra → position normale
    return desiredPos;
}

// Distance de la tête au point d'impact
var distHit = start.distance(hit.point);

// rayon virtuel de la caméra
var radius  = this.cameraRadius;
var minDist = 0.5;              // distance mini tête ↔ caméra

// centre de la caméra doit rester "radius" AVANT le mur
var allowedDist = distHit - radius;

// clamp
allowedDist = Math.max(minDist, allowedDist);
allowedDist = Math.min(allowedDist, this.distance);

// Direction réelle tête -> caméra
var dir = desiredPos.clone().sub(start);
var len = dir.length();
if (len === 0) return start;

dir.normalize();

// Centre de la caméra = start + dir * allowedDist
return start.clone().add(dir.scale(allowedDist));

};
Old scritp :
var CameraMovement = pc.createScript(‘cameraMovement’);

// ---------- Paramètres exposés ----------
CameraMovement.attributes.add(‘mouseSpeed’, {
type: ‘number’,
default: 1.4,
description: ‘Sensibilité souris’
});

CameraMovement.attributes.add(‘distance’, {
type: ‘number’,
default: 4,
description: ‘Recul derrière le joueur (m)’
});

CameraMovement.attributes.add(‘height’, {
type: ‘number’,
default: 1.6,
description: ‘Hauteur au-dessus du pivot (m)’
});

/* ---------- Initialisation ---------- */
CameraMovement.prototype.initialize = function () {
this.eulers = new pc.Vec3(); // (x = yaw, y = pitch)
this.touchCoords = new pc.Vec2();

const app = this.app;
app.mouse.on('mousemove', this.onMouseMove, this);
app.mouse.on('mousewheel', this.onMouseWheel, this);

this.camera = this.entity.camera;
this.initialFov = this.camera.fov;
this.minFov = this.initialFov - 20;
this.maxFov = this.initialFov + 20;

this.on('destroy', () => {
    app.mouse.off('mousemove', this.onMouseMove, this);
    app.mouse.off('mousewheel', this.onMouseWheel, this);
});

this.rayEnd = app.root.findByName('RaycastEndPoint');

};

/* ---------- Variation FOV avec molette ---------- */
CameraMovement.prototype.onMouseWheel = function (event) {
if (!this.camera) return;

// Sensibilité augmentée : plus rapide à la molette
this.camera.fov = pc.math.clamp(
    this.camera.fov - event.wheel * 1,  // ↑ sensibilité ajustée ici
    this.minFov,
    this.maxFov
);

};

/* ---------- Mise à jour après simulation ---------- */
CameraMovement.prototype.postUpdate = function (dt) {
const pivot = this.entity.parent;

const targetY = this.eulers.x + 180;
const targetX = this.eulers.y;
pivot.setEulerAngles(-targetX, targetY, 0);

this.entity.setPosition(this.getWorldPoint());
this.entity.lookAt(
    pivot.getPosition().x,
    pivot.getPosition().y + this.height,
    pivot.getPosition().z
);

};

/* ---------- Déplacement souris ---------- */
CameraMovement.prototype.onMouseMove = function (e) {
if (pc.Mouse.isPointerLocked()) {
this.eulers.x -= (this.mouseSpeed * e.dx) / 60;
this.eulers.y += (this.mouseSpeed * e.dy) / 60;

    this.eulers.x = (this.eulers.x + 360) % 360;
    this.eulers.y = pc.math.clamp(this.eulers.y, -60, 45);
}

};

/* ---------- Calcule la position finale de la caméra ---------- */
CameraMovement.prototype.getWorldPoint = function () {
const pivotPos = this.entity.parent.getPosition();
const upVec = new pc.Vec3(0, this.height, 0);
const backOffset = this.entity.parent.forward.clone().scale(-this.distance);
const desiredPos = pivotPos.clone().add(backOffset).add(upVec);

const start = pivotPos.clone().add(upVec);
const hit   = this.app.systems.rigidbody.raycastFirst(start, desiredPos);

return hit ? hit.point : desiredPos;

};
Thanks you for reading this…

I’d just realised, that I can change the near clip setting. It was 0,3, if I use 0,01 with my actual script, no more “through the wall” sight in the middle of the screen… sorry for bothering the comminithy, but at least this can help someone else.