Pokażę, czy próbując zrozumieć jako programistę JS web / UI, mogę pomóc. Nie sięgaj też zbyt daleko w agnostycyzm językowy. Warto zapoznać się z wieloma wzorcami ustalonymi w innych językach, ale można je stosować w JS w różny sposób ze względu na jego elastyczność lub naprawdę nie są konieczne ze względu na plastyczny charakter języka. Możesz skorzystać z okazji, jeśli napiszesz swój kod, myśląc, że JS ma ten sam zestaw granic, co bardziej klasyczny język zorientowany na OOP.
Przede wszystkim, jeśli chodzi o czynnik „nie używaj OOP”, pamiętaj, że obiekty JavaScript są jak placek zabaw w porównaniu do innych języków i faktycznie musisz zrobić wszystko, aby zbudować koszmar kaskadowo-spadkowy, ponieważ JS nie jest klasą na podstawie i komponowanie przychodzi o wiele bardziej naturalnie. Jeśli wdrażasz jakiś głupiutki lub prototypowy system hand-me-down w swoim JS, rozważ to porzucenie. W JS używamy zamknięć, prototypów i przekazujemy funkcje jak cukierki. Jest obrzydliwe, brudne i złe, ale także mocne, zwięzłe i tak to lubimy.
Podejścia oparte na dziedziczeniu są w rzeczywistości przedstawiane jako anty-wzorzec we wzorach projektowych i nie bez powodu, jak każdy, kto zszedł ponad 15 poziomów klas lub struktur podobnych do klas, aby dowiedzieć się, gdzie, do cholery, wypaliła wersję metody przychodził z może ci powiedzieć.
Nie wiem, dlaczego tak wielu programistów uwielbia to robić (zwłaszcza faceci Java z jakiegoś powodu piszą JavaScript), ale jest to okropne, nieczytelne i całkowicie nie do utrzymania, gdy jest przyzwyczajone do nadmiaru. Dziedziczenie jest w porządku tu i tam, ale tak naprawdę nie jest konieczne w JS. W językach, w których jest to bardziej kuszący skrót, powinien być naprawdę zarezerwowany dla bardziej abstrakcyjnych problemów związanych z architekturą, a nie dla bardziej dosłownych schematów modelowania, takich jak frankensteining implementacji zombie poprzez łańcuch dziedziczenia obejmujący BunnyRabbit, ponieważ tak się stało. To nie jest dobre ponowne użycie kodu. To koszmar konserwacji.
Jako silniki oparte na jednostkach / komponentach / systemach JS dev uderzyły mnie jako system / wzorzec do rozłączania problemów projektowych, a następnie komponowania obiektów do implementacji na bardzo szczegółowym poziomie. Innymi słowy, zabawa dziecka w języku takim jak JavaScript. Ale pozwól, że zobaczę, czy najpierw mam problemy z tym.
Podmiot - konkretna rzecz, którą projektujesz. Mówimy bardziej w kierunku właściwych rzeczowników (ale oczywiście nie). Nie „Scena”, ale „IntroAreaLevelOne”. IntroAreaLevelOne może znajdować się wewnątrz jakiegoś pola sceneEntity, ale skupiamy się na czymś innym niż inne powiązane rzeczy. W kodzie jednostka jest tak naprawdę tylko nazwą (lub identyfikatorem) powiązaną z szeregiem rzeczy, które musi zaimplementować lub ustanowić (komponenty), aby była użyteczna.
Komponenty - rodzaje rzeczy, których potrzebuje jednostka. To są rzeczowniki ogólne. Jak WalkingAnimation. W WalkingAnimation możemy uzyskać bardziej szczegółowe, takie jak „Shambling” (dobry wybór dla zombie i potworów roślinnych) lub „ChickenWalker” (doskonały dla robotów typu ed-209ish z odwrotnym połączeniem). Uwaga: Nie jestem pewien, jak to może oddzielić od renderowania takiego modelu 3D - więc może to bzdura, ale jestem bardziej pro JS niż doświadczonym twórcą gier. W JS umieściłbym mechanizm mapowania w tym samym pudełku z komponentami. Komponenty same w sobie prawdopodobnie nie są logiczne i stanowią raczej mapę drogową informującą systemy, co należy wdrożyć, jeśli systemy są nawet potrzebne (w mojej próbie ECS niektóre komponenty to tylko zbiory zestawów właściwości). Po ustanowieniu komponentu „
Systemy - prawdziwe mięso programowe jest tutaj. Systemy AI są zbudowane i połączone, renderowanie zostało osiągnięte, ustalone sekwencje animacji itp. Rozwalam i pozostawiam je głównie wyobraźni, ale w przykładzie System.AI bierze wiele właściwości i wydziela funkcję, która służy do dodawania procedur obsługi zdarzeń do obiektu, który ostatecznie zostaje wykorzystany przy implementacji. Kluczową rzeczą w System.AI jest to, że obejmuje wiele typów komponentów. Możesz uporządkować wszystkie elementy AI za pomocą jednego komponentu, ale zrobienie tego to niezrozumienie sensu granulacji.
Pamiętaj o celach: Chcemy ułatwić podłączenie interfejsu GUI, aby osoby niebędące projektantami mogły łatwo modyfikować różne rzeczy poprzez maksymalizowanie i dopasowywanie komponentów w paradygmacie, który ma dla nich sens, i chcemy uciec od popularne schematy dowolnego kodu, które są o wiele łatwiejsze do napisania niż do modyfikacji lub utrzymania.
W JS może coś takiego. Deweloperzy gier, proszę powiedz mi, czy okropnie się mylę:
//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game
//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){
//note: {} in JS is an object literal, a simple obj namespace (a dictionary)
//plain ol' internal var in JS is akin to a private member
var default={ //most NPCs are humanoids and critters - why repeat things?
speedAttributes:true,
maneuverAttributes:true,
combatAttributes:true,
walkingAnimation:true,
runningAnimation:true,
combatAnimation:true,
aiOblivious:true,
aiAggro:true,
aiWary:true, //"I heard something!"
aiFearful:true
};
//this. exposes as public
this.zombie={ //zombies are slow, but keep on coming so don't need these
runningAnimation:false,
aiFearful:false
};
this.laserTurret={ //most defaults are pointless so ignore 'em
ignoreDefault:true,
combatAttributes:true,
maneuverAttrubtes:true, //turning speed only
};
//also this.nerd, this.lawyer and on and on...
//loop runs on instantiation which we're forcing on the spot
//note: it would be silly to repeat this loop in other entity collections
//but I'm spelling it out to keep things straight-forward.
//Probably a good example of a place where one-level inheritance from
//a more general entity class might make sense with hurting the pattern.
//In JS, of course, that would be completely unnecessary. I'd just build a
//constructor factory with a looping function new objects could access via
//closure.
for(var x in npcEntities){
var thisEntity = npcEntities[x];
if(!thisEntity.ignoreDefaults){
thisEntity = someObjectXCopyFunction(defaults,thisEntity);
//copies entity properties over defaults
}
else {
//remove nonComponent property since we loop again later
delete thisEntity.ignoreDefaults;
}
}
})() //end of entity instantiation
var npcComponents = {
//all components should have public entityMap properties
//No systems in use here. Just bundles of related attributes
speedAttributes: new (function SpeedAttributes(){
var shamblingBiped = {
walkingAcceleration:1,
topWalking:3
},
averageMan = {
walkingAcceleration:3,
runningAcceleration:4,
topWalking: 4,
topRunning: 6
},
programmer = {
walkingAcceleration:1,
runningAcceleration:100,
topWalking:2
topRunning:2000
}; //end local/private vars
//left is entity names | right is the component subcategory
this.entityMap={
zombie:shamblingBiped,
lawyer:averageMan,
nerd:programmer,
gCostanza:programmer //makes a cameo during the fire-in-nursery stage
}
})(), //end speedAttributes
//Now an example of an AI component - maps to function used to set eventHandlers
//functions which, because JS is awesome we can pass around like candy
//I'll just use some imaginary systems on this one
aiFearful: new (function AiFearful(){
var averageMan = Systems.AI({ //builds and returns eventSetting function
fearThreshold:70, //%hitpoints remaining
fleeFrom:'lastAttacker',
tactic:'avoidIntercept',
hazardAwareness:'distracted'
}),
programmer = Systems.AI({
fearThreshold:95,
fleeFrom:'anythingMoving',
tactic:'beeline',
hazardAwareness:'pantsCrappingPanic'
});//end local vars/private members
this.entityMap={
lawyer:averageMan,
nerd:averageMan, //nerds can run like programmers but are less cowardly
gCostanza:programmer //makes a cameo during the fire-in-nursery stage
}
})(),//and more components...
//Systems.AI is general and would get called for all the AI components.
//It basically spits out functions used to set events on NPC objects that
//determine their behavior. You could do it all in one shot but
//the idea is to keep it granular enough for designers to actually tweak stuff
//easily without tugging on developer pantlegs constantly.
//e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents
function createNPCConstructor(npcType){
var components = npcEntities[npcType],
//objConstructor is returned but components is still accessible via closure.
objConstructor = function(){
for(var x in components){
//object iteration <property> in <object>
var thisComponent = components[x];
if(typeof thisComponent === 'function'){
thisComponent.apply(this);
//fires function as if it were a property of instance
//would allow the function to add additional properties and set
//event handlers via the 'this' keyword
}
else {
objConstructor.prototype[x] = thisComponent;
//public property accessed via reference to constructor prototype
//good for low memory footprint among other things
}
}
}
return objConstructor;
}
var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
npcConstructors[x] = createNPCConstructor(x);
}
Teraz, kiedy potrzebujesz NPC, możesz budować z nim npcBuilders.<npcName>();
GUI może podłączyć się do obiektów npcEntities i komponentów i pozwolić projektantom na modyfikowanie starych encji lub tworzenie nowych encji poprzez proste mieszanie i dopasowanie komponentów (chociaż nie ma tam żadnego mechanizmu dla komponentów innych niż domyślne, ale specjalne komponenty mogą być dodawane na bieżąco kod, o ile istnieje dla niego zdefiniowany komponent.