I'm making an engine for a game. Give me some good advice

I’m making an engine for a game. Give me some good advice.
I did it in a complicated way and it lags, maybe there are options to make it simpler.
I use Kinematics on the character. Because the dynamics don’t suit me

var PlayerController = pc.createScript('playerController');


PlayerController.attributes.add('speed', { type: 'number', default: 7 });

PlayerController.prototype.initialize = function () {
    this.gamepadEnabled = false;
    this.setupUI();

    this.Test = 0;
    this.Test1 = 0;
    this.Test2 = 0;
    

    this.entity.collision.on('collisionstart', this.onCollisionStart, this);
};

PlayerController.prototype.update = function (dt) {
    if (this.app.keyboard.wasPressed(pc.KEY_Q)) {
        this.gamepadEnabled = true;
    }

    if (this.gamepadEnabled) {
        const gamepad = navigator.getGamepads()[0];
        if (gamepad) {
            this.handleMovement(gamepad, dt);
            this.displayGamepadInfo(gamepad);
        }
    }

   
};

PlayerController.prototype.onCollisionStart = function (result) {
    this.Test2 = result.other.name === "Box4" ? 1 : 0;
    this.Test1 = result.other.name === "Box1" ? 1 : 0;
    this.Test = result.other.name === "Box" ? 0 : 0;
    this.Test = result.other.name === "Ramp" ? 1 : 0;
};

PlayerController.prototype.handleMovement = function (gamepad, dt) {
    let horiz = -gamepad.axes[0] || 0;
    let vert = -gamepad.axes[1] || 0;

    const deadZone = 0.2;
    horiz = Math.abs(horiz) < deadZone ? 0 : horiz;
    vert = Math.abs(vert) < deadZone ? 0 : vert;

    let moveX = 0, moveY = 0, moveZ = 0;
    let angle = null;

    if (vert > 0) { moveZ = this.speed * dt; angle = 0; }
    else if (vert < 0) { moveZ = -this.speed * dt; angle = 180; }
    else if (horiz < 0) { moveX = -this.speed * dt; angle = 270; }
    else if (horiz > 0) { moveX = this.speed * dt; angle = 90; }
      if (this.Test1 === 1) {
        moveX = 0;
        const pushBack = 0.5;
        if (gamepad.axes[0] > 0) moveX += pushBack;
        else if (gamepad.axes[0] < 0) moveX -= pushBack;
        this.Test1 = 0;
    }

    if (this.Test2 === 1) {
        moveZ = 0;
        const pushBack = 0.5;
        if (-gamepad.axes[1] > 0) moveZ -= pushBack;
        else if (-gamepad.axes[1] < 0) moveZ += pushBack;
        this.Test2 = 0;
    }

    
    if (this.Test === 1 && moveX !== 0) {
        const rad = 32.3 * (Math.PI / 180);
        const direction = gamepad.axes[0] > 0.1 ? -1 : 1;

        const originalMoveX = moveX;
        moveX *= Math.cos(rad);
        moveY = direction * Math.abs(originalMoveX) * Math.sin(rad);

     
    }

    this.entity.anim.setBoolean('walk', angle !== null);
    if (angle !== null) {
        const targetRot = new pc.Quat().setFromEulerAngles(0, angle, 0);
        this.entity.setRotation(targetRot);
    }

   
    this.entity.translate(moveX, moveY, moveZ);
};

PlayerController.prototype.applyPushback = function (axisValue, strength) {
    if (axisValue > 0.1) return -strength;
    if (axisValue < -0.1) return strength;
    return 0;
};

PlayerController.prototype.displayGamepadInfo = function (gamepad) {
    const pos = this.entity.getPosition();
    this.gamepadInfo.innerHTML =
        `<strong>Gamepad Info:</strong><br>` +
        gamepad.axes.map((val, i) => `Axis ${i}: ${val.toFixed(2)}<br>`).join('') +
        gamepad.buttons.map((btn, j) => `Button ${j}: ${btn.pressed ? 'Pressed' : 'Released'}<br>`).join('') +
        `<br><strong>Position:</strong> X: ${pos.x.toFixed(2)} Z: ${pos.z.toFixed(2)} Y: ${pos.y.toFixed(2)}<br>`;
};

PlayerController.prototype.setupUI = function () {
    this.gamepadInfo = document.createElement('div');
    Object.assign(this.gamepadInfo.style, {
        position: 'absolute',
        top: '50px',
        left: '10px',
        backgroundColor: 'rgba(0,0,0,0.5)',
        color: 'white',
        padding: '10px',
        fontFamily: 'Arial',
        fontSize: '12px'
    });
    document.body.appendChild(this.gamepadInfo);
};

Hi @karbanalexey and welcome!

I’d prefer not to base player movement on collisions and names. Instead, you could consider using a downward raycast to determine ground angles from the hit normal, and possibly some forward raycasts to detect obstacles in front.

Could you please specify what lags and when it lags?

https://youtu.be/P7AYfGtjdVQ Example of game lags

I see what you mean. To be honest, I think it’s just your logic that is failing. For example, maybe the bottom of the collision component has contact with an entity, while the side of the collision component has also contact with an entity.

When you use raycasts, you only need to set the y position to the hit point of the raycast downward. You can use the raycast forward for collision detecting.

I did it a little differently, I lowered the player collider a little bit down, analogous to raycast, and here is the result. Maybe there are lessons or examples somewhere on how engines are created?

1 Like

With engine you mean a game?

Maybe the course below is something that can help.

Thanks a lot !

1 Like

I repeated the script as in the video but there is no turn, what could it be?

var Test = pc.createScript('test');
Test.attributes.add('speed', { type: "number"});
Test.attributes.add('modelEntity', { type: "entity"});
// initialize code called once per entity
Test.prototype.initialize = function() {

};

// update code called every frame
Test.prototype.update = function(dt) {
   var x = 0;
   var z = 0;
if (this.app.keyboard.isPressed(pc.KEY_W)){
    z+=1;
}   
if (this.app.keyboard.isPressed(pc.KEY_S)){
    z-=1;
}   if (this.app.keyboard.isPressed(pc.KEY_A)){
    x+=1;
}   if (this.app.keyboard.isPressed(pc.KEY_D)){
    x-=1;
}   

    
    const movement = new pc.Vec3(x,0,z).normalize().scale(dt*this.speed)
   this.entity.rigidbody.applyForce(movement);

   const newAngle = 90 - (Math.atan2(z,x)*pc.math.RAD_TO_DEG);
   this.modelEntity.setEulerAngles(0,newAngle,0);
    

};

Test video

The Angular Factor of the y-axis should be 1.

I fixed the code, there is already movement but another problem appeared, when descending from the ramp gravity does not work, increasing mass or force or gravity immobilizes the player. What are the options to fix this?
video of lags

var PlayerController = pc.createScript('playerController');


PlayerController.attributes.add('speed', { type: "number" });
PlayerController.attributes.add('modelEntity', { type: "entity" });

PlayerController.prototype.initialize = function () {
    this.gamepadEnabled = false;
    this.setupUI();

   this.force = new pc.Vec3();

   
};

PlayerController.prototype.update = function (dt) {
    if (this.app.keyboard.wasPressed(pc.KEY_Q)) {
        this.gamepadEnabled = true;
    }

    if (this.gamepadEnabled) {
        const gamepad = navigator.getGamepads()[0];
        if (gamepad) {
            this.handleMovement(gamepad, dt);
            this.displayGamepadInfo(gamepad);
        }
    }

 
};



PlayerController.prototype.handleMovement = function (gamepad, dt) {
    let x = -gamepad.axes[0];
    let z = -gamepad.axes[1];
    const deadZone = 0.05;
    z = Math.abs(z) < deadZone ? 0 : z;
    x = Math.abs(x) < deadZone ? 0 : x;

    const isMoving = x !== 0 || z !== 0;

    
    if (this.entity.anim) {
        this.entity.anim.setBoolean('walk', isMoving);
    }

    if (isMoving) {
        this.force.set(x, 0, z).normalize().scale(this.speed);
        this.entity.rigidbody.applyForce(this.force);

        const angleRad = Math.atan2(x, z);
        const targetY = pc.math.RAD_TO_DEG * angleRad;
        const currentEuler = this.modelEntity.getEulerAngles();
        const newY = pc.math.lerpAngle(currentEuler.y, targetY, dt * 100);
        this.modelEntity.setEulerAngles(0, newY, 0);
    }

    this.modelEntity.setPosition(this.entity.getPosition());
    //this.entity.rigidbody.applyForce(0, -500, 0);
};




PlayerController.prototype.displayGamepadInfo = function (gamepad) {
    const pos = this.entity.getPosition();
    this.gamepadInfo.innerHTML =
        `<strong>Gamepad Info:</strong><br>` +
        gamepad.axes.map((val, i) => `Axis ${i}: ${val.toFixed(2)}<br>`).join('') +
        gamepad.buttons.map((btn, j) => `Button ${j}: ${btn.pressed ? 'Pressed' : 'Released'}<br>`).join('') +
        `<br><strong>Position:</strong> X: ${pos.x.toFixed(2)} Z: ${pos.z.toFixed(2)} Y: ${pos.y.toFixed(2)}<br>`;
};

PlayerController.prototype.setupUI = function () {
    this.gamepadInfo = document.createElement('div');
    Object.assign(this.gamepadInfo.style, {
        position: 'absolute',
        top: '50px',
        left: '10px',
        backgroundColor: 'rgba(0,0,0,0.5)',
        color: 'white',
        padding: '10px',
        fontFamily: 'Arial',
        fontSize: '12px'
    });
    document.body.appendChild(this.gamepadInfo);
};

In my own game applying a downward force works fine. You can try to decrease Linear Damping. If you do this you also need to decrease your movement force.

Hi, I’m having problems moving the enemy, can you give me some advice on how to fix it?
https://www.youtube.com/watch?v=zn1gQhEm14w

var enemyController = pc.createScript('enemyController');

// ---------- ATTRIBUTES ----------
enemyController.attributes.add('speed', { type: "number", default: 6 });
enemyController.attributes.add('modelEntity', { type: "entity" });
enemyController.attributes.add('changeDirTime', { type: "number", default: 2 });

// ---------- INIT ----------
enemyController.prototype.initialize = function () {

    this.force = new pc.Vec3();

    // эмуляция осей геймпада
    this.axisX = 0;
    this.axisZ = 0;

    this.moveX = 0;

    // таймер смены направления
    this.dirTimer = 0;

    this.entity.rigidbody.teleport(5, 5, -1);
    this.modelEntity.setPosition(this.entity.getPosition());

    this.pickRandomDirection();
    

};

// ---------- UPDATE ----------
enemyController.prototype.update = function (dt) {

    // ---------- RANDOM DIRECTION ----------
    this.dirTimer += dt;
    if (this.dirTimer >= this.changeDirTime) {
        this.dirTimer = 0;
        this.pickRandomDirection();
    }

    this.handleMovement(dt);
    this.checkGroundDistance();
    
};

// =====================================================
// =========== RANDOM 4-DIRECTION (AXES) ===============
// =====================================================

enemyController.prototype.pickRandomDirection = function () {
    const dir = Math.floor(Math.random() * 4);

    // 1 в 1 как геймпад
    switch (dir) {
        case 0: // UP
            this.axisX = 0;
            this.axisZ = 1;
            break;
        case 1: // DOWN
            this.axisX = 0;
            this.axisZ = -1;
            break;
        case 2: // LEFT
            this.axisX = -1;
            this.axisZ = 0;
            break;
        case 3: // RIGHT
            this.axisX = 1;
            this.axisZ = 0;
            break;
    }
};

// =====================================================
// ==================== MOVEMENT =======================
// =====================================================

enemyController.prototype.handleMovement = function (dt) {

    let x = this.axisX;
    let z = this.axisZ;

    // 👉 сохраняем направление (для ground check)
    this.moveX = x;

    const isMoving = x !== 0 || z !== 0;
    
    // ---------- ANIMATION ----------
    if (this.modelEntity.anim) {
    this.modelEntity.anim.setBoolean('walk', isMoving);
    }


    if (isMoving) {

        // ---------- MOVEMENT (КАК БЫЛО) ----------
        this.force.set(x, 0, z).normalize().scale(this.speed);
        this.entity.rigidbody.applyForce(this.force);

        // ---------- ROTATION (КАК БЫЛО) ----------
        const angleRad = Math.atan2(x, z);
        const targetY = pc.math.RAD_TO_DEG * angleRad;
        const currentEuler = this.modelEntity.getEulerAngles();
        const newY = pc.math.lerpAngle(currentEuler.y, targetY, dt * 100);
        this.modelEntity.setEulerAngles(0, newY, 0);
    }

    // ---------- MODEL FOLLOW ----------
    this.modelEntity.setPosition(this.entity.getPosition());
};

// =====================================================
// ================= GROUND CHECK ======================
// =====================================================

enemyController.prototype.checkGroundDistance = function () {

    const start = this.entity.getPosition().clone();
    const end = start.clone().add(new pc.Vec3(0, -60, 0));

    const result = this.app.systems.rigidbody.raycastFirst(start, end);

    if (result && result.entity !== this.entity) {
        const distance = start.y - result.point.y;

        if (distance > 0.2) {

            // 👉 движение вправо = -X (КАК БЫЛО)
            if (this.moveX < -0.1) {
                this.entity.rigidbody.applyForce(0, -10000, 0);
            } else {
                this.entity.rigidbody.applyForce(0, -1000, 0);
            }
        }
    }
};
var PlayerController = pc.createScript('PlayerController');


PlayerController.attributes.add('speed', { type: "number" });
PlayerController.attributes.add('modelEntity', { type: "entity" });

PlayerController.prototype.initialize = function () {
    this.gamepadEnabled = false;
    this.setupUI();

    this.force = new pc.Vec3();
    this.entity.rigidbody.teleport(5, 5, -1);
   // this.entity.rigidbody.teleport(95, 45, -1);
    this.modelEntity.setPosition(this.entity.getPosition());

    window.addEventListener("gamepadconnected", (e) => {
        console.log("Gamepad connected:", e.gamepad.id);
        this.gamepadEnabled = true;
    });

    window.addEventListener("gamepaddisconnected", (e) => {
        console.log("Gamepad disconnected");
        this.gamepadEnabled = false;
    });
};


PlayerController.prototype.update = function (dt) {
    if (this.app.keyboard.wasPressed(pc.KEY_Q)) {
        this.gamepadEnabled = true;
    }

    if (this.gamepadEnabled) {
        const gamepad = navigator.getGamepads()[0];
        if (gamepad) {
            this.handleMovement(gamepad, dt);
            this.displayGamepadInfo(gamepad);
        }
    }

 this.checkGroundDistance();
};



PlayerController.prototype.handleMovement = function (gamepad, dt) {
    let x = -gamepad.axes[0];
    let z = -gamepad.axes[1];
    const deadZone = 0.05;

    z = Math.abs(z) < deadZone ? 0 : z;
    x = Math.abs(x) < deadZone ? 0 : x;

    // 👉 сохраняем направление
    this.moveX = x;

    const isMoving = x !== 0 || z !== 0;

    if (this.entity.anim) {
        this.entity.anim.setBoolean('walk', isMoving);
    }

    if (isMoving) {
        this.force.set(x, 0, z).normalize().scale(this.speed);
        this.entity.rigidbody.applyForce(this.force);

        const angleRad = Math.atan2(x, z);
        const targetY = pc.math.RAD_TO_DEG * angleRad;
        const currentEuler = this.modelEntity.getEulerAngles();
        const newY = pc.math.lerpAngle(currentEuler.y, targetY, dt * 100);
        this.modelEntity.setEulerAngles(0, newY, 0);
    }

    this.modelEntity.setPosition(this.entity.getPosition());
};





PlayerController.prototype.displayGamepadInfo = function (gamepad) {
    const pos = this.entity.getPosition();
    this.gamepadInfo.innerHTML =
        `<strong>Gamepad Info:</strong><br>` +
        gamepad.axes.map((val, i) => `Axis ${i}: ${val.toFixed(2)}<br>`).join('') +
        gamepad.buttons.map((btn, j) => `Button ${j}: ${btn.pressed ? 'Pressed' : 'Released'}<br>`).join('') +
        `<br><strong>Position:</strong> X: ${pos.x.toFixed(2)} Z: ${pos.z.toFixed(2)} Y: ${pos.y.toFixed(2)}<br>`;
};

PlayerController.prototype.setupUI = function () {
    this.gamepadInfo = document.createElement('div');
    Object.assign(this.gamepadInfo.style, {
        position: 'absolute',
        top: '50px',
        left: '10px',
        backgroundColor: 'rgba(0,0,0,0.5)',
        color: 'white',
        padding: '10px',
        fontFamily: 'Arial',
        fontSize: '12px'
    });
    document.body.appendChild(this.gamepadInfo);
};

PlayerController.prototype.checkGroundDistance = function () {
    const start = this.entity.getPosition().clone();
    const end = start.clone().add(new pc.Vec3(0, -60, 0));

    const result = this.app.systems.rigidbody.raycastFirst(start, end);

    if (result && result.entity !== this.entity) {
        const distance = start.y - result.point.y;

        if (distance > 0.2) {

            // 👉 движение вправо = -X
            if (this.moveX < -0.1) {
                this.entity.rigidbody.applyForce(0, -10000, 0);
            } else {
                this.entity.rigidbody.applyForce(0, -1000, 0);
            }
        }
    }
};

What is the problem exactly?

the enemy does not turn in the direction of movement.

I just checked your project. The setup of the player and enemy doesn’t look correct to me. I’m not sure why it appears to work for the player, but that’s probably just coincidence.

The parent entity should contain the main components, such as the script, rigidbody and collision component. The model child entity should contain the render component with the visual model. Make sure this model entity is assigned to the appropriate script attribute.

The setup should be similar to the example project below.

https://playcanvas.com/project/808772/overview/look-at-with-physics