How to best implement 3D spaceflight in PlayCanvas?

Hey all,

I am working on making a 3d spaceship type of game. I would like to control the ship in 3D space in an intuitive manner. I am wondering how gamedevs would usually do this in games like the X series. This is what I have so far: https://playcanvas.com/project/352733/code . The parent object I am flying is dynamic.

The main problem I have so far is making the cube face in the direction its flying (which I already solved) but also making the ship turn in space in a smooth fashion so as to be able to fly it anywhere easily and intuitively. My current spaceship as you can see in the project is a dynamic character controller (with a dynamic rigid body), containing a cube model, containing a camera. I have to make the cube face the direction its parent is moving in with the following code (cant apply this to the actual dynamic character controller since its dynamic and you cant use lookAt for dynamic objects) :

   update: function (dt) {
        if(!parent.rigidbody.linearVelocity.equals(pc.Vec3.ZERO))
            {
                var pointinfront = new pc.Vec3();
                var position = new pc.Vec3();
                position = this.entity.getPosition();
                var lv = new pc.Vec3();
                lv = parent.rigidbody.linearVelocity;
                lv.normalize().scale(10);
                pointinfront.add2(lv, position);
                this.entity.lookAt(pointinfront);
            }
       
    }

I am thinking my next step would be apply a force perpendicular to the velocity to the dynamic character controller in the update function to make it turn in a nice curve. However I would like to know if I am doing it wrong, and what would be the conventional way to do this.

Many thanks.

It all depends what sort of ‘3D space-flight’ you want.

I tend to base my space-flight on the Descent series (1995), where the ship has good stopping ability.
But other games try to be more realistic, and let the ship drift all over the place.

In Practice, both are pretty much the same except for the acceleration controller.

So my game is here:
http://simplesix.totalh.net/

You can browse the scripts here:
http://simplesix.totalh.net/Scripts/

In particular you are probably interested in the file “player.js” function “updateControl.js”:

function updateControl(dt, dict){
//Takes a dictionary of form: {action:percent}
if (dict['Em'] == 1 && this.energy > dt*EM_THRUST_CONSUME){
	nspeed = SPEED*EM_THRUST_RATE
	nturn = TURN*EM_THRUST_RATE
	naccel = ACCEL*EM_THRUST_RATE
	ntaccel = TACCEL*EM_THRUST_RATE
	this.energy -= dt*EM_THRUST_CONSUME
	console.log(this.energy)
} else {
	nspeed = SPEED
	nturn = TURN
	naccel = ACCEL
	ntaccel = TACCEL
}

this.mvec.y = -dict['Sf'] //Strafe forwards
this.mvec.z = dict['Su'] //Strafe up
this.mvec.x = dict['Ss'] //Strafe Sideways
this.mvec.scale(nspeed)

cm = glob2loc(this.obj.rigidbody.linearVelocity, this.obj)
this.mvec = this.m_pid.update(cm, this.mvec, dt)
this.mvec = clampVecCube(this.mvec, naccel)
this.mvec = loc2glob(this.mvec, this.obj)	   

this.rvec.y = dict['Roll']
this.rvec.z = dict['Pan']
this.rvec.x = dict['Tilt']

this.rvec.scale(nturn)
cr = glob2loc(this.obj.rigidbody.angularVelocity, this.obj)
this.rvec = this.r_pid.update(cr, this.rvec, dt)

this.rvec = clampVecCube(this.rvec, ntaccel)
this.rvec = loc2glob(this.rvec, this.obj)
this.obj.rigidbody.applyTorque(this.rvec)
this.obj.rigidbody.applyForce(this.mvec)
}

The functions glob2loc and loc2glob are in the file 'common.js’
The motion is controlled by a proportional controller in ‘pid.js’ (the call pid.update() passes the target and position into the PID).

You may wish to consider using raycastFirst() along with lerpAngle() and teleport(). You can achieve smooth transitions in orientation with raycastFirst() and lerpAngle() using mouse position for the driving input. You may then apply the resulting Euler Angles to your dynamic rigidbody object via teleport().

Hi, your links are broken. Can you tell me how do you get the correct moving force?
After applyTorque, the orientation of the entity is changed, and the moving force must push to the right direction. EulerAngles are not linear, Quaternions are not fit for sin, cos。

So how do you get the correct force vector? :flushed:

Sorry. Shortly after posting that I bought a domain name.
The code for the project is visible on github:

So the first thing I do is create a pc.Vec3 from the player input (player.js -> updateControl() and control.js -> controlUpdate(). Then I feed that into a [proportional controller][2], before applying it as a force/torque. (player -> updateControl() near the end).
You have to convert from local co-ordinates into global co-ordinates, and to do that I use these functions:

function glob2loc(vec, entity){
    lvec = new pc.Vec3()
    lvec.x = vec.dot(entity.right)
    lvec.y = vec.dot(entity.forward)
    lvec.z = vec.dot(entity.up)
    return lvec
}

function loc2glob(vec, entity){
    gvec = new pc.Vec3()
    gvec.add(entity.right.clone().scale(vec.x))
    gvec.add(entity.forward.clone().scale(vec.y))
    gvec.add(entity.up.clone().scale(vec.z))
    return gvec
}

The function glob2loc converts from a global vector to a local vector
The function loc2glob converts from a local vector to a global vector.
[2]: https://gitlab.com/sdfgeoff/SimpleSix/blob/master/Scripts/pid.js

Thanks a lot for your reply. That’s exactly the effect I need! I am playing with your game and your code :smile: