Adding new positions and moves; ie swim, crouch, crawl, prone
by Erik Madison · 06/23/2003 (1:33 pm) · 166 comments
This is loosely based on the only 2 forum threads I found dealing with swimming. Neither of them worked well enough for my needs, so I began this as a dirty rewrite. I also solved quite a few of my problems by reading and rereading Daniel Nielsens tutorials and resources.
I'm not real adept at showing anyone how or why I do something, so I apologize if this resource isn't as clear as it could be. I will try and work out any problems you may have.
Only 2 files are changed for the meat of the system, along with 2 scripts for a keybinding and callback access.
FILE: Which file to edit
FILE: Player.cc
FILE: player.h
FILE: default.bind.cs
FILE: commands.cs
I'm not real adept at showing anyone how or why I do something, so I apologize if this resource isn't as clear as it could be. I will try and work out any problems you may have.
Only 2 files are changed for the meat of the system, along with 2 scripts for a keybinding and callback access.
FILE: Which file to edit
[i]Which function to work in[/i] surrounding original code [b]New code[/b] surrounding original code
FILE: Player.cc
[i]PlayerData::ActionAnimationDef PlayerData::ActionAnimationList[NumTableActionAnims] =
{
[/i]
{ "back", { 0,-1,0 } }, // BackBackwardAnim
{ "side", { -1,0,0 } }, // SideLeftAnim,
// These are set explicitly based on player actions
[b]
// 6 new animations for the extreme basics
{ "swimroot" },
{ "swim" },
{ "crouchroot" },
{ "crouchforward" },
{ "crawlroot" },
{ "crawlforward" },[/b]
{ "fall" }, // FallAnim
{ "jump" }, // JumpAnim
[i]PlayerData::PlayerData()
{
[/i]
minJumpSpeed = 500;
maxJumpSpeed = 2 * minJumpSpeed;
[b]
swimForce = 75;
swimEnergyDrain = 0;
minSwimEnergy = 0;
[/b]
horizMaxSpeed = 80;
horizResistSpeed = 38;
[i]
bool PlayerData::preload(bool server, char errorBuffer[256])
{[/i]
if (minJumpEnergy < jumpEnergyDrain)
minJumpEnergy = jumpEnergyDrain;
[b]
if (minSwimEnergy < swimEnergyDrain)
minSwimEnergy = swimEnergyDrain;
[/b]
// Validate some of the data
if (recoverDelay > (1 << RecoverDelayBits) - 1) {
[i]
void PlayerData::initPersistFields()
{
[/i]
addField("jumpSurfaceAngle", TypeF32, Offset(jumpSurfaceAngle, PlayerData));
addField("jumpDelay", TypeS32, Offset(jumpDelay, PlayerData));
[b]
addField("swimForce", TypeF32, Offset(swimForce, PlayerData));
addField("swimEnergyDrain", TypeF32, Offset(swimEnergyDrain, PlayerData));
addField("minSwimEnergy", TypeF32, Offset(minSwimEnergy, PlayerData));
[/b]
addField("boundingBox", TypePoint3F, Offset(boxSize, PlayerData));
addField("boxHeadPercentage", TypeF32, Offset(boxHeadPercentage, PlayerData));
[i]
void PlayerData::packData(BitStream* stream)
{[/i]
stream->write(jumpSurfaceAngle);
stream->writeInt(jumpDelay,JumpDelayBits);
[b]
stream->write(swimForce);
stream->write(swimEnergyDrain);
stream->write(minSwimEnergy);
[/b]
stream->write(horizMaxSpeed);
stream->write(horizResistSpeed);
[i]
void PlayerData::unpackData(BitStream* stream)
{[/i]
stream->read(&jumpSurfaceAngle);
jumpDelay = stream->readInt(JumpDelayBits);
[b]
stream->read(&swimForce);
stream->read(&swimEnergyDrain);
stream->read(&minSwimEnergy);
[/b]
stream->read(&horizMaxSpeed);
stream->read(&horizResistSpeed);
[i]
Player::Player()
{[/i]
mFalling = false;
[b] mSwimming = false;
mPlayerPosition = 1; // 1=stand, 2=crouch, 3=prone
[/b] mContactTimer = 0;
[i]
bool Player::onAdd()
{[/i]
mState = NullState;
setState(state);
[b]
setPlayerPosition(1); // 1=stand, 2=crouch, 3=prone
[/b]
if (serverAnim.action != PlayerData::NullAnimation) {
[i]
void Player::updateMove(const Move* move)
{[/i]
}
else
mJumpSurfaceLastContact++;
[b]
// Acceleration from Swimming
// I don't understand physics, nor 3d math. Forgive me if this
// looks horrid. It seems to work fairly well though, so I'll
// be using it for now.
if (!isMounted() && canSwim())
{
mSwimming = true; // Not actually used, but perhaps good to have
VectorF pv;
mEnergy -= mDataBlock->swimEnergyDrain;
Point3F headRotation = getHeadRotation();
pv = moveVec;
pv *= moveSpeed;
F32 impulse = (5000 / mMass) * TickSec; // times player.strength as well ?
VectorF horiz = moveVec;
horiz.normalize();
horiz *= 0.9;
acc += horiz * impulse;
VectorF swimAcc = pv - (mVelocity + acc);
F32 swimSpeed = swimAcc.len();
F32 maxSwimAcc = (mDataBlock->swimForce / mMass) * TickSec;
if (swimSpeed > maxSwimAcc) {
swimAcc *= maxSwimAcc / swimSpeed;
// Con::errorf("Swimming too fast!");
}
if (swimSpeed > moveSpeed)
swimSpeed = moveSpeed;
acc += swimAcc;
acc.z = (-headRotation.x * (mDataBlock->swimForce / mMass) * 0.25f);
} else {
mSwimming = false;
}
[/b]
// Add in force from physical zones...
acc += (mAppliedForce / mMass) * TickSec;
[i] Further down[/i]
if (!isGhost()) {
// Vehicle Dismount
if(move->trigger[2] && isMounted())
Con::executef(mDataBlock,2,"doDismount",scriptThis());
if(!inLiquid && mWaterCoverage != 0.0f) {
Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType));
[b] setPlayerPosition(3);[/b]
inLiquid = true;
}
else if(inLiquid && mWaterCoverage <= 0.0f) {
Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType));
[b] setPlayerPosition(1);[/b]
inLiquid = false;
}
} else {
if(!inLiquid && mWaterCoverage >= 1.0f) {
[b] setPlayerPosition(3);[/b]
inLiquid = true;
}
else if(inLiquid && mWaterCoverage < 0.5f) {
if(getVelocity().len() >= mDataBlock->exitSplashSoundVel && !isMounted())
alxPlay(mDataBlock->sound[PlayerData::ExitWater], &getTransform());
[b] setPlayerPosition(1);[/b]
inLiquid = false;
}
}
[i]
bool Player::canJump()
{[/i]
// Added position check, you cant jump while crouch, crawl
return mState == MoveState && mDamageState == Enabled && !isMounted() && !mJumpDelay && mEnergy >= mDataBlock->minJumpEnergy && mJumpSurfaceLastContact < JumpSkipContactsMax[b] && getPlayerPosition() == 1[/b];
}
[b]
bool Player::canSwim()
{
return mState == MoveState && mDamageState == Enabled && !isMounted() && mEnergy >= mDataBlock->minSwimEnergy && mWaterCoverage >= 0.8f;
}
[/b]
[b]
void Player::setPlayerPosition(S32 position)
{
F32 len_x, len_y, len_z;
if (position != mPlayerPosition) {
if (isProperlyAdded()) {
// Special case, this one bumps us up to the next position
if (position == 0) {
position = mPlayerPosition + 1;
if (position > 3)
position = 1;
}
switch (position) {
case 1: // Stand, walk
{
len_x = 1;
len_y = 1;
len_z = 2.3;
break;
}
case 2: // Crouch, sit
{
len_x = 1;
len_y = 1;
len_z = 1.1;
break;
}
case 3: // Crawl, prone
{
len_x = 1;
len_y = 2.3;
len_z = 0.7;
break;
}
}
mObjBox.max.x = len_x * 0.5;
mObjBox.max.y = len_y * 0.5;
mObjBox.max.z = len_z;
mObjBox.min.x = -mObjBox.max.x;
mObjBox.min.y = -mObjBox.max.y;
mObjBox.min.z = 0;
}
mPlayerPosition = position;
}
if (isServerObject())
setMaskBits(MoveMask);
}
[/b]
// This function needs a lot more work. It works well enough for
// basic testing/playing around purposes. Also, without quite a
// few more animations available, a more robust job here would be
// for naught.
[i]
void Player::pickActionAnimation()
{[/i]
bool forward = true;
U32 action = PlayerData::RootAnim;
[b]
if (getPlayerPosition() == 1) {
////////// enclosing original code in my own stuff
///////// End of original code
}
// Swim code, Position code
else if (getPlayerPosition() == 2) {
action = (mVelocity.len() < 0.5) ? PlayerData::CrouchRootAnim : PlayerData::CrouchForwardAnim;
}
else if (getPlayerPosition() == 3) {
action = (mVelocity.len() < 0.5) ? PlayerData::CrawlRootAnim : PlayerData::CrawlForwardAnim;
if (mWaterCoverage != 0.0f) {
action = (mVelocity.len() < 0.5) ? PlayerData::SwimRootAnim : PlayerData::SwimAnim;
}
}
[/b]
setActionThread(action,forward,false,false);
}
[i]
bool Player::updatePos(const F32 travelTime)
{[/i]
setPosition(start,mRot);
setMaskBits(MoveMask);
updateContainer();
[b]
// this prevents the rocket launch effect when a player in water surfaces
if (mBuoyancy != 0 && mWaterCoverage <= 0.5f)
{
mVelocity.z += mBuoyancy * mGravity * mGravityMod * TickSec;
}
[/b]
if (!isGhost()) {
[i]
U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{[/i]
[b]
if (stream->writeFlag(mask & MoveMask))
stream->writeInt(getPlayerPosition(),5);
[/b]
if (stream->writeFlag((mask & ImpactMask) && !(mask & InitialUpdateMask)))
stream->writeInt(mImpactSound, PlayerData::ImpactBits);
[i] Further down......[/i]
stream->writeFlag(mFalling);
[b]
stream->writeFlag(mSwimming);
[/b]
stream->writeInt(mState,NumStateBits);
[i]
void Player::unpackUpdate(NetConnection *con, BitStream *stream)
{[/i]
[b]
if (stream->readFlag()) {
S32 pos = stream->readInt(5);
setPlayerPosition(pos);
}[/b]
if (stream->readFlag())
mImpactSound = stream->readInt(PlayerData::ImpactBits);
[i] Further down......[/i]
mFalling = stream->readFlag();
[b]
mSwimming = stream->readFlag(); // fafhrd, swim code
[/b]
ActionState actionState = (ActionState)stream->readInt(NumStateBits);
[b][i]NEW: Updated 2/1/04 to coincide with HEAD's change to consolemethod style commands.
Change the below code (or ignore if this is your first time installing this) to what follows [/b][/i]
[i]
static S32 cgetPlayerPosition(SimObject *ptr, S32, const char **)
{
Player* obj = static_cast<Player*>(ptr);
return obj->getPlayerPosition();
}
static void csetPlayerPosition(SimObject *ptr, S32, const char **argv)
{
Player* obj = static_cast<Player*>(ptr);
obj->setPlayerPosition(dAtof(argv[2]));
}
[/i]
void Player::consoleInit()
Con::addCommand("Player", "getPlayerPosition", cgetPlayerPosition, "obj.getPlayerPosition()", 2, 2);
Con::addCommand("Player", "setPlayerPosition", csetPlayerPosition, "obj.setPlayerPosition(pos)", 3, 3);
[b][i] CHANGE to this code[/b][/i]
[b]
ConsoleMethod( Player, getPlayerPosition, S32, 2, 2, "(1=stand, 2=crouch, 3=crawl)")
{
return object->getPlayerPosition();
}
ConsoleMethod( Player, setPlayerPosition, bool, 3, 3, "(1=stand, 2=crouch, 3=crawl)")
{
object->setPlayerPosition(dAtof(argv[2]));
}
[/b]FILE: player.h
[i]
struct PlayerData: public ShapeBaseData {
[/i]
F32 jumpSurfaceAngle; // Angle from vertical in degrees
S32 jumpDelay; // Delay time in ticks
[b]
F32 swimForce;
F32 swimEnergyDrain;
F32 minSwimEnergy;
[/b]
F32 boxHeadPercentage;
F32 boxTorsoPercentage;
[i] Further down......[/i]
BackBackwardAnim,
SideLeftAnim,
// These are set explicitly based on player actions
[b] SwimRootAnim,
SwimAnim,
CrouchRootAnim,
CrouchForwardAnim,
CrawlRootAnim,
CrawlForwardAnim,[/b]
FallAnim,
JumpAnim,
[i]
class Player: public ShapeBase
{[/i]
ActionState mState;
bool mFalling; // Falling in mid-air
[b] bool mSwimming;
S32 mPlayerPosition; // 1=stand, 2=crouch, 3=prone [/b]
S32 mJumpDelay; // Delay till next jump
[i] Further down......[/i]
F32 getJumpForceModifier() { return mJumpForceModifier; }
[b]
void setPlayerPosition(S32 pos);
S32 getPlayerPosition() { return mPlayerPosition; }
[/b]
protected:
void setState(ActionState state, U32 ticks=0);
void updateState();
[i] Further down......[/i]
bool canJump();
[b] bool canSwim();[/b]
bool haveContact() {return !mContactTimer;}FILE: default.bind.cs
[b]
moveMap.bind( keyboard, c, changePlayerPosition );
function changePlayerPosition(%val)
{
if (%val)
// calling it with a 0 will bump us up to the next position
CommandtoServer('SetPlayerPos', 0);
}
[/b]FILE: commands.cs
[b]
function serverCmdSetPlayerPos(%client,%pos)
{
if (isObject(%client.player))
%client.player.setPlayerPosition(%pos);
}
function serverCmdGetPlayerPos(%client)
{
if (isObject(%client.player))
return %client.player.getPosition();
else
return 0;
}
[/b]About the author
Recent Blogs
• Dynamic GUI• Mob Look
• Faster Polysoup
• DreamGames and Titas
• Working with pickActionAnimation()
#2
06/23/2003 (4:54 pm)
Correct Josh, modifying the datablock will affect ALL players that use the same datablock.
#3
Anyway, what I was thinking to do for rop was simply modify the bounds box in the object itself, not the definition in the datablock. So you would add a couple of get/set like methods to change the object's specific bounds box without affecting other players.
06/23/2003 (5:38 pm)
Right josh, seems you learnt well my padawan :PAnyway, what I was thinking to do for rop was simply modify the bounds box in the object itself, not the definition in the datablock. So you would add a couple of get/set like methods to change the object's specific bounds box without affecting other players.
#4
Above code has been changed and tested to no longer use the datablock.
06/23/2003 (7:25 pm)
Sorry about that :) I didn't test with anyone else, and didn't have any mobs in my test level to notice.Above code has been changed and tested to no longer use the datablock.
#5
Cheers
06/24/2003 (11:03 am)
I haven't tried this yet, but it's an awesome resource. It finally adds basic FPS features to Torque that were sorely lacking. Will this ever be considered for inclusion in CVS?Cheers
#6
Anyway, thanks for the nice resource. :)
06/25/2003 (8:42 am)
Does anyone know why ' and " characters are changed to ´ or whatever in the code sections? It's REALLY annoying, and I've seen it in more than one resource.Anyway, thanks for the nice resource. :)
#7
ryan
07/02/2003 (6:14 am)
Those are the html versions of the characters, like the less than symbol is < it is a pain to copy-paste code, just paste in notepad or the like, and that strips them out (at least thats how i get around it)ryan
#8
07/12/2003 (5:00 pm)
I tried to Add this to my code engine and ran into what im sure is a very simple problem. the .CS files that i had are unreadable. what am i supposed to edit those in?
#9
07/14/2003 (8:39 am)
Is there a trick to getting the "forward" and "swim" animations to work? The positions change fine, but as soon as I move forward the "run" animation always plays. The "swimroot" kicks in when my player touches the ground under water and "swim" doesn't seem to kick in at all. If no one else has these problems I must have done something wrong.
#10
erPosition' : static functions with block scope are illegal
C:\Engine\torque\engine\game\player.cc(3679) : error C2601: 'cgetPlayerPosition' : local function definitions are illegal
C:\Engine\torque\engine\game\player.cc(3684) : error C2267: 'csetPlayerPosition' : static functions with block scope are illegal
C:\Engine\torque\engine\game\player.cc(3684) : error C2601: 'csetPlayerPosition' : local function definitions are illegal
C:\Engine\torque\engine\game\player.cc(3856) : error C2065: 'cgetPlayerPosition' : undeclared identifier
C:\Engine\torque\engine\game\player.cc(3857) : error C2065: 'csetPlayerPosition' : undeclared identifier
whichs points at this code:
static S32 cgetPlayerPosition(SimObject *ptr, S32, const char **){ Player* obj = static_cast(ptr); return obj->getPlayerPosition();}static void csetPlayerPosition(SimObject *ptr, S32, const char **argv){ Player* obj = static_cast(ptr); obj->setPlayerPosition(dAtof(argv[2]));}
And then the console stuff.
using VC++6 and HEAD Release...
no where do I have the line F32 getJumpForceModifier() { return mJumpForceModifier; } in player.h or anywhere...
Thanks for any help or ideas as to why? :-)
09/03/2003 (11:39 pm)
I'm gettings these errors:erPosition' : static functions with block scope are illegal
C:\Engine\torque\engine\game\player.cc(3679) : error C2601: 'cgetPlayerPosition' : local function definitions are illegal
C:\Engine\torque\engine\game\player.cc(3684) : error C2267: 'csetPlayerPosition' : static functions with block scope are illegal
C:\Engine\torque\engine\game\player.cc(3684) : error C2601: 'csetPlayerPosition' : local function definitions are illegal
C:\Engine\torque\engine\game\player.cc(3856) : error C2065: 'cgetPlayerPosition' : undeclared identifier
C:\Engine\torque\engine\game\player.cc(3857) : error C2065: 'csetPlayerPosition' : undeclared identifier
whichs points at this code:
static S32 cgetPlayerPosition(SimObject *ptr, S32, const char **){ Player* obj = static_cast
And then the console stuff.
using VC++6 and HEAD Release...
no where do I have the line F32 getJumpForceModifier() { return mJumpForceModifier; } in player.h or anywhere...
Thanks for any help or ideas as to why? :-)
#11
Stick those sections under "struct Death {" in public.
11/24/2003 (12:12 am)
Edward for where to stick those mods *around* jumpforcemodifier..Stick those sections under "struct Death {" in public.
#12
Im trying to get it so that when the player press forawrd/run, they will swim in the direction they are facing instead of only moving horizontal and floating up/down depending on headRotation.
11/24/2003 (12:18 am)
Oh and can anyone tell me how to get the forward movement to go in the direction of HeadRotation?Im trying to get it so that when the player press forawrd/run, they will swim in the direction they are facing instead of only moving horizontal and floating up/down depending on headRotation.
#13
Edit.. no its mHead. you could write a quick ConsoleMethod that returns the mHead variable.. then you can do what you want with it.
11/24/2003 (6:27 am)
eyeVec? Its been a while since I looked at the player code. (thats in C++)Edit.. no its mHead. you could write a quick ConsoleMethod that returns the mHead variable.. then you can do what you want with it.
#14
11/24/2003 (1:22 pm)
its just the getting the player to move in the headRotation direction ONLY when the press forward (with that acc) that im having trouble with.
#15
11/29/2003 (5:03 am)
Im haveing a bit of trouble with this... more with the animations then anything.... Ive got it all put, and Ive got an animation setup for crouchroot (which I changed to "kneel" in the code) and have it named kneel, but when I change position, it just sticks to the last animation that was runing... any ideas????
#16
well, i'm getting these errors, andi can't figure out what i did wrong.
--------------------Configuration: Torque Lib - Win32 Release--------------------
Compiling...
player.cc
C:\torque\engine\game\player.cc(3645) : error C2267: 'cgetPlayerPosition' : static functions with block scope are illegal
C:\torque\engine\game\player.cc(3645) : error C2601: 'cgetPlayerPosition' : local function definitions are illegal
C:\torque\engine\game\player.cc(3651) : error C2267: 'csetPlayerPosition' : static functions with block scope are illegal
C:\torque\engine\game\player.cc(3651) : error C2601: 'csetPlayerPosition' : local function definitions are illegal
C:\torque\engine\game\player.cc(3829) : error C2065: 'cgetPlayerPosition' : undeclared identifier
C:\torque\engine\game\player.cc(3830) : error C2065: 'csetPlayerPosition' : undeclared identifier
Error executing cl.exe.
engine.lib - 6 error(s), 0 warning(s)
01/05/2004 (5:46 pm)
huh, looks like my last post did'nt show upwell, i'm getting these errors, andi can't figure out what i did wrong.
--------------------Configuration: Torque Lib - Win32 Release--------------------
Compiling...
player.cc
C:\torque\engine\game\player.cc(3645) : error C2267: 'cgetPlayerPosition' : static functions with block scope are illegal
C:\torque\engine\game\player.cc(3645) : error C2601: 'cgetPlayerPosition' : local function definitions are illegal
C:\torque\engine\game\player.cc(3651) : error C2267: 'csetPlayerPosition' : static functions with block scope are illegal
C:\torque\engine\game\player.cc(3651) : error C2601: 'csetPlayerPosition' : local function definitions are illegal
C:\torque\engine\game\player.cc(3829) : error C2065: 'cgetPlayerPosition' : undeclared identifier
C:\torque\engine\game\player.cc(3830) : error C2065: 'csetPlayerPosition' : undeclared identifier
Error executing cl.exe.
engine.lib - 6 error(s), 0 warning(s)
#17
01/21/2004 (5:03 am)
Make sure your terminating functions. If you just copy and pasted what was above you will have problems because he only shows snippets of what needs to be changed. Most of the functions above aren't terminated.
#18
01/25/2004 (12:03 pm)
ok, thats comfusing, define terminating?
#19
01/26/2004 (2:36 pm)
terminated would be with an ending curly brace. IE a '}'
#20
This will solve all the above errors (Except head rotation. still hunting that one down...)
02/01/2004 (1:09 pm)
UPDATED: The above code has been changed slightly to reflect the new manner of calling console commands. I'm not sure when Torque switched, but I noticed it around 1.2This will solve all the above errors (Except head rotation. still hunting that one down...)

Torque Owner Josh Albrecht
mDataBlock->boxSize.set(...);
I have a very bad feeling about that. You should NEVER modify datablock information while the game is running, at least that is the impression I was under.
You may want to simply remove that bit of code and replace it with the stuff that modifies the mObjBox below.