Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Einfache, leicht erweiterbare NPC Steuerung
#1
Hallöchen!

Ich wollte auf einer Sim ein NPC als Maskottchen umherwandern lassen.
Dazu habe ich mir ein einfaches System mit drei kleinen Scripten ausgedacht. Da es auch andere vielleicht gebrauchen können,
poste ich es frech hierher ^^

Das erste Script kommt in den NPC (bzw. in ein Objekt was er trägt).
Er wandert dann zum ersten Checkpoint, wo er sein nächstes Ziel abfragt.

Das zweite Script kommt in die Checkpoints, die an alle Abzweigungen gebaut werden. Es enthält alle UUIDs der erreichbaren Nachbar – Checkpoints
und wählt per Zufall einen davon aus. Ich habe mir dafür einen kleinen Phantomwürfel gerezzt, den ich wg der
begrenzten Reichweite (um Überschneidungen in Räumen zu vermeiden "hört" der NPC nur 1,5m weit auf Befehle) auf halber Avatarhöhe schweben lasse.

Und das dritte Script kommt in ein Schalterobjekt um die Checkpoints simweit sichtbar/unsichtbar zu schalten.






Das Script für den NPC:
Code:
// NPC Steuerungssystem
// MoniTill 8.13 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script für Objekt am Avatar/NPC



key zielchkp = "0c3a6a5b-df1f-4d43-8370-775cfc1fbcda"; // Den ersten Checkpoint nach dem Rezzen ansteuern
//  Diese Zeile muss angepasst werden. Der Rest nur bei Bedarf


integer NPCkanal = 15432; // Der Kanal für das System. Falls NPCs unterschiedliche Wege nutzen sollen, einfach versch. Kanäle verw.
vector ziel;
vector altziel;
float maxZeit = 60 ; //Wenn in 60 Sek. noch nicht am Ziel, wird er wohl aufgehalten, also Teleport und fertig!^^
float startzeit;
integer fragenzaehler;      // Wenn er zurück soll, fragt er mehrmals (5x)nach.
                            // Falls dann immer noch der alte Checkpoint kommt,
                            // steckt er wohl in einer Sackgasse u. muss wirklich zurück


integer flag;

// Für die Verwendung in SL muss osNpcMoveToTarget u. osIsNpc entfernt u. osTeleportOwner ersetzt werden
// dann sollte es auch dort laufen.... glaube ich :O)..

default
{
    state_entry()
    {


        llSetStatus(STATUS_PHYSICS, TRUE);
      
        llSleep(10);
    llResetTime();
        list checkpointPOSstr = llGetObjectDetails(zielchkp,[OBJECT_POS]);
    ziel = llList2Vector(checkpointPOSstr,0);
  
  
    llSetTimerEvent(2.0);    
    llListen( NPCkanal, "", NULL_KEY, "" );
    }

    timer()
    {
    
    key  npc=llGetOwner();

    if (osIsNpc(npc) == TRUE)
    {
        //Wenn er als echter NPC unterwegs ist, gilt diese Zeile....
        osNpcMoveToTarget(npc,ziel, OS_NPC_NO_FLY) ;
    }
    else
    {
        //..und beim Test mit Viewer gilt diese.
        llMoveToTarget(ziel,0.05);    
        // Bei Verwendung in SL muss die "if/else" Schleife weg und nur der llMoveToTarget stehen bleiben
        
        
     }  


    if (llVecDist(ziel,llGetPos()) < 1.5)
    {
        flag= 1;
        llWhisper(NPCkanal,"*ziel*");
        llResetTime();
        startzeit=llGetTime();
    }
  
  
    if (startzeit +maxZeit < llGetTime() )
    {
        osTeleportOwner(ziel,ZERO_VECTOR);
        // Bei Verwendung in SL muss dieser Befehl ersetzt werden
    }
   }

  
  
  
   listen( integer channel, string name, key id, string msg)  
   {  
      list checkpointPOSstr = llGetObjectDetails(id,[OBJECT_POS]);
      vector checkpointPOS = llList2Vector(checkpointPOSstr,0);
      float distanz = llVecDist(checkpointPOS,llGetPos());
    
    
    
      if (flag == 1 && llGetSubString(msg, 0, 5)== "*gehe*" && distanz < 1.5)
      {
        vector msgz =(vector)llGetSubString(msg, 6, -1);
  
        if (msgz != altziel  || fragenzaehler > 4 )
        {
            //llWhisper(0,(string)fragenzaehler + (string)ziel+ (string)altziel);
            altziel = ziel;
            ziel = msgz;
            flag= 0;


            fragenzaehler = 0;
        }
        else
        {
            fragenzaehler += 1;
        }
  
      }  
  
   }
}




Das Script für die Checkpoints:
Code:
// NPC Steuerungssystem
// MoniTill 8.13 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script für die Checkpoints



// Liste der anderen Checkpoints, die von diesem ohne Hindernisse erreichbar sind
list npcWeg = ["4f081952-f657-4e78-8b8b-f9633ae7729f","0c3a6a5b-df1f-4d43-8370-775cfc1fbcda"];
// Diese Zeile muss angepasst werden. Der Rest nur bei Bedarf


integer NPCkanal = 15432;  // Der Kanal für das System
integer i;
integer ii;
key npc;  

integer randInt(integer n)
{
      return (integer)llFrand(n + 1);
}

integer randIntBetween(integer min, integer max)
{
      return min + randInt(max - min);
}
      

default
{
    state_entry()
    {

        ii = llGetListLength(npcWeg);
    llListen( NPCkanal, "", NULL_KEY, "" );
    }

    
    listen( integer channel, string name, key id, string msg)  
    {

    if (msg == "ckptranson")
    {
        llSetAlpha(0.0, ALL_SIDES);
    }
  
    if (msg == "ckptransoff")
    {
        llSetAlpha(1.0, ALL_SIDES);
    }


    if (msg == "*ziel*")
    {
        i = randIntBetween(0, ii - 1);
        string checkpointPOSstr =llList2String( llGetObjectDetails(llList2String(npcWeg,i),[OBJECT_POS]),0);
        llWhisper(NPCkanal,"*gehe*"+checkpointPOSstr );
    }
   }
}





Das Script für den Schalter:
Code:
// NPC Steuerungssystem
// MoniTill 8.13 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script für Schalter um die Checkpoints unsichtbar/sichtbar zu machen


integer NPCkanal = 15432; // Der Kanal für das System
integer ckptrans;



default
{
  


   touch_start(integer nummer)
   {
        if(ckptrans == 0)
        {
            llRegionSay(NPCkanal,"ckptranson");
        ckptrans = 1;
        }
        else
        {
            llRegionSay(NPCkanal,"ckptransoff");
        ckptrans = 0;
        }
   }
}
Zitieren
#2
Erstmal herzlich willkommen bei uns im Gridtalk.Smile

Toller Einstand,..vielen Dank für die tollen Scripte.Wink
Zitieren
#3
Vielen Dank!!!!!!Big Grin
Zitieren
#4
Hier kommt Teil zwei vom NPC System. Die Checkpoints und der Schalter können bleiben wie sie sind. Hinzu kommt ein Shutdown resistenter NPC Rezzer
Er kopiert bei Aktivierung den Avatar zum NPC und schreibt die Daten in eine Notecard, die beim Regionsstart gelesen wird. Das Skript kommt in ein Gegenstand in der Region, welcher den NPC rezzen soll

Code:
// NPC Steuerungssystem
// MoniTill 4.14 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script für Objekt welches den NPC rezzt



string vorname = "Fritze";
string nachname = "Flink";
vector posit = <1,0,0>;        //Position relativ vom NPC Rezz-Objekt
integer rezkanal = 3486;

integer npcon = FALSE;
key npc ;


default
{

  changed(integer change)
    {
       if (change & CHANGED_REGION_START)
       {
        npcon = FALSE;
        llResetScript();
        }
    }



state_entry() {

                
   string notecard = llGetInventoryName(INVENTORY_NOTECARD, 0);
   if (notecard != ""   && npcon == FALSE) {
      npc = osNpcCreate(vorname, nachname,llGetPos()+posit, "appearance");
      npcon = TRUE;
      }

                
                
   llListen(rezkanal, "", NULL_KEY, "");
                
}



listen(integer channel, string name, key id, string msg) {






if (llToLower(msg) == "npcstart" && llGetOwnerKey(id) == llGetOwner()&& npcon == FALSE) {
  osAgentSaveAppearance(llGetOwner(), "appearance");
npc = osNpcCreate(vorname, nachname,llGetPos()+posit, "appearance");
npcon = TRUE;
}

if (llToLower(msg) == "npckill" && llGetOwnerKey(id) == llGetOwner()) {  
  osNpcRemove(npc);
   llRemoveInventory("appearance");
npcon = FALSE;
}


if (llToLower(msg) == "npcsweiter" && npcon == FALSE) {
  
npc = osNpcCreate(vorname, nachname,llGetPos()+posit, "appearance");
npcon = TRUE;
}





if (llToLower(msg) == "npcsstop" ) {  
  osNpcRemove(npc);
  
npcon = FALSE;
}



}
}





Da man aber die Steuerung trägt, die ja mitkopiert werden soll, rennt man wie Hein Blöd von Checkpoint zu Checkpoint und hat kaum eine Chance den Rezzer zu starten. Deshalb läuft die Bedienung über Regionsay . Dafür steckt man das folgende Script in einen Würfel, den man sich ans HUD hängt.

Code:
// NPC Steuerungssystem
// MoniTill 4.14 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script für HUD vom zukünftigen NPC, welches den Rezzer steuert.



integer rezkanal = 3486;

integer menu_handler;
integer menu_channel;
menu(key user,string title,list buttons)
{
    llListenRemove(menu_handler);
    menu_channel = (integer)(llFrand(999999.0) * -1);
    menu_handler = llListen(menu_channel,"","","");
    llDialog(user,title,buttons,menu_channel);
    llSetTimerEvent(30);
}






default
{
    state_entry()
    {
      
    }


  touch_start(integer total_number)
    {
//       llSay(0, "HUD 2");

            menu(llDetectedKey(0),"\nKommandos für NPC Rezzer\n",
            ["npcstart","npckill","npcsweiter","npcsstop"]);

                  
            


}
     listen(integer channel,string name,key id,string msg)
{
            
  if(msg != "") {   llRegionSay(rezkanal,msg);}    
  
    
      }    
    


}

Die Notcard bietet folgende Möglichkeiten:
npcstart Kopiert den Avatar und speichert seine Daten für einen Regions Neustart
npckill Entfernt den NPC und löscht die Daten. Beim Neustart der Region erscheint er dann nicht mehr

npcsstop Entfernt alle NPCs in der Region erhält aber ihre Daten.
npcsweiter Das Gegenstück ruft sie wieder herbei

Das ist praktisch für Wartungsarbeiten, oder auch für einen Sensor der die NPCs nur aktiviert wenn ein Ava erscheint. Der setzt dann auf dem gewählten Kanal einfach die beiden Befehle ab...

Und zum Schluss die neue NPC Steuerung, die man sich in ein Objekt am Körper packt.
Code:
// NPC Steuerungssystem
// MoniTill 9.13 V1.1
// Lizenz: Gemeinfrei (Public Domain)

// Script für Objekt am Avatar/NPC
// V1.1:  Erweitert um Hook u. Aktions-Checkpoint



key zielchkp = "36b14c59-9b4d-4653-9d2b-995d66109eae"; // Den ersten Checkpoint nach dem Rezzen ansteuern
//  Diese Zeile muss angepasst werden. Der Rest nur bei Bedarf


integer NPCkanal = 15432; // Der Kanal für das System
integer hookKanaldesNPC =16001;

vector ziel;
vector altziel;
float maxZeit = 60 ; //Wenn in 60 Sek. noch nicht am Ziel, wird er wohl aufgehalten, also Teleport und fertig^^
float startzeit;
integer fragenzaehler;  // Wenn er zurück soll, fragt er mehrmals (5x) nach.
                            // Falls dann immer noch der alte Checkpoint kommt,
                            // steckt er wohl in einer Sackgasse u. muss wirklich zurück


integer flag;
integer warteFlag = TRUE;
integer aktionsFlag;
integer aktionsZeit;


integer randInt(integer n)
{
      return (integer)llFrand(n + 1);
}

integer randIntBetween(integer min, integer max)
{
      return min + randInt(max - min);
}



default
{


on_rez(integer p)
    {
        llResetScript();
    }




state_entry()
    {


        llSetStatus(STATUS_PHYSICS, TRUE);
      
        llSleep(10);
    llResetTime();
        list checkpointPOSstr = llGetObjectDetails(zielchkp,[OBJECT_POS]);
    ziel = llList2Vector(checkpointPOSstr,0);
  
  
    llSetTimerEvent(0.5);    
    llListen( NPCkanal, "", NULL_KEY, "" );
    llListen( hookKanaldesNPC, "", NULL_KEY, "" );
    }

    timer()
    {
    
    key  npc=llGetOwner();

    if (osIsNpc(npc) == TRUE)
    {

      if (warteFlag == TRUE)
      {
        //Wenn er als echter NPC unterwegs ist, gilt diese Zeile....
        osNpcMoveToTarget(npc,ziel, OS_NPC_NO_FLY) ;
      }
      else
      {
        osNpcStopMoveToTarget(npc);
      }
        
     }
        
     else
     {
       if (warteFlag == TRUE)
       {
          //..und beim Test mit Viewer gilt diese.
         llMoveToTarget(ziel,0.05);    
      
       }
       else
       {
          llStopMoveToTarget();
       }

     }  



    
    

    if (llVecDist(ziel,llGetPos()) < 1.5 && warteFlag == TRUE)
    {

    if (aktionsFlag == 1)
    {
    aktionsFlag =0;
llResetTime();
startzeit=llGetTime();
llSay (NPCkanal,"*aktionende*");
    
    }
    
    
    flag= 1;
        llWhisper(NPCkanal,"*ziel*");
        llResetTime();
        startzeit=llGetTime();
    }
  
  

  // Zu lange unterwegs od aufgehalten, dann teleportieren
  if (startzeit +maxZeit < llGetTime() && warteFlag == TRUE )
    {
        osTeleportOwner(ziel,ZERO_VECTOR);
        // Bei Verwendung in SL muss dieser Befehl ersetzt werden
    }
// Zeit für sit od. touch Aktion ist um. weiter gehts....
if (startzeit + aktionsZeit < llGetTime() && aktionsFlag == 1)
{

warteFlag = TRUE;


}    
    
    
    
    
    
    }

  
  
  
   listen( integer channel, string name, key id, string msg)  
   {  
      list checkpointPOSstr = llGetObjectDetails(id,[OBJECT_POS]);
      vector checkpointPOS = llList2Vector(checkpointPOSstr,0);
      float distanz = llVecDist(checkpointPOS,llGetPos());
key  npc=llGetOwner();
      
            if (flag == 0 && msg == "*warte*" && channel == hookKanaldesNPC )
            {
      warteFlag = FALSE;
      }
      
            if (flag == 0 && msg == "*weiter*" && channel == hookKanaldesNPC )
            {
      warteFlag = TRUE;
        llResetTime();
        startzeit=llGetTime();
      }
      
      
      if (flag == 1 && llGetSubString(msg, 0, 6)== "*nutze*" && distanz < 1.5)
      {
      warteFlag = FALSE;
aktionsFlag = 1;
      string xx =llGetSubString(msg, 7, -1);
       list paraliste = llParseString2List(xx,[","],[" "]);
key aktionsObjekt =(key)llList2String(paraliste,0) ;
string was =llList2String(paraliste,1) ;      
aktionsZeit  = randIntBetween((integer)llList2String(paraliste,2),(integer)llList2String(paraliste,3));

if (was="s" )
{
if (osIsNpc(npc) == TRUE)
{
osNpcSit(npc, aktionsObjekt, OS_NPC_SIT_NOW);
}
else
{
llOwnerSay("sit:"+aktionsObjekt+" Dauer in Sek. "+(string)aktionsZeit);
}

}
else
{
if (osIsNpc(npc) == TRUE)
{
osNpcTouch(npc, aktionsObjekt,  LINK_ROOT) ;
}
else
{
llOwnerSay("touch:"+aktionsObjekt+" Dauer in Sek. "+(string)aktionsZeit);
}



}




       }
    
    
      if (flag == 1 && llGetSubString(msg, 0, 5)== "*gehe*" && distanz < 1.5)
      {
        vector msgz =(vector)llGetSubString(msg, 6, -1);
  
        if (msgz != altziel  || fragenzaehler > 4 )
        {
            //llWhisper(0,(string)fragenzaehler + (string)ziel+ (string)altziel);
            altziel = ziel;
            ziel = msgz;
            flag= 0;


            fragenzaehler = 0;
        }
        else
        {
            fragenzaehler += 1;
        }
  
      }  
  
   }
}


Neu hinzugekommen ist ein Hook, an dem andere Scripte die Steuerung unbegrenzte Zeit unterbrechen können. Beispielsweise ein Chatscript, ein Follover oder eine Animation.... Sie setzten einfach auf dem Hookkanal (16001) „*warte*“ bzw. "*weiter*" ab.
Die andere Neuerung mit dem Aktions-Checkpoint, da soll der NPC an ausgesuchten Objekten Sit u. Touch Aktionen durchführen, läuft noch nicht richtig.

Wenn was klemmt oder unklar ist, bitte melden! Wink
Zitieren
#5
So, hier kommt die erste NPC Anwendung.
Ein Script was den NPC zu zufälligen Zeiten (einstellbar) anhält und eine Animation (bei mehreren, werden sie zufällig gewählt) ausführen lässt.
Ist zB für Tiere interessant. Ein Pferd trabt über die Wiese und hält hier und da um zu grasen oder sich eine Zecke aus dem Fell zu beißen...

Oder man kann auch einen menschlichen NPC ab u. an ein spontanes Tänzchen aufführen lassen Smile

Das Script kommt zusammen mit den Animationen in ein Objekt am NPC. Es darf aber nicht das gleiche sein wie die Steuerung. Die Kommunikation läuft über die Listen-Funktion und die „hört“ leider nicht was das eigene Objekt sagt. Mit Viewer lässt es sich nur sehr bedingt testen, da „llStopMoveToTarget“ nicht richtig funktioniert. Man bewegt sich noch einige Zeit weiter. Aber beim NPC funktionierts mit „osNpcStopMoveToTarget“ sehr gut! Der hält sofort an Smile


Code:
// NPC Steuerungssystem
// MoniTill 4.14 V1
// Lizenz: Gemeinfrei (Public Domain)

// Script um den NPC ab und an Animationen ausführen zu lassen.


// Gehdauer in Sekunden bis zur Animation (Bitte anpassen)
integer gehDauerMin =8;
integer gehDauerMax =30;

// Animationsdauer in Sekunden (Bitte anpassen)
integer animZeitMin = 10;
integer animZeitMax = 20;
// Hookkanal des NPCs- Jeder Bot muss einen eigenen haben, sonst gibts Probleme
// weil sie sich gegenseitig stoppen...
integer hookKanaldesNPC =16001;





integer gehZeit = 0;
integer zeitZaehler =0;
integer animAnzahl;

integer randInt(integer n)
{
     return (integer)llFrand(n + 1);
}


integer randIntBetween(integer min, integer max)
{
    return min + randInt(max - min);
}


default
{

on_rez(integer p)
    {
        llResetScript();
    }




state_entry()
    {
      llSleep(30);   // Startet mit 30 Sekunden Verzögerung
                     // damit vorher in jedem Fall die NPC Steuerung läuft
      animAnzahl= llGetInventoryNumber(INVENTORY_ANIMATION)-1;
      gehZeit = gehDauerMax;
      llSetTimerEvent(1);
      
    

     }



timer()
    {
       if (zeitZaehler > gehZeit){

       // Wähle eine zufällige Animation aus dem Inventar
       string aktAnim = llGetInventoryName(INVENTORY_ANIMATION, randInt(animAnzahl));

          // Ist eine Animation vorhanden, nutzen wir sie  
          if (aktAnim != "") {

          // Die NPC Steuerung unterbrechen
          llSay (hookKanaldesNPC,"*warte*");


          key owner = llGetOwner();
          llRequestPermissions(owner, PERMISSION_TRIGGER_ANIMATION);

            // Erstmal alle laufenden Animationen stoppen und dann....
            list anims = llGetAnimationList(llGetPermissionsKey());      
            integer len = llGetListLength(anims);
            integer i;
            for (i = 0; i < len; ++i) llStopAnimation(llList2Key(anims, i));

            
          //....die Puppen tanzen lassen.  
          llStartAnimation(aktAnim);
          llSleep(randIntBetween(animZeitMin,animZeitMax));

          llStopAnimation(aktAnim);
          gehZeit = randIntBetween(gehDauerMin,gehDauerMax);
          llReleaseControls();
          zeitZaehler = 0;

          llSay (hookKanaldesNPC,"*weiter*");
          
          }

      }
      else
      {
          zeitZaehler += 1;
      }

    }


}
Zitieren
#6
Danke dir für deine super Scripte habe es getestet läuft sehr gut. Smile

Mein Test NPC macht bei jedem point eine pause und geht nicht gleich einfach weiter kann das noch geändert werden?
[-] The following 1 user says Thank You to Rebecka Alder for this post:
  • MoniTill
Zitieren
#7
Smile

Die Wartedauer hängt überwiegend vom  llSetTimerEvent  ab.  In der alten Steuerung hatte ich ihn auf 2 Sekunden gesetzt, in der neuen Version auf 0.5. Je niedriger die Zeitspanne desto höher die Belastung für den Server. Setze ihn mal auf 0.2 Sekunden Wink

Eine andere Verzögerung entsteht wenn er zurückgeschickt werden soll. Er fragt dann mehrere Male nach, bis statistisch kein anderes Ziel mehr kommen kann, er also in einer Sackgasse steckt und tatsächlich zurück muss. Da lässt sich leider nichts dran machen.
Degolburg:
24h online und ca. 10 % fertig
Taxi: 85.214.150.139:9000:Degolburg
Zitieren
#8
Super danke hat geklapt  Wink
Zitieren


Gehe zu:


Benutzer, die gerade dieses Thema anschauen: 1 Gast/Gäste