ChrisVossen

Members
  • Content count

    0
  • Joined

  • Last visited

Community Reputation

1341 Excellent

About ChrisVossen

  • Rank
    Newbie
  1. It's finally time to begin fleshing out our player class. This class will manage the first-person user controls. We will start by setting up player.h. This header file will contain the declarations of the player class as well as the various variables and functions that will be held within the player class. At this time copy and paste the following code into player.h: #pragma once #include "MyGame.h" using namespace Leadwerks; class Player: public Node { private: Camera* camera; float standheight; float crouchheight; float cameraheight; float move; float strafe; float movementspeed; float maxacceleleration; float sensitivity; float smoothedcamerapositiony; float cameraypositionsmoothing; float cameralooksmoothing; float runboost; float jump; float jumpforce; float jumpboost; int footstepwalkfrequency; int footsteprunfrequency; long footsteptimer; bool running; bool crouched; bool landing; Vec2 mousespeed; Vec3 normalizedmovement; Vec3 cameraposition; Vec3 playerrotation; Sound* footstepsound[4]; Sound* landsound[4]; Sound* jumpsound[1]; public: Player(); virtual ~Player(); virtual void UpdateControls(); virtual void Update(); }; Setting Up the Player Class The player.cpp file will contain all the logic and code for setting up FPS player mechanics. We start with a basic player class that contains a constructor, destructor, and two empty functions: #include "MyGame.h" using namespace Leadwerks; Player::Player() { } Player::~Player() { } void Player::UpdateControls() { } void Player::Update() { } Since the player class is a child of the node class it will inherit an entity member from its parent. In the player constructor we assign a value to this entity member with a call to Pivot::Create(). A pivot is an invisible entity with no special properties, it is essentially an instantiation of an empty entity: entity = Pivot::Create(); We now want to setup the player physics properties for the entity: entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); And finally position the player at the origin: entity->SetPosition(0,0,0,true); With the code additions our player class will now look as such: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); } Player::~Player() { } void Player::UpdateControls() { } //Update function void Player::Update() { } Adding in a Camera In a FPS the player's camera acts as the player's head, in that it should be positioned at a height directly above the player's shoulders and be restricted to normal human movements. For the player's height we will create and initialize three separate variables in the constructor: standheight=1.7; crouchheight=1.2; cameraheight = standheight; We then create the camera, position it to the height of a standing player, and narrow the camera's field of view: camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); camera->SetFOV(70); We also don't want to forget to deal with the camera when an instance of the player class gets deleted. so in the destructor we add in: if (camera) { camera->Release(); camera = NULL; } After these changes the player class will now look like this: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); //Initialize values standheight=1.7; crouchheight=1.2; cameraheight = standheight; //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); camera->SetFOV(70); //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); } Player::~Player() { if (camera) { camera->Release(); camera = NULL; } } void Player::UpdateControls() { } //Update function void Player::Update() { } Up next, we'll talk about movement with keyboard input.
  2. This is a continuation of a multi-part tutorial on Building a First-Person Shooter - Part 1.0: Creating a Room. Setting up the VS 2010 project Now that we've finished building our level it is now time to begin the coding side of the project. For this lesson, we will be using Visual Studio 2010 on Windows, but you can follow the equivalent steps for Xcode on Mac. Right-click on the project name in the project list and select the Open Folder menu item from the context menu that pops up. This will open the project folder in Windows Explorer (or Finder on Mac). Navigate to the Projects/Windows folder and open the file MyGame.sln. At this point in time we are going to create a few blank C++ and Header files that we will be fleshing out throughout the tutorial. Creating C++ Files In the Visual Studio's Solution Explorer (normally located on the left side of the screen) right click on the Source folder and select Add->New Item, A new item window will pop up and we are going to select "C++ File (.cpp)" and name the file "Player" we also want to change the location of this file so on the right side of "Location" click the browse button and navigate to "MyGame/Source" and click "Select Folder" finally click Add and our new Player.cpp file will appear in the Solution Explorer. We will also want a "Node.cpp" file so repeat the process again but this time name the file "Node". Creating Header Files Adding header files into Visual Studios 2010 is essentially the same process as adding in a cpp file. This time we will click on the "Header Files" folder in the solution explorer, right click and select Add->New Item. The window from before will pop up, but now we select "Header File (.h)" instead. Once again we will want these files saved in the "MyGame/Source" folder so remember to save to the correct folder. We are going to make three headers for this tutorial so repeat the steps of adding a new file for Player.h, Node.h, and MyGame.h. Now we're ready to start coding. MyGame.h Inside MyGame.h we are going to set a series of #define statements that will allow other files to just make a single #define call. You will notice a call to #pragma once, this is a preprocessor directive that says "only include the following files if they're not already included". After this call we insert #define calls to leadwerks.h, node.h, and player.h: #pragma once #include "Leadwerks.h" #include "Node.h" #include "Player.h" App Class By default the App class contains two functions for structuring a game. App::Start() will be called when the game begins, and App::Loop() will be called continuously until the game ends. Inside App.h we are going to remove the default camera and add in a Player, the resulting file should look as such: #pragma once #include "Leadwerks.h" #include "MyGame.h" using namespace Leadwerks; class App { public: Window* window; Context* context; World* world; Player* player; App(); virtual ~App(); virtual bool Start(); virtual bool Loop(); }; Since we removed the default camera from App.h we will also need to remove the initialization call within the App constructor inside App.cpp: App::App() : window(NULL), context(NULL), world(NULL){} Next we are going to create a new instance of a player in App::Start() as well as call the player's Update function in App::Loop(): //Create the player player = new Player; //Update the player player->Update(); Also inside the App::Start() function, we are going to load an ambient background sound, then have that sound play on a continuous loop. (We'll replace this with something more advanced later on, but this is fine for now): Sound* sound = Sound::Load("Sound/Ambient/cryogenic_room_tone_10.wav"); Source* source = Source::Create(); source->SetSound(sound); source->SetLoopMode(true); source->Play(); By the end of these changes your finished App class should look like the following: #include "App.h" #include "MyGame.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL) {} App::~App() { //delete world; delete window; } bool App::Start() { //Create a window window = Window::Create("MyGame"); //Create a context context = Context::Create(window); //Create a world world = World::Create(); //Create the player player = new Player; std::string mapname = System::GetProperty("map","Maps/start.map"); if (!Map::Load(mapname)) Debug::Error("Failed to load map \""+mapname+"\"."); //Move the mouse to the center of the screen window->HideMouse(); window->SetMousePosition(context->GetWidth()/2,context->GetHeight()/2); Sound* sound = Sound::Load("Sound/Ambient/cryogenic_room_tone_10.wav"); Source* source = Source::Create(); source->SetSound(sound); source->SetLoopMode(true); source->Play(); world->SetAmbientLight(0,0,0,1); return true; } bool App::Loop() { //Close the window to end the program if (window->Closed() || window->KeyDown(Key::Escape)) return false; //Update the game timing Time::Step(); //Update the world world->Update(); //Update the player player->Update(); //Render the world world->Render(); //Sync the context context->Sync(true); return true; } Node Class Next we are going to create a base class which we will call Node. All classes in our game will be derived from this base class. This is called inheritance, because each class inherits members and functions from the class it's derived from. We can override inherited class functions with new ones, allowing us to create and extend behavior without rewriting all our code each time. The Node class itself will be derived from the Leadwerks Object class, which is the base class for all objects in Leadwerks. This will give us a few useful features right off the bat. Our Node class can use reference counting, and it can also be easily passed to and from Lua. The node header file will get just one member, an Entity object: #pragma once #include "MyGame.h" using namespace Leadwerks; class Node : public Object { public: Entity* entity; Node(); virtual ~Node(); }; In the Node.cpp file, we'll add the code for the Node constructor and destructor: #include "MyGame.h" Node::Node() : entity(NULL) { } Node::~Node() { if (entity) { if (entity->GetUserData()==this) entity->SetUserData(NULL); entity->Release(); entity = NULL; } } Our code foundation has now been laid and it is finally time to move onto developing the player class, which will be the subject of our next lesson.
  3. For a final touch we are going to add in sound effects. We begin by declaring a few variables to deal with the footstep sound effects times and frequencies: footsteptimer=Time::GetCurrent(); footstepwalkfrequency=400; footsteprunfrequency=320; Next we load in our sound effects which are located in the MyGame/Sound folder. For variety's sake we have four separate footstep sounds, four landing sounds, and one jumping grunt sound effect: //Load Sound files for (int i=0; i0.0) { jumpsound[0]->Play(); landing = true; if (velocity.z>movementspeed*0.85) { normalizedmovement.z *= jumpboost; normalizedmovement.z = min(normalizedmovement.z,movementspeed*runboost); maxaccel = 10; } } Directly above the previous code we now add in a check for the landing variable. If the player is landing then we play the landsound and immediately set landing to be false. //Play landing sounds if(landing) { landsound[(int)Math::Random(0,3)]->Play(); landing = false; } All that is left to do is implement the footstep sounds. We first need to calculate ground speed , this will help us determine how often to play the footstep sounds: Vec3 velocity = entity->GetVelocity(false); float groundspeed = velocity.xz().Length(); After determining the ground speed it is time for the footsteps code. We start off by checking if that a footstep sound is not currently playing by seeing if the current time is greater than the footsteptimer, we also make sure that the player is not jumping, and that the player is actually moving. If all of these cases are true then we check to make sure the player is moving fast enough to warrant a sound effect, so if the groundspeed is greater than 1.0 we play a footstep. Directly after we decide how much to increment the footsteptimer, if the player is running fast we will increment the timer more than if the player is walking, this is determined by whether or not groundspeed has surpassed a little bit more than movement speed: //Play footsteps if (Time::GetCurrent()>footsteptimer && jump==0.0 && normalizedmovement.Length()>0.0) { if (groundspeed>1.0) { int soundindex = (int)Math::Random(0,3); footstepsound[soundindex]->Play(); if (groundspeed>movementspeed*1.2) { footsteptimer = Time::GetCurrent() + footsteprunfrequency; } else { footsteptimer = Time::GetCurrent() + footstepwalkfrequency; } } } We finally have our complete player class. You can move around with the 'W' 'A' 'S' 'D' keys, run with Shift, jump with Space, and crouch with 'C'. Hit F5 and see the game in action: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); entity->SetUserData(this); //Initialize values standheight=1.7; crouchheight=1.2; cameraheight = standheight; move = 0.0; strafe = 0.0; movementspeed = 3.0; maxacceleleration = 0.5; sensitivity=1.0; smoothedcamerapositiony = 0; cameraypositionsmoothing = 3.0; cameralooksmoothing = 2.0; runboost = 2.0; jump = 0.0; jumpforce = 6.0; jumpboost = 2.0; footstepwalkfrequency=400; footsteprunfrequency=320; footsteptimer=Time::GetCurrent(); running=false; crouched = false; landing = false; //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); //Load Sound files for (int i=0; iRelease(); camera = NULL; } } void Player::UpdateControls() { Window* window = Window::GetCurrent(); Context* context = Context::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); jump = window->KeyDown(Key::Space) * jumpforce; if (!entity->GetAirborne()) { crouched = window->KeyDown(Key::C); } running = Window::GetCurrent()->KeyDown(Key::Shift); //Get the mouse movement float sx = context->GetWidth()/2; float sy = context->GetHeight()/2; //Get the mouse position Vec3 mouseposition = window->GetMousePosition(); //Move the mouse to the center of the screen window->SetMousePosition(sx,sy); //Get change in mouse position float dx = mouseposition.x - sx; float dy = mouseposition.y - sy; //Mouse smoothing mousespeed.x = Math::Curve(dx,mousespeed.x,cameralooksmoothing/Time::GetSpeed()); mousespeed.y = Math::Curve(dy,mousespeed.y,cameralooksmoothing/Time::GetSpeed()); //Adjust and set the camera rotation playerrotation.x += mousespeed.y*sensitivity / 10.0; playerrotation.y += mousespeed.x*sensitivity / 10.0; //Prevent inhuman looking angles playerrotation.x = Math::Clamp(playerrotation.x,-90,90); } //Update function void Player::Update() { UpdateControls(); if (entity->GetCrouched()) { cameraheight = Math::Inc(crouchheight,cameraheight,0.1); } else { cameraheight = Math::Inc(standheight,cameraheight,0.1); } float maxaccel = maxacceleleration; Vec3 velocity = entity->GetVelocity(false); float groundspeed = velocity.xz().Length(); float movespeed = this->movementspeed; //Run if shift key is pressed if (running) movespeed *= runboost; //Make sure movements are normalized so that moving forward at the same time as strafing doesn't move your character faster normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; if (entity->GetAirborne()) //Player is in the air { //if the player is in the air don't let them jump jump = 0.0; } else //Player is on the ground { //Play landing sounds if(landing) { landsound[(int)Math::Random(0,3)]->Play(); landing = false; } //Give an extra boost if jumping if (jump>0.0) { jumpsound[0]->Play(); landing = true; if (velocity.z>movementspeed*0.85) { normalizedmovement.z *= jumpboost; normalizedmovement.z = min(normalizedmovement.z,movementspeed*runboost); maxaccel = 10; } } //Play footsteps if (Time::GetCurrent()>footsteptimer && jump==0.0 && normalizedmovement.Length()>0.0) { if (groundspeed>1.0) { int soundindex = (int)Math::Rnd(0,4); footstepsound[soundindex]->Play(); if (groundspeed>movementspeed*1.2) { footsteptimer = Time::GetCurrent() + footsteprunfrequency; } else { footsteptimer = Time::GetCurrent() + footstepwalkfrequency; } } } } //Set camera rotation camera->SetRotation(playerrotation,true); //Set player input if (running) maxaccel *= 2.0; entity->SetInput(playerrotation.y,normalizedmovement.z,normalizedmovement.x,jump,crouched,maxaccel); //Set the camera position & smooth cameraposition = entity->GetPosition(); if (cameraposition.y>smoothedcamerapositiony) { smoothedcamerapositiony = Math::Curve(cameraposition.y, smoothedcamerapositiony, cameraypositionsmoothing / Time::GetSpeed()); cameraposition.y=smoothedcamerapositiony; } else { smoothedcamerapositiony = cameraposition.y; } camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); }
  4. At this point making the player run at a faster speed is a simple two step process, we need to detect if the shift key is hit and then multiply movespeed. We start off by adding in two variables to detect if the player is running and a multiplier value for increasing movespeed: running=false; runboost = 2.0; In the UpdateControls function we add in a line to check if the shift key has been hit: running = Window::GetCurrent()->KeyDown(Key::Shift); Then in the player Update, multiply movespeed by the runboost factor and increase maxaccel: //Run if shift key is pressed if (running) movespeed *= runboost; if (running) maxaccel *= 2.0; Your player class should now look like the following: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); entity->SetUserData(this); //Initialize values standheight=1.7; crouchheight=1.2; cameraheight = standheight; move = 0.0; strafe = 0.0; movementspeed = 3.0; maxacceleleration = 0.5; sensitivity=1.0; smoothedcamerapositiony = 0; cameraypositionsmoothing = 3.0; cameralooksmoothing = 2.0; runboost = 2.0; running=false; //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); } Player::~Player() { if (camera) { camera->Release(); camera = NULL; } } void Player::UpdateControls() { Window* window = Window::GetCurrent(); Context* context = Context::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); running = Window::GetCurrent()->KeyDown(Key::Shift); //Get the mouse movement float sx = context->GetWidth()/2; float sy = context->GetHeight()/2; //Get the mouse position Vec3 mouseposition = window->GetMousePosition(); //Move the mouse to the center of the screen window->SetMousePosition(sx,sy); //Get change in mouse position float dx = mouseposition.x - sx; float dy = mouseposition.y - sy; //Mouse smoothing mousespeed.x = Math::Curve(dx,mousespeed.x,cameralooksmoothing/Time::GetSpeed()); mousespeed.y = Math::Curve(dy,mousespeed.y,cameralooksmoothing/Time::GetSpeed()); //Adjust and set the camera rotation playerrotation.x += mousespeed.y*sensitivity / 10.0; playerrotation.y += mousespeed.x*sensitivity / 10.0; //Prevent inhuman looking angles playerrotation.x = Math::Clamp(playerrotation.x,-90,90); } //Update function void Player::Update() { UpdateControls(); float maxaccel = this->maxacceleleration; float movespeed = this->movementspeed; //Run if shift key is pressed if (running) movespeed *= runboost; //Make sure movements are normalized so that moving forward at the same time as strafing doesn't move your character faster normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; //Set camera rotation camera->SetRotation(playerrotation,true); //Set player input if (running) maxaccel *= 2.0; entity->SetInput(playerrotation.y,normalizedmovement.z,normalizedmovement.x,0,false,maxaccel); //Set the camera position & smooth cameraposition = entity->GetPosition(); camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); } Jumping and Crouching Since jumping and crouching both affect camera height we are going to implement both of them at the same time. Like normal we add in the needed variables into the constructor: jump = 0.0; jumpforce = 6.0; jumpboost=2.0; crouched = false; Now we turn our attention to the UpdateControl function. We check if the space key has been hit and then multiply it by a jump force. We also look to see if the 'C' button is hit, but only if the player is not in the air: jump = window->KeyDown(Key::Space) * jumpforce; if (!entity->GetAirborne()) { crouched = window->KeyDown(Key::C); } Player Update is our next focus and we begin to set the camera to its proper height. If the player is crouching we increment the camera height by the crouchheight but if the player is standing normally we increment it by standheight: if (entity->GetCrouched()) { cameraheight = Math::Inc(crouchheight,cameraheight,0.1); } else { cameraheight = Math::Inc(standheight,cameraheight,0.1); } To implement the jumping mechanic we continue our work in the Update function. If the entity is in the air we set jump to 0 to prevent the player from in-air jumps. To achieve the long jump feeling we want from our FPS we decided to boost the player's velocity while in-air: if (entity->GetAirborne()) //Player is in the air { //if the player is in the air don't let them jump jump = 0.0; } else //Player is on the ground { //Give an extra boost if jumping if (jump>0.0) { if (velocity.z>movementspeed*0.85) { normalizedmovement.z *= jumpboost; normalizedmovement.z = min(normalizedmovement.z,movementspeed*runboost); maxaccel = 10; } } } Now that we have a jump and crouch value we have to remember to change our SetInput function to the correct values: entity>SetInput(playerrotation.y,normalizedmovement.z,normalizedmovement.x,jump,crouched,maxaccel); With all the jumping and crouching going on, our camera's y position will be changing often, this means that we should now smooth the camera's y movements: //Set the camera position & smooth cameraposition = entity->GetPosition(); if (cameraposition.y>smoothedcamerapositiony) { smoothedcamerapositiony = Math::Curve(cameraposition.y, smoothedcamerapositiony, cameraypositionsmoothing / Time::GetSpeed()); cameraposition.y=smoothedcamerapositiony; } else { smoothedcamerapositiony = cameraposition.y; } camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); Our quickly growing player class will now look like so: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); entity->SetUserData(this); //Initialize values standheight=1.7; crouchheight=1.2; cameraheight = standheight; move = 0.0; strafe = 0.0; movementspeed = 3.0; maxacceleleration = 0.5; sensitivity=1.0; smoothedcamerapositiony = 0; cameraypositionsmoothing = 3.0; cameralooksmoothing = 2.0; runboost = 2.0; jump = 0.0; jumpforce = 6.0; jumpboost = 2.0; footstepwalkfrequency=400; footsteprunfrequency=320; footsteptimer=Time::GetCurrent(); running=false; crouched = false; landing = false; //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); } Player::~Player() { if (camera) { camera->Release(); camera = NULL; } } void Player::UpdateControls() { Window* window = Window::GetCurrent(); Context* context = Context::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); jump = window->KeyDown(Key::Space) * jumpforce; if (!entity->GetAirborne()) { crouched = window->KeyDown(Key::C); } running = Window::GetCurrent()->KeyDown(Key::Shift); //Get the mouse movement float sx = context->GetWidth()/2; float sy = context->GetHeight()/2; //Get the mouse position Vec3 mouseposition = window->GetMousePosition(); //Move the mouse to the center of the screen window->SetMousePosition(sx,sy); //Get change in mouse position float dx = mouseposition.x - sx; float dy = mouseposition.y - sy; //Mouse smoothing mousespeed.x = Math::Curve(dx,mousespeed.x,cameralooksmoothing/Time::GetSpeed()); mousespeed.y = Math::Curve(dy,mousespeed.y,cameralooksmoothing/Time::GetSpeed()); //Adjust and set the camera rotation playerrotation.x += mousespeed.y*sensitivity / 10.0; playerrotation.y += mousespeed.x*sensitivity / 10.0; //Prevent inhuman looking angles playerrotation.x = Math::Clamp(playerrotation.x,-90,90); } //Update function void Player::Update() { UpdateControls(); if (entity->GetCrouched()) { cameraheight = Math::Inc(crouchheight,cameraheight,0.1); } else { cameraheight = Math::Inc(standheight,cameraheight,0.1); } float maxaccel = maxacceleleration; Vec3 velocity = entity->GetVelocity(false); float groundspeed = velocity.xz().Length(); float movespeed = this->movementspeed; //Run if shift key is pressed if (running) movespeed *= runboost; //Make sure movements are normalized so that moving forward at the same time as strafing doesn't move your character faster normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; if (entity->GetAirborne()) //Player is in the air { //if the player is in the air don't let them jump jump = 0.0; } else //Player is on the ground { //Give an extra boost if jumping if (jump>0.0) { if (velocity.z>movementspeed*0.85) { normalizedmovement.z *= jumpboost; normalizedmovement.z = min(normalizedmovement.z,movementspeed*runboost); maxaccel = 10; } } } } //Set camera rotation camera->SetRotation(playerrotation,true); //Set player input if (running) maxaccel *= 2.0; entity->SetInput(playerrotation.y,normalizedmovement.z,normalizedmovement.x,jump,crouched,maxaccel); //Set the camera position & smooth cameraposition = entity->GetPosition(); if (cameraposition.y>smoothedcamerapositiony) { smoothedcamerapositiony = Math::Curve(cameraposition.y, smoothedcamerapositiony, cameraypositionsmoothing / Time::GetSpeed()); cameraposition.y=smoothedcamerapositiony; } else { smoothedcamerapositiony = cameraposition.y; } camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); }
  5. Now it's time to let the player look around using mouse movements. We start again by adding variables for mouse controls into the constructor: sensitivity=1.0; cameralooksmoothing = 2.0; cameraypositionsmoothing = 3.0; smoothedcamerapositiony = 0; In UpdateControls we will need access to the games context which is the renderable area of the window, which is essentially the area of the game window inside the windows border. We use this context to find the center of the screen and store the coordinates as sx and sy. Context* context = Context::GetCurrent(); //Get the mouse movement float sx = context->GetWidth()/2; float sy = context->GetHeight()/2; Next we save the current mouse position and then return the mouse to the center of the screen: //Get the mouse position Vec3 mouseposition = window->GetMousePosition(); //Move the mouse to the center of the screen window->SetMousePosition(sx,sy); Now that we know where the center of the screen is and the current mouse position we can figure out the difference between the two which tells us which direction to look: //Get change in mouse position float dx = mouseposition.x - sx; float dy = mouseposition.y - sy; We want to set the mouse speed by smoothing between the previous mouse speed and the distance from the center of the screen. //Mouse smoothing mousespeed.x = Math::Curve(dx,mousespeed.x,cameralooksmoothing/Time::GetSpeed()); mousespeed.y = Math::Curve(dy,mousespeed.y,cameralooksmoothing/Time::GetSpeed()); Using the mouse speed we increment the player's rotation and scale it by sensitivity, thus the higher the sensitivity the quicker the mouse movements: //Adjust and set the camera rotation playerrotation.x += mousespeed.y*sensitivity / 10.0; playerrotation.y += mousespeed.x*sensitivity / 10.0; To prevent the player from inhuman neck movements we clamp the y rotation values at -90 and 90: //Prevent inhuman looking angles playerrotation.x = Math::Clamp(playerrotation.x,-90,90); In the player Update function we rotate the camera by the playerrotation value finally allowing for our character to look around: //Set camera rotation camera->SetRotation(playerrotation,true); At this point the player character can move around with keyboard inputs and look around using the mouse: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); entity->SetUserData(this); //Initialize values sensitivity=1.0; cameralooksmoothing = 2.0; move = 0.0; strafe = 0.0; cameraypositionsmoothing = 3.0; maxacceleleration = 0.5; movementspeed = 3.0; standheight=1.7; crouchheight=1.2; cameraheight = standheight; smoothedcamerapositiony = 0; //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true); //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); } Player::~Player() { if (camera) { camera->Release(); camera = NULL; } } void Player::UpdateControls() { Window* window = Window::GetCurrent(); Context* context = Context::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); //Get the mouse movement float sx = context->GetWidth()/2; float sy = context->GetHeight()/2; //Get the mouse position Vec3 mouseposition = window->GetMousePosition(); //Move the mouse to the center of the screen window->SetMousePosition(sx,sy); //Get change in mouse position float dx = mouseposition.x - sx; float dy = mouseposition.y - sy; //Mouse smoothing mousespeed.x = Math::Curve(dx,mousespeed.x,cameralooksmoothing/Time::GetSpeed()); mousespeed.y = Math::Curve(dy,mousespeed.y,cameralooksmoothing/Time::GetSpeed()); //Adjust and set the camera rotation playerrotation.x += mousespeed.y*sensitivity / 10.0; playerrotation.y += mousespeed.x*sensitivity / 10.0; //Prevent inhuman looking angles playerrotation.x = Math::Clamp(playerrotation.x,-90,90); } //Update function void Player::Update() { UpdateControls(); float maxaccel = this->maxacceleleration; float movespeed = this->movementspeed; //Make sure movements are normalized so that moving forward at the same time as strafing doesn't move your character faster normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; //Set camera rotation camera->SetRotation(playerrotation,true); entity->SetInput(playerrotation.y,normalizedmovement.z,normalizedmovement.x,0,false,maxaccel); //Set the camera position cameraposition = entity->GetPosition(); camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); }
  6. Having a player that just stands watching the world go by isn't much of a game so let's add in some movement. We will start by adding a few new variables to the constructor: move = 0.0; strafe = 0.0; cameraypositionsmoothing = 3.0; maxacceleleration = 0.5; movementspeed = 3.0; The player UpdateControls function will be added to the start of the player Update and will manage user inputs via the keyboard and mouse: UpdateControls(); Inside UpdateControls we get the current window and detect if any of the W,A,S,D keys are currently pressed. We will track forward and backward movements in the variable called move and left and right movements in the variable called strafe. KeyDown() will return a Boolean value of true or false, but in C++ these values are essentially the integers 1 (true) and 0 (false). So if you look at the code for move pressing the 'W' key would translate to move = 1 - 0 which results in 1. Window* window = Window::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); Now that we have values for move and strafe we need to normalize them so that moving while strafing doesn't move the character faster than normal. Then after normalizing we scale the movement to the correct movespeed: float maxaccel = this->maxacceleleration; float movespeed = this->movementspeed; normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; We now call the entity SetInput with the normalizedmovement the z axis being forward and backwards movement and the x axis being left and right. We also make use of the maxaccel value set earlier: entity->SetInput(0,normalizedmovement.z,normalizedmovement.x,0,false,maxaccel); Finally we set the camera position to the same position as the entity and make sure to set it at the correct height of where a head would sit: cameraposition = entity->GetPosition(); camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); Running the resulting code will allow you to move around using the W,A,S,D keys: #include "MyGame.h" using namespace Leadwerks; Player::Player() { //Create the entity entity = Pivot::Create(); entity->SetUserData(this); //Initialize values move = 0.0; strafe = 0.0; cameraypositionsmoothing = 3.0; maxacceleleration = 0.5; movementspeed = 3.0; standheight=1.7; crouchheight=1.2; cameraheight = standheight; smoothedcamerapositiony = 0; //Create the player camera camera = Camera::Create(); camera->SetPosition(0,entity->GetPosition().y+cameraheight,0,true); //Set up player physics entity->SetPhysicsMode(Entity::CharacterPhysics); entity->SetCollisionType(Collision::Character); entity->SetMass(10.0); //Player position entity->SetPosition(0,0,0,true); } Player::~Player() { if (camera) { camera->Release(); camera = NULL; } } void Player::UpdateControls() { Window* window = Window::GetCurrent(); //Get inputs from the controller class move = window->KeyDown(Key::W) - window->KeyDown(Key::S); strafe = window->KeyDown(Key::D) - window->KeyDown(Key::A); } //Update function void Player::Update() { UpdateControls(); float maxaccel = this->maxacceleleration; float movespeed = this->movementspeed; //Make sure movements are normalized so that moving forward at the same time as strafing doesn't move your character faster normalizedmovement.z = move; normalizedmovement.x = strafe; normalizedmovement = normalizedmovement.Normalize() * movespeed; //Set player input entity->SetInput(0,normalizedmovement.z,normalizedmovement.x,0,false,maxaccel); //Set the camera position cameraposition = entity->GetPosition(); camera->SetPosition(cameraposition.x, cameraposition.y + cameraheight, cameraposition.z ); }
  7. This is the first lesson in a set of tutorials that demonstrate how to build a complete first-person shooter game, from start to finish. Download the files used in this lesson: FPSGame.zip Getting Started We're going to write this game in the programming language C++. C++ compiles to native code, which gives us the fastest possible performance on mobile devices. The object-oriented nature of C++ makes it great for games. We begin by creating a new C++ project. To open the project manager, select the File > Project Manager menu item. When it appears, press the Import button and select the zip file posted at the top of this lesson. (In older versions of Leadwerks, you will need to create a new project called "FPSGame" and then manually extract the zip file into this project's folder.) If you do not have a copy of the Leadwerks engine, a free 30-day trial can be downloaded to use with this tutorial Using Brushes to Sketch Out a Room Since this tutorial is focused on the player class we are only going to sketch out a simple rectangular room for our player to run around in. Select the box primitive brush from either the Objects tab or the shortcut box brush icon located on the left toolbar. In the X/Z viewport sketch out a box that has a width and length of 16 and height of about 0.25. This will be the floor of our level. Create four more boxes to form the walls, and finally top it off with a ceiling. Room Materials At this point our room should look like a very bland box, luckily it's time to spice things up and bring our room to life with materials. Left click on the asset browser and expand then select the Materials->Spaceship folder. Inside should be a group of textures and materials. Next drag and drop the "scificeilingb" material to the ceiling box in the 3D viewport, the changes should immediately appear. Scroll down in the asset browser to the "scififloorb" material and drag and drop it onto the floor box. Finally select the "scifiwall_basea2" material and drag it onto all four walls. UV Manipulations When you take some time to look around the room, the first thing that jumps out at you is that the material pattern is repeating itself very often thus drawing too much attention to itself. To remedy this we are going to scale the materials texture. Start off by opening the objects tab on the right toolbar. Next we are going to change to selection mode to "Select Surface" Edit->Select Surface or alternatively click the "Select Surface" shortcut icon located second from the top on the left toolbar. In the 3D viewport left click on the floor of the room, then under the Texture Mapping section of the objects tab change both scale values to 4.0. After repeating the texture scaling on all four walls and the ceiling it will be time to move onto lights. Lights Now that we have our room's materials set it is time to add some lights to the room. For this tutorial we are going to add in four point lights, these lights emit light in a radius similar to that of a traditional light bulb. On the left toolbar there is an icon showing a light bulb, this is the point light shortcut, left click on the icon. With the light bulb icon selected, left click anywhere in a 2D view port and press the Enter key and a point light will be added into the map. Create four of these to illuminate your room. Now it's time to adjust the light ranges. Left click on the map tab and select the four point lights in the Scene Browser. Next left click on the "Light" tab and set the range value to 9.0. This will increase the radius of the light. Our final step is to create the map's lightmap, so that our lights take effect on static objects. In the top toolbar navigate to Tools->Calculate Lighting (or press ctrl+L), a Calculate Lighting window should pop up. Click Render and after the lighting is calculated close both windows and we are done adding lights. The level is now complete, now it's time to create a player to run around in our room. Before moving on don't forget to save the map. Select the File > Save menu item and save the map as "start.map". The next lesson will cover setting up Visual Studio so we can code some functionality into the level.
  8. The class is quite large, but when the goal is to teach the basics of FPS mechanics I'd rather spend the majority of the tutorial inside 1 class rather than jumping between multiple classes and interfaces.    entity-SetPosition explicitly sets the players position to the origin, which happens to be the center of the room created earlier.    Not having all members initialized in the constructor is extremely poor programming. When making these tutorials I started with the complete game, then worked my way backwards. My guess is that the un-initialized members are actually initialized in one of the later tutorials. (My bad)  
  9. No real reasons in particular just trying to stick to the basics. 
  10. It's very redundant I know, but it is an attempt to have the tutorials be inclusive to all levels of programming skill.    There should be a tutorial released for the next few Mondays!
  11. Leadwerks 3 Tutorial: Building a First-Person Shooter

    Leadwerks 3 doesn't compile CSG geometry into a BSP tree, rather the geometry gets collapsed to model geometry at load time and uses the scene octree for culling. So it scales gracefully with large environments.   The easiest way to test this claim is go in and quickly sketch out a huge level and click "Run". This sounded fun so so I went in and made a bunch of large boxes to represent a city and ran around my beast of a level. (This was purely just to show that large environments run at 60 fps, so I didn't add any materials that is why the scene only has 1 texture.)       
  12. [color=rgb(0,0,0)][font=Verdana][/font][/color] [color=rgb(0,0,0)][font=Verdana]It may have taken a few weeks but the "Building a First-Person Shooter" tutorial series is finally up and running. In this multiple part series we will build a FPS from the ground up using Leadwerks 3 and Visual Studios 2010 C++. Part 1.0 is all about creating a room and covers:[/font][/color] Using brushes to sketch out a room Materials UV Manipulations Lights & Lightmaps [color=rgb(0,0,0)][font=Verdana]If you're interested check out the tutorial: http://www.leadwerks.com/werkspace/page/tutorials/_/building-a-first-person-shooter-part-10-creating-a-room-r22 Or if you just want to fool around with a trial version, you can download a 30 day demo here: http://www.leadwerks.com[/font][/color]
  13. Native Code Tutorials

    [color=rgb(0,0,0)][font=arial]Between the release of LE3 and the GDC, life at Leadwerks has been busy to say the least. Now that things have settled down (a little) it is time for something that I have been looking forward to for a while: Tutorials![/font][/color] [color=rgb(0,0,0)][font=arial]At GDC we were constantly talking about the power and flexibility that direct native code programming allows and how users are not locked into only component based programming. But then you look at the example game Darkness Awaits and see only component based programming and just Lua scripts....[/font][/color] [color=rgb(0,0,0)][font=arial]It's time to right these wrongs.[/font][/color] [color=rgb(0,0,0)][font=arial]A comprehensive series of tutorials are underway that will teach how to use the many tools of the engine as well as demonstrate the capabilities of direct native code programming.[/font][/color] [color=rgb(0,0,0)][font=arial]This series is currently in the design phase and I am working with universities to make sure that the tutorials not only teach the engine but also cover key game development topics.[/font][/color] [color=rgb(0,0,0)][font=arial]Here are a few high level design points for the tutorials:[/font][/color] Teach the engine and its tools Using native code with Leadwerks (aka how to use C++ with visual studios or Xcode along with the Leadwerks 3 engine) First Person Shooter [color=rgb(0,0,0)][font=arial]As always I'd love to hear suggestions and ideas! I'll keep updates coming as tutorial development ramps up![/font][/color]