Weiche Schatten für Models

Sie kennen das Problem: wenn Ihr Model sich vom Licht in die Dunkelheit bewegt, ändert sich plötzlich der Ambient. Das gleiche geschieht, wenn es aus der Dunkelheit ins Licht geht. Wäre es nicht schön, wenn die Models ihren Ambientwert weich ändern, je nach der Helligkeit im Level? Die Idee hinter dem Code ist einfach: wir setzen unlit = on für unser Model um es unempfindlich gegenüber jeder Lichtquelle zu machen, holen uns den tex_light Wert vom Boden unter dem Model und setzen dann den Ambient entsprechend.

Um den Unterschied zwischen normalen und weichem Shading zu zeigen habe ich 2 Wachen in einem Testlevel plaziert. Einer benutzt die Standard patrol_path Action, der andere die neue patrol_path_ok Action, eine leicht modifizierte Version:

action patrol_path_ok
{
actor_init();
smooth_shadow(); // the only change
// attach next path
temp.pan = 360;
temp.tilt = 180;
temp.z = 1000;
result = scan_path(my.x,temp);
if (result == 0) { my._MOVEMODE = 0; } // no path found

// find first waypoint
ent_waypoint(my._TARGET_X,1);

while (my._MOVEMODE > 0)
{
// find direction
temp.x = MY._TARGET_X - MY.X;
temp.y = MY._TARGET_Y - MY.Y;
temp.z = 0;
result = vec_to_angle(my_angle,temp);

force = MY._FORCE;

// near target? Find next waypoint
// compare radius must exceed the turning cycle!
if (result < 25) { ent_nextpoint(my._TARGET_X); }

// turn and walk towards target
actor_turnto(my_angle.PAN);
actor_move();

// Wait one tick, then repeat
wait(1);
}
}

Wie Sie sehen ruft die zusätzliche Zeile eine kleine Funktion namens smooth_shadow auf. Hier ist sie:

function smooth_shadow()
{
my.unlit = on;
while (1)
{
vec_set(temp, my.x);
temp.z -= 1000;
trace_mode = ignore_me + ignore_passable + ignore_models + ignore_sprites + scan_texture;
trace (my.x, temp); // get tex_light
my.ambient = tex_light / 2.5;
waitt(4); // trace 4 times a second
}
}

Diese Funktion tracet 1000 Quants nach unten, um den tex_light Wert zu erhalten - das ist der Ambient der Shadow Map. Tex_light liegt zwischen 0 und 255, also teilen wir es durch 2.5, um den Ambient des Models im Bereich von 0 bis 100 einzustellen. Falls Sie finden, daß das Model dann zu dunkel ist, benutzen Sie einen Wert kleiner als 2.5.

Um das Test Level zu sehen, kopieren Sie den \smooth Ordner in ihr Gamestudio Verzeichnis, öffnen smooth.wmp, builden und starten. Sie werden 2 Wachen sehen, die demselben Pfad folgen - Sie dürfen raten, welcher das weiche Shading benutzt.

Flickgen

Haben Sie sich jemals gefragt, ob A5 als WDL-Generator benutzt werden kann? Einige User haben gute Partikelgeneratoren geschrieben; man setzt einige Werte, bzw. stellt einige Slier ein und auf Knopfdruck wird eine WDL-Datei erzeugt. Diese muß nur noch in Ihr Projekt eingebunden werden und alles läuft fabelhaft. Wie ist das möglich?

Als erstes müssen Sie einen Raum erstellen und eine gute Kameraposition setzen. Erstellen Sie einige Slider / Knöpfe / was auch immer - diese manipulieren, was Sie sehen. Wenn Ihnen das Ergebnis gefällt, drücken Sie einen Knopf und eine WDL-Datei wird erzeugt. Diese kann Kommentare, etc. enthalten, aber Sie können sicher sein, daß sie genau die Actions / Funktionen enthält, die den Effekt erzeugen. Klingt einfach, nicht wahr? Und so wird es gemacht:

Flickgen steht für "Flickering Light Generator" - er erzeugt Code für flackernde Lichter. Sie können die Farbe (RGB), Reichweite und die Zeit einstellen, die das Licht an bzw. aus ist. Bevor Sie starten, sehen Sie sich die test.wdl Datei im \flickgen Verzeichnis an - sie wurde von Flickgen erzeugt.

Jedes solche Projekt hat seine eigene Main Funktion:

function main()
{
d3d_lightres = 1; // better dynamic lights
level_load (flickgen_wmb);
wait (2);
mouse_init();
}

Die erste Zeile in der Main Funktion erhöht die Qualität der dynamischen Lichter; der Testlevel wird geladen und der Mauszeiger wird angezeigt:

function mouse_init()
{
mouse_map = arrow_pcx;
mouse_mode = 2;
while (1)
{
mouse_pos.x = pointer.x;
mouse_pos.y = pointer.y;
wait (1);
}
}

Mein Testlevel enthält ein Model mit der folgenden Action:

action camera_init
{
my.invisible = on;
my.passable = on;
vec_set (camera.pos, my.pos);
camera.pan = my.pan;
camera.tilt = my.tilt;
}

Ich habe ein Pfeil Model benutzt; ändern Sie seine Position und Ausrichtung, um besser sehen zu können, denn die Kamera richtet sich nach diesem aus. Das Objekt, welches das flackernde Licht erzeugt, ist ein Würfel mit folgender Action:

action flicker
{
my.passable = on;
while (1)
{
my.z = height;
my.lightred = red;
my.lightgreen = green;
my.lightblue = blue;
my.lightrange = distance;
waitt (interval1);
my.lightrange = 0;
waitt (interval2);
}
}

Die Action an sich ist recht simpel, aber alle Variablen darin werden von Slidern kontrolliert; height ist die Höhe der Lichtquelle, red, green und blue sind die jeweiligen RGB-Farbwerte, distance ist der Wert für die Reichweite (lightrange) und interval1 bzw. interval2 sind die "ein" bzw. "aus" Zeiten für das Licht. Und so funktionieren die Slider:

panel adjust_pan // main panel
{
bmap = panel_pcx;
layer = 20;
pos_x = 0;
pos_y = 0;
vslider = 2, 10, 100, slider_pcx, 0, 255, red;
vslider = 22, 10, 100, slider_pcx, 0, 255, green;
vslider = 42, 10, 100, slider_pcx, 0, 255, blue;
vslider = 62, 10, 100, slider_pcx, 1, 100, interval1;
vslider = 82, 10, 100, slider_pcx, 1, 100, interval2;
vslider = 102, 10, 100, slider_pcx, 0, 500, distance;
vslider = 122, 10, 100, slider_pcx, 10, 500, height;
flags = d3d, overlay, refresh, visible;
}

Die erste Definition plaziert den Slider an der Position (2,10), setzt seine vertikale Größe auf 100 Pixel, benutzt die slider_pcx Datei für den Slider selbst und ändert die Variable "red" zwischen 0 und 255. Die anderen Slider funktionieren genauso.

Falls Sie sich Flickgen schon einmal angesehen haben, werden Sie festgestellt haben, daß sie korrekten Werte der Variablen auf dem Bildschirm angezeigt werden. Dafür benutze ich ein einfaches Panel:

panel digits_pan // shows the rgb... values
{
layer = 21; // appears over adjust_pan
pos_x = 0;
pos_y = 0;
digits = 0, 130, 3, univers_font, 1, red;
digits = 20, 140, 3, univers_font, 1, green;
digits = 40, 130, 3, univers_font, 1, blue;
digits = 60, 140, 3, univers_font, 1, interval1;
digits = 80, 130, 3, univers_font, 1, interval2;
digits = 100, 140, 3, univers_font, 1, distance;
digits = 120, 130, 3, univers_font, 1, height;
flags = d3d, overlay, refresh, visible;
}

Der Wert für "red" erscheint an der Position (0,130), benutzt 3 Ziffern in der Schriftart univers_font. Sein Wert wird mit 1 multipliziert. Flickgen hat 2 weitere Knöpfe: Save und Quit. In Wahrheit sind diese Knöpfe zwei kleine Panels - und ihre Definition sind so einfach, wie es eben geht:

panel quit_pan // shows the quit button
{
bmap = quit_pcx;
layer = 21; // appears over adjust_pan
pos_x = 10;
pos_y = 190;
flags = d3d, overlay, refresh, visible;
on_click quit;
}

panel save_pan // shows the save button
{
bmap = save_pcx;
layer = 21; // appears over adjust_pan
pos_x = 10;
pos_y = 160;
flags = d3d, overlay, refresh, visible;
on_click save_file;
}

function quit()
{
exit; // exit the engine
}

Wenn der "Quit" Knopf gedrückt wird, dann wird die zugehörige Quit Funktion aufgerufen. Wie Sie sehen beendet diese Funktion die Engine - das ist was wir wollen, nicht wahr? Schauen wir uns nun die Definitionen für 2 Texte und einige Strings an; wir werden sie gleich brauchen:

text action_txt
{
layer = 21;
pos_x = 55;
pos_y = 176;
font = albertus_font;
string = action_str;
}

text filename_txt
{
layer = 21;
pos_x = 55;
pos_y = 204;
font = albertus_font;
string = filename_str;
}

string action_str = " "; // holds the action name
string filename_str = " "; // holds the file name
string empty_str = ""; // an empty string
string content_str; // holds the content of the file
string indent_str = " "; // 4 spaces here but you can use any other indent value
string temp_str = " "; // just a temporary string used to convert numbers to strings

Wenn Sie auf "Save" klicken, dann wird die etwas häßliche save_file Funktion aufgerufen:

function save_file()
{
action_txt.visible = on; // show the text
str_cpy (action_str, empty_str);
while (str_cmpi (action_str, empty_str) == 1) // make sure that the player has typed something
{
inkey action_str; // show the cursor -> store the input in filename_str
wait (1);
}
filename_txt.visible = on; // show the text
str_cpy (filename_str, empty_str); // clear previous inputs
while (str_cmpi (filename_str, empty_str) == 1) // make sure that the player has typed something
{
inkey filename_str; // show the cursor -> store the input in filename_str
wait (1);
}
waitt (16); // wait a second
action_txt.visible = off; // hide the text
filename_txt.visible = off; // hide the text

Der Text action_txt wird angezeigt und action_str wird zurückgesetzt. Wir möchten den Namen unserer neuen Action in action_str speichern; würden wir mit inkey arbeiten, so könnte der User "ENTER" drücken, ohne etwas einzugeben - und das wäre nicht gut. Wir verhindern dies, indem wir die inkey Anweisung in einer Schleife ausführen: solange action_str == "" (also nichts), wird inkey laufen, bis irgendetwas in dem Feld "Action Name" eingetragen ist. Dasselbe geschieht mit dem Rest des Codes, welches den Dateinamen in filename_str sichert. Die Texte verschwinden dann nach 1 Sekunde.

str_cpy (content_str, empty_str); // clear the string
str_cat (content_str, "// include this file in your main c-script file\n// place any model in your level and attach it the ");
str_cat (content_str, action_str);
str_cat (content_str, " action");
str_cat (content_str, "\n\naction ");
str_cat (content_str, action_str);
str_cat (content_str, "\n{\n");
str_cat (content_str, indent_str);
str_cat (content_str, "while (1)\n");

Der content_str String wird benutzt, um all den Text zwischenzuspeichern, der in unsere WDL-Datei kommt. Bitte öffnen Sie die Datei test.wdl oder eine andere, die mit Flickgen erzeugt wurde, sonst könnte es etwas kompliziert zu verstehen sein. Der String wird zurückgesetzt und dann werden die kommentierten Zeilen angefügt. Die Kommentare sollten den Namen unserer Action enthalten, also hängen wir action_str an. Nun ist Zeit, das Wort "Action" zur 2. Kommentarzeile zuzufügen. Zur Erinnerung: \n ist ein Zeilenumbruch. Nach 2 solcher Zeilenumbrüche schreiben wir "action" gefolgt von einem Leerzeichen und dem Namen aus action_str. Neue Zeile, geschweifte Klammer, neue Zeile. Nun ist die Zeit, unseren indent_str einzufügen, der aus 4 Leerzeichen besteht, dazu kommt der Text "while (1)" und ein weiterer Zeilenumbruch. Sehen Sie all dies in der generierten WDL-Datei? Weiter:

str_cat (content_str, indent_str);
str_cat (content_str, "{\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightred = ");
red = int(red);
str_for_num (temp_str, red);
str_cat (content_str, temp_str);

Wir hängen einen weiteren indent_str an, eine geschweifte Klammer und ein Zeilenumbruch, 2 indent_str Strings und dann fügen wir den Text "my.lightred = " ein. Der Wert, der durch den Slider für "red" gesetzt ist wird in eine Zahl umgewandelt, dann in einen String und an content_str angehängt. Dasselbe geschieht für green, blue und distance - glauben Sie es mir einfach.

str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightgreen = ");
green = int(green);
str_for_num (temp_str, green);
str_cat (content_str, temp_str);
str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightblue = ");
blue = int(blue);
str_for_num (temp_str, blue);
str_cat (content_str, temp_str);
str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightrange = ");
distance = int(distance);
str_for_num (temp_str, distance);
str_cat (content_str, temp_str);

Nun ist Zeit, die Zeilen für waitt(x) einzufügen; wir haben einen Zeilenumbruch, 2 indent_str Strings, den "waitt (" String, den Wert des Intervalls (in einen String umgewandelt), ")", nächste Zeile usw.

str_cat (content_str, ";\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "waitt (");
interval1 = int(interval1);
str_for_num (temp_str, interval1);
str_cat (content_str, temp_str);
str_cat (content_str, ");\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "my.lightrange = 0;\n");
str_cat (content_str, indent_str);
str_cat (content_str, indent_str);
str_cat (content_str, "waitt (");
interval2 = int(interval2);
str_for_num (temp_str, interval2);
str_cat (content_str, temp_str);
str_cat (content_str, ");\n");
str_cat (content_str, indent_str);
str_cat (content_str, "}\n");
str_cat (content_str, "}");

Wenn Sie noch wach sind, werden Sie bemerken, daß die unten stehenden Zeilen ein ".wdl" an unseren filename_str anfügen, diese Datei für Schreibzugriff öffnen, content_str hineinschreiben und die Datei schließen. Der Alptraum ist vorüber!

str_cat (filename_str, ".wdl");
filehandle = file_open_write (filename_str);
file_str_write (filehandle, content_str);
file_close (filehandle);
}

Der Slider mit Namen P (position) bewegt das Licht hoch und runter. Wenn Sie ein flackerndes Licht an der Decke Ihres Raumes möchten, 300 Quants über dem Boden, setzen Sie P auf 300 und sehen Sie, wie es aussieht.

Mit Hilfe dieses Codes als Anfang können Sie beliebige C-Script Wizards erstellen: Partikelgeneratoren, Skripteditoren, etc