Jump to content

A Unity Like Character Controller


MackeyK24
 Share

Recommended Posts

Hey guys...

Especially you @Deltakosh ... I need your help... I have created a Built-In Character Controller (like the one in unity) that basically controls character movement with full jumping, falling and grounding support. Also allows complete user input control with one toggle 'player.enableInput = true'... I am have trouble with the last line...

if the 'Auto-Turning' feature is enabled I want the character to always face the direction of the horizontal and vertical input... Kinda like Unreal Engine Third Person Camera usage... I am using the 'lookAt' function to do that now but it 'Snaps To' that direction... so if you went from left to right it would slap to face the other direction instead of rotating 'a it more slower' to the that direction... I don't see a lookAt speed so I don't know how to handle that... I am still a newbie so I don't know all the 'Translation/Rotation' code I could manually using to achieve the same thing... Anyways... any help on fixing the lookout issue... Again ... would be awesome...

Here is my current Character Controller code for your reference, have a look... this is how you will use components in the BabylonJS Toolkit :)

 

UPDATE

New Character Controller Code:

/* Babylon Character Movement Controller Component */
/* <reference path="{*path*}/Assets/Babylon/Library/babylon.d.ts" /> */

module BABYLON {
    export class CharacterController extends BABYLON.MeshComponent {
        public gravity:number = 0.0;
        public moveSpeed:number = 6.0;
        public jumpForce:number = 8.0;
        public dropForce:number = 20.0;
        public enableInput:boolean = false;
        public autoTurning:boolean = false;
        public rotateSpeed:number = 0.25;
        public applyGrounding:boolean = true;
        public keyboardJump:number = BABYLON.UserInputKey.SpaceBar;
        public buttonJump:number = BABYLON.Xbox360Button.A;
        public isJumping():boolean { return this._jumping; }
        public isFalling():boolean { return this._falling; }
        public isGrounded():boolean { return this._grounded; }
        public getVelocity():BABYLON.Vector3 { return this.manager.getLinearVelocity(this.mesh); }
        public getAngular():BABYLON.Vector3 { return this.manager.getAngularVelocity(this.mesh); }
        public onUpdateInput:(velocity:BABYLON.Vector3, horizontal:number, vertical:number, mousex:number, mousey:number, jumped:boolean)=>void = null;

        private _jumping:boolean = false;
        private _falling:boolean = false;
        private _grounded:boolean = true;
        private _turnIdentity:boolean = false;
        private _slerpIndentity:BABYLON.Quaternion = null;
        private _lookPosition:BABYLON.Vector3 = BABYLON.Vector3.Zero();
        private _inputVelocity:BABYLON.Vector3 = BABYLON.Vector3.Zero();
        private _movementVelocity:BABYLON.Vector3 = BABYLON.Vector3.Zero();
        private _contactThreashold:number = 0.5;
        public constructor(owner: BABYLON.AbstractMesh, scene: BABYLON.Scene, tick: boolean = true, propertyBag: any = {}) {
            super(owner, scene, tick, propertyBag);
            this.gravity = this.scene.gravity.y;
            this.moveSpeed = this.getProperty("moveSpeed", 6.0);
            this.jumpForce = this.getProperty("jumpForce", 8.0);
            this.dropForce = this.getProperty("dropForce", 20.0);
            this.applyGrounding = this.getProperty("grounding", true);
            this.enableInput = this.getProperty("enableInput", false);
            this.autoTurning = this.getProperty("autoTurning", false);
            this.rotateSpeed = this.getProperty("rotateSpeed", 0.25);
            this._turnIdentity = false;
            this._slerpIndentity = BABYLON.Quaternion.Identity();
            this._movementVelocity.y = this.gravity;
        }

        public move(velocity:BABYLON.Vector3, angular:BABYLON.Vector3 = null, jump:boolean = false):void {
            this._movementVelocity.x = velocity.x * this.moveSpeed;
            this._movementVelocity.z = velocity.z * this.moveSpeed;
            if (jump === true && this._grounded === true && this.jumpForce > 0.0) {
                this._jumping = true;
                this._movementVelocity.y = this.jumpForce;
                this.updateGroundingState();
            }
            // Apply scene gravity with drop force delta
            if (this._movementVelocity.y > this.gravity) {
                this._movementVelocity.y -= (this.dropForce * this.manager.deltaTime);
                if (this._movementVelocity.y < this.gravity) {
                    this._movementVelocity.y = this.gravity;
                }
            }
            // Update current movement velocity with physics
            this.manager.moveWithPhysics(this.mesh, this._movementVelocity, angular);
        }

        protected start():void {
            this._jumping = false;
            this._falling = false;
            this._grounded = true;
            this.updateGroundingState();
            this.onCollisionEvent((collider:BABYLON.AbstractMesh, tag:string) => {
                if (this.manager.checkCollision(this.mesh, collider, BABYLON.CollisionContact.Bottom, this._contactThreashold) === true) {
                    this._jumping = false;
                    this._movementVelocity.y = this.gravity;
                    this.updateGroundingState();
                }
            });
        }

        protected fixed() :void {
            var falling:boolean = false;
            var velocity:BABYLON.Vector3 = this.getVelocity();
            if (velocity != null && velocity.y < -0.1) {
                falling = true;
            }
            this._falling = falling;
            this.updateGroundingState();
            // Update user input velocity
            if (this.enableInput === true) {
                var horizontal:number = this.manager.getUserInput(BABYLON.UserInputAxis.Horizontal);
                var vertical:number = this.manager.getUserInput(BABYLON.UserInputAxis.Vertical);
                var mousex:number = this.manager.getUserInput(BABYLON.UserInputAxis.MouseX);
                var mousey:number = this.manager.getUserInput(BABYLON.UserInputAxis.MouseY);
                var jumped:boolean = false;
                // Apply movement and jumping input
                if (this._grounded === true) {
                    this._inputVelocity.x = horizontal;
                    this._inputVelocity.z = vertical;
                    jumped = (this.manager.getKeyInput(this.keyboardJump) || this.manager.getButtonInput(this.buttonJump));
                }
                // Update custom movement user input
                if (this.onUpdateInput != null) {
                    this.onUpdateInput(this._inputVelocity, horizontal, vertical, mousex, mousey, jumped);
                }
                // Update avatar position and rotation
                this.move(this._inputVelocity, null, jumped);
                if (this.autoTurning === true && (horizontal !== 0.0 || vertical !== 0.0)) {
                    if (this._turnIdentity === false) {
                        this.mesh.rotationQuaternion = BABYLON.Quaternion.Identity();
                        this._turnIdentity = true;
                    }
                    // Rotate actor to face horizontal and vertical movement direction
                    this._lookPosition.x = -horizontal;
                    this._lookPosition.z = -vertical;
                    var position = this.mesh.position.add(this._lookPosition);
                    this.manager.lookAtPosition(this.mesh, position, this._slerpIndentity, this.rotateSpeed);
                }
            }
        }

        private updateGroundingState():void {
            this._grounded = (this.applyGrounding === false || (this._jumping === false && this._falling === false));
        }
    }
}

 

Link to comment
Share on other sites

To be clear, this is the line I need to fix:

this.mesh.lookAt(this.mesh.position.add(new BABYLON.Vector3(-horizontal, 0.0, -vertical)));

... It snaps to direction instead of a more natural rotation... Maybe I shouldn't be using that for this use case... I don't know, I'm still new :(

 

Link to comment
Share on other sites

1 hour ago, Virax said:

Hello,

The Vector3 class has a Lerp method !

You should try it! It should smooth the transition between the position you're adding 

https://doc.babylonjs.com/classes/2.5/vector3#static-lerp-start-end-amount-rarr-vector3-classes-2-5-vector3-

But do you have a playground example to be more clear ?

Cant do playground ... its a toolkit typescript component... but the lookAt like is where I'm talking about....

if you were going to rewrite that 'lookAt' line with a Leap ... How would you do it... Can you pleas write re-write that 'lookAt' line from above to do your leaping to replace look at :)

 

Link to comment
Share on other sites

Yea, I'd like to see this in JS form with proper terrain slope sliding. I tried to get an answer in my other thread on this, & the people in there are great, they're WONDERFUL people, but it didn't pan out too well. Players shouldn't be able to climb steep > 30 degree inclines. Nor' should they be able to jump the steep incline. And a camera that can slide across the ground and zoom in on the player smoothly when looking up. If you want a Unity Character Controller, this is the way to go about one.

Link to comment
Share on other sites

6 hours ago, adam said:

Yo @adam  Thanks man... thats perfect... But something I just didn't get... I saw the Lerp examples but what I don't understand is how the box.lookAt(sphere.position) get influenced by the slerping:

 

    var tempQuat = BABYLON.Quaternion.Identity();
    var slerpAmount = .2;

    box.rotationQuaternion = BABYLON.Quaternion.Identity();

    scene.registerBeforeRender(function(){

        tempQuat.copyFrom(box.rotationQuaternion);
        box.lookAt(sphere.position);
        BABYLON.Quaternion.SlerpToRef(tempQuat, box.rotationQuaternion, slerpAmount, box.rotationQuaternion)

    });

 

It looks the SLERP code is some kinda of 'WRAPPER' around the lookAt that ALWAYS applies 'Slerped Rotation' factor to handle the change in rotation from the tempQuat to what it is AFTER the lookAt has changed rotation and kinda of OVERIDE what the lookAt did with an 'EASED' rotation...

So we really get the Current rotation and store in tempQuat... Then do the LookAt to update the box rotation  (that does not change from before) but after that in the the same frame (so you never immediate see the initial lookAt snapping position) we adjust that rotation using our tempQuat and snapped to LookAt rotations and ease between them... I think... Thats what I was not to sure about... being a newbie at all the actual 3D gaming code and translations and rotations and matrix stuff.

Thanks for that snippet ... so I can try to understand why it works and how works :)

 

Link to comment
Share on other sites

Yo @adam

That is working perfectly... So much so I created a helper function on my unity like scene manager API to handle look at position wit option rotation slerping:

/** Rotates owner to look at the vector position and optionally apply slerping to the rotation
    * 
    *  To use slerping the owner mesh rotation should be set to identity before loop
    *  Example: owner.rotationQuaternion = BABYLON.Quaternion.Identity();
    * 
    *  The slerpIdentity is a temp reference holder for the current owner rotation
    *  Example: var slerpIdentity = BABYLON.Quaternion.Identity();
    * 
    *  The slerpAmount control the amouts of slerping applied to rotation. default 0.25
    */
public lookAtPosition(owner: BABYLON.AbstractMesh, position:BABYLON.Vector3, slerpIdentity:BABYLON.Quaternion = null, slerpAmount:number = 0.25):BABYLON.AbstractMesh {
    if (slerpIdentity != null && slerpAmount > 0.0) slerpIdentity.copyFrom(owner.rotationQuaternion);
    var result:BABYLON.AbstractMesh = owner.lookAt(position);
    if (slerpIdentity != null && slerpAmount > 0.0) BABYLON.Quaternion.SlerpToRef(slerpIdentity, owner.rotationQuaternion, slerpAmount, owner.rotationQuaternion)
    return result;
}

 

Then in my character controller update loop:

 

// Rotate actor to face horizontal and vertical movement direction
this._lookPosition.x = -horizontal;
this._lookPosition.z = -vertical;
var position = this.mesh.position.add(this._lookPosition);
this.manager.lookAtPosition(this.mesh, position, this._slerpIndentity, this.rotateSpeed);

Thanks again bro... very much :)

 

Link to comment
Share on other sites

8 hours ago, Mythros said:

Yea, I'd like to see this in JS form with proper terrain slope sliding. I tried to get an answer in my other thread on this, & the people in there are great, they're WONDERFUL people, but it didn't pan out too well. Players shouldn't be able to climb steep > 30 degree inclines. Nor' should they be able to jump the steep incline. And a camera that can slide across the ground and zoom in on the player smoothly when looking up. If you want a Unity Character Controller, this is the way to go about one.

Sup @Mythros ... Thanks for the response  :)

 

I would imagine this would deal with more the Physics Imposter setup ... Plus you could always (in the toolkit scene component life-cycle) compensate the character position and velocity  (and even utilize the underlying physics imposter to calculate at what angle you are colliding with)... I do this in onCollisionEvent to check whether after a jump or any collision really, if the source collision 'normal axis' is pointing with a certain 'threshold'...

So you could augment the physics and help is out to provide this 'Sloping' issues....  

BTW... Using my toolkit for unity... If I create a terrain that has heavy sloped hills and I move around with collision... I can just run up... it slides back down just fine... now I'm not sure exactly what property in Babylon and or the physics engine controls that... But seems to work just fine on the Terrain Meshes that I create in the toolkit and use as a ground mesh in Babylon:

 

var canJump = false;

    var contactNormal = new CANNON.Vec3(); // Normal in the contact, pointing *out* of whatever the player touched
    var upAxis = new CANNON.Vec3(0,1,0);
    cannonBody.addEventListener("collide",function(e){
        var contact = e.contact;

        // contact.bi and contact.bj are the colliding bodies, and contact.ni is the collision normal.
        // We do not yet know which one is which! Let's check.
        if(contact.bi.id == cannonBody.id)  // bi is the player body, flip the contact normal
            contact.ni.negate(contactNormal);
        else
            contactNormal.copy(contact.ni); // bi is something else. Keep the normal as it is

        // If contactNormal.dot(upAxis) is between 0 and 1, we know that the contact normal is somewhat in the up direction.
        if(contactNormal.dot(upAxis) > 0.5) // Use a "good" threshold value between 0 and 1 here!
            canJump = true;
    });

 

If that helps any :)

 

Link to comment
Share on other sites

22 hours ago, Mythros said:

If you want a Unity Character Controller, this is the way to go about one.

BTW... My toolkit always uses the phrase 'Unity-Like' ... So I am actually implementing from scratch using the BabylonJS API every feature in the the toolkit...

there is never a UNITY content or script that gets exported... Just the Toolkit Script Components that use WHATEVER logic you put and the built in components that the toolkit internally supports...

Just to clarify the phrase 'Unity-Like' :)

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...