Author: Statler
Adding acceleration into func_rotating:
Adding acceleration into the func_rotating entity may seem a
daunting task when looking at the code for accelerative movement
with platforms, but it is actually a lot easier to understand.
Fortunately, the edict_t struct already has accel and decel fields,
so we have everything we need to make it work. We will need to
do the following things:
To set things up, let us first look at the base func_rotating code
to understand how it works.
If you'll look at the SP_func_rotating function, you'll notice
that apart from the usual type setup stuff for an entity (checking
spawnflags, making sure some base values are set), all it does
is set the use function, and call gi.setmodel() and gi.linkentity().
So let's take a look at the use function and see if we can tell where
the actual rotation is taking place:
What we have here is a simple check to see if the entity is currently
rotating. If the entity's avelocity is not equal to vec3_origin (which
is the vector {0, 0, 0}, then turn off the sound, clear the angular
velocity, and turn off its touch function.
If the entities angular velocity is equal to all zeros (i.e. it currently
has no angular velocity) then give it a sound, call VectorScale to set
the avelocity vector up correctly, and turn on its touch function if
need be.
Not complicated at all eh.. so understanding that, all we need to do
to add acceleration is to bring a think function into play for the
entity that follows this logic:
If the entity is currently stopped or decelerating, then begin
accelerating up to full speed.
If the entity is currently accelerating or at full speed, then begin
decelerating down to full stop.
To reach this end, we shall setup two think functions, Think_RotateAccel
and Think_RotateDecel.
First of all, the easiest way to ensure that the logic is enforced
correctly is to bring a state machine into play, and for this we want
to setup some state variables as #include lines at the top of the
g_func.c file. So if you haven't already, load up g_func.c and lets
get to coding...
Add the following lines up at the top with the other #include lines:
(a + indicates added lines)
We will use these later on to keep track of what state the entity is
currently in. Now drop down to the SP_func_rotating declaration and
modify it thusly:
Not much new for that function, just some code to make sure the intial
values for accel and decel are set properly. Make sure you add the line
near the top that sets the initial motion state of the entity
(STATE_STOPPED). Also, notice that the accel and decel values are
multiplied by 0.1. Because of how we will be using the values, lower
numbers will work better. This way, the accel and decel values look
similar to the speed values to the level designer and I think are a
bit easier to understand. (also, it was done this way with plats, heh)
Alright, next is the use function, and it gets pretty mangled, so
let's just write it again from scratch:
The above code determines the current state of the machine. If
it it currently accelerating or at full speed, then it checks
the decel value. If it is zero, then there is no need to decelerate
so it calls the original lines to stop it instantly and changes the
state to STATE_STOPPED. Otherwise the state is changed into
STATE_DECEL and the proper think function is set. If the entity is
currently decelerating or stopped, similar logic is used.
We are almost done now. All that remains are the two think functions:
Think_RotateAccel and Think_RotateDecel. The basics of the acceleration
is this: the direction vector is scaled by the speed to get the angular
velocity in the original entity. To simulate acceleration, we can
use the accel value to increment the current speed from stopped to
the speed value using the think functions. Place the code for the
think functions up above the other func_rotating functions.
And here they are:
I hope things were documented well enough to make sense. Now let's
think about an example of how this would work. Let's say you have
a level with this freshly redesigned func_rotating entity in it. It
has a speed value of 100 and accel and decel values of 100. When the
entity is spawned, the state is set to STATE_STOPPED. If the entity
is triggered or is set to start on, then the use function is called.
Since we are in STATE_STOPPED and accel is != 0, then the think function
is setup and will be called at the next frame. The accel function
increments the current speed from 0 to full speed (in this case, 100)
in increments of the accel value, which we set to 100, but was internally
changed to 10. The VectorScale is called each time to set the proper
values in the avelocity vector. The think function is recalled every
frame until the current_speed has accelerated up to or above the
speed value. If it has gone over, it readjusts to speed and calls
VectorScale again to get it running at full speed. The state is then
changed to STATE_FULLSPEED and the think function is set to null.
Because of how the state machine is setup, you can make a func_rotating
that is targetted and it can be toggled while accelerating or
decelerating and still adjust properly. Specifically, if you have
a button to control a func_rotating, hit the button to start it
rotating, and hit the button again before it has reached full speed,
it will decelerate from its current speed back down to zero (unless
you are infatuated by it and keep hitting the button over and over).
Well I hope you enjoyed this little tutorial. I sure enjoyed writing
it (I think). And you thought those finite state machines you learned
in class would never come in handy. :)
-Statler
Tutorial by Statler This site, and all content and graphics displayed
on it,
Difficulty: Easy
void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (!VectorCompare (self->avelocity, vec3_origin))
{
self->s.sound = 0;
VectorClear (self->avelocity);
self->touch = NULL;
}
else
{
self->s.sound = self->moveinfo.sound_middle;
VectorScale (self->movedir, self->speed, self->avelocity);
if (self->spawnflags & 16)
self->touch = rotating_touch;
}
}
#define STATE_TOP 0
#define STATE_BOTTOM 1
#define STATE_UP 2
#define STATE_DOWN 3
+ #define STATE_STOPPED 0
+ #define STATE_ACCEL 1
+ #define STATE_FULLSPEED 2
+ #define STATE_DECEL 3
/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
You need to have an origin brush as part of this entity. The center of that brush will be
the point around which it is rotated. It will rotate around the Z axis by default. You can
check either the X_AXIS or Y_AXIS box to change that.
"speed" determines how fast it moves; default value is 100.
"dmg" damage to inflict when blocked (2 default)
"accel" acceleration speed when activated, goes from 0 to speed
"decel" deceleration speed when deactivated, goes from speed to 0
Good values for acceleration run from 20-100. Do not use acceleration
values that are more than 10x the speed setting, or it will have no effect.
REVERSE will cause the it to rotate in the opposite direction.
STOP mean it will stop moving instead of pushing entities
*/
void SP_func_rotating (edict_t *ent)
{
ent->solid = SOLID_BSP;
if (ent->spawnflags & 32)
ent->movetype = MOVETYPE_STOP;
else
ent->movetype = MOVETYPE_PUSH;
+ ent->moveinfo.state = STATE_STOPPED; // rotating thingy starts out idle
// set the axis of rotation
VectorClear(ent->movedir);
if (ent->spawnflags & 4)
ent->movedir[2] = 1.0;
else if (ent->spawnflags & 8)
ent->movedir[0] = 1.0;
else // Z_AXIS
ent->movedir[1] = 1.0;
// check for reverse rotation
if (ent->spawnflags & 2)
VectorNegate (ent->movedir, ent->movedir);
if (!ent->speed)
ent->speed = 100;
if (!ent->dmg)
ent->dmg = 2;
+ if (ent->accel < 0) /* sanity check */
+ ent->accel = 0;
+ else
+ ent->accel *= 0.1;
+
+ if (ent->decel < 0) /* sanity check */
+ ent->decel = 0;
+ else
+ ent->decel *= 0.1;
+
+ ent->moveinfo.current_speed = 0;
// ent->moveinfo.sound_middle = "doors/hydro1.wav";
ent->use = rotating_use;
if (ent->dmg)
ent->blocked = rotating_blocked;
if (ent->spawnflags & 1)
ent->use (ent, NULL, NULL);
if (ent->spawnflags & 64)
ent->s.effects |= EF_ANIM_ALL;
if (ent->spawnflags & 128)
ent->s.effects |= EF_ANIM_ALLFAST;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
{
/* first, figure out what state we are in */
if (self->moveinfo.state == STATE_ACCEL || self->moveinfo.state == STATE_FULLSPEED)
{
/* if decel is 0 then just stop */
if (self->decel == 0) {
VectorClear(self->avelocity);
self->moveinfo.current_speed = 0;
self->touch = NULL;
self->think = NULL;
self->moveinfo.state = STATE_STOPPED;
} else { /* otherwise decelerate */
self->think = Think_RotateDecel;
self->nextthink = level.time + FRAMETIME;
self->moveinfo.state = STATE_DECEL;
} /* decelerate */
/* setup touch function if needed */
if (self->spawnflags & 16)
self->touch = rotating_touch;
}
else
{
self->s.sound = self->moveinfo.sound_middle;
/* check if accel is 0. If so, just start the rotation */
if (self->accel == 0) {
VectorScale (self->movedir, self->speed, self->avelocity);
self->moveinfo.state = STATE_FULLSPEED;
}
else { /* accelerate baybee */
self->think = Think_RotateAccel;
self->nextthink = level.time + FRAMETIME;
self->moveinfo.state = STATE_ACCEL;
/* setup touch function if needed */
if (self->spawnflags & 16)
self->touch = rotating_touch;
}
}
void Think_RotateAccel (edict_t *self)
{
if (self->moveinfo.current_speed >= self->speed) { /* has reached full speed*/
/* if calculation causes it to go a little over, readjust */
if (self->moveinfo.current_speed != self->speed)
VectorScale (self->movedir, self->speed, self->avelocity);
self->think = NULL;
self->moveinfo.state = STATE_FULLSPEED;
return;
} /* has reached full speed */
/* if here, some more acceleration needs to be done */
/* add acceleration value to current speed to cause accel */
self->moveinfo.current_speed += self->accel;
VectorScale (self->movedir, self->moveinfo.current_speed, self->avelocity);
self->nextthink = level.time + FRAMETIME;
} /* Think_RotateAccel */
void Think_RotateDecel (edict_t *self)
{
if (self->moveinfo.current_speed <= 0) { /* has reached full speed*/
/* if calculation cause it to go a little under, readjust */
if (self->moveinfo.current_speed != 0)
{
VectorClear (self->avelocity);
self->moveinfo.current_speed = 0;
}
self->think = NULL;
self->moveinfo.state = STATE_STOPPED;
return;
} /* has reached full stop */
/* if here, some more deceleration needs to be done */
/* subtract deceleration value from current speed to cause decel */
self->moveinfo.current_speed -= self->decel;
VectorScale (self->movedir, self->moveinfo.current_speed, self->avelocity);
self->nextthink = level.time + FRAMETIME;
} /* Think_RotateDecel */
are ©opyrighted to the Quake DeveLS team.
All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4 or IE 3