creating mover hierarchies

i’ve often talked about this, but only till a month ago did i really try to do it.

this type of thing should be done in the engine, of course, by using some kind of local coordinate system that is based on a child’s parent, but whatever.

this is qc, so it’s haxored in as best i could.  this means that it’s not usable in quite a few situations, namely for moving other movers around.

all bsp movers like doors and buttons store both the start and end points of their movements in .pos1 and .pos2 vectors.  this means that you would need to update these as well.  and then you’d have to account for exceptions like if the child is moving when the parent starts to move (or write locking functions to disallow child movement).

i found it best to limit myself to things like torches, triggers and some custom entities that don’t move (like guis and non-moving rotaters).

first, let’s figure out how we want to do this.

i elected to use two strings, .bind and .bindName (similar to target and targetname).  you could use bind -> targetname, but there may be situations where this could cause trouble (i encountered some).  better to create all knew fields and avoid that entirely.

essentially, what you’re going to want to do is set the origin of the children each frame to maintain their offset with the parent.

this can be done easily by writing a small bit of code into startFrame.  you can create a haxor .string field called ‘isParentMoving’ or somesuch, and search for matches with the string “TRUE” (which we will set later).  after that, just set the origin to the parent’s + the offset:

local    entity    childEntity, _parent;
local    vector    offset, childVec;

childEntity = find(world, isParentMoving, "TRUE");
while(childEntity != world)
{
    if (childEntity.bindParent == world) //this will set up bindParent for any that aren't.
         childEntity.bindParent = find(world, bindName, childEntity.bind);        

    _parent = childEntity.bindParent;
    offset = childEntity.bindOffset;

    childVec = _parent.origin + offset;
    childVec = childVec + (_parent.velocity * frametime); //project forward in time for next frame to maintain correct position
    setorigin(childEntity, childVec);    

    childEntity = find(childEntity, isParentMoving, "TRUE");
}

note that there’s an if check for .bindParent.  if it’s not set, we set it to the parent entity and this will save us from doing an extra search next frame to find the parent again.

as you can see, we need an “offset”, a vector between the child and parent.  we can do this easily because the SUB_CalcMove function is the only one used for movement, so everything passes through there that moves.

if (self.bindName)
{
     bindChild = find(world, bind, self.bindName);
     while (bindChild != world)
     {
          bindChild.isParentMoving = "TRUE";
          bindChild.bindOffset = bindChild.origin - self.origin;

          bindChild = find(bindChild, bind, self.bindName);
     }
}

because we used bindName instead of targetname, it’s really easy to check if an entity has children or not.

remember, this will be the parent who is calling this function, so we need to loop through all the children, set the offsets and also set the isParentMoving string so it will be found by the bit of code in startFrame.

i briefly toyed with the idea of creating a queue out of entities so that you could drop children entities into the queue that needed to have their origins updated, but found that i was too lazy to write data structures and just left it up to the find() function.

btw, don’t forget to turn off the entity updates in SUB_CalcMoveDone:

if (self.bindName)
{
     bindChild = find(world, bind, self.bindName);
     while (bindChild != world)
     {
          bindChild.isParentMoving = "FALSE";

          setorigin(bindChild, self.origin + bindChild.bindOffset);
          bindChild.velocity = '0 0 0';

          bindChild = find(bindChild, bind, self.bindName);
     }
}

you may be wondering:  it’d be easier to just set velocity!  let the engine handle the movement.

the problem is that if you have entities bound that are MOVETYPE_NONE, velocity settings will not have any effect.

also, for MOVETYPE_PUSH entities, they will only move if their .nextthink is set to other than 0.  Some MOVETYPE_PUSH entities may be (and probably are) using their nextthinks for something else, so cheap hacking in a nextthink may create problems with those entities.

better to just setorigin() and be done with it.  note, however, child bsp models will not push the player, so he may become stuck if a child moves into the player via the parent.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: