Nicht eingeloggt
Registrieren

Unreal Script Tutorial

Diese Tutorials-Reihe beschreibt den Umgang mit der in der UnrealEngine verwendeten Scriptsprache UnrealScript.

 



Für wen dieses Tut etwas bringt :
- man kennt sich mit UT aus
- man hat es bisher noch nicht geschafft einer Mutator zu coden (dann ist man zu gut )
- man kann halbwegs englisch (zum Code lesen!)
UScript ist eine OOP-Sprache, da muß man den UT-Code verstehen, um sinnvoll zu Programmieren, und der ist auf Englisch.
- man ist motiviert genug alles 10.000mal selbst auszuprobieren
- und Der wichtigste Punkt: Man hat schon einigermassen Programmiererfahrung (mehr als Print "Hello world")
Ich hab jetzt nicht die Zeit auch noch Programmieren zu erklären, das würde den Rahmen dieses Tuts sicher sprengen !
Es geht mir nämlich darum UScript zu erklären und nicht "Ich lerne programmieren mit UScript", so haben die Schon-Programmierer etwas von diesem Tut und die Leute die noch nicht Programmieren können, müssen, wenn sie UScript lernen wollen ja sowieso Programmieren lernen.
Wer also noch Programmieren lernen will, sollte sich mal das C-Tut´s ansehen.
- Dazu gehört auch die zugehörige Mathematik. (wie berechne ich z.b., daß eine Grafik in jeder Auflösung an der selben Entfernung vom rechten oberen Rand ist)


Part I - Den Code zum Laufen bringen

In diesem Abschnitt wird keine einzige Zeile Code erklärt, sondern nur wie man den geschriebenen Code überhaupt zum Laufen bringt, so daß er nicht nur Text ist, sondern in UT irgendwas bewirkt.
Dafür gibt es zwei Möglichkeiten:

Coden im Ed
Ich schenke dieser Methode nicht gerade große Beachtung, da es auch einfacher geht. Für kleine Projekte oder zum Anfang kann das ja mal ganz praktisch sein, deshalb erkläre ichs trotzdem.
Also erstmal den Ed starten und dann zum Classes Browser wechseln.
Das Wichtigste was man können muß, ist eine neue Klasse zu erstellen. Dazu eine vorhandene Klasse auswählen und dann Rechtsklick und New... wählen. Darauf erscheint folgendes:


Unter Name wird der Name eurer neuen Klasse eingetragen, unter Package das Package (wie die erstellte .u Datei heißt), kein vorhandenes Package angeben (auch nicht Textur, Map, etc. Packages, der Name zählt, die Endung ist egal).
Per OK wird die neue Klasse erstellt. Im Classen-Editor des Ed habt ihr dann links die geöffneten Klassen zum Editieren. In eure neu erstellte Klasse könnt ihr dann den Code eintippen. Per Compile (unter Tools) kann der Code dann kompiliert werden, das erkennt man daran, daß er farbig markiert wird.
Wenn der Code dann kompiliert ist (fehlerfrei) muß noch das Package abgespeichert werden. Theoretisch sollte das so funktionieren, bei mir schmiert der Ed da aber seit 436 ab
(Beim Coden im Ed ist nur zu beachten, daß die Defaultproperties per Rechtsklick auf die Klasse editiert werden und nicht im Script stehen dürfen !)
Zum Erstellen der .u Datei einfach das Package speichern (Nicht vergessen !)


Coden ohne Ed per UCC
Bevor man jetzt neuen Code erstellen will, sollte man alle UT-Klassen exportieren. Das geht über den Classes-Browser mit der Option Export All Scripts:


Das dauert 'ne Weile und dabei werden jede Menge Verzeichnisse im UT-Verzeichnis erstellt. In diesen befindet sich ein Classes Verzeichnis worin .uc Dateien sind.

Zur Erklärung: Die Namen der Verzeichnisse unterhalb des UT-Verzeichnisses sind die Namen der jeweiligen Packages. In diesen Verzeichnissen befindet sich ein Verzeichnis Classes in dem die Klassen als .uc Dateien gespeichert sind. Eine Klasse ist eine .uc Datei.
Wenn man Code schreibt macht man das in einem neuen Package, d.h. man erstellt ein Verzeichnis unterhalb des UT-Verzeichnisses und darin ein Classes Verzeichnis, in das die .uc Dateien kommen.



Vorbereitung einer Kompilierung:
Wenn man jetzt das erste Package kompilieren will ist folgendes zu tun:


Wenn der Code in euren .uc Dateien korrekt ist, solltet ihr jetzt eine funktionierende .u Datei haben.

Zum Testen: Ein Verzeichnis 'TEST' in UT erstellen. Darin ein Verzeichnis Classes erstellen. In diesem Verzeichnis eine Datei TestClass.uc erstellen. Darin folgenden Text:


class TestClass expands Actor;

function PostBeginPlay()


defaultproperties




Jetzt in der unrealtournament.ini unter den EDITPACKAGES zeilen EDITPACKAGES=Test eintragen.
Dann die Ms-Dos-Eingabeaufforderung öffnen, ins UT/System Verzeichnis wechseln und 'ucc make' eingeben. Nach einiger Zeit sollte der Text Compilation successfull erscheinen. In eurem UT/System Verzeichnis sollte sich jetzt die Datei Test.u befinden.

Jetzt UT starten und in der Console eingeben: Summon Test.Testclass
UT beenden. In der Datei Unrealtournament.LOG sollte sich jetzt eine Zeile befinden in der steht: ScriptLog: Hello World

Kurze Erklärung: PostBeginPlay() wird in jedem Actor nachdem er ins Spiel gekommen ist aufgerufen. Man kann in UT durch den Befehl summon Package.Class einen Actor ins Spiel bringen.
Die function Log() dient dazu Texte ins Logfile zu schreiben und ist damit für Debugzwecke sehr gut geeignet. Man kann nicht nur Strings, sondern auch Variablen ausgeben. Um Strings/Variablen zu einem String zu Verknüpfen dient '$' oder '@' das '@' fügt zusätzlich ein Leerzeichen ein, man kann z.b. schreiben:

Log("Die Variable a ist: "$a$" und die Variable b ist:"@b@"!");

Wenn man den Code noch einmal neu kompilieren möchte indem man 'ucc make' startet, muß zuerst die Datei Test.u gelöscht werden.

Das ganze mag etwas umständlich erscheinen, aber mit einer Batch Datei läßt sich vieles automatisieren. Außerdem gibt es eine Menge Coding Tools, die einem die Arbeit abnehmen und wo man nur noch Coden muß.
Ich persönlich benutze UClasses von Meltdown. Des weiteren wäre noch WOTGreal zu nennen. Für die Benutzung von UClasses gibts auf der D/L Seite eine ausführliche Dokumentation.

Grundsätzliches von UScript:
  • Bei allen Angaben (außer String-Vergleichen) ist Groß-/Kleinschreibung unwichtig, das heißt die Variable Hund ist gleich hund, HUND oder HuNd.
  • Jeder Befehl wird mit einem ; abgeschlossen.
  • // leitet einen Kommentar ein, der bis zum Ende der Zeile geht.
  • Alles was zwischen /* und */ steht ist zeilenübergreifend auskommentiert.

Variablentypen und Schlüsselwörter:
Variablentypen
Die meisten von UScripts Variablentypen sind aus anderen Programmiersprachen übernommen, so daß sich deren Verwendung von selbst versteht. Hier eine (hoffentlich vollständige) Liste:
Float: ein float eben, Gleitkommazahl mit Vorzeichen.
Int: Ganzzahl mit Vorzeichen.
Byte: ein Byte, Ganzzahl von 0-255
Bool: ein Bool, wahr=true, falsch=false
String: ein String, eine Zeichenkette wird in "" gefasst.

Des weiteren gibt es eine Reihe UScript spezifischer Typen:
Vector: ein Vector, bestehend aus den drei floats X,Y und Z.
Rotator: bestimmt die Rotation eines Körpers. besteht aus drei Ints Pitch, Yaw und Roll. Diese werden der Reihe nach auf den Körper angewandt, d.h. zuerst wird er um Pitch nach Rechts/Links gedreht, dann um Yaw nach oben/unten geneigt und schließlich um Roll auf der Längsachse gerollt.
Name: Names sind Namen, d.h. sie bezeichnen etwas. Man kann sie als uneditierbare Strings auffassen, sie werden in '' gefasst und sie bezeichnen z.b. States (dazu später).
Class: In UT gibt es nicht nur Classen, sondern auch den Typ Class, man kann also eine Variable als Klassenvariable definieren. Wenn man var class<XXX> schreibt, dann gibt XXX die Einschränkung an, d.h. daß nur ChildClasses der Klasse XXX hierin stehen.
Alle Klassen: Des weiteren kann jede Klasse als Variable definiert werden um eine Referenz derselben zu erfassen.

Soviel zu den Typen selbst. Alle lassen sich den Rechnungen die für diesen Typ üblich sind unterwerfen, so daß man z.b. zwei Ints mit + verknüpfen kann, zwei Bools kann man mit == vergleichen, etc.
Sämtlich Operatoren finden sich in der Datei Object.uc, hier sind auch Dinge wie z.b. Dot für das Skalarprodukt aufgelistet, auf die man vielleicht nicht sofort kommt.

Schlüsselwörter, etc.:
Struct: ein struct in dem man verschiedene Typen zu einem zusammenfassen kann. Ein paar gute selbsterklärende Beispiele sind in Object.uc.
Enum: Eine Aufzählung, ähnlich einem Struct definiert man einen neuen Typ, nur das man dessen Werte im enum auflistet, hierzu findet man beispiele z.b. in Actor.uc wie EDrawType und ERenderStyle.

Ich hoffe ich habe nichts wichtiges vergessen, nun zur Deklaration der Variablen:
var[([Gruppe])]|local [config][const] Typ Name[ArrayCount];

Ich habe hier der Einfachheit halber nur die wichtigsten Dinge aufgelistet. Im einzelnen bedeutet das:
var|local: Damit beginnt die Deklaration, in Klassen wird var, in Funktionen local verwandt.
(Gruppe): Optional kann die Variable editierbar sein, dies geschieht durch setzen der () hinter var, dann kann man diese Variable im Ed in den Properties sehen. Setzt man nur () erscheint die Variable in der Gruppe der jeweiligen Klasse, gibt man einen Namen wie z.b. (Display) an erscheint die Variable in der entsprechenden Gruppe in den Properties.
const: eine Konstante, eine solche Variable kann nicht (direkt) geändert werden. Manche wichtige Variablen der Engine sind const wie z.b. Location, diese können aber evtl. über spezielle funktionen wie SetLocation dennoch geändert werden.
config: Deklariert man eine Variable als config, so wird diese in einer .ini gespeichert wenn man für diese Klasse die Konfiguration speichert.
[ArrayCount]: Wenn in [] eine zahl steht wird diese Variable als Array definiert, ArrayCount gibt die Zahl der Elemente an, die von 0 -> ArrayCount-1 gehen.

Soviel zu Variablen. Jetzt zu Klassen. (wer nicht weiß was das ist, erfährt es nachher im Abschnitt OOP)
In UScript ist jede Klasse eine .uc Datei. Dabei MUß der Name der .uc Datei mit dem der Klasse übereinstimmen. Am Anfang einer Klasse steht immer dies:
class Name expands|extends Parentklasse [abstract][native][nativereplication][config[(XXX)]];
Name: ist der Name der neuen Klasse und Parentklasse der der übergeordneten Klasse. Es ist egal ob dazwischen expands oder extends steht, das hat die gleiche Wirkung.
Die Schlüsselwörter dahinter bewirken:
abstract: Die Klasse wird niemals selbst verwandt, sondern nur untergeordnete Klassen.
native/nativereplication: Diese Klasse hat Variablen/Funkionen/Funktionalität, die nicht allein im UScript code liegen.
config/config(Datei): Die Klasse läßt sich per ini Datei konfigurieren. d.h. einige Variablen sind als config deklariert und wenn die Konfiguration gespeichert wird werden diese mitgespeichert.
Gibt man keine Datei an, so wird in der unrealtournament.ini gespeichert.

Die Defaultproperties:
Am Ende jeder Klasse steht ein Absatz der minimal so aussieht:


defaultproperties


Hier können, müssen aber nicht, einige Variablen anders als ihr normaler defaultwert (0, "", '', false) eingestellt werden. Man kann z.b. Zahlen vorgeben, strings initialisieren, etc.
Dies geschieht indem man die Variable ganz normal zuweist:
irgendeinint=1500
Dabei ist es wichtig, daß hier keine Leerzeichen zwischen den werten stehen!
Wird eine Klasse instanziiert werden jetzt die Werte der defaultproperties verwandt, man kann hier auch Werte von übergeordneten Klasse einstellen, es werden dann jeweils die der Werte verwandt, die in der am meisten untergeordneten Klasse bis hin zu der die instanziiert wird verwandt.
Wenn Variablen in einer config Datei gespeichert wurden, so werden diese anstelle der Defaultproperties verwandt.
Werden andere Werte im Editor selbst eingestellt, so werden diese verwandt.

Funktionen und States:

Funktionen:
Eine Funktion wird folgendermaßen definiert:
[final][static][native][singular] function [Typ] name([out][optional] typ parameter, ...)Ich habe mich hier auf das wichtigste beschränkt.
Ein Typ als wiedergabewert kann, muß aber nicht angegeben werden, die parameter können außer ihrem Typ und Bezeichner zwei Schlüsselwörter erhalten. Wenn optional angegeben wird ist dieser Parameter optional und muß nicht übergeben werden. Wird Out angegeben dann werden alle Änderungen, die die funktion mit der variablen macht auch auf die variable übertragen die übergeben wurde und nicht nur auf den parameter. So kann man effizient mehrere Werte zurückgeben.
Des weiteren gibt es einige Wörter die vor der function stehen, sie haben folgende Wirkung:
final: Die funktion kann nicht in einer untergeordneten Klasse überschrieben werden.
static: Die funktion kann direkt auf eine Klasse angewandt werden und muß nicht auf einer Instanz dieser Klasse angewant werden.
native: Die funktion ist nicht in UScript, sondern in c++ geschrieben, kann aber in UScript verwandt werden. native functions sind meistens final.
singular: Wenn die funktion aus sich selbst heraus (oder über eine andere funktion, die von dieser aufgerufen wurde) aufgerufen wird, wird sie nicht mehr aufgerufen. Dadurch werden unendliche Schleifen verhindert.

Die function steht dann in . Wenn ein Wert zurückgegeben werden soll geschieht dies mit return wert; , man kann jede funktion durch return; vorzeitig verlassen.

Wenn beim funktionsaufruf nicht nur der funktionsname, sondern Super.funktion(); aufgerufen wird, dann wird die funktion der direkt übergeordneten Klasse aufgerufen, mit Super(Klasse).funktion(); läßt sich die funktion der angegeben darüberliegenden Klasse aufrufen. Mit Global.funktion(); ruft man die Non-State Version der funktion auf.

States:
States sind neu in UScript. Es gibt immer einen bestimmten State in dem sich ein Objekt befindet, wenn kein besonderer angegeben ist, ist dies der State '' (global). In einem State können funktionen neu geschrieben werden, es können bestimmte Abläufe ausgeführt werden, etc.
Zur Definition:
[auto] state[()] Name [expands anotherstate]
Also mit dem state Schlüsselwort definiert man einen State. Die Klammern () dienen wieder dazu diesen editierbar zu machen (er taucht dann unter InitialState auf), wenn man auto angibt geht UScript automatisch am Anfang des Spiels in diesen State. Name bezeichnet den State, und mit expands können funktionen des anderen States übernommen werden.
Der Statecode steht dann in .
Man kann dann am anfang des States eine Zeile mit ignores fn1, fn2, ...; schreiben. Alle Funktionen die hier aufgelistet sind, werden nicht ausgeführt, wenn man sich in diesem State befindet. Man kann jetzt in diesem State Funktionen neu schreiben, die man in seiner Klasse schon einmal hat und dort natürlich etwas anderes bewirken. Wenn man hieraus Global.funktion(); aufruft wird die funktion ausgeführt, die im nicht-state code der Klasse steht.
Wenn man in einen State geht wird die funktion BeginState() des states automatisch ausgeführt, beim Verlassen des States wird EndState() ausgeführt.
Man kann jederzeit in einer Klasse GotoState(name NeuerState); benutzen um in einen anderen State zu gelangen. Zum verlassen eines States benutzt man GotoState('');
In einem State kann Code stehen, der nicht in einer funktion steht !
Dafür schreibt man nach den funktionen seines States das label Begin: und dann den Code. Dieser wird beim Eintritt in den State ausgeführt. Man kann auch selbst Labels definieren und diese per Goto anspringen.
Das wichtigste ist, daß hier und nur hier latente funktionen ausgeführt werden. latente funktionen sind funktionen bei denen die ausführung des codes hier eine bestimmte Zeit aussetzt. Am einfachsten ist der Befehl sleep(time); der einfach die unter time angegebene Zeit wartet. Diese funktionen sind z.b. bei Waffen wichtig, um eine konstante Schussanzahl/Sekunde zu gewährleisten. Der Grund für diese Beschränkung, daß man sie nur hier aufrufen kann, ist auch klar: latente funktionen sind nicht gerade performance fördernd.

Ablaufkontrolle:
Hier wird wohl nicht viel neues stehen also:
For - Schleifen
For (Anfangswert;Bedingung;Befehl):
Der Code der For Schleife steht in , wenn man nur einen Befehl hat, kann man die weglassen.
Wie in jeder For-Schleife gibt es einen Anfangswert, dann eine Bedingung, die bei jedem Durchlauf überprüft wird und einen befehl der ausgeführt wird. Die einfachste sieht dann so aus:

for(i=0;i<Wert;i++)
Befehl();

Do-Until Schleifen
Do until (Bedingung);
Die Befehle werden solange ausgeführt, bis die Bedingung wahr ist.

While - Schleifen
While (Bedingung)
Während die Bedingung wahr ist, werden die Befehle ausgeführt.
break
Mit break; springt man sofort aus der aktuelle Schleife.
if
if (Bedingung) [ else ]
Wenn die Bedinung wahr ist, werden die Befehle ausgeführt, ist sie falsch werden die Befehle unter else ausgeführt. Bei else kann auch if stehen also: else if (noch eine Bedingung)

Switch
Switch(Variable)
case Wert:
Befehle();
break;
[case Wert:
Befehle();
break;]
[default:
Befehle();
break;]

Unter Switch wird eine Variable angegeben, hat diese einen unter Case angegebenen Wert so werden die Befehle ausgeführt, mit break wird die Ausführung beendet. Stimmt keiner der Werte mit der Variable überein, dann werden die Befehle unter Default ausgeführt.


Soviel erstmal generell zu UScript. Man muß nicht alles was etwas spezieller war verstanden haben, aber wer nichtmal über den float herausgekommen ist, sollte doch erstmal etwas anderes machen Für die englischsprechenden ist dieses Dokument auf jeden Fall lesenswert! Ich hoffe mal nichts wichtiges vergessen zu haben. Weiter gehts mit OOP.


JohnMcL


ENDE

 

Einleitung:
Diejenigen, die OOP (ObjektOrientierte Programmierung) schon kennen, können diesen Abschnitt relativ schnell überlesen, da die OOP von UScript relativ eindeutig, einfach und leicht zu verstehen ist. Für die, die noch nichts von OOP wissen versuche ich erstmal zu erklären was OOP ist.


Grundbegriffe:
Kapselung / Klasse
Ein Grundbegriff von OOP ist die Klasse. Das ist auch das was ihr in UScript macht. Ihr schreibt euren Code in Klassen. Eine .uc Datei ist eine Klasse.
In einer Klasse sind Eigenschaften und Methoden gekapselt, übersetzt heißt das in eurer Klasse definiert ihr am Anfang ein paar Variablen. Diese Variablen sind Eigenschaften dieser Klasse.
Dann gibt es die Methoden eurer Klasse, das sind die Funktionen die ihr in die Klasse schreibt.
Eine Klasse besteht als aus einer Sammlung von Eigenschaften (Variablen) und Methoden (Funktionen).
Vererbung
Eine Klasse allein ist nicht der Sinn von OOP, die Funktionalität entsteht erst durch die Vernetzung der einzelnen Klassen durch eine Baumstruktur (in UScript), d.h. jede Klasse hat eine (und nur eine) übergeordnete Klasse, kann aber mehrere untergeordnete Klassen haben.
Wenn jetzt eine Klasse einer anderen untergeordnet ist, erbt sie alle Eigenschaften und Methoden der übergeordneten Klasse. Im Klartext bedeutet das, wenn eine Klasse einer anderen untergeordnet ist, dann ist das so, als ob alle Variablen und Funktionen der übergeordneten Klasse in der untergeordneten stehen. Sie stehen aber nicht drin. Man kann dann in dieser Klasse alle Funktionen der Klasse selbst aber auch die der übergeordneten Klasse verwenden. Das geht hoch bis zur obersten Klasse (bei UT object.uc), da ja die übergeordnete Klasse alles auch von dieser übergeordneten Klasse erbt und die von der ihr übergeordneten wieder, usw.
In UScript wird eine Klasse am einfachsten so definiert: class Dings expands Parent;Das was hinter expands steht ("Parent") ist die übergeordnete Klasse, von der diese Klasse alles erbt.

Der Sinn dabei ist, dem ganzen eine logische Struktur zu geben. D.h. in der obersten Klasse stehen die allgemeinsten Dinge und je weiter nach unten man in der Struktur geht, desto spezieller wirds.
In der Klasse object.uc sind z.b. die funktionen +, -, *, / definiert. Das sind sicherlich Dinge, die man beim Programmieren in jeder Klasse braucht.
In der Klasse Ammo ist die Variable AmmoAmount definiert, da man nur in Ammo und allen untergeordneten Klassen, also allen Ammos wissen muß wieviel Ammo denn jetzt der Spieler noch hat.
Für ein Object (also object.uc) ist das unwichtig, denn z.b. die Menus in UT sind auch Objects, da wäre es verschwendet einen Ammoamount zu wissen.
Polymorphie
Polymorphie bedeutet in etwa "Vielseitigkeit", d.h. eine Funktion kann in verschiedenen Klassen anders aussehen. Ich versuche auch hier ein möglichst passendes Beispiel:
Ich möchte dazu auf die Klasse CTFGame verweisen, das ist der Capture the flag spielmodus. In der Definition ist angegeben expands TeamGamePlus (das ist TDM), d.h. ohne Veränderungen würde CTFGame alles von TeamGamePlus erben und wäre damit TDM. Nur die Änderungen die in CTFGame vorgenommen wurden machen aus dem TDM ein CTF.
Hier zeigt sich der erste Vorteil von OOP, man braucht für CTF nicht ein komplettes CTF scripten, wenn man schon ein gesamtes TeamGame hat (ctf spielt man ja in teams), das einzige was man tun muß, ist Flaggen einzubauen und mit diesen zu punkten und schon hat man CTF. Nun etwas praxisnaher:


function Logout(pawn Exiting)


Dies ist ein Ausschnitt aus CTFGame. Fangen wir von vorne an:
function Logout(pawn Exiting) Die function Logout wird dann aufgerufen, wenn ein Spieler sich aus dem Spiel ausloggt, d.h. nicht mehr am spiel teilnimmt - disconnected.
if ( Exiting.PlayerReplicationInfo.HasFlag != None )
CTFFlag(Exiting.PlayerReplicationInfo.HasFlag).Sen dHome();
Das muß man nicht genau verstehen, aber man kann es lesen. Übergeben wird als Parameter ein Pawn (irgendeine "Figur" im Spiel -> Bots, oder echte Spieler) das Exiting heißt, also das Spiel verläßt. Dann wird überprüft, ob dieser Spieler die Flagge hat (HasFlag) und wenn das so ist, soll er sie ja nicht mit aus dem Spiel mitnehmen (Superwitz ), also wird diese Flag zurück zur Base gesandt (SendHome).
Dies passiert lediglich bei einem Logout in einem CTFGame, der Logout code aus TDM enthält diese Zeile nicht, da es dort auch keine Flaggen gibt.
Eine Zeile fehlt noch: Super.Logout(Exiting);
Da man ja dadurch, daß man die function Logout hier neu geschrieben hat um sie für CTF tauglich zu machen nun eine andere Logout function hat, als in TDM hat man hier die Überprüfungen, die in TDM gemacht werden "vergessen" (z.b. die Größe des Teams des verlassenden Spielers herabsetzen), dazu dient diese Zeile, Super ist immer die darüberliegende Klasse, mit Super.Logout ruft man nun noch die Logout version von TeamGamePlus auf um alle diese Überprüfungen einzubauen.
Man konnte also so die Logout function 1. neu definieren und 2. den Inhalt der alten bewahren.
Im großen geschieht das bei OOP mit der ganzen Klasse auch, durch das hinzufügen der neuen funktionen (oder verändern der alten) fügt man neue Dinge hinzu, bewahrt sich aber das bisher geleistete.

Ich denke es ist halbwegs klar geworden warum man das ganze macht, man spart sich damit sehr viel Arbeit und es ist einfacher. In der Anwendung merkt man das besser. Wenn man UScript (oder einfach OOP) kann weiß man OOP zu schätzen


Einen Begriff hätte ich noch gerne erklärt und zwar:


Instanzen
Bis jetzt sollte man verstanden haben was eine Klasse ist und was darin vorgeht. Jetzt bleibt nur die Frage: Wie kriege ich diese Klasse ins Spiel ?
Man instanziiert sie, d.h man lädt sie in den Speicher und bindet sie damit in das Spiel ein. Bei UT geschieht dies verschieden. Da wären zuerst mal alle GameInfo klassen (das sind die Spielmodi), logischerweise braucht man in einer Map nur einen Spielmodus, wenn ein neues Spiel in der Map gestartet wird, wird dabei automatisch die passende GameInfo klasse instanziiert und somit beginnt das Spiel.
Wenn man Actors in eine Map setzt (im ED) werden diese auch beim Mapbeginn ins Spiel gebracht, solche Dinge sind also für Coder relativ uninteressant. Das wichtige ist das dynamische Instanziieren im Code. Man hat z.b. einen Trigger gecoded und setzt diesen im Ed in ein Level, wenn dieser getriggeret wird, dann soll ein Effekt (explosion, leuchten , was weiß ich) erscheinen, das ist zwar ein doofes Beispiel, aber das ist egal. Man hat jetzt seine Effektklasse gecoded, die einfach eine tolle Explosion erzeugt, nun wollen wir im Trigger diese Klasse ins Spiel bringen und müssen also eine Instanz erzeugen. Für alle Actors (unterklassen von Actor, das sind GameInfos, Mutatoren, Waffen , alles was in einer Map ist) ist in Actor die function Spawn() definiert.
Spawn dient zum instanziieren einer Klasse in einer Map. Dazu müssen wir Spawn aufrufen und dann als Parameter die Klasse übergeben, die wir instanziieren wollen.
Ich verweise hier auf den Variablentyp Class am Anfang von Part II, denn dazu dient dieser Typ. Wollen wir einen Effekt ins Spiel bringen sieht das so aus:

var class EffectClass;
var Effects MyEffect;

function MakeEffect()


Am Anfang sind zwei Variablen definiert, die erste verweist auf eine Klasse für den Effect, die zweite verweist auf einen bestimmten Effekt (den, den wir ins Spiel bringen).
In der Funktion MakeEffect() wird zuerst der EffectClass eine Klasse zugewiesen (die es natürlich auch geben muß), dann wird im zweiten Schritt Spawn diese Klasse übergeben, und diese wird dann durch den Aufruf von Spawn instanziiert. Spawn gibt als Rückgabewert eine Referenz auf diesen Actor im Spiel zurück, auf den wir jetzt mit MyEffect zugreifen können.
Für Objects (z.b. Menus, etc.) gibt es die function New, bzw. bei allen Menus vorgefertigte funktionen in UScript.
Weiter geht's, jetzt aber wirklich mit:


von.JohnMcL
 
 ENDE
 
 
 

Bevor man anfängt ...
... irgendetwas zu coden, sollte man sich erstmal Gedanken darüber machen, was man Coden will, und wie man das am besten erreicht.
Mein Beispiel für dieses Tut einen Q3Health Mutator, d.h. nach dem Respawnen beginnt man mit Health 130 und sobald irgendjemand im Spiel mehr als 100 Health hat (auch nach Keg O Health, etc.) wird heruntergezählt.
Da ich ja schon ein bißchen coden kann (), hab ich dafür grad mal 5 Minuten gebraucht und keine Compiler errors gehabt. Insgesamt ist das nämlich relativ simpel wenn man weiß wie's geht. Um einen Mutator zu coden sollte man erstmal wissen was genau (vom Code her) Mutators sind, wie sie funktionieren und wie man Mutators richtig coded.


Wie funktioniert ein Mutator:
Ein Mutator dient (codeseitig) dazu bestimmte Funktionen, die normalerweise nur von dem normalen Spiel aufgerufen werden, nochmal von jedem Mutator "bearbeiten" zu lassen, bzw. zu modifizieren, so daß im Spiel Änderungen auftreten.
Das funktioniert so, daß im Spiel ein Hauptmutator - der BaseMutator - ist. Dieser hat eine Variable NextMutator, und jeder andere Mutator hat auch so eine Variable. Die Variable NextMutator enthält jeweils den nächsten Mutator im Spiel, so daß man eine ganze Kette von Mutatoren erhält. So etwas nennt man Linked-List, das ist etwas sehr praktisches, das es in UT noch öfters gibt.
Wenn jetzt im Spiel z.b. die funktion ScoreKill auftritt (da werden punkte für einen Kill vergeben) dann wird diese funktion im Spiel ganz normal ausgeführt. Danach wird die funktion im BaseMutator aufgerufen. Dieser macht damit was er will und ruft die Funktion dann in dem Mutator auf der als nächster unter NextMutator in der Liste ist. Dieser Mutator darf dann wiederum mit der Funktion machen was er will (mutieren eben, das ist der Sinn von Mutatoren) und ruft dann die Funktion im nächsten Mutator der Liste auf. So darf jeder Mutator im Spiel einmal diese Funktion "bearbeiten", das einzige was dabei wichtig ist, das ein Mutator die Funktion im jeweils nächsten Mutator aufruft, da sonst der Rest der Liste die Funktion nicht mehr kriegt und somit ausgeschaltet ist !


Los geht's - Health 130:
Jetzt aber wirklich. Also erstmal in einem neuen Package eine neue Klasse unterhalb von Mutator erstellen und jetzt mit dem ersten Punkt beginnen. Das wäre, das man immer mit 130, statt 100 Health startet, dazu benötigt man erstmal eine Funktion die aufgerufen wird, wenn ein Player respawned.
Glücklicherweise gibt's dazu ModifyPlayer(), erstmal kopieren wir die Version aus Mutator.uc, das erspart schreibarbeit und man hats auch gleich richtig


function ModifyPlayer(Pawn Other)




Der Kommentar weist darauf hin, daß diese Funktion jedesmal, wenn ein Spieler restartet aufgerufen wird, also genau das, was wir wollen. Die beiden Zeilen danach sind eben dazu da, die Funktion auch im nächsten Mutator in der Liste aufzurufen und so dessen Funktionalität zu gewährleisten.
Uns wird der Parameter Other vom Typ Pawn übergeben, logischerweise ist das das Pawn, das restartet. Wenn mans nicht schon so weiß, dann kann man in der Klasse Pawn nachsehen, dort befindet sich der Eintrag var() travel int Health; Das travel können wir momentan einfach ignorieren, wichtig ist nur, daß die Variable Health dort definiert ist, wofür die ist kann man sich ja denken. Also erhöhen wir das Health jetzt mal auf 130. Dazu fügen wir folgendes ein und erhalten:


function ModifyPlayer(Pawn Other)




Other wird uns übergeben, da in der Klasse Pawn Health definiert ist, können wir mit Other.Health darauf zugreifen. Zuerst ist es ratsam zu überprüfen, ob das Health kleiner als 130 ist, da ja andere Mutatoren das Health erhöht haben könnten und der Sinn unseres Mutators ist es das Health auf 130 zu erhöhen und nicht auf 130 abzusenken. Wenn das so ist wird der Variable Health in Other der Wert 130 zugewiesen. Das war's soweit.
Health Absenkung
Nun wollen wir das Health noch pro Sekunde um einen Punkt absenken. Am einfachsten ist es dazu eine Funktion zu haben, die jede Sekunde aufgerufen wird. Dazu gibt es in UT die Funktion Timer(). Diesen Timer müssen wir erstmal starten. Dazu definieren wir die Funktion PostBeginPlay():


function PostBeginPlay()




PostBeginPlay ist ein Event, events sind Funktionen, die von der Engine aufgerufen werden und nicht nur aus UScript. PostBeginPlay wird immer einmal kurz nach Spielbeginn aufgerufen und ist geeignet solche Initialisierungen zu machen wie hier. Wir rufen nur die Funktion SetTimer auf. Diese hat nur zwei Parameter: SetTimer( float NewTimerRate, bool bLoop );
Mit dem ersten legen wir die Häufigkeit des Timers fest (hier 1.0 = 1 mal pro Sekunde), der zweite Parameter dient dazu, daß der Timer immer wieder im gleichen Abstand aufgerufen wird und nicht nur einmal nach der verstrichenen Zeit. Da wir jede Sekunde das Health absenken wollen setzen wir hier true.

Als nächsten kommt die Funktion Timer(). Hat man einen Timer mit SetTimer wie oben gesetzt, wird in diesem Actor die Funktion Timer() aufgerufen. Bei uns sieht sie dann so aus:


function Timer()




Was passiert hier jede Sekunde ?
Zuerst wird die lokale Variable P vom Typ Pawn definiert, damit man eine Referenz auf ein Pawn erhalten kann. Dann folgt eine For-Schleife. Eine Schleife die ungefähr so aussieht, dient dazu alle Elemente einer LinkedList (s. vorher) zu durchlaufen. Dazu muß man die Elemente der For-Schleife einfach genau betrachten. Zuerst wird der Startwert mit Level.PawnList festgelegt. Level ist das jetzige Level und PawnList ist eine Variable vom Typ Pawn die in der Klasse LevelInfo definiert ist, sie enthält den ersten Wert dieser Liste in der alle Pawns im Spiel aufgelistet sind. Dann folgt die Abbruchsbedingung mit P!=None, solange also das Pawn P nicht None ist wird die Liste durchlaufen. P ist logischerweise am Ende der Liste None, denn irgendein Pawn muß ja auch das letzte in der Liste sein. Dann die Anweisung: P=P.NextPawn, also wird P (dem Pawn das jetzt als nächstes bearbeitet werden soll) P(dem Pawn das gerade bearbeitet wird).NextPawn(das nächste) zugewiesen, so daß man der Reihe nach die Liste durchgeht.
Bei jedem Durchlauf hat man dann ein Pawn P aus dem Spiel. Dabei überprüft man zuerst, ob denn Health noch über 100 ist, da man das Health ja nicht weiter als 100 absenken will, ist P.Health als noch größer als 100, wird 1 Healthpunkt abgezogen. Dies geschieht durch das -- hinter P.Health.

Das war's. Der erste funktionierende Mutator. Es gibt sicher noch einige Sachen, die man hätte schöner machen können, aber das muß ja jetzt nicht sein.
Um ihn zu testen startet UT mit der option ?mutator=package.class
Falls ihr das Package z.b. MyFirstMutator und die Klasse Q3Health genannt habt startet z.b.:
unrealtournament.exe dm-deck16][?mutator=MyFirstMutator.Q3Health
Wenn der Mutator im Spiel in der Mutatorenliste erscheinen soll, muß eine .int Datei erstellt werden. Nehmt dazu einfach eine bestehende .int Datei uns kopiert die passenden Zeilen da raus.
Zur Vollständigkeit nochmal der gesamte Code der Klasse Q3Health (so hab ich's genannt):

// ================================================== ==========
// Q3Health.Q3Health
// ================================================== ==========

class Q3Health expands Mutator;

function ModifyPlayer(Pawn Other)


function PostBeginPlay()


function Timer()


defaultproperties



Letzte Worte:
Für eventuelle Nachfragen stehe ich (und andere) immer im UnrealEd.de forum zur Verfügung (falls es das dann noch gibt). Ich beantworte diese auch gerne, solange diese sich nicht selbst durch sorgfältiges Lesen des Tuts (dazu hab ich's geschrieben), Nachdenken oder simples Ausprobieren beantworten.
Wer nur Code abschreibt, lernt auch nix. Falls man den Mutator versteht und weitermachen will, kann man ja erstmal mit dem Code rumspielen und dann ein paar neue kleine Sachen machen. Eine TC jetzt anzufangen weil der Mutator funktioniert ist übertrieben
Wer meint nen echten Fehler (rechtschreibfehler interessieren mich nicht) gefunden zu haben, kann mir ja Bescheid sagen, bei dem ganzen Text weiß ichs auch nicht mehr ...


von.JohnMcL

 

In diesem Tutorial erklärt euch GiGGsY, wie man eine eigene Waffe verscripten muß, um sie in Unreal Tournament einsetzen zu können. Mit vielen ausführlichen Scriptbeispielen wird erklärt, wie man vorgehen muß. Klassifizierungen, Animationen und Texturierungen der Skins werden erklärt. Aber auch das Einbinden der 3D-Modelle, sowie deren Verscriptung wird verdeutlicht. Mit dem praktische Allzweck-Script, welches enthalten ist, läßt sich praktisch jede Waffe steuern. GiGGsY hat uns das alles schöne vorbereitet. Ihr könnt es einfach markieren und kopieren (Copy/Paste) und schon ist die Halbe Arbeit getan, der Rest ist individuelle Abänderung und Eigencreativität.

Was wir brauchen:
  • Ein 3D-Design Programm (3DSMAX r3)
  • Ein fertiges 3D-Model, dass der einer Waffe ähnelt (Dagger)
  • Ein Zeichenprogramm zum Texturen ändern (Photoshop,Paintshop,...)
  • Ein Script Programm (WoTGreal, Notepad)
  • MS-DOS Eingabeaufforderung (außer ihr habt ein alternativ-Programm zum kompilieren)
  • Englischkenntnisse wären brauchbar

Das, was in den Klammern steht ist dass, was ich „besitze“ bzw. empfehle. Ich werde mich hier also auf diese Programme konzentrieren. Die Waffe die wir erstellen heißt bei mir „Dagger“, wie sie bei euch heißt ist mir egal, nur wenn ihr in meinem Script das Wort „Dagger“ lest, müsst ihr es halt mit eurem Namen der Waffe austauschen. Da wir gerade beim Script angekommen sind: dieses stammt nicht von mir! Es ist eine modifizierte Version des Chainsaw-Scripts.




1.) Erkundung des Scripts:
Am Anfang steht die Interpretation des Scripts, d.h. man soll erkennen welcher Code für was gut ist. Vorerst ist für uns nur der Importteil des Scripts interessant, sprich die #exec-Teile.


a) Die Klassifizierung:
Ich weiß, dass das jetzt nicht der korrekte Ausdruck dafür ist, aber naja...wir sind hier ja nicht in der Schule Die Klassifizierung dient dazu, um UT zu sagen wo man die Waffe findet, bzw. welche Eigenschaften man schon vordefiniert hat (für mehr Infos siehe Coding-Tut von JohnMcL: http://www.unrealed.de ).

//================================================== ==================
// Dagger.
//================================================== ==================
class Dagger extends TournamentWeapon;

Wenn ihr nicht wisst dass ein „ // “ einen Kommentar einleitet, dann empfehle ich unbedingt das gerade erwähnte Tutorial...


b) Der Animations-Teil:
Meiner Meinung nach ist das der wichtigste Teil. Nehmen wir den Dolch als Beispiel, wenn man jetzt eine Schwingbewegung mit der Waffe macht, so berechnet nicht die Engine jedes mal einen Schwung, sondern spielt eine Animations-Sequenz ab. Dies sollte einem klar sein bevor man mit der Waffe weitermacht.



#exec MESH IMPORT MESH=daggerM ANIVFILE=MODELS\dagger_a.3D DATAFILE=MODELS\dagger_d.3D X=0 Y=0 Z=0
#exec MESH ORIGIN MESH=daggerM X=0 Y=0 Z=-10 YAW=0 PITCH=0 ROLL=0
#exec MESH SEQUENCE MESH=daggerM SEQ=All STARTFRAME=0 NUMFRAMES=75
#exec MESH SEQUENCE MESH=daggerM SEQ=Select STARTFRAME=2 NUMFRAMES=16
#exec MESH SEQUENCE MESH=daggerM SEQ=Idle STARTFRAME=16 NUMFRAMES=1
#exec MESH SEQUENCE MESH=daggerM SEQ=Still STARTFRAME=16 NUMFRAMES=1
#exec MESH SEQUENCE MESH=daggerM SEQ=Swipe STARTFRAME=17 NUMFRAMES=14
#exec MESH SEQUENCE MESH=daggerM SEQ=Jab STARTFRAME=51 NUMFRAMES=12
#exec MESH SEQUENCE MESH=daggerM SEQ=Jab2 STARTFRAME=52 NUMFRAMES=12
#exec MESH SEQUENCE MESH=daggerM SEQ=Down STARTFRAME=67 NUMFRAMES=8


In der 1.Zeile sieht man, welche Dateien verwendet werden: die dagger_a.3D und die dagger_d.3D, diese werden noch im Laufe dieses Tutorials entstehen. Wenn ihr später euer Script auf dieses „anpasst“ übernehmt bitte auch das „M“ hinter dem „Dagger“,d.h. wenn eure Waffe „Axt“ heißt so nennt den Mesh und die Animations-Sequenzen „AxtM“ um eventuelle Fehler zu vermeiden. Hier erkennt man jetzt sehr gut, welche Animation wieviele Frames hat und wann diese beginnen.

Eine Auflistung der Bedeutungen der Animations-Sequenzen:
  • All: eh klar oder?
  • Select: wenn man die Waffe auswählt, bzw. „zieht“
Idle: soviel ich mitbekommen habe, ist das der „Leerlauf“
  • Still: wie Idle
  • Swipe: ist die Sekundär-Attacke
  • Jab: ist die Primär-Attacke
  • Jab2: wenn man auf den Feuerknopf (Primär) bleibt (=Dauerfeuer)
  • Down: wenn man die Waffe deaktiviert, bzw. wieder „einsteckt“

Mehr zu den Animations-Sequenzen im Export-Teil.


c) Texuten/Skins:
Jede Waffe braucht eine Textur, auch ein Skin genannt.


#exec TEXTURE IMPORT NAME=JdaggerM1 FILE=MODELS\dagger1.PCX GROUP=Skins LODSET=2
#exec TEXTURE IMPORT NAME=JdaggerM2 FILE=MODELS\dagger2.PCX GROUP=Skins LODSET=2
#exec MESHMAP SCALE MESHMAP=daggerM X=0.005 Y=0.005 Z=0.01
#exec MESHMAP SETTEXTURE MESHMAP=daggerM NUM=0 TEXTURE=JdaggerM1
#exec MESHMAP SETTEXTURE MESHMAP=daggerM NUM=1 TEXTURE=JdaggerM2


Dies dürfte erfahrenen Decoration-Modelern bekannt vorkommen. Es funktioniert hier genauso...
Für alle die es noch nicht wissen:

  • Texture Import Name: ist der Name den die Textur dann im UED haben wird
  • File=Models\daggerX.PCX: diese Textur wird importiert
  • NUM=0: das müsst ihr in 3DSMAX festlegen, komme später darauf zurück

Solltet ihr hier auf Neuland stoßen, empfehle ich euch ein paar Meshimport-Tutorials rein zu ziehen! Siehe http://www.unrealed.de ,unter Tutorials werdet ihr fündig...denn müsste ich das auch noch erklären, würde ich doppelt soviel Zeit brauchen.

Das war vorerst das Wichtigste des #exec-Teils, der Rest wiederholt sich dann immerwieder (für jede Ansicht). Genaueres dazu bitte später...


d) DefaultProperties:
Unter den DefaultProperties stehen eben die.....DefaultProperties. Diese wurden in irgendeiner Überklasse festgelegt, oder eben in diesem Script definiert.



defaultproperties




Um nur einige zu nennen. Wie bereits gewohnt, mehr dazu später wenn wir es brauchen. Ich hoffe ich habe euch vorerst mal einen kleinen Einblick verpasst, was auf uns zukommt, es mag vielleicht am Anfang etwas kompliziert erscheinen, aber ich bin zuversichtlich, dass wir es gemeinsam schaffen können!

Kommen wir nun zum Kapitel 2, dem eigentlichen Start...


2.) Das 3D-Model der Waffe(3DSMAX):
Es stellt sich nun die Frage, ob ich Model mit einem „L“ oder zwei scheiben soll. Das kommt darauf an wie man es ausspricht, da ich aber auch Script und nicht Skript schreibe, werde ich es einfach unter NEUDEUTSCH einordnen und weiterhin mit einem „L“ schreiben...
Genug davon; wir wollen ja „bald“ fertig werden LOL!


a) Grundmodel:
In diesem Programm entsteht unser 3D-Model, die Texturkoordinaten und natürlich auch die Animationen. Eine Waffe kann maximal 8 verschiedene Texturen/Skins verwenden. Diese sollten den Namen Skin0,Skin1,....Skin7 haben, um später beim kompilieren keine Warnungen zu erhalten. Vielleicht noch eine „lustige“ Geschichte dazu: da ich mir eigentlich (fast) alles selber beibringen hab müssen (ich kling schon wie meine ehem. Lateinlehrerin), wusste ich zunächst nichts von meinem Fehler(das mit Skin0,..etc.). Ich hatte also immer diese GRÜNE, ekelige Textur auf meinen Waffen...ich glaube sie heißt ‚Engine.SActor‘ oder so... Es kostete mir ~ ½ Jahr bis ich auf meinen Fehler drauf kam! Jeden Tag verbrachte ich damit, irgendwelche Fehler im Script zu suchen, wo ich natürlich total FEHL am Platz war. Also, macht bloß nicht den selben Fehler! (die Pointe der Geschichte ist ja wirklich GENIAL)


Jetzt haben wir unser fertiges 3D-Model vor uns, alle Skins sind angebracht und richtig bezeichnet. Da wir hier UT-Exporter verwenden werden, brauchen wir aus unserer Waffe KEIN einzelnes Objekt zu machen. (Mit 3DS2UNR wäre das schon erforderlich gewesen.) Ich denke mehr als 8 Einzelteile sollten es nicht sein bzw. können es gar nicht sein, außer man verwendet 2 mal den selben Skin...

b) Animieren:
Wir markieren alle Objekte der Waffe und gehen auf „AUSWAHL SPEICHERN“ und legen eine Datei mit dem Namen „select.max“ an. Diese wird das selektieren der Waffe beinhalten. Die Animation dauert z.Bsp. von Frame 2 – Frame 18.

Nun markieren wir wieder alle Objekte und legen die Datei „jab.max“ an (...Auswahl speichern). Der Keyframe-Regler steht immer noch auf 18. Wir markieren unsere Objekte und löschen alle Keypoints bis auf den letzten(=Frame 18), um die Endposition bei zu behalten. Unter „jab“ versteht man die Primärattacke, „jab2“ ist der „Dauerbetrieb“ der Primärattacke. Nun steht also dem animieren der Primärattacke nichts mehr im Wege. Die Animation dauert z.Bsp. von Frame 19 – Frame 31.

!!! Speichern nicht vergessen !!!


Jetzt öffnen wir die Datei „select.max“. Wir gehen nun genau so vor, wie wir es bei der „jab“-Animation gemacht haben. Alle Keypoints bis auf den letzten löschen, aber jetzt kommts:



1. Markiert den letzten Keypoint und verschiebt ihn auf z.Bsp. Frame 70
2. Speichert die Datei unter „down.max“.

Bahn frei zur Endanimation; wie der Name schon sagt, beinhaltet die „down“-Animation das sogenannte „deselektieren“ wenn ihr versteht was ich meine. Die Animation dauert z.Bsp. von Frame 70 – Frame 80.


Was uns noch fehlt: Falls ihr eine Sekundärattacke einbauen wollt, tut dies jetzt und speichert sie unter dem Namen „swipe.max“. Dabei müsst ihr so wie bei der „jab“-Animation vorgehen...nur die Frames sollten sich nicht überschneiden, wenn also jetzt die „jab“-Animation von Frame 19 – Frame 31 geht, dann geht die „swipe“-Animation von Frame 35 – Frame 60. Hierbei muss man auch wieder aufpassen, dass sich das Ende der „swipe“-Animation nicht mit der „down“-Animation überschneidet.

Alle Animationen übersichtlich zusammen geschrieben, sieht das zirka so aus:

Select : 2-18
Jab : 19-31
Swipe : 35-60
Down : 70-80
------------------
Alle: 0-80



Am besten wäre es, wenn ihr euch das auch so auf einen Zettel aufschreibt (mit euren Werten).

c) Export:
Das ist wohl jetzt der schwierigste Teil...zum erklären. Wenn man es aber verstanden hat, dürfte es kein Problem sein. Wir öffnen die Datei „select.max“, markieren sie und gehen auf EXPORTIEREN. Wenn ihr UT-Exporter noch nicht installiert habt, dann tut das bitte JETZT! (www.unrealed.de) Wir nennen die Datei „dolch_d“, UT-Exporter macht dann eine „*.3D“ Datei daraus. Klickt auf „speichern“ und dieses Menü erscheint:


Unter Frame Range geben wir die Länge der „select“-Animationssequenz ein. Hier in unseren Beispiel ist das 0-18. (man fängt immer mir NULL an!)
„Append animations“ nicht anhaken! Mit OK bestätigen und es dürfte keine Fehl- oder Warnmeldungen geben...

Jetzt öffnen wir die Datei „jab.max“, markieren sie (ich glaub das muss man gar nicht, schreibs aber trotzdem) und gehen wieder auf EXPORTIEREN.



/* Wir sehen jetzt, dass auch eine „dolch_a.3D“ erzeugt wurde.*/

Markiert die „dolch_d.3D“ und klickt auf „speichern“ (wir überschreiben die Datei!) Dann kommen wir wieder ins gewohnte Menü. Wir geben die Frame Range von „jab“ an: 19-31. Dieses Mal MÜSSEN wir „Append animations“ anhaken.


MERKE: Es darf nie eine Lücke entstehen! Nehmen wir als Beispiel den Übergang von „jab“ auf „swipe“:

  • „jab“ geht von 19-31
  • „swipe“ geht von 35-60


Wie wir erkennen können ist da eine Lücke zwischen 31 und 35. Wer diese übersieht kann den ganzen Exportteil nochmal machen! Die Lösung des Problems ist relativ einfach, es gibt 2 Möglichkeiten:

  • wir exportieren „jab“ von 19-34
  • oder wir exportieren „swipe“ von 32-60

Wenn ihr dies stets im Auge behaltet, kann eigentlich gar nichts mehr schief gehen. Da wir bei den restlichen Animations-Sequenzen genau so verfahren müssen, spare ich mir hiermit deren Erklärung.

Ist der Export-Vorgang abgeschlossen, so könnt ihr zum nächsten Kapitel voranschreiten. Wer es bis hierher erfolgreich geschafft hat, der kann eigentlich nicht mehr scheitern...




3.) Script und alles was dazu gehört:
Wir müssen zuerst einmal die Basis für unser Script legen. Startet den „Windows-Explorer“ und legt im UT-Verzeichnis den Ordner „Dolch“ an (oder wie eure Waffe halt heißt). In diesem Ordner legt ihr wiederum zwei Unter-Ordner an: „Classes“ und „Models“. Kopiert/verschiebt die dolch_d.3d, dolch_a.3d und eure Texturen/Skins in den Model-Ordner.



/*Bedenkt bitte, dass die Texturen/Skins vom Format *.PCX sein müssen */
/*max. 256 Farben haben dürfen und deren Größe eine 2er Potenz sein muss! */
/*z.Bsp.: 128*128, 128*256, 256*512,.....*/



Ich hoffe ihr habt das Tool WoTGreal, wenn nicht sofort saugen: WotGreal Download Startet also WoTGreal und fügt folgenden Text ein:



//================================================== ===========================
// ChainSaw.
//================================================== ===========================
class ChainSaw extends TournamentWeapon;


#exec MESH IMPORT MESH=chainsawM ANIVFILE=MODELS\chains_a.3D DATAFILE=MODELS\chains_d.3D X=0 Y=0 Z=0
#exec MESH ORIGIN MESH=chainsawM X=0 Y=0 Z=0 YAW=0 PITCH=0 ROLL=0
#exec MESH SEQUENCE MESH=chainsawM SEQ=All STARTFRAME=0 NUMFRAMES=70
#exec MESH SEQUENCE MESH=chainsawM SEQ=Select STARTFRAME=2 NUMFRAMES=15
#exec MESH SEQUENCE MESH=chainsawM SEQ=Idle STARTFRAME=17 NUMFRAMES=1
#exec MESH SEQUENCE MESH=chainsawM SEQ=Still STARTFRAME=17 NUMFRAMES=1
#exec MESH SEQUENCE MESH=chainsawM SEQ=Swipe STARTFRAME=27 NUMFRAMES=11
#exec MESH SEQUENCE MESH=chainsawM SEQ=Jab STARTFRAME=42 NUMFRAMES=20
#exec MESH SEQUENCE MESH=chainsawM SEQ=Jab2 STARTFRAME=48 NUMFRAMES=13
#exec MESH SEQUENCE MESH=chainsawM SEQ=Down STARTFRAME=62 NUMFRAMES=6
#exec TEXTURE IMPORT NAME=Jchainsaw1 FILE=MODELS\chain1.PCX GROUP=Skins LODSET=2
#exec TEXTURE IMPORT NAME=Jchainsaw2 FILE=MODELS\chain2.PCX GROUP=Skins LODSET=2
#exec TEXTURE IMPORT NAME=Jchainsaw3 FILE=MODELS\chain3.PCX GROUP=Skins LODSET=2
#exec TEXTURE IMPORT NAME=Jchainsaw4 FILE=MODELS\chain4.PCX GROUP=Skins LODSET=2
#exec MESHMAP SCALE MESHMAP=chainsawM X=0.004 Y=0.006 Z=0.012
#exec MESHMAP SETTEXTURE MESHMAP=chainsawM NUM=0 TEXTURE=Jchainsaw1
#exec MESHMAP SETTEXTURE MESHMAP=chainsawM NUM=1 TEXTURE=Jchainsaw2
#exec MESHMAP SETTEXTURE MESHMAP=chainsawM NUM=2 TEXTURE=Jchainsaw3
#exec MESHMAP SETTEXTURE MESHMAP=chainsawM NUM=3 TEXTURE=Jchainsaw4

#exec MESH IMPORT MESH=ChainSawPick ANIVFILE=MODELS\chainpick_a.3D DATAFILE=MODELS\chainpick_d.3D X=0 Y=0 Z=0
#exec MESH ORIGIN MESH=ChainSawPick X=150 Y=-10 Z=0 YAW=0 ROLL=-64
#exec MESH SEQUENCE MESH=ChainSawPick SEQ=All STARTFRAME=0 NUMFRAMES=1
#exec MESH SEQUENCE MESH=ChainSawPick SEQ=Still STARTFRAME=0 NUMFRAMES=1
#exec TEXTURE IMPORT NAME=JChainSawPick1 FILE=MODELS\ChainSaw.PCX GROUP=Skins LODSET=2
#exec MESHMAP SCALE MESHMAP=ChainSawPick X=0.07 Y=0.07 Z=0.14
#exec MESHMAP SETTEXTURE MESHMAP=ChainSawPick NUM=1 TEXTURE=JChainSawPick1

#exec MESH IMPORT MESH=CSHand ANIVFILE=MODELS\Chainpick_a.3D DATAFILE=MODELS\Chainpick_d.3D
#exec MESH ORIGIN MESH=CSHand X=-430 Y=-70 Z=0 YAW=0 ROLL=-64
#exec MESH SEQUENCE MESH=CSHand SEQ=All STARTFRAME=0 NUMFRAMES=1
#exec MESH SEQUENCE MESH=CSHand SEQ=Still STARTFRAME=0 NUMFRAMES=1
#exec MESHMAP SCALE MESHMAP=CSHand X=0.04 Y=0.04 Z=0.08
#exec MESHMAP SETTEXTURE MESHMAP=CSHand NUM=1 TEXTURE=JChainSawPick1

#exec TEXTURE IMPORT NAME=IconSaw FILE=TEXTURES\HUD\WpnCSaw.PCX GROUP="Icons" MIPS=OFF
#exec TEXTURE IMPORT NAME=UseSaw FILE=TEXTURES\HUD\UseCSaw.PCX GROUP="Icons" MIPS=OFF

#exec MESH NOTIFY MESH=chainsawM SEQ=Swipe TIME=0.45 FUNCTION=Slash

#exec AUDIO IMPORT FILE="Sounds\ChainSaw\ChainIdle.WAV" NAME="ChainIdle" GROUP="ChainSaw"
#exec AUDIO IMPORT FILE="Sounds\ChainSaw\ChainPickup.WAV" NAME="ChainPickup" GROUP="ChainSaw"
#exec AUDIO IMPORT FILE="Sounds\ChainSaw\ChainPowerDown.WAV" NAME="ChainPowerDown" GROUP="ChainSaw"
#exec AUDIO IMPORT FILE="Sounds\ChainSaw\SawHit.WAV" NAME="SawHit" GROUP="ChainSaw"


var() float Range;
var() sound HitSound, DownSound;
var Playerpawn LastHit;


function float RateSelf( out int bUseAltMode )


function float SuggestAttackStyle()



function float SuggestDefenseStyle()


function Fire( float Value )


simulated function PlayFiring()



function AltFire( float Value )
{

GotoState('A