TUTORIAL 8 - Ladders!
von Calrathan
Wenn du Quake2, Halflife oder irgendwelche anderen neueren 3D Shooter gespielt
hast, hast du Leitern gesehen und benutzt. Wie könnte man besser hoch und runter kommen ?
id Software setzt mit Quake3 vollauf Jumppads,
aber es gibt Hunderte von anderen Mod-Machern da draussen,
die etwas realistischeres auf die Beine stellen wollen, und Jump- oder
Beschleunigungspads sind für diesen Zweck nicht geeignet! Also warum liest
du das hier? Natürlich um in Quake3 Leitern zu implementieren!
1. SPIELER BEWEGUNG
Zuerst gibt es ein paar Hintergrundinformationen zu bg_pmove.c.
Diese Datei legt alle verschiedenen Arten, auf die ein Client sich bewegen
kann, fest. Da diese Datei sowohl in game als auch in cgame enthalten ist,
müssen wir auf jeden Fall daran denken beide Module zu kompilieren bevor
wir unseren neuen Mod testen, sonst werden Ranger / Mynx / Bitterman / WerAuchImmer
einen Parkinson-Anfall simulieren. =)
Die Spielerbewegungen bestehen im Grunde aus zwei Teilen. Fast
alle Bewegungsbefehle werden von void PmoveSingle
(pmove_t *pmove) aus aufgerufen. Diese Funktion kalkuliert mit der Hilfe
vieler if-Abfragen und dem Zustand des Spielers, welche Bewegungen er machen
darf. Die verschiedenen Möglichkeiten sind: PM_NoclipMove, PM_DeadMove, PM_FlyMove, PM_GrappleMove, PM_AirMove, PM_WaterJumpMove, PM_WaterMove, PM_WalkMove und PM_AirMove.
Dieses Tutorial wird dir zeigen, wie man ein PM_LadderMove einbaut, und wie
man die dazugehörigen Funktionen und Modifikationen zum Erklimmen der Leiter
schreibt.
2. DEFINITIONEN
Wie in jedem C/C++ Programm musst du, bevor du etwa tust, etwas definieren.
Also lasst uns dieses tun. Wir brauchen irgendwo ein Flag, dass sich merkt,
ob wir auf einer Leiter sind, damit wir dies nicht in jedem Client Frame
neu überprüfen müssen. In der Datei bg_pmove.c gibt es die globale Definition
der Strukturen "pml" vom Typ "pml_t". Diese Spielerbewegungsstruktur eignet sich
sehr gut um unser Flag darin einzubauen. Öffne nun die Datei bg_local.h und
suche die Definition dieser Struktur. Dort angekommen tragen wir die fettgeschrieben
Zeile ein:
typedef struct {
vec3_t forward, right, up;
float frametime;
int msec;
qboolean walking;
qboolean groundPlane;
trace_t groundTrace;
qboolean ladder; // diesen Ja/Nein Wert benutzen wir um zu speichern,
ob wir auf einer Leiter sind oder nicht
float impactSpeed;
vec3_t previous_origin;
vec3_t previous_velocity;
int previous_waterlevel;
} pml_t;
Das war aber noch nicht Alles, was wir definieren müssen. Öffne
die Datei bg_pmove.c und schaue dir oben die // movement
parameters an. Du wirst feststellen, dass es verschiende Einstellungen
für verschiedene Typen der Bewegung gibt. Die "scale" Variablen geben die
maximale Geschwindigkeit in diesem Bereich an. Nachdem wir uns vertikal bewegen
sind wir logischerweise nicht so schnell, wie wenn wir normal rennen. Bewegung
auf Leitern ist eine sehr genaue Sache, daher wollen wir unsere maximale
Geschwindigkeit sofort erreichen, und wir wollen auch sofort stoppen können.
(Kannst du dir vorstellen, eine Leiter weiter hinaufzurutschen,
nachdem du aufgehört hast dich zu bewegen?). Unter Berücksichtigung dieser
Faktoren habe ich folgende Werte für die Leitern ausgesucht. Wenn du willst,
spiel ruhig ein wenig damit herum und schau was passiert.
// movement parameters
float pm_stopspeed = 100.0f; //
das f hinter Zahlen bedeuted, das hier explizit float benutzt wird (und nicht
double)
float pm_duckScale = 0.25f;
float pm_swimScale = 0.50f;
float pm_wadeScale = 0.70f;
float pm_ladderScale
= 0.50f; // setzt die Maximalgeschwindigkeit auf
die Hälfte der normalen
float pm_accelerate = 10.0f;
float pm_airaccelerate = 1.0f;
float pm_wateraccelerate = 4.0f;
float pm_flyaccelerate = 8.0f;
float pm_ladderAccelerate
= 3000.0f; // Die Endgeschwindigkeit ist (nahezu)
sofort erreicht
float pm_friction = 6.0f;
float pm_waterfriction = 1.0f;
float pm_flightfriction = 3.0f;
float pm_spectatorfriction = 5.0f;
float pm_ladderfriction
= 3000.0f; //
Der Abbremsvorgang ist (nahezu) sofort erledigt [hohe Reibung]
3. REIBUNG!
Nachdem wir unsere Variablen für Friction (Reibung), Acceleration (Beschleunigung)
und die Skalierung der Geschwindigkeit implementiert haben, wird es Zeit,
diese zu benutzen. Wir suchen uns die Funktion static
void PM_Friction( void ) aus der Datei
bg_pmove.c. Unter der Abfrage nach dem Spectator Modus ergänzen wir etwas:
if ( pm->ps->pm_type
== PM_SPECTATOR) {
drop += speed*pm_spectatorfriction*pml.frametime;
}
if (
pml.ladder ) // Wenn wir auf der Leiter sind...
{
drop += speed*pm_ladderfriction*pml.frametime; // ... fügen wir die Reibung
der Leiter hinzu
}
4. DIE BEWEGUNG AUSFÜHREN
Wir sind nun an dem Punkt, an dem wir noch die Leiterfunktionen selbst schreiben
müssen. Die Erste wird die aktuellen Spielerbewegungen ausführen, die Zweite
wird benutzt, um zu überprüfen, ob wir uns auf einer Leiter befinden. Die
Reihenfolge ist nicht wirklich wichtig, es ist nur wichtig, dass wir diese
Funktionen in bg_pmove.c einfügen, und zwar VOR void PmoveSingle
(pmove_t *pmove). Anderenfalls wird es nicht funktionieren!
Vorab ein wenig dazu, wie die Funktionen entstanden sind. Ich
habe den Code der bereits fertigen Funktion PM_WaterMove() benutzt für PM_LadderMove().
Wieso? Nunja, im Grunde verfolgen die beiden Funktionen die selbe Idee. Im
Wasser kannst du dich um volle 360° bewegen. Auch bei Leitern haben wir
diese Freiheit. Das klingt jetzt etwas komisch, ist aber so, solange man
auf einer Leiter steht. Das bringt uns zur CheckLadder() Funktion, die überprüft,
ob wir auf der Leiter stehen. Diese funktioniert sehr einfach. Wir untersuchen
den Bereich eines dreidimensionalen viereckigen (bzw. 8-eckigen in 3D) Bereichs
in der Größe des Spielermodells. Wenn irgendetwas in dieser Box die Leiter
berührt, dann nehmen wir an, dass wir auf dieser sind. Ein Flag wird gesetzt,
und die Funktion kehrt zurück.
/*
===================
PM_LadderMove()
by: Calrathan [Arthur Tomlin]
Die Funktion funktioniert sicher für
VERTIKALE Leitern..
Leitern in einen gewissen Winkel (wie urban2 für
AQ2) wurde damit nicht getestet
===================
*/
static void PM_LadderMove( void ) {
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
float vel;
PM_Friction ();
scale = PM_CmdScale( &pm->cmd );
// Absichten des Users
if ( !scale ) {
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
}
else { // wenn der User
sich bewegt, muß das berechnet werden
for (i=0 ; i<3 ; i++)
wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove
+ scale * pml.right[i]*pm->cmd.rightmove;
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
if ( wishspeed > pm->ps->speed
* pm_ladderScale ) {
wishspeed = pm->ps->speed * pm_ladderScale;
}
PM_Accelerate (wishdir, wishspeed, pm_ladderAccelerate);
// das SOLLTE schräge Leitern mit
einem Winkel funktionieren lassen, jedoch ungetestet!
if ( pml.groundPlane && DotProduct(
pm->ps->velocity,
pml.groundTrace.plane.normal ) < 0 ) {
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane [the ladder section under our feet]
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
VectorNormalize(pm->ps->velocity);
VectorScale(pm->ps->velocity, vel, pm->ps->velocity);
}
PM_SlideMove( qfalse ); // Bewegung
ohne Schwerkraft
}
/*
=============
CheckLadder [ ARTHUR TOMLIN ]
=============
*/
void CheckLadder( void )
{
vec3_t flatforward,spot;
trace_t trace;
pml.ladder = qfalse;
// Überprüfen auf Leiter
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
VectorMA (pm->ps->origin, 1, flatforward, spot);
pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs,
spot,
pm->ps->clientNum, MASK_PLAYERSOLID);
if ((trace.fraction < 1) && (trace.surfaceFlags & SURF_LADDER))
pml.ladder = qtrue;
}
5. ZULETZT NOCH...
Der letzte Schritt ist es, die Funktion void PmoveSingle (pmove_t *pmove)
zu ändern. Diese Funktion steuert fast alle Spielerbewegungen. Dieser Teil
ist recht einfach. Wir prüfen, ob wir auf einer Leiter sind. Falls ja, aktivieren
wir die "Leiter-physics". Wir fügen dies unter der Bewegung in Wasser
ein, wodurch unsere Leitern im Wasser ignoriert werden.
PM_DropTimers();
CheckLadder(); // ARTHUR TOMLIN check and see if they're on
a ladder
#ifdef MISSIONPACK
if ( pm->ps->powerups[PW_INVULNERABILITY]
) {
PM_InvulnerabilityMove();
} else
#endif
if ( pm->ps->powerups[PW_FLIGHT] ) {
// flight powerup doesn't allow jump and has different friction
PM_FlyMove();
} else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL)
{
PM_GrappleMove();
// We can wiggle a bit
PM_AirMove();
} else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP)
{
PM_WaterJumpMove();
} else if ( pm->waterlevel > 1
) {
// swimming
PM_WaterMove();
} else if (pml.ladder) {
PM_LadderMove();
} else if ( pml.walking ) .........................
6. LEITERN IN EINE MAP EINBAUEN
Wir haben es geschafft, der Leiterncode ist fertig. Aber wir haben immer
noch ein Problem. Wir brauchen erstmal eine Karte, die unsere Leitern auch
unterstützt. Um jedes "Ding", dass eine Leiter sein soll, muß eine Box erstellt
werden. Diese Box muß mit der texture "common/ladderclip" belegt werden.
Ändere die Datei common.shader in quake3\baseq3\scripts\common.shader, und
füge folgende Zeilen ein:
textures/common/ladderclip
{
qer_trans 0.40
surfaceparm nolightmap
surfaceparm nomarks
surfaceparm nodraw
surfaceparm nonsolid
surfaceparm playerclip
surfaceparm noimpact
surfaceparm ladder
}
Bevor du gehst... es ist wichtig, dass du sowohl das "game"
als auch das "cgame" Modul kompilierst! Nur wenn in deinem \mymod Verzeichnis
die Dateien cgamex86.dll und qagamex86.dll liegen (bzw. die entsprechenden
.qvm Dateien) funktionieren die Vorherberechnungen der Bewegungen auf Leitern
richtig. Kompiliere dann noch
deine Map mit einer
Leiter, und es sollte alles funktionieren! Leitern! Endlich!
7. ADDENDUM
Bekannte Fehler: Die Beine des Spielers bleiben im vorhergehenden Zustand,
was manchmal so aussieht, als würde man in der Luft rennen. Wenn es mal ein
Spieleranimationstutorial gibt, dann kann man den "Fehler" damit beheben.
:o)
Für die Nicht-Mapper habe ich eine kleine Testmap erstellt.
Die .pk3 Datei muß in das \mymod Verzechnis kopiert werden, die map kann
man mit \map ladder laden.

|