3D-Spiel auf three.js, nw.js.

Ich habe beschlossen, eine neue Version meines alten Browsergames zu veröffentlichen, das seit einigen Jahren als Anwendung in sozialen Netzwerken erfolgreich ist. Dieses Mal wollte ich es auch als Anwendung für Windows (7-8-10) entwerfen und in verschiedenen Geschäften platzieren. Natürlich können Sie in Zukunft Assemblys für MacOS und Linux erstellen.


Der Spielcode ist vollständig in reinem Javascript geschrieben. Zum Anzeigen von 3D-Grafiken wird die Bibliothek three.js als Verknüpfung zwischen dem Skript und WebGL verwendet. Dies war jedoch in der alten Browserversion der Fall. Das Wichtigste in diesem Projekt war für mich der Grund, parallel zum Spiel eine eigene Bibliothek hinzuzufügen, die three.js mit den Werkzeugen für die bequeme Arbeit mit Szenenobjekten, deren Animation und vielen anderen Funktionen ergänzt. Ich habe es dann für eine lange Zeit aufgegeben. Es ist Zeit, zu ihr zurückzukehren.


Meine Bibliothek enthält praktische Werkzeuge zum Hinzufügen und Entfernen von Objekten zur Szene, zum Ändern der Eigenschaften einzelner Teile von Objekten (Netze), zur rahmenratenunabhängigen Animation von 3D-Objekten, einen Sky-Shader mit einer Sternenhimmel-Textur bei Nacht und vieles mehr. Ich werde Ihnen von einigen erzählen. In Bezug auf den Himmel habe ich seine Erstellung mit einer einzigen Funktion implementiert, die eine Reihe von Eingabeparametern verwendet, den Shader initialisiert, die Wolkentextur lädt (falls erforderlich) und mit der Aktualisierung des Himmels mit einer bestimmten Iteration beginnt.

Dort ist jedoch alles etwas komplizierter - für periodische, aber selten aufgerufene Funktionen funktioniert tatsächlich eine andere Konstruktion mit setInterval (), bei der Ereignisse in unterschiedlichen Intervallen ausgelöst werden können, und dies alles wird auf einen gemeinsamen Nenner reduziert und rechts berechnet Zeit benötigte Ereignisse auf der Liste. Dort können Sie auch das Sky-Update-Intervall werfen. Die Bewegung von 3D-Gaming-Objekten für mehr Glätte wurde jedoch bereits durch requestAnimationFrame () implementiert ...

Da wir also über den Himmel sprechen, werden wir damit beginnen.

Himmel



Das Hinzufügen des Firmaments zur Szene ist wie folgt.

Zuerst müssen Sie der Szene das Standardlicht three.js mit seinen maximalen (anfänglichen) Helligkeitswerten hinzufügen. Die gesamte Szene mit ihren Objekten, Licht und anderen Attributen wird im Apscene-Namespace gespeichert, um den globalen Raum nicht zu überladen.

//  (AmbLight)   apscene.AmbLight=new THREE.AmbientLight(0xFFFFFF); apscene.scene.add(apscene.AmbLight); //  (AmbLightBk)   (      ) apscene.AmbLightBk=new THREE.AmbientLight(0xFFFFFF); apscene.sceneb.add(apscene.AmbLightBk); // (, )   var SFog=new THREE.FogExp2(0xaaaaaa, 0.0007); apscene.scene.fog=SFog; // (, )   var SFogBk=new THREE.FogExp2(0xa2a2aC, 0.0006); apscene.sceneb.fog=SFogBk; //  apscene.hemiLight=new THREE.HemisphereLight(0xFFFFFF, 0x999999, 1); apscene.scene.add(apscene.hemiLight); //        .       .        three.js   ,   ,     ,  ,   ... apscene.dirLight=new THREE.DirectionalLight(...) apscene.dirLightBk=new THREE.DirectionalLight(...) 

Danach können Sie die Animation des Himmels bereits mit Shadern, Texturen (Blackjack und ... na gut, okay) über eine meiner Funktionen ausführen:

 m3d.graph.skydom.initWorld( //  ,  ,         {saveStart:false}, { //ltamb (light ambient,    ) -       ltamb:{a1:-2, a2:8, k1:0.2, k2:0.75, obj:[ {obj:apscene.AmbLight.color, key:['r','g','b']} ]}, //a1  a2 -      (altitude,      ),     ,   obj,     (k1..k2),   k1  k2 -         // ,    ltamb :     -2  8 (    ),    (apscene.AmbLight.color),    r, g  b,    0.2  0.75     ( ,    ,    0xFFFFFF). //key -    ,        three.js //        (apscene.AmbLightBk.color).    (  altitude)  -4  12          0.3 ... 0.99    .  -4 , ,    0.3  ,   12   0.99   ltambb:{a1:-4, a2:12, k1:0.3, k2:0.99, obj:[ {obj:apscene.AmbLightBk.color, key:['r','g','b']} ]}, //      : ltdir:{a1:-2, a2:8, k1:0.0, k2:1, obj:[ {obj:apscene.dirLight, key:['intensity']}, {obj:apscene.dirLightBk, key:['intensity']} ]}, //       (apscene.dirLightBk)   ,    ,       . // ,      three.js    - inensity ( , apscene.dirLight.intensity).  ,  ,       three.js    . //  .   ,     ( 0.15  )     12  8 (, ,    8  12), : lthem:{a1:8, a2:12, k1:0.15, k2:0.3, obj:[ {obj:apscene.hemiLight, key:['intensity']} ]}, //         .       . //    ()   : ltambfog:{a1:-2, a2:8, k1:0.4, k2:1, obj:[ {obj:apscene.scene.fog.color, key:['r','g','b']} ]}, //  : ltambbfog:{a1:-2, a2:12, k1:0.25, k2:1, obj:[ {obj:apscene.sceneb.fog.color, key:['r','g','b']} ]}, //     .  ,         ,  . ltambfogd:{a1:8, a2:12, k1:0.2, k2:0.35, obj:[ {obj:apscene.scene.fog, key:['density']} ]}, //  ltambbfogd:{a1:6, a2:12, k1:0.2, k2:0.28, obj:[ {obj:apscene.sceneb.fog, key:['density']} ]}, //  ,      ( ,   ),   ,     ,   , -«»     ,      skyplane1..6: planeAmb:{a1:-5, a2:12, k1:0.5, k2:1.0, obj:[ {obj:apscene.user.skyplane1.material.color, key:['r','g','b']}, {obj:apscene.user.skyplane2.material.color, key:['r','g','b']}, {obj:apscene.user.skyplane3.material.color, key:['r','g','b']}, {obj:apscene.user.skyplane4.material.color, key:['r','g','b']}, {obj:apscene.user.skyplane5.material.color, key:['r','g','b']}, {obj:apscene.user.skyplane6.material.color, key:['r','g','b']} ]} //,     ,     ,  . }; 

Infolgedessen erscheint ein dynamischer Himmel auf der Szene mit einer sanften Änderung der Sonnenhöhe und dementsprechend einer Änderung der Tageszeit.

Es ist nicht notwendig, alle Arten von Beleuchtung auf der Bühne zu verwenden. Und es ist nicht notwendig, alle Parameter abhängig von der Tageszeit zu ändern. Wenn Sie jedoch mit ihrer Helligkeit spielen, können Sie ein ziemlich realistisches Bild der Veränderung von Tag und Nacht erstellen. Sie können die Parameter beliebig benennen. Die Hauptsache besteht darin, die Schlüssel der darin enthaltenen Objekte so zu beobachten, wie sie in three.js angegeben sind.

Wie es aussieht, können Sie das Video aus der Demoszene sehen:


Dies ist ein anderes Spiel. Es ist nur so, dass der Horizont nicht mit verschiedenen Objekten überfüllt ist und daher die Arbeit dieses Skripts am deutlichsten sichtbar ist. Aber in dem Spiel, über das die Geschichte diskutiert wird, wird genau der gleiche Ansatz verwendet. Die hohe Geschwindigkeit des Zeitablaufs wird hier nur zu Demonstrationszwecken eingestellt, und so fließt die Zeit natürlich langsamer, wobei derselbe Schritt der Iteration das Firmament aktualisiert. An dieser Demo ist übrigens ein Wasser-Shader beteiligt, auch mit variablen Parametern, abhängig von der Höhe der Sonne ... Aber ich habe es noch nicht fertiggestellt.

Leistung


All dies ist für Eisen sehr wenig anspruchsvoll. Im Chrome-Browser wird der Xeon E5440 unter LGA775 (und mit 4 GB RAM) um 20% und der Kern der GT730-Grafikkarte um 45% geladen. Dies liegt aber nur an der Animation des Wassers. Wenn wir über ein Spiel sprechen, bei dem es kein Wasser gibt, aber eine Stadt, dann dieses:


Dann bewegt sich das Auto zu der Zeit durch die Stadt - Prozent 45%, Grafikkarte 50%. Im Prinzip funktioniert es mit einem gewissen fps-Drawdown (bis zu 30 Bilder pro Sekunde) sogar auf Pentium4 3 GHz (1 Gbit RAM) und auf einem Tablet mit Intel Atom 1,3 GHz (2 Gbit RAM) ziemlich gut.

All diese Hardware ist extrem schwach und andere ähnliche Spiele auf WebGL und HTML5, sogar einige 2D-Spiele, verlangsamen mich gottlos, bis es unmöglich wird, sie zu spielen. Wie sie sagen, schreibe das Spiel selbst, wie du es brauchst, und spiele.

Szene



Die 3D-Szene in three.js ist ein Szenenobjekt, und das untergeordnete Array besteht aus allen 3D-Modellen, die in die Szene geladen werden. Um nicht für jedes Modell einen Bootloader-Aufruf zu registrieren, habe ich beschlossen, die gesamte Spielszene in Form einer bestimmten Konfiguration mit einem großen assoziativen Array locd: {} (z. B. Standortdaten) festzulegen, das alle Einstellungen enthält - Lichter, Pfade vorinstallierter Texturen und Bilder für die Benutzeroberfläche, Pfade zu allen Modellen, die auf die Bühne geladen werden sollen, und vieles mehr. Im Allgemeinen ist dies die vollständige Konfiguration der Szene. Es wird einmal in der js-Datei des Spiels gesetzt und meinem Szenenlader zugeführt.

Und dieses locd: {} -Objekt enthält insbesondere die Pfade zu den einzelnen 3D-Modellen, die geladen werden müssen. Der Nullpfad ist der gemeinsame Pfad und dann die relativen Pfade für jedes Objekt, z.
 ['path/myObj', scale, y, x,z, r*Math.PI, 1, '', '', '', 1, ['','','',''], 'scene'] 

Es versteht sich, dass alle Modelle aus dem 3D-Editor in das JSON-Format exportiert werden, dh Pfade wie path / myObj.json haben. Darauf folgen die Skala (da der Editor mit einer für das Spiel nicht geeigneten Skala gespeichert werden kann), die Position des Objekts in der Höhe (y) entlang der Achsen (x) und (z), dann der Drehwinkel ® des Modells in (y), eine Reihe optionaler Parameter und Der Name der Szene, in die das Modell geladen werden soll - in der Hauptszene (Szene) oder im Hintergrund (Szeneb).

Ja, dies musste nicht in Form eines einfachen, sondern in Form eines assoziativen Arrays implementiert werden. Die Reihenfolge der Parameter ist also auch ohne Dokumentation oder zumindest ohne die Art der Funktion, die diese Parameter übernimmt, nicht nachvollziehbar. Sie werden es nicht verstehen. Ich denke, in Zukunft werde ich diese Zeilen in assoziativen Arrays neu erstellen. In der Zwischenzeit sieht es so aus:
 landobj: [ ['gamePath/'], [ ['landscape/ground', 9.455, 0, 0,0, 0*Math.PI, 1, '', '', '', 1, ['','','',''], 'scene'], ['landscape/plants', 9.455, 0, 0,0, 0*Math.PI,1, '', '', '', 1, ['','','',''], 'scene'], ['landscape/buildings/house01', 2, 0, -420,420, -0.75*Math.PI, 1, '', '', '', 1, ['','','',''], 'scene'], ... ] ], 

Diese Modelle werden auf die Bühne geladen und in den hier angegebenen Koordinaten im Raum platziert. Grundsätzlich können alle Modelle als einzelnes Objekt geladen, dh als ganze Spielszene aus dem Editor exportiert und in Koordinaten (0; 0; 0) geladen werden. Dann wird es nur eine Zeile geben: Landschaft / Boden - ich habe ground.json - dies ist der Hauptteil der Spielwelt. In diesem Fall ist es jedoch schwierig, einzelne Objekte der Szene zu manipulieren, da Sie zuerst in die Browserkonsole schauen und sich merken müssen, welches der Kinder dieses riesigen Geländes ist. Und dann kontaktieren Sie sie über Nummern. Daher werden Roaming-Spielmodelle am besten mit separaten Objekten geladen. Anschließend kann über das assoziative Array, das speziell für diesen Zweck automatisch erstellt wird, namentlich auf sie zugegriffen werden.

Die vollständige Konfiguration des Spiels könnte beispielsweise so aussehen:
 locd:{ // name: 'SeaBattle', type: 'game', menulabel: '', //    x:-420, y:70, z:-420, rot: -0.5, //    intsdistance: 200, // ambLtColor: 0xFFFFFF, ambLtColorBk: 0xFFFFFF, lightD: [0xDDDDDD,0.3,1000, 200000,0.3,-0.0003, -190,200000,-140,0,0,0, 200,-200,200,-200], lightDBk: [0xFFFFFF,0.3,10000, 40000,0.3,-0.0035, -190,1200,-140,0,0,0, 50000,-50000,50000,-50000], lightH: [0xFFFFFF,0x999999,1, 0,500,0], //      lightsP: [], //     lightsPDynamicAr: [ [0xffffff, 4, [0, -2000, 0], [50, 1.5] ] //[[distance], [decay]] ], //         (x,y,z) userPointLights: [ [0, -2000, 0] ], //   lightsS: [ [0xffffbb, 1.0, [0, 250, 180], [0, 0, 180], 0.5, 600,600,600, -0.0005] ], //  shadowMapCullFace:0, shadowsMode: 'all', //all,list,flag //  imagePaths: [ 'game/img/', 'interface.png', 'interface2.png' ], // 3D  landobj: [ ['game/models/'], [ ['landscape/land',1, 0, 0,0, 0.0*Math.PI,1,'','','',1,['','','',''],'scene'], ['landscape/sbp',1, 0, 0,180, 1.0*Math.PI,1,'','','',1,['','','',''],'scene'], ['landscape/sbu',1, 0, 0,1120, 1.0*Math.PI,1,'','','',1,['','','',''],'scene'] ] ], //        staffobj: [ ['game/models2/'], [ ] ], //  progobj: [ [ ] ] }, 

Ja, es ist besser, alle diese Subarrays in assoziative Arrays zu wiederholen, da sonst die Reihenfolge der darin enthaltenen Parameter nicht klar ist ...

3D-Modelle



Eine andere interessante Sache. Modelle laden. Meine Bibliothek akzeptiert 3D-Modelle mit Texturen und setzt abhängig von den Namen automatisch einige Parameter auf ihre einzelnen Elemente (Netze). Tatsache ist, dass, wenn das Modell beispielsweise so eingestellt ist, dass es einen Schatten wirft, es von jedem in seiner Zusammensetzung enthaltenen Netz geworfen wird. Es ist nicht immer erforderlich, dass das gesamte Modell einen Schatten vollständig wirft oder andere Eigenschaften erhält, die die Leistung stark beeinträchtigen. Wenn Sie also ein bestimmtes Flag aktivieren, das signalisiert, dass jedes Netz separat betrachtet werden muss, können Sie beim Laden bestimmen, welches Netz diese oder jene Eigenschaft hat und welches nicht. Nun, zum Beispiel ist es absolut nicht erforderlich, dass der Schatten durch das flache horizontale Dach des Hauses oder durch viele kleinere irrelevante Details des Modells vor einem großen Hintergrund geworfen wird. Trotzdem kann der Player diese Schatten nicht sehen, und die Leistung des Videoprozessors wird verwendet, um sie zu verarbeiten.

Dazu können Sie im Grafikeditor (Blender, Max usw.) sofort die Namen der Netze (im Feld Objektname) nach einer bestimmten Regel angeben. Es muss einen Unterstrich (_) geben. Bedingte Steuerzeichen sollten sich auf der linken Seite befinden, zum Beispiel: d - Doppelseite (Netz ist in beide Richtungen, ansonsten - in eine Richtung), c (Schatten werfen) - wirft einen Schatten, r (Schatten empfangen) - nimmt Schatten auf. Das heißt, der Name des Rohrnetzes im Haus kann beispielsweise cr_tube sein. Viele andere Buchstaben werden ebenfalls verwendet. Zum Beispiel ist „l“ ein Collider, dh die Wand des Hauses mit dem Namen crl_wall01 lässt den Spieler nicht durch sich selbst hindurch und wirft und wirft auch einen Schatten. Es ist nicht erforderlich, Kollider wie ein Dach oder einen Türgriff herzustellen, wodurch die Leistung beeinträchtigt wird. Wie Sie bereits verstanden haben, analysiert meine Bibliothek beim Laden eines Modells die Namen von Netzen und gibt ihnen die entsprechenden Eigenschaften für die Szene. Dazu müssen jedoch alle Netze korrekt benannt werden, bevor das Modell aus dem 3D-Editor exportiert wird. Dies spart erheblich Leistung.

Alle Steuerflags für Netze innerhalb eines Objekts:
col_ ... ist der Collider. Ein solches Netz wird einfach als transparenter, unsichtbarer Collider angezeigt. Im Editor kann er wie alles aussehen, nur seine Form ist wichtig. Zum Beispiel kann es sich um ein Parallelepiped um das gesamte Modell handeln, wenn der Spieler dieses Modell (Gebäude, großer Stein usw.) nicht durchlaufen muss.

l_ ... ist ein kollidierbares Objekt. Geben Sie einem Netz eine Collider-Eigenschaft.

i_ ... - Kreuzungen. Das Netz wird der Schnittpunktliste hinzugefügt, mit der Sie beispielsweise darauf klicken können, um dem Spiel Interaktivität zu verleihen.

j_ ... auch Kreuzungen. Das gleiche wie oben, nur eine neuere Version - mit einem verbesserten Algorithmus zum Auffinden von Schnittpunkten im Spiel und weniger Ressourcenverbrauch.

e_ ... - Kreuzungen für die Türen von Häusern (Ein- / Ausgang). Schließt Schnittpunkte über anderen Objektnetzen aus. Es wird verwendet, wenn es in Häusern zu einem bestimmten Zeitpunkt erforderlich ist, nur Türen interaktiv zu machen, ausgenommen alle anderen interaktiven Elemente. Mit Fantasie können Sie sich diese und eine Menge anderer Anwendungen einfallen lassen.

c_ ... - Schatten werfen. Das Netz wirft einen Schatten.

r_ ... - Schatten empfangen. Ein Netz akzeptiert Schatten von allen anderen Netzen, die sie werfen.

d_ ... - bilateral (doppelseitig). Auf beiden Seiten sichtbar, überlagert sich die Textur auf beiden Seiten.

t_ ... - transparent (transparent), wenn das gesamte Objekt in three.js auf alphatest gesetzt ist.

u_ ... - transparent (transparent) mit einer festen Dichte (0,4), wenn das gesamte Objekt nicht alphatest in three.js angibt.

g_ ... - Glas. Feste Transparenz ist eingestellt (0,2).

h_ ... - unsichtbar (versteckt). Für Teile des Objekts (Netze), die beim Hinzufügen des Objekts zur Szene ausgeblendet werden sollen. Eingetragen in die Liste der versteckten.

v_ ... sichtbar. Alle Objekte außer den mit "h" gekennzeichneten sind bereits sichtbar, aber mit dem Flag "v" werden sie in eine separate Liste eingetragen, die zur weiteren Verschleierung oder für andere Manipulationen sichtbar ist.

Infolgedessen kann der Name des Netzes ungefähr so ​​lauten: crltj_box1 (wirft, akzeptiert einen Schatten, Collider, transparent, interaktiv). Und noch ein Netz als Teil desselben Modells: cr_box2 (wirft nur Schatten und nimmt Schatten). Natürlich können Steuerzeichen in beliebiger Reihenfolge festgelegt werden. Auf diese Weise können Sie über den Editor die zukünftige Anzeige von Teilen des Objekts im Spiel oder vielmehr einige ihrer Eigenschaften steuern und gleichzeitig Rechenleistung sparen.

Die Essenz des Spiels


Die Bedeutung des Spiels, um das es in der Geschichte geht, besteht darin, sich um den Umfang des quadratischen Feldes zu bewegen und Unternehmen zu kaufen. Das Feld besteht aus 3D-Straßen. Das Wirtschaftsmodell des Spiels unterscheidet sich erheblich von dem seiner Art. Wenn in meinem Spiel jemand ein Unternehmen gründet, sinkt Ihr erwarteter Gewinn. Und umgekehrt, wenn Sie etwas entdecken, nimmt es zu. Alle Gewinn- und Verlustberechnungen werden in der Steueraufsicht im Feld Start durchgeführt. Sie können auch einen Kredit bei einer Bank aufnehmen, mit Wertpapieren handeln und eine Reihe anderer Dinge tun. Ich habe das KI-Verhalten im Vergleich zur alten Version verbessert. Fast alle 3D-Modelle und Texturen des Spiels wurden überarbeitet und die Leistung optimiert. Mehr Einstellungen vorgenommen und vieles mehr.

Animation



Um die Bewegung von Spielobjekten zu animieren, wird die einfachste Engine verwendet, die bei einer bestimmten Bildrate (die auf die 60. begrenzt ist) alle numerischen Parameter für ein bestimmtes Zeitintervall ändert und Zwischenwerte an den Handler weitergibt. Im Handler wird beispielsweise das Modell angezeigt.

Z.B. Wir müssen das Objekt obj im Raum von der Position (10; 10; 50) zum Punkt (100; 300; 60) bewegen. Wir setzen 3 Parameter, indem wir ihre Anfangs- und Endwerte angeben. Die x-Koordinate variiert von 10 bis 100, y - von 10 bis 300 und z - von 50 bis 60. Und all dies sollte beispielsweise in 4 Sekunden geschehen.
 m3d.lib.anim.add( 'moveobj', 1, 4000, 'and', {userpar1:111, obj:my3DObject}, [ {lim1:10, lim2:100, sstart:10, sfin:100, t:0}, {lim1:10, lim2:300, sstart:10, sfin:300, t:0}, {lim1:50, lim2:60, sstart:50, sfin:60, t:0} ], function(){ myPeriodicFun(this); }, function(){ myFinishFun(this); } ); m3d.lib.anim.play(); 

Die erste Zeile mit 5 Parametern: moveobj - Name der Animation (beliebig), 1 - Streamnummer (Sie können Objekte in einer unbegrenzten Anzahl von Streams parallel animieren), 4000 - Animationszeit von 4 Sekunden und - während eines nicht verwendeten Parameters, der in Zukunft für die Übergangslogik verantwortlich sein wird Zwischen Animationen innerhalb desselben Streams ist userpar ein beliebiges assoziatives Array, das als Parameter an den Handler übergeben wird, z. B. mit einem berechneten Radius, Sinus, Cosinus und im Allgemeinen allen für diese Animation berechneten Werten, um nicht in berechnet zu werden über den Zeitpunkt jeder Iteration. Oder in Bezug auf ein 3D-Objekt, das tatsächlich animiert wird.

Als nächstes folgt ein Array mit veränderlichen Parametern. Wir finden, dass der erste Parameter die Änderung der x-Koordinate ist, der zweite ist y, der dritte ist z. Wir schreiben für jedes in lim1 und lim2, von welcher und zu welcher Größe es sich ändern wird. In sstart und sfin geben wir dieselben Werte an. Hier können Sie beispielsweise einen Start von einem anderen Wert aus angeben, dann „scrollt“ der Parameter in einem Kreis von diesem zu ihm, umgeht lim2 und startet eine neue „Umdrehung“ mit lim1. Nun, dies ist zum Beispiel notwendig, wenn unsere Animation zwischen einigen Werten (lim1 und lim2) geloopt wird, aber wir müssen sie nicht von vorne beginnen (dh nicht von lim1), sondern von einem Zwischenwert.

t: 0 legt nur fest, dass die Animation für diesen Parameter 1 Mal entsprechend der Gesamtzeit (4000) ausgeführt wird, als ob sie darauf gestreckt wäre. Wenn wir eine andere Zahl als die Hauptzeit einstellen, wird dieser Parameter wiederholt und bis zur Zeit der Hauptanimation (4000) wiederholt. Dies ist beispielsweise praktisch, um die Drehung des Objekts einzustellen, wenn der Winkel wiederholt die 360-Grad-Linie überschreiten und auf 0 zurückgesetzt werden muss.

Als nächstes folgen 2 Rückrufe - der, der bei jeder Iteration ausgeführt wird, und der, der nach Abschluss der gesamten Animation einmal ausgeführt wird (Austrittspunkt).

Der erste Rückruf von myPeriodicFun (this) kann beispielsweise folgendermaßen aussehen:
 myPeriodicFun:function(self) { var state=self.par[0].state, state_old=self.par[0].state_old; var state2=self.par[1].state, state_old2=self.par[1].state_old; var state3=self.par[2].state, state_old3=self.par[2].state_old; if ((state!=state_old)||(state2!=state_old2)||(state3!=state_old3)) { var obj=self.userpar.obj; obj.position.x=state; obj.position.y=state2; obj.position.z=state3; ap.cameraFollowObj(obj); }; }, 

Das heißt, bei jeder Iteration der Bewegung wird ein Parameter (self) in diese Funktion geworfen, der die berechneten Zwischenwerte für alle angegebenen Animationsparameter enthält: self.par [0] .state, self.par [1] .state, self.par [2] .state. Dies ist unser x, y und z zur aktuellen Zeit. Wenn die Animation beispielsweise 4 Sekunden dauert, ist x nach 2 Sekunden gleich (100-10) / 2 = 45. Und so für alle Koordinaten. Dementsprechend zeigen wir in myPeriodicFun unser Objekt einfach in diesen Koordinaten an.Wenn der Browser auf dieser Hardware verzögert oder nur langsam ausgeführt wird, ist dies nicht beängstigend: Die gesamte Animationszeit ändert sich nicht, nur die Bildrate sinkt und das Bild wird zu einer Diashow.

Warum also f ((state! = State_old) ... prüfen, dh ob der neu berechnete Wert gleich dem alten ist (state_old merkt sich den berechneten Wert in der vorherigen Iteration), also zum Beispiel, wenn sich einige Parameter ändern kleiner als eins, dann zeichnen Sie nicht das gesamte Objekt neu und geben Sie nicht die Leistung des Systems dafür aus, und die Animations-Engine erzeugt Ganzzahlen in state und state_old, die beispielsweise als Schritt gleich einem Pixel interpretiert werden können. Und wenn sich das Objekt nicht relativ zur vorherigen Position bewegt Selbst um 1 Pixel ist es nicht erforderlich, es neu zu zeichnen, da e Die Position auf dem Bildschirm ändert sich nicht.

Im Allgemeinen wird unter Animation eine einfache Änderung einer beliebigen Anzahl von Parametern in einer bestimmten Zeit mit der Übermittlung ihrer Zwischenwerte an eine Rückruffunktion verstanden. Sie können beispielsweise einen weiteren 4. Parameter hinzufügen, der für den Drehwinkel des Objekts verantwortlich ist. Und Sie können im Allgemeinen die Parameter vieler Objekte in einer Animation zusammenfassen, wenn sie sich irgendwie gleichmäßig bewegen. Sie können Animationen in verschiedene Streams einfügen, die dann parallel verarbeitet werden. Sie können einem einzelnen Thread (m3d.lib.anim.add ()) eine ganze Folge von Animationen hinzufügen, die nacheinander ausgeführt werden. Darüber hinaus hat jeder Thread seine eigene unabhängige Sequenz. Die Hauptsache ist, dass das System genug Strom hat und nicht alles zu einer Diashow wird.

PS. Streams werden hier einfach durch sequentielle Aufzählung bei jeder Iteration aller parallelen Animationen und Berechnung von Zwischenwerten aller ihrer Parameter für jede von ihnen realisiert. Das heißt, es gibt kein echtes Multithreading in Javascript.

Dieselbe „Engine“ kann auch zum Animieren der Elemente der Schnittstelle verwendet werden, indem sie so eingestellt werden, dass sie 2 Koordinaten in der Bildschirmebene ändern und diese Elemente in Rückruffunktionen gemäß den erhaltenen Zwischenwerten anzeigen. Was in der Tat in meinem Spiel gemacht wird.

Dynamische Schatten



Das Anzeigen von Schatten in der gesamten Szene erwies sich als so verschwenderisch, dass beim Einschalten die FPS mehrmals abfielen. Das ist nicht gut. Wir werden die Schatten der Objekte in einem bestimmten kleinen Quadrat um den Spieler herum anzeigen. Ich werde gleich sagen, dass eine solche Technik die Bildrate erheblich erhöht.

Hier ist nichts kompliziert. Die Schatten von three.js werden in eine bestimmte Schattenkamera geworfen, die in der Box angegeben ist (shadowCameraLeft, shadowCameraRight, shadowCameraTop, shadowCameraBottom). Es kann auf die gesamte Szene ausgedehnt werden oder so gemacht werden, dass es der Hauptkamera folgt und nur Schatten um sie herum geworfen werden. Das einzige, was ich diesem System hinzugefügt habe, ist der Schritt, durch den die Schatten aktualisiert werden. Es ist absolut nicht erforderlich, dieses Update jedes Mal durchzuführen, wenn der Player zuckt, da dies das System mit Berechnungen lädt. Lassen Sie ihn einen bestimmten Mindestabstand entlang einer der drei Achsen überwinden, damit die Schatten aktualisiert werden.

In meiner Bibliothek wird beim Initialisieren der 3D-Welt ein Steuerobjekt erstellt, das jederzeit die Koordinaten der Kamera enthält. Dort können Sie auch den Benutzerparameter contr.cameraForShadowStep einstellen, der den Schritt der Kameraposition enthält, an der sich die Position der Schattenkamera ändert.

Wenn beispielsweise das Parallelepiped der Schattenkamera die Abmessungen 700 x 700 x 700 hat, kann contr.cameraForShadowStep beispielsweise auf 20 eingestellt werden. Wenn sich der Spieler auf einer der Achsen um 20 bewegt, wird die Anfangsposition erneut gespeichert und die Schatten um den Spieler herum werden aktualisiert. Der Maßstab der 3D-Welt kann beliebig sein, abhängig vom Maßstab, in dem alle Modelle im 3D-Editor erstellt wurden. Und es ist wahrscheinlich, dass Sie anstelle von 700x700x700 und 20 7000x7000x7000 und 200 verwenden müssen. Dies ändert jedoch nichts an der Essenz.

Übrigens, wenn sich die Sonne am Himmel bewegt, werden die Schatten unabhängig von diesem System aktualisiert, da sich dort die Richtung der Schatten ändern sollte. Das heißt, sie werden aktualisiert, auch wenn der Spieler ohne Bewegung steht. Dort wird die Funktion der Aktualisierung der Schatten entsprechend der Aktualisierungsperiode des Himmels dumm genannt.

Punktlichtsystem



Das Vorhandensein von mehr als einem Dutzend Punktlichtquellen auf der Bühne ist in fps so stark wie dynamische Schatten. Und macht es sogar unmöglich, den alten "Hanf" zu spielen. Darüber hinaus spielt es keine Rolle, ob sich diese Quellen im Sichtfeld des Spielers (die Reichweite des weltweiten Renderings kann eingestellt werden) oder in beträchtlicher Entfernung befinden. Wenn sie dumm auf der Bühne sind, funktioniert alles langsam. Daher habe ich im Einstellungsmenü des Spiels, das vor dem Laden der 3D-Welt aufgerufen werden kann, Optionen für die Anzahl solcher Quellen (2, 4 oder 8) angegeben, die als "Licht der Laternen" bezeichnet werden.


Daher funktioniert es nachts nicht, alle Lichter gleichzeitig auf der Bühne einzuschalten. Oben habe ich in der Beschreibung des Weltinitialisierungsschemas Arrays von userPointLights undlightsPDynamicAr vorgestellt. In userPointLights werden die Koordinaten aller Lampenlampen auf der Bühne in einem Array festgelegt. UndlightsPDynamicAr enthält die Lichteinstellungen für alle 8 Instanzen. Abhängig von der Einstellung der Anzahl der Lichter nimmt die Bibliothek die erste davon und fügt sie der Szene im Sichtfeld des Players hinzu.

Während sich der Spieler bewegt, wird nach 2-8 Laternen gesucht, die dem Spieler durch das Koordinatenarray der userPointLights-Laternen am nächsten sind. Und Punktlichtquellen bewegen sich unter ihnen. Mit anderen Worten, 2-8 Beleuchtungslampen folgen dem Spieler und umgeben ihn. Darüber hinaus erfolgt dies auch nicht mit jedem Frame von fps, sondern mit einem bestimmten Schritt. Es ist absolut nicht erforderlich, die Suchfunktion 60 Mal pro Sekunde zu starten, insbesondere wenn sich der Spieler nicht bewegt. Lassen Sie dann die bereits um ihn herum gefundenen Lichter brennen.

So sieht es in Bewegung aus (Xeon E5440, GeForce GT730):


Verteilungsaufbau


Da ich keine ausgefeilte Entwicklungsumgebung verwende (außer dem erweiterten Editor), habe ich eine Bat-Datei geschrieben, in der der Google Closure Compiler aufgerufen wird, um den Code jeder * .js-Datei zu verschleiern. Und dann wird dort nwjc.exe aus dem nw.js-Bundle aufgerufen, um js in Binärdateien (* .bin) zu kompilieren. Ich werde ein Beispiel für eine der Dateien geben:
java -jar D: \ webserver \ Closure \ compiler.jar --js D: \ webserver \ proj \ m3d \ www \ game \ bus \ bus.js --js_output_file D: \ webserver \ proj \ nwProjects \ bus \ game \ bus \ bus.js

cd D: \ "Programme" \ Web2Exe \ down \ nwjs-sdk-v0.35.5-win-ia32

D: \ "Programme" \ Web2Exe \ down \ nwjs-sdk-v0.35.5-win -ia32 \ nwjc.exe D: \ Webserver \ proj \ nwProjects \ bus \ game \ bus \ bus.js D: \ webserver \ proj \ nwProjects \ bus \ game \ bus \ bus.bin

del D: \ webserver \ proj \ nwProjects \ bus \ game \ bus \ bus.js

Als Nächstes verwende ich das einfache Web2Executable-Dienstprogramm, um eine exe-Datei mit einer Assembly für Windows zu erstellen. Ich habe nw.js Version 0.35.5 gewählt, obwohl auch neuere Versionen verfügbar sind. Ich habe keine Auswirkungen von ihnen bemerkt, außer dass die Baugruppe vergrößert wurde.


Das Dienstprogramm kann die ausgewählte Version von nw.js in den angegebenen Ordner selbst herunterladen. Die Ausgabe ist eine Baugruppe. Die ausführbare Datei von 35 Megabyte enthält tatsächlich das Spiel selbst. Alles andere ist Node-Webkit. Der Gebietsschema-Ordner enthält offensichtlich Dateien mit einigen Ressourcen in verschiedenen Sprachen. Ich habe sie gelöscht und nur diejenigen hinterlassen, die mit Englisch zu tun haben. Dies hat übrigens den Start der russischsprachigen Version des Spiels nicht verhindert (im Spiel wechselt die Sprache zwischen Russisch und Englisch). Warum all diese Dateien dann, weiß ich nicht. Aber ohne Englisch fängt nichts an.

Die gesamte Montage dauerte schließlich 167 MB.


Dann habe ich alles mit einem der für diesen Zweck entwickelten kostenlosen Dienstprogramme in eine ausführbare Verteilungsdatei gepackt, und die Ausgabe war 70,2 MB Businessman3DSetup.exe.



Posting


Ich habe die Baugruppe an verschiedene App Stores gesendet. Die meisten von ihnen sind noch dabei, mein Spiel zu moderieren. Im Moment hat es nur Juckreiz veröffentlicht. Ich warne Sie sofort, das Spiel ist bezahlt, der Preis beträgt 3 $. Es gibt noch keine Einkäufe, aber ich habe mich noch nicht mit der Werbung befasst. GOG weigerte sich zu veröffentlichen und verwies auf die Tatsache, dass das Spiel recht einfach und nisch ist. Ich stimme im Prinzip zu. Ich denke, Epic Store wird das Gleiche tun.

Die veröffentlichte Version des Spiels ist Einzelbenutzer mit Bots in einer Menge von 1 bis 5. Sprache - Russisch und Englisch. Ich beabsichtige, die Netzwerkversion zu beenden. Es wird jedoch noch darüber nachgedacht, es in Form derselben Anwendung oder in Form einer Webbrowser-Version zu veröffentlichen, die sofort unter Windows, Linux, iOs und MacOs verfügbar ist und im Allgemeinen überall dort, wo der Browser WebGL unterstützt. Tatsächlich ist das Webkit ein Browser und das Spiel funktioniert gut darin, in Firefox, in Edge und sogar in IE11 unter Windows, obwohl es in letzterem sehr langsam ist.

Schlussfolgerungen



Ich bin wohl noch nicht bereit, meinen Motor für den allgemeinen Gebrauch auszulegen, da er noch nicht fertig ist. Ich werde zuerst ein anderes Spiel darauf schreiben, nur das aus dem Demo-Video über Schiffe, denn in diesem Spiel können Sie die Arbeit mit dem Wasser-Shader „durchgehen“. Außerdem plane ich dort eine einfache Physik-Engine zu implementieren. Ja, und Sie müssen noch alle anderen Funktionen ausführen, um alle Fehler zu beheben. Und es ist besser, dies bei zwei Spielen als bei einem zu tun, da einige Nuancen möglich sind. In der Zwischenzeit ist meine Engine meiner Meinung nach für ein Spiel immer noch zu stark geschärft.

Außerdem bin ich mir nicht sicher, ob jemand das alles braucht. Wenn Sie nüchtern aussehen, schreibt niemand Spiele in reinem Javascript. Aber ich mag es, weil die Spiele für den Browser ziemlich einfach und schnell sind. Sie werden schnell geladen, benötigen nicht viel RAM und arbeiten im Vergleich zu Mitbewerbern in HTML5 recht schnell, selbst im Vergleich zu 2D. Ich denke, ich werde mehr als ein Browser-Spiel (und nicht nur) für all diese Entwicklungen veröffentlichen.

Source: https://habr.com/ru/post/de450926/


All Articles