Hello, I am new to the panda3d engine (I started about 10 days ago). I created a first person camera that works with the bullet character controller. I am having issues with the character moving around smoothly though. It’s not a camera issue or even a bullet issue(I think), but rather I can’t figure out how to keep the character’s speed from being “Jumpy”.
Here is a quick video demonstrating the problem (kinda): https://www.youtube.com/watch?v=V6bc2dtFtUg
The main problem is that the speed is not consistent with the frame rate. I know I am supposed to implement delta-time from the ClockObject to make it sync up, but I can’t seem to get it right.
This is my move function from the video:
void move(){
/* Tried using delta-time to replace the 25 below,
but it only made it more dramatic. */
float dt = co->get_dt();
LVector3f playMove(0,0,0);
if(arr[0]){ // Forward (W/Up arrow)
playMove.add_x(-sin(camera.get_h()*(PI/180))*25); // the *25 is the speed variable
playMove.add_y(cos(camera.get_h()*(PI/180))*25);
}
if(arr[1]){ // Backward (S/Down arrow)
playMove.add_x(sin(camera.get_h()*(PI/180))*25);
playMove.add_y(-cos(camera.get_h()*(PI/180))*25);
}
if(arr[2]){ // Left (A/Left arrow)
playMove.add_x(-cos(-camera.get_h()*(PI/180))*25);
playMove.add_y(sin(-camera.get_h()*(PI/180))*25);
}
if(arr[3]){ // Right (D/Right arrow)
playMove.add_x(cos(-camera.get_h()*(PI/180))*25);
playMove.add_y(-sin(-camera.get_h()*(PI/180))*25);
}
playerControllerNode->set_linear_movement(playMove,true); // Angular movement is not needed :)
if(arr[4]){ // Spacebar
playerControllerNode->do_jump();
}
}
Is there any good way to implement delta-time into the trig functions? “-sin(camera.get_h()*(PI/180))*25”
Full source code if you need it: http://pastie.org/9721822
or here:
//#undef NDEBUG
#include "pandaFramework.h"
#include "pandaSystem.h"
#include "genericAsyncTask.h"
#include "asyncTaskManager.h"
#include "bulletWorld.h"
#include "bulletPlaneShape.h"
#include "bulletBoxShape.h"
#include "bulletCharacterControllerNode.h"
#include "bulletCapsuleShape.h"
#include <load_prc_file.h>
#include <math.h>
#define PI 3.14159265
// Global stuff
PT(AsyncTaskManager) taskMgr = AsyncTaskManager::get_global_ptr();
NodePath camera, actor;
WindowFramework *window;
ClockObject *co = ClockObject::get_global_clock();
BulletCharacterControllerNode *playerControllerNode;
NodePath playerNP;
float cen_x, cen_y; // center of screen coordinates
bool unlockMouse = false; // If this is true, then the mouse will be ignored.
bool arr[5] = {false}; // Keyboard booleans (wheather or not the key is down or not)
BulletWorld *get_physics_world() {
static BulletWorld *physics_world = new BulletWorld();
return physics_world;
}
void move(){
/* Tried using delta-time to replace the 25 below,
but it only made it more dramatic. */
float dt = co->get_dt();
LVector3f playMove(0,0,0);
if(arr[0]){ // Forward (W/Up arrow)
playMove.add_x(-sin(camera.get_h()*(PI/180))*25);
playMove.add_y(cos(camera.get_h()*(PI/180))*25);
}
if(arr[1]){ // Backward (S/Down arrow)
playMove.add_x(sin(camera.get_h()*(PI/180))*25);
playMove.add_y(-cos(camera.get_h()*(PI/180))*25);
}
if(arr[2]){ // Left (A/Left arrow)
playMove.add_x(-cos(-camera.get_h()*(PI/180))*25);
playMove.add_y(sin(-camera.get_h()*(PI/180))*25);
}
if(arr[3]){ // Right (D/Right arrow)
playMove.add_x(cos(-camera.get_h()*(PI/180))*25);
playMove.add_y(-sin(-camera.get_h()*(PI/180))*25);
}
playerControllerNode->set_linear_movement(playMove,true); // Angular movement is not needed :)
if(arr[4]){ // Spacebar
playerControllerNode->do_jump();
}
}
void mouseLook(){
if(window->get_graphics_window() && !unlockMouse){
/* Gets the difference between the mouse's horiz position and the horiz center of the screen */
int mx = window->get_graphics_window()->get_pointer(0).get_x() - cen_x;
/* Difference between the mouse's vert position and the vert center of the screen */
int my = window->get_graphics_window()->get_pointer(0).get_y() - cen_y;
/* p is the camera's pitch, h is the camera's heading (A.K.A. Yaw) */
float p = camera.get_p(), h = camera.get_h();
/* The *0.15 is the speed (or sensitivity) */
camera.set_h(h-mx*0.15); // Look Left/Right
camera.set_p(p-my*0.15); // Look Up/Down
window->get_graphics_window()->move_pointer(0,cen_x,cen_y); // Keeps mouse locked in window.
if(p>90)camera.set_p(90); else if(p<-90)camera.set_p(-90); // Locks the camera's pitch, to prevent the player from looking upside down.
}
}
// Task to move the camera
AsyncTask::DoneStatus GameLoop(GenericAsyncTask* task, void* data) {
mouseLook(); // Mouse moves the rotation of the screen.
move(); // Character Movement
camera.set_pos(playerNP.get_x(),playerNP.get_y(),playerNP.get_z()+1.75); // Camera's position is put with the character nodepath.
get_physics_world()->do_physics(co->get_dt(), 1, 1.0 / 60.0); // does physics 60 times a second.
return AsyncTask::DS_cont; // loops the function
}
void keyEvent(const Event * e, void * data) {
// I wonder if there is a better/efficient way of doing this.
string name = e->get_name();
if(name=="w" || name=="arrow_up") arr[0] = true;
else if(name=="w-up" || name=="arrow_up-up") arr[0] = false;
if(name=="s" || name=="arrow_down") arr[1] = true;
else if(name=="s-up" || name=="arrow_down-up") arr[1] = false;
if(name=="a" || name=="arrow_left") arr[2] = true;
else if(name=="a-up" || name=="arrow_left-up") arr[2] = false;
if(name=="d" || name=="arrow_right") arr[3] = true;
else if(name=="d-up" || name=="arrow_right-up") arr[3] = false;
if(name=="space") arr[4] = true; else if(name=="space-up") arr[4] = false;
if(name=="escape") exit(0);
if(name=="tab") if(unlockMouse)unlockMouse=false; else unlockMouse=true;
}
int main(int argc, char *argv[]) {
// Open a new window framework and set the title
PandaFramework framework;
framework.open_framework(argc, argv);
framework.set_window_title("My Panda3D Window");
load_prc_file_data("", "show-frame-rate-meter 1"); // shows framerate
/* Defines keyboard keys */
framework.define_key("arrow_up", "Moves forward", &keyEvent, NULL);
framework.define_key("arrow_up-up", "Stops moving forward", &keyEvent, NULL);
framework.define_key("arrow_down", "Moves backward", &keyEvent, NULL);
framework.define_key("arrow_down-up", "Stops moving backward", &keyEvent, NULL);
framework.define_key("arrow_right", "Moves right", &keyEvent, NULL);
framework.define_key("arrow_right-up", "Stops moving right", &keyEvent, NULL);
framework.define_key("arrow_left", "Moves left", &keyEvent, NULL);
framework.define_key("arrow_left-up", "Stops moving left", &keyEvent, NULL);
framework.define_key("w", "Moves forward", &keyEvent, NULL);
framework.define_key("w-up", "Stops moving forward", &keyEvent, NULL);
framework.define_key("a", "Moves left", &keyEvent, NULL);
framework.define_key("a-up", "Stops moving left", &keyEvent, NULL);
framework.define_key("s", "Moves backward", &keyEvent, NULL);
framework.define_key("s-up", "Stops moving backward", &keyEvent, NULL);
framework.define_key("d", "Moves right", &keyEvent, NULL);
framework.define_key("d-up", "Stops moving right", &keyEvent, NULL);
framework.define_key("escape", "Exits the game", &keyEvent, NULL);
framework.define_key("tab", "unlocks the mouse", &keyEvent, NULL);
framework.define_key("space", "Jump Button", &keyEvent, NULL);
framework.define_key("space-up", "Stops Jumping", &keyEvent, NULL);
window = framework.open_window();
camera = window->get_camera_group();
WindowProperties props = window->get_graphics_window()->get_properties();
props.set_cursor_hidden(true); // Hides the mouse
window->get_graphics_window()->request_properties(props);
window->enable_keyboard();
cen_x = window->get_graphics_window()->get_x_size()/2; // Horizontal center of screen.
cen_y = window->get_graphics_window()->get_y_size()/2; // Vertical center of screen.
Camera* c = window->get_camera(0);
c->get_lens()->set_fov(90); // My preferred fov level is 90.
c->get_lens()->set_near(0.1); // Prevents near clipping with objects.
get_physics_world()->set_gravity(0, 0, -9.8);
float height = 1.75, radius = 0.4;
BulletCapsuleShape* shape = new BulletCapsuleShape(radius, height - 2*radius); // Capsule Shape for player
playerControllerNode = new BulletCharacterControllerNode(shape, 0.4, "Player"); // New Bullet Character Controller
playerNP = window->get_render().attach_new_node(playerControllerNode); // attaches a new node based off the player controller
playerNP.set_pos(-2, 0, 14); // Sets the inital position of the player nodepath
CollideMask mask(BitMask32(0x10));
playerNP.set_collide_mask(mask);
get_physics_world()->attach_character(playerControllerNode); // attaches the character to the world.
BulletPlaneShape *floor_shape = new BulletPlaneShape(*new LVecBase3f(0, 0, 1), 1); // Infinite plane floor shape
BulletRigidBodyNode *floor_rigid_node = new BulletRigidBodyNode("Ground"); // The floor's body node
floor_rigid_node->add_shape(floor_shape);
NodePath np_ground = window->get_render().attach_new_node(floor_rigid_node); // NodePath based off the floor
np_ground.set_pos(0, 0, -2); // sets the position
get_physics_world()->attach_rigid_body(floor_rigid_node); // attaches it to the world.
// The example environment. Just for looks, no physics attached to it.
NodePath environ = window->load_model(framework.get_models(), "models/environment");
environ.reparent_to(window->get_render());
environ.set_scale(0.25 , 0.25, 0.25);
environ.set_pos(-8, 42, -1);
BulletBoxShape *box_shape = new BulletBoxShape(*new LVecBase3f(1, 1, 1)); // Box Shape
BulletRigidBodyNode *box_rigid_node = new BulletRigidBodyNode("Box"); // Box Node
box_rigid_node->set_mass(1); // Sets Mass to the node
box_rigid_node->add_shape(box_shape); // Adds the shape to the node
NodePath np_box = window->get_render().attach_new_node(box_rigid_node); // The nodepath of the box's physics
np_box.set_pos(0, 0, 5); // sets the box's initial position
get_physics_world()->attach_rigid_body(box_rigid_node); // adds the box to the world.
actor = window->load_model(framework.get_models(), "box"); // Loads the box's model
actor.set_scale(2); // scales it up by 2 times.
actor.set_pos(-1,-1,-1); // offsets the model to match the physical bounds.
np_box.flatten_light();
actor.reparent_to(np_box); // binds this to the box's physics.
taskMgr->add(new GenericAsyncTask("The Game Loop", &GameLoop, (void*) NULL)); // gameloop
framework.main_loop();
framework.close_framework();
return (0);
}