Game Development Community

Making a Player Fly

by Mark Pumphrey · in Torque Game Engine · 07/15/2005 (3:31 am) · 125 replies

Firstly I would like to state that "Yeah, I have seen the posts about flying but....". They don't work and/or full of bugs. I would like to also state a general request that if you make a post regarding code to edit the original post instead continually trying to 'add' corrections later on making it harder than hell to read. Also, EVERYONE has a copy and paste function of some kind, use it. Saying that "here is some code than will change your life, insert it anywhere" means nothing and helps even less. Ok, enough of that. Has anyone actually made a player fly just like in Tribes?

About the author

Recent Threads

#101
12/17/2007 (1:33 pm)
Now the last problem... the GUI.

This is what I have in the playGui.gui:

//--- OBJECT WRITE BEGIN ---

new GameTSCtrl(PlayGui) {

   canSaveDynamicFields = "1";

   Profile = "GuiContentProfile";

   HorizSizing = "right";

   VertSizing = "bottom";

   position = "0 0";

   Extent = "1024 768";

   MinExtent = "8 8";

   canSave = "1";

   Visible = "1";

   hovertime = "1000";

   applyFilterToChildren = "1";

   cameraZRot = "0";

   forceFOV = "0";

      noCursor = "0";

      helpTag = "0";



   new GuiButtonCtrl() {
      canSaveDynamicFields = "0";
      Profile = "GuiButtonProfile";
      HorizSizing = "right";
      VertSizing = "bottom";
      position = "150 425";
      Extent = "140 30";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      command = "toggleFlying();";
      tooltipprofile = "GuiDefaultProfile";
      ToolTip = "Press this button to toggle flying";
      hovertime = "1000";
      text = "Toggle Flying";
      groupNum = "-1";
      buttonType = "PushButton";
   };
};
//--- OBJECT WRITE END ---

function toggleFlying()
{
    serverCmdtoggleFly();
}

I don't see anything wrong with it though.
#102
12/17/2007 (1:35 pm)
I just tried putting the function in the .cs file.
#103
12/17/2007 (2:43 pm)
Ah cool - thanks for the typo correction. I'll update my code.

Tell me all the steps you've taken. Also paste links to resources you've used. I'll copy what you did and see if I can get it working for you.
#104
12/17/2007 (5:18 pm)
Ok it's pretty long, most of it is going to be quoted:

Quote:So I put it all together. I haven't tested this much, but I do know that flight tends to be a bit jittery. I'm not sure if it was always this way, if not I'll check it out. I didn't go to far to understand everything so I when you go out of flying mode it may stop doing something that the flying needs. Here goes.

find


// Acceleration due to gravity
VectorF acc(0,0,mGravity * mGravityMod * TickSec);



change it to


VectorF acc;//we have to create acc out here and then declare it inside the statements or else the compiler
//won't think that it exists
if(mFlying){
acc = VectorF(0.0f, 0.0f, 0.0f);
}else{
acc = VectorF(0.0f, 0.0f, mGravity * mGravityMod * TickSec);
}



Next scroll down just a little and find


// Acceleration on run surface
if (runSurface) {
mContactTimer = 0;



change it to


// Acceleration on run surface
if (runSurface||mFlying) {
if(mFlying)
mContactTimer = 30;
else
mContactTimer = 0;



Then scroll down a bit more and find


if (pvl) {
VectorF nn;
mCross(pv,VectorF(0,0,1),&nn);
nn *= 1 / pvl;
VectorF cv = contactNormal;
cv -= nn * mDot(nn,cv);
pv -= cv * mDot(pv,cv);
pvl = pv.len();
}



change it to


if (pvl&&!mFlying) {
VectorF nn;
mCross(pv,VectorF(0.0f, 0.0f, 1.0f),&nn);
nn *= 1.0f / pvl;
VectorF cv = contactNormal;
cv -= nn * mDot(nn,cv);
pv -= cv * mDot(pv,cv);
pvl = pv.len();
}


Edited on Dec 06, 2007 19:36
Next post...
#105
12/17/2007 (5:19 pm)
Quote:I've been following Matt's initial progression for a bit here. All I have had to do thus far is add an if statement or modify and existing one. It gets a bit tougher, but I tried to make it as nice as possible. For those of you who looked at the code you may have wondered where mFlying came from, I will provide the code that defines that later. Anyways time for the big code block.

Scroll all the way back up to the top of Player::updateMove() and find


// Update current orientation
if (mDamageState == Enabled) {
F32 prevZRot = mRot.z;
delta.headVec = mHead;

F32 p = move->pitch;
if (p > M_PI) p -= M_2PI;
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
mDataBlock->maxLookAngle);

F32 y = move->yaw;
if (y > M_PI) y -= M_2PI;

GameConnection* con = getControllingClient();
if (move->freeLook && ((isMounted() && getMountNode() == 0) ||
(con && !con->isFirstPerson())))
{
mHead.z = mClampF(mHead.z + y,
-mDataBlock->maxFreelookAngle,
mDataBlock->maxFreelookAngle);
}
else
{
mRot.z += y;
// Rotate the head back to the front, center horizontal
// as well if we're controlling another object.
mHead.z *= 0.5;
if (mControlObject)
mHead.x *= 0.5;
}

// constrain the range of mRot.z
while (mRot.z < 0)
mRot.z += M_2PI;
while (mRot.z > M_2PI)
mRot.z -= M_2PI;

delta.rot = mRot;
delta.rotVec.x = delta.rotVec.y = 0;
delta.rotVec.z = prevZRot - mRot.z;
if (delta.rotVec.z > M_PI)
delta.rotVec.z -= M_2PI;
else if (delta.rotVec.z < -M_PI)
delta.rotVec.z += M_2PI;

delta.head = mHead;
delta.headVec -= mHead;
}
MatrixF zRot;
zRot.set(EulerF(0, 0, mRot.z));

// Desired move direction & speed
VectorF moveVec;
F32 moveSpeed;
if (mState == MoveState && mDamageState == Enabled)
{
zRot.getColumn(0,&moveVec);
moveVec *= move->x;
VectorF tv;
zRot.getColumn(1,&tv);
moveVec += tv * move->y;

// Clamp water movement



- continued in next post -
#106
12/17/2007 (5:21 pm)
Quote:- continue -

Now replace that code block with


// Update current orientation
if (mDamageState == Enabled) {
Point3F prevRot = mRot;
delta.headVec = mHead;

F32 p = move->pitch;
if (p > M_PI_F)
p -= M_2PI_F;
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
mDataBlock->maxLookAngle);

F32 y = move->yaw;
if (y > M_PI_F)
y -= M_2PI_F;

GameConnection* con = getControllingClient();
if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
{
mHead.z = mClampF(mHead.z + y,
-mDataBlock->maxFreelookAngle,
mDataBlock->maxFreelookAngle);
}
else
{
mRot.x += p;
mRot.z += y;
// Rotate the head back to the front, center horizontal
// as well if we're controlling another object.
mHead.z *= 0.5f;
if (mControlObject)
mHead.x *= 0.5f;
}

// constrain the range of mRot.z
while (mRot.z < 0.0f)
mRot.z += M_2PI_F;
while (mRot.z > M_2PI_F)
mRot.z -= M_2PI_F;

// constrain the range of mRot.x
if(mRot.x > 1.5706)
mRot.x = 1.5706;
else if(mRot.x < -1.5706)
mRot.x = -1.5706

delta.rot = mRot;
delta.rotVec = prevRot - mRot;
if (delta.rotVec.z > M_PI_F)
delta.rotVec.z -= M_2PI_F;
else if (delta.rotVec.z < -M_PI_F)
delta.rotVec.z += M_2PI_F;

delta.head = mHead;
delta.headVec -= mHead;
}
MatrixF zRot;
zRot.set(EulerF(0.0f, 0.0f, mRot.z));
if(mFlying){
MatrixF xRot;
xRot.set(EulerF(mRot.x, 0, 0));
zRot.mul(zRot, xRot); //well its not zrot anymore but the code is a bit more efficient this way
}


// Desired move direction & speed
VectorF moveVec;
F32 moveSpeed;
if (mState == MoveState && mDamageState == Enabled)
{
zRot.getColumn(0,&moveVec); //note that if you are flying its not zRot it is xzRot
moveVec *= move->x;
VectorF tv;
zRot.getColumn(1,&tv);
moveVec += tv * move->y;
if(mFlying){
zRot.getColumn(2,&tv);
moveVec += tv * move->z;
}

// Clamp water movement



well if I made any mistakes that is probably where they are. It does work though and I was surprised with how clean the code worked out to be.
#107
12/17/2007 (5:21 pm)
Quote:(I'm just going to copy what Matt said here because it works)
Now if you are like me and think that the falling animation looks better for flying around then jump over to Player::pickActionAnimation() and scroll down till you find:


if (mContactTimer >= sContactTickTime) {
// Nothing under our feet
action = PlayerData::RootAnim;
}



change it to


if (mContactTimer >= sContactTickTime) {
// Nothing under our feet
if(mFlying)
action = PlayerData::FallAnim;
else
action = PlayerData::RootAnim;
}



Well that's where Matt left off but I get to keep going, hopefully I remember it all.

So lets go back up near the top to 'Player::Player()' under


mState = MoveState;
mFalling = false;



put


mFlying = false;



find


ConsoleMethod( Player, getState, const char*, 2, 2, "Return the current state name.")
{
return object->getStateName();
}



above it put


void Player::setFlying(bool Flying){
mFlying = Flying;
}
bool Player::getFlying(){
return mFlying;
}
//----------------------------------------------------------------------------
ConsoleMethod( Player, toggleFlying, bool, 2, 3, "(bool Flying)")
{
bool Flying = (argc > 2)? dAtob(argv[2]): !object->getFlying();
object->setFlying(Flying);
return 1;
}



I'm not sure if that's the "right" place but it works as well as any.

Now we are moving on over to player.h

under


ActionState mState; ///< What is the player doing? @see ActionState
bool mFalling; ///< Falling in mid-air?



put


bool mFlying; ///< Should we take off towards the sun?



a little bit under that find


static void consoleInit();



under it add


void setFlying(bool Flying);
bool getFlying();



Well I think that's all done. I didn't add Tom Spilman's change in this list but you should probably scroll up and add it, and make sure to add the onread packet stuff as well.
#108
12/17/2007 (5:23 pm)
Quote:in void Player::readPacketData(GameConnection *connection, BitStream *stream)


if (stream->readFlag()) {
// Only written if we are not mounted
stream->read(&pos.x);
stream->read(&pos.y);
stream->read(&pos.z);
stream->read(&mVelocity.x);
stream->read(&mVelocity.y);
stream->read(&mVelocity.z);
stream->setCompressionPoint(pos);
delta.pos = pos;
mJumpSurfaceLastContact = stream->readInt(4);
}
else
pos = delta.pos;
stream->read(&mHead.x);
stream->read(&mHead.z);
stream->read(&rot.x);
stream->read(&rot.y);
stream->read(&rot.z);
stream->read(&mFlying);



then in void Player::writePacketData(GameConnection *connection, BitStream *stream)...


if (stream->writeFlag(!isMounted())) {
// Will get position from mount
stream->setCompressionPoint(pos);
stream->write(pos.x);
stream->write(pos.y);
stream->write(pos.z);
stream->write(mVelocity.x);
stream->write(mVelocity.y);
stream->write(mVelocity.z);
stream->writeInt(mJumpSurfaceLastContact > 15 ? 15 : mJumpSurfaceLastContact, 4);
}
stream->write(mHead.x);
stream->write(mHead.z);
stream->write(mRot.x);
stream->write(mRot.y);
stream->write(mRot.z);
stream->write(mFlying);



for the pack/unpackUpdate change to this...

in void Player::unpackUpdate(NetConnection *con, BitStream *stream)...


if(stream->readFlag())
{
stream->readNormalVector(&mVelocity, 10);
mVelocity *= stream->readInt(13) / 32.0f;
}
else
{
mVelocity.set(0.0f, 0.0f, 0.0f);
}

//rot.y = rot.x = 0.0f;
rot.x = stream->readFloat(7) * M_2PI_F;
rot.y = stream->readFloat(7) * M_2PI_F;
rot.z = stream->readFloat(7) * M_2PI_F;

mHead.x = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;
mHead.z = stream->readSignedFloat(6) * mDataBlock->maxLookAngle;

mFlying = stream->readFlag();

delta.move.unpack(stream);



then in U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)...


if(stream->writeFlag(len > 0.02f))
{
Point3F outVel = mVelocity;
outVel *= 1.0f/len;
stream->writeNormalVector(outVel, 10);
len *= 32.0f; // 5 bits of fraction
if(len > 8191)
len = 8191;
stream->writeInt((S32)len, 13);
}
stream->writeFloat(mRot.x / M_2PI_F, 7);
stream->writeFloat(mRot.y / M_2PI_F, 7);
stream->writeFloat(mRot.z / M_2PI_F, 7);

stream->writeSignedFloat(mHead.x / mDataBlock->maxLookAngle, 6);
stream->writeSignedFloat(mHead.z / mDataBlock->maxLookAngle, 6);

stream->writeFlag(mFlying);

delta.move.pack(stream);



NOTE! You must write and read data in the same order other wise the data gets swapped around

Also, I haven't tested the code above but in theory it works - the concept is to pack the data and write the data in the correct order on the server and then unpack and read the data on the client

It would cool if someone could check this for me.

@Tyler - if you still can't get it to work I will run the update myself as I have a vampire that I want to fly, and will show you how I did it.

EDIT : changed line

stream->readFlag(mFlying);


to...

stream->writeFlag(mFlying);


Edited on Dec 17, 2007 17:44
#109
12/17/2007 (5:28 pm)
Find
stream->write(mHead.x);
   stream->write(mHead.z);
   stream->write(mRot.x);

and add:

stream ->write(mRot.y);
stream ->write(mRot.z);
stream ->write(mFlying);

Do the same in the read section.
#110
12/17/2007 (5:32 pm)
Now, compile it and put this into your server folder:

function serverCmdtoggleFlying(%client)
{
    %client.player.setTransform(LocalClientConnection.player.getTransform());
    if (isObject(%client.player))
       %client.player.toggleFlying();
}

and put this in config.cs (default.bind.cs for tutorial.base)

config.cs:
moveMap.bindCmd(keyboard, "t", "commandToServer(\'toggleFly\');", "");
default.bind.cs:
GlobalActionMap.bindCmd(keyboard, "t", "commandToServer(\'toggleFly\');", "");

Press t to start flying!
#111
12/17/2007 (5:34 pm)
Hey I think I'm gonna make this a resource since no one has bothered.

EDIT: Is it against the law for me to put this as a resource? I didn't really make the whole thing so that's the problem.
#112
12/17/2007 (10:56 pm)
Please do make this a resource. I'm sure nobody would mind you making this into a resource. Just acknowledge others in the resource and link this thread. It should be fine.
#113
12/18/2007 (2:27 am)
@Tyler - so did you get it to work in the end?
#114
12/18/2007 (9:53 am)
Everything is working except the GUI button.
#115
12/18/2007 (5:03 pm)
How do I get a button to call a server cmd?
#116
12/18/2007 (5:19 pm)
I just typed serverCmdtoggleFlying in the console. It did the setTransform thing, but not the important toggleFlying.
#117
12/19/2007 (5:46 am)
I think the actual command might just be toggleFlying without serverCmd at the beginning. It might also be that the actual function needs to be in a server script and is defined as just function toggleFlying() and you call it from the client by using serverCmdtoggleFlying - the serverCmd eliminates name space clashes with identical functions on the client - though it's all just guessing at this point.
#118
12/19/2007 (6:49 pm)
Not toggleFlying. Although it seems that if I do serverCmdtoggleFlying(); for the button command, it does the setTransform part, but it won't toggle the flying.
#119
12/19/2007 (6:54 pm)
I just go it to work! You need to make a function that calls that function like:

function serverCmdcallFlying(%client)
{
    %client.player.setTransform(LocalClientConnection.player.getTransform());
    serverCmdactivateFlying();
}

funtion serverCmdactivateFlying(%client)
{
    if (isObject(%client.player))
       %client.player.toggleFlying();
}

Then have the GUI button's command be:

command = serverCmdcallFlying();

It works perfectly!
#120
12/20/2007 (1:14 am)
Nice work Tyler! - I hope you compile this into a resource. I'm going to give it a go soon.