
Or objects if you want to use a different name. I
saw a bunch of people talking on the warzone bbs about what they want from
tutorials and something along these lines was suggested; So here it is.
The first thing you will need if you want to
create an object you can interact with (a bomb, a bullet , something along
those lines) is a model. You could use one of the ones that comes with quake 2
or you could create your own. If you don't want to create your own (or can't)
then get something like Qped (Qped
homepage) to open up the pak0.pak file that comes
with quake 2, to find the pathname of the model you want to use. If you can
create your own model, then note down the pathname (I'd suggest putting the
model somewhere like yourmoddir/models/mymodel.md2)
After you have the model, then you will need to
have a set of coordinates to create the model at. Quite commonly people will
use the coords of the player model. Thats what I will be doing in the example
at the bottom.
With the information to hand, you can now add the
object to the quake 2 environment. It won't do anything, but sit there and look
pretty, but I'll get back to that.
If you have looked through the code you will have
noticed that all definitions of edict_t 's(edict_t = object it's just the name
of the structure that holds all the information) have had a star in front of
the variable name. This is because you never actually create an edict_t
yourself. All you do is create a variable that will store the location of one
that the engine has created for you. This means there is a limit of 1024 edict_t's.
While this may seem like a limitation, it was done to make it easier to code.
without the list that exists it would be hard to keep track of all the
edict_t's there are in the game. To assign a real edict_t to your pointer, you
will need to call the function G_Spawn this will return the location of a free
edict_t.
To make a edict_t visible you have to link it to
the world. to do this you have to use the game interface function (one that you
can't even get at) gi.linkentity.
Here is an example of how you would create an
entity and make it visible. I've encapsulated it in a function that looks like
a standard player command function. How to make player commands is a different
tutorial entirely
void Example_Command(edict_t *ent) // the edict_t being passed in is the player{edict_t *object; // the pointer to our object (or at least, it will be when I'm finished) object=G_Spawn(); // here we get a free edict_t and assign it's location to object so we can manipulate it VectorCopy (ent->s.origin, object->s.origin); // here we take the players location and assign it to the new objects location. // we have to use VectorCopy as origin is a <a href="glossary.html#vec3_t" traget=glos>Vec3_t</a> object->s.modelindex = gi.modelindex ("models/items/band.md2"); // here we set the model to be used for the object. I picked this one at random. gi.linkentity (object); // This is the object linked to the game. It will now show up, not doing anything. }
Fairly simple, don't you agree? Thats the real
basics, they don't do anything interesting and aren't even solid.
This is where things become slightly more useful.
The way Quake 2 handles you touching objects is by setting bounds on the object
and when something passes within them, a function is called. the bounds are 2 Vec3_t's mins and maxs. They
default to 0 so if you don't set them you have to pass through the center of
the object to have anything happen. You set the fields of maxes to positive
floats and those of mins to negitive ones, and a box is generated from them.
You set object.touch to the name of the touch function you want to use. It has
to be of the following format
void touch_function (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
Change the function name to something meaningful.
self refers to the object being touched, other refers to the edict_t touching it.
The others are unimportant at this time
Look at the Rocket function in g_weapon.c for an
example
Say the model you want to use for your object has
animation (multiple frames), one way to get the animation working would be to
use the think function of the object (I'll get back to this shortly), but this
isn't a good idea, especailly if you want your object to do something every now
and then rather than every frame. Thankfully there is a way round this and that
is object->prethink. you set this equal to a function of a set parameter
list and every frame it will be executed. below is a simple piece of code to
make you object animate. This code will work for a model that has 18 frames and
will cycle through it at 10 hertz (1 frame every 0.1 seconds). The code could
be a little difficult to understand but I'll explain it
void ItemAnimate (edict_t *ent){ int lastframe = 18; ent->s.frame = ent->s.frame + 1 < lastframe ? ent->s.frame + 1 : 0;}
to use this you set
object->prethink=ItemAnimate As for an explanation. lastframe is set to 18
when it is created. The line below that checks the value of (ent->s.frame+1
< lastframe) , this will be a boolean, true or false. If it is true
ent->s.frame is set to ent->s.frame+1 and if it is false it is set to 0.
This means the model will cycle though all 18 frames ( 0 to 17). Thanks to
Warzone for showing me this.
This is simple. You have to pick a type of solid
you want the object to be. Generally SOLID_BBOX will be what you want. Then you
set object->solid to it. The object->maxs and object->mins will define
how much space becomes solid. Types of solid
|
NAME |
Definition |
|
SOLID_NOT |
no interaction with other objects |
|
SOLID_TRIGGER |
only touch when inside, after moving |
|
SOLID_BBOX |
touch on edge |
|
SOLID_BSP |
bsp clip, touch on edge |
As a default you cannot shoot objects to make them
go away. To allow an object to die you have to set object->health to a
positive value and object->takedamage to DAMAGE_YES
You will also have to set its maxs and mins to
some value, unless you want it to be really hard to hit (a point is harder to
hit than a box). You now have to create a function to be executed when the
object dies. it has to be of the following format
void object_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
While you could just G_FreeEdict the object, quake
2 doesn't like this, so I would suggest setting the think function to
G_FreeEdict and nextthink to level.time+0.1. A sample object is given below.
void Example_Command(edict_t *ent){edict_t *object;object=G_Spawn();VectorCopy (ent->s.origin, object->s.origin);object->s.modelindex = gi.modelindex ("models/items/band.md2");// all this was explained above VectorSet (object->mins, -8, -8, -8); // The size of the object is set hereVectorSet (object->maxs, 8, 8, 8); // it is a cube 16 units each edge centered on the origin object->health=10; // not very toughobject->takedamage=DAMAGE_YES; // Can now be damagedobject->die=exampledie; // will execute exampledie when it takes more than 10 damage object->solid=SOLID_BBOX; // solid so it will stop people passing through it. gi.linkentity (object); // This is the object linked to the game. It will now show up, not doing anything. }
And now for an example die function
void exampledie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point){self->takedamage=DAMAGE_NO; // So it doesn't take more damge and try to reexecute it's self when the next line happensT_RadiusDamage(self, NULL, 200, NULL, 300, MOD_HGRENADE); // I wanted it to blow up :)ent->nextthink=level.time+0.1; // explained laterent->think=Become_Explosion1; // this is a util function, that spawns a temporary explosion and gets rid of the edict that spawns it}
I suppose you could throw some gibs , but thats
not what the focus of this tutrorial is
This is probably the most useful feature of
objects. You set the think field of an edict_t and the nextthink field, and
when level.time equals nextthink the function pointed to by think is executed.
This is gererally used for some big flashy effect, like a big explosion, or
alternitvly it is used to make the object go away by G_FreeEdict ing it. I'll
give an example of both
.
Example 1. Makeing an object that explodes after
30 seconds.
void Example_Command(edict_t *ent){edict_t *object;object=G_Spawn();VectorCopy (ent->s.origin, object->s.origin);object->s.modelindex = gi.modelindex ("models/items/band.md2");VectorSet (object->mins, -8, -8, -8);VectorSet (object->maxs, 8, 8, 8);object->takedamage=DAMAGE_NO; // You could set it to yes, but makesure you turn the damage off before it explodesobject->solid=SOLID_BBOX;// all this was explained aboveobject->think=examplethink;object->nextthink=level.time+30; // examplethink will be executed after 30 seconds gi.linkentity (object); // This is the object linked to the game. It will now show up, not doing anything.} void examplethink(edict_t *ent){T_RadiusDamage(ent, NULL, 200, NULL, 300, MOD_HGRENADE); // I wanted it to blow up :)Become_Explosion1(ent); // creates an explosion and releases the edict_t}
Example 2. getting rid of the objects after a time
void Example_Command(edict_t *ent){edict_t *object;object=G_Spawn();VectorCopy (ent->s.origin, object->s.origin);object->s.modelindex = gi.modelindex ("models/items/band.md2");VectorSet (object->mins, -8, -8, -8);VectorSet (object->maxs, 8, 8, 8);object->takedamage=DAMAGE_NO;object->solid=SOLID_BBOX;// all this was explained aboveobject->think=G_FreeEdict; // gets rid of the edict_t object->nextthink=level.time+30; // examplethink will be executed after 30 seconds gi.linkentity (object); // This is the object linked to the game. It will now show up, not doing anything.}
Thats it for now. This is a work in progress. It
will get updated as I have time.