Condițiile de conducere sunt excelente pentru a crea modele de mișcare realiste, dar ele sunt chiar mai mari dacă le puteți controla, utiliza și combina cu ușurință. În acest tutorial, voi discuta și va acoperi implementarea unui manager de mișcări pentru toate comportamentele discutate anterior.
Notă: Deși acest tutorial este scris folosind AS3 și Flash, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului. Trebuie să aveți o înțelegere de bază a vectorilor de matematică.
După cum sa discutat anterior, fiecare comportament de direcție produce o forță rezultantă (numită "forță de direcție") care este adăugată la vectorul de viteză. Direcția și magnitudinea acelei forțe vor conduce caracterul, făcându-l să se miște după un model (căutați, fugiți, rătăciți și așa mai departe). Calculul general este:
direcție = căuta (); // aceasta poate fi orice comportament direcție = trunchi (direcție, max_force) direcție = direcție / viteză masă = trunchiat (viteză + direcție, max_speed) poziție = poziție + viteză
Deoarece forța de direcție este un vector, ea poate fi adăugată la orice alt vector (la fel ca viteza). Cu toate acestea, adevărata "magie" constă în faptul că puteți adăuga mai multe forțe de direcție împreună - este la fel de simplu ca:
direcție = nimic (); // vectorul nul, adică "magnitudinea forței zero" direcție = direcție + căuta (); direcție = direcție + fugă (); (...) direcție = trunchi (direcție, max_force) direcție = direcție / viteză masă = trunchiat (viteză + direcție, max_speed) poziție = poziție + viteză
Forțele de direcție combinate vor avea ca rezultat un vector care reprezintă toate acele forțe. În fragmentul de cod de mai sus, forța de direcție rezultată va face ca personajul să caute ceva în timp in acelasi timp va fugi de ceva altfel.
Verificați mai jos câteva exemple de forțe de direcție combinate pentru a produce o singură forță de direcție:
Combinația dintre forțele de direcție va produce fără efort eforturi de mișcare extrem de complexe. Imaginați-vă cât de greu ar fi să scrieți codul pentru a face un personaj să caute ceva, dar în același timp să evitați o zonă specifică, fără a utiliza vectori și forțe?
Aceasta ar necesita calcularea distanțelor, a zonelor, a căilor, a grafurilor și a altora. Dacă lucrurile se mișcă, toate aceste calcule trebuie repetate din când în când, deoarece mediul se schimbă constant.
Cu comportamentele de direcție, toate forțele sunt dinamic. Acestea sunt menite să fie calculate la fiecare actualizare de joc, astfel încât acestea vor reacționa în mod natural și fără probleme la schimbările de mediu.
Demo-ul de mai jos arată navele care vor căuta cursorul mouse-ului, dar vor părăsi centrul ecranului, ambele în același timp:
Pentru a folosi mai multe comportamente de direcție în același timp într-un mod simplu și simplu, a manager de misiune vine la îndemână. Ideea este de a crea o "cutie neagră" care să poată fi conectată la orice entitate existentă, ceea ce îi face capabili să efectueze acele comportamente.
Managerul are o referință la entitatea în care este conectat ("gazda"). Managerul va oferi gazdei un set de metode, cum ar fi căuta()
și fugă ()
. De fiecare dată când astfel de metode sunt invocate, managerul își actualizează proprietățile interne pentru a produce un vector de forță de direcție.
După ce managerul procesează toate invocările, va adăuga forța de direcționare rezultată la vectorul de viteză al gazdei. Aceasta va schimba magnitudinea și direcția vectorului de viteză al gazdei în funcție de comportamentele active.
Figura de mai jos demonstrează arhitectura:
Managerul are un set de metode, fiecare reprezentând un comportament distinct. Fiecare comportament trebuie să fie furnizat cu diferite informații externe pentru a funcționa.
Comportamentul căutării, de exemplu, are nevoie de un punct în spațiul folosit pentru a calcula forța de direcție spre acel loc; urmăresc nevoile mai multor informații din ținta sa, cum ar fi poziția și viteza actuale. Un punct din spațiu poate fi exprimat ca o instanță a lui Punct
sau Vector2D
, ambele clase destul de comune în orice cadru.
Cu toate acestea, ținta utilizată în comportamentul de urmărire poate fi orice. Pentru ca managerul de mișcări să fie destul de generic, trebuie să primească o țintă care, independent de tipul său, este capabilă să răspundă la câteva "întrebări", cum ar fi "Care este viteza dvs. curentă?"Folosind unele principii de programare orientate pe obiect, se poate realiza cu interfețe.
Presupunând interfața IBoid
descrie o entitate capabilă să fie gestionată de managerul de mișcări, orice clasă din joc poate folosi comportamente de direcție, atâta timp cât implementează IBoid
. Interfața are următoarea structură:
interfața publică IBoid function getVelocity (): Vector3D; funcția getMaxVelocity (): Număr; funcția getPosition (): Vector3D; funcția getMass (): Număr;
Acum că managerul poate interacționa cu toate entitățile de joc într-un mod generic, structura sa de bază poate fi creată. Managerul este compus din două proprietăți (forța de direcție rezultată și referința gazdă) și un set de metode publice, câte unul pentru fiecare comportament:
clasa publica SteeringManager public var steering: Vector3D; public var gazdă: IBoid; // funcția publică constructor SteeringManager (gazdă: IBoid) this.host = host; this.steering = Vector3D nou (0, 0); // Funcția publică API (o metodă pentru fiecare comportament) funcția publică caută (țintă: Vector3D, slowingRadius: Number = 20): void funcția publică fugă (țintă: Vector3D): void evadarea funcției publice (țintă: IBoid): void exercitarea funcției publice (țintă: IBoid): void // Metoda de actualizare. // ar trebui să fie numit după ce toate comportamentele au fost invocate pentru actualizarea funcției publice (): void // Resetați forța de direcție internă. Funcția privată API internă doSeek (țintă: Vector3D, slowingRadius: Number = 0): Vector3D funcția privată doFlee (țintă: Vector3D): Vector3D funcția privată doWander (): Vector3D funcția privată doEvade (țintă: IBoid): Vector3D funcție privată doPursuit (țintă: IBoid): Vector3D
Atunci când managerul este instanțiat, trebuie să primească o referință la gazda în care este conectat. Acesta va permite managerului să schimbe vectorul vitezei gazdă în funcție de comportamentul activ.
Fiecare comportament este reprezentat de două metode, unul public și unul privat. Folosiți ca un exemplu:
funcția publică dorită (țintă: Vector3D, slowingRadius: Number = 20): void funcția privată doSeek (țintă: Vector3D, slowingRadius: Number = 0): Vector3D
Publicul căuta()
va fi invocat pentru a spune managerului să aplice acel comportament specific. Metoda nu are valoare de retur și parametrii acesteia sunt corelați cu comportamentul însuși, cum ar fi un punct din spațiu. Sub capota metodei private doSeek ()
va fi invocată și valoarea de retur, forța de direcție calculată pentru acel comportament specific, va fi adăugată la manager direcție
proprietate.
Următorul cod demonstrează implementarea căutării:
// Metoda de publicare. // Primeste o tinta de a cauta si o incetinire a ritului (folosita pentru a ajunge la sosire). funcția publică urmărește (țintă: Vector3D, slowingRadius: Number = 20): void steering.incrementBy (doSeek (target, slowingRadius)); // Implementarea reală a căutării (cu cod de sosire inclus) a funcției private doSeek (țintă: Vector3D, slowingRadius: Number = 0): Vector3D var force: Vector3D; var distanța: număr; dorit = target.subtract (host.getPosition ()); distanța = dorită; lungime; desired.normalize (); dacă (distanța <= slowingRadius) desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius); else desired.scaleBy(host.getMaxVelocity()); force = desired.subtract(host.getVelocity()); return force;
Toate celelalte metode de comportament sunt implementate într-un mod foarte asemănător. urmărire()
metoda, de exemplu, va arăta astfel:
urmărirea funcției publice (obiectiv: IBoid): void steering.incrementBy (doPursuit (target)); funcția privată doPursuit (țintă: IBoid): Vector3D distance = target.getPosition () scădea (host.getPosition ()); var updatesNeeded: Număr = distance.length / host.getMaxVelocity (); var tv: Vector3D = target.getVelocity () clona (); tv.scaleBy (updatesNeeded); targetFuturePosition = target.getPosition () clone () adăugați (tv); returnați doSeek (targetFuturePosition);
Folosind codul din tutoriile anterioare, tot ce trebuie sa faci este sa le adaptezi sub forma de comportament()
și doBehavior ()
, astfel încât acestea pot fi adăugate la managerul de mișcări.
De fiecare dată când se invocă o metodă de comportament, forța rezultată pe care o produce este adăugată la manager direcție
proprietate. În consecință, proprietatea va acumula toate forțele de direcție.
Când toate comportamentele au fost invocate, managerul trebuie să aplice forța curentă de direcție la viteza gazdei, astfel că se va deplasa în funcție de comportamentele active. Se efectuează în Actualizați()
metoda managerului de miscare:
funcția publică (): void var velocity: Vector3D = host.getVelocity (); var pozitie: Vector3D = host.getPosition (); trunchi (direcție, MAX_FORCE); direcție.scaleBy (1 / host.getMass ()); velocity.incrementBy (direcție); truncate (viteza, host.getMaxVelocity ()); position.incrementBy (viteză);
Metoda de mai sus trebuie invocată de către gazdă (sau orice altă entitate de joc) după ce toate comportamentele au fost invocate, altfel gazda nu își va schimba niciodată vectorul de viteză pentru a se potrivi comportamentelor active.
Să presupunem o clasă numită Pradă
ar trebui să se miște folosind comportamentul de direcție, dar în prezent nu are nici un cod de conducere, nici managerul de mișcare. Structura sa va arata astfel:
clasa publică Prey public var position: Vector3D; viteza publică var: Vector3D; public var mass: Număr; funcția publică Prey (posX: Număr, posY: Număr, totalMass: Număr) position = new Vector3D (posX, posY); viteza = Vector3D nou (-1, -2); masa = totalMass; x = position.x; y = position.y; funcția publică de actualizare (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocitate.scaleBy (1 / masă); truncă (viteză, MAX_VELOCITY); pozitie = pozitie.add (viteza); x = position.x; y = position.y;
Folosind acea structură, instanțele din clasă se pot mișca utilizând integrarea Euler, la fel ca prima demonstrație a tutorialului de căutare. Pentru a putea folosi managerul, este nevoie de o proprietate care să se refere la managerul de mișcări și trebuie să implementeze IBoid
interfaţă:
clasa publica Instrumente de vopsire IBoid public var position: Vector3D; viteza publică var: Vector3D; public var mass: Număr; public var steering: SteeringManager; funcția publică Prey (posX: Număr, posY: Număr, totalMass: Număr) position = new Vector3D (posX, posY); viteza = Vector3D nou (-1, -2); masa = totalMass; direcție = nou SteeringManager (acest lucru); x = position.x; y = position.y; funcția publică de actualizare (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocitate.scaleBy (1 / masă); truncă (viteză, MAX_VELOCITY); pozitie = pozitie.add (viteza); x = position.x; y = position.y; // Mai jos sunt metodele pe care le solicită interfața IBoid. funcția publică getVelocity (): Vector3D viteza de retur; funcția publică getMaxVelocity (): Număr return 3; funcția publică getPosition (): Vector3D retur; funcția publică getMass (): Numărul return mass;
Actualizați()
metoda trebuie modificată corespunzător, astfel încât managerul poate fi actualizat și:
funcția publică (): void // Asigurați-vă că pradă rătăcesc în jurul ... steering.wander (); // Actualizați managerul, astfel încât acesta va schimba vectorul vitezei pradă. // Managerul va efectua și integrarea Euler, schimbând // vectorul "position". steering.update (); // După ce managerul și-a actualizat structurile interne, tot ce trebuie să facem este să ne actualizăm poziția în funcție de vectorul "position". x = position.x; y = position.y;
Toate comportamentele pot fi utilizate în același timp, atâta timp cât toate apelurile metodice sunt efectuate înaintea managerului Actualizați()
invocare, care aplică forța de direcție acumulată la vectorul de viteză al gazdei.
Codul de mai jos demonstrează o altă versiune a lui Prey Actualizați()
dar de data aceasta va căuta o poziție pe hartă și va evita un alt personaj (ambele în același timp):
funcția publică (): void var destinație: Vector3D = getDestination (); // locul pentru a căuta var vânător: IBoid = getHunter (); // a lua entitatea care ne vânează // Căutați destinația și evitați vânătorul (în același timp!) steering.seek (destinație); steering.evade (vânător); // Actualizați managerul, astfel încât acesta va schimba vectorul vitezei pradă. // Managerul va efectua și integrarea Euler, schimbând // vectorul "position". steering.update (); // După ce managerul și-a actualizat structurile interne, tot ce trebuie să facem este să ne actualizăm poziția în funcție de vectorul "position". x = position.x; y = position.y;
Demo-ul de mai jos demonstrează un model complex de mișcare în care mai multe comportamente sunt combinate. Există două tipuri de caractere în scenă: Vânător si Pradă.
Vânătorul va fi urmări o pradă dacă se apropie destul de mult; va continua atâta timp cât durează aprovizionarea cu stamina; atunci când nu mai are stamina, urmărirea este întreruptă și vânătorul va umbla până când își recuperează nivelele de rezistență.
Iată vânătorii Actualizați()
metodă:
funcția publică (): void if (odihnă && stamina ++> = MAX_STAMINA) resting = false; dacă (prada! = null &&! resting) direcție.pursuit (prada); stamina - = 2; dacă (stamina <= 0) prey = null; resting = true; else steering.wander(); prey = getClosestPrey(position); steering.update(); x = position.x; y = position.y;
Pradă va umbla pe termen nelimitat. Dacă vânătorul se apropie prea mult, va merge se sustrage. Dacă cursorul mouse-ului este aproape și nu există vânător în jur, prada va căuta cursorul mouse-ului.
Iată Preia lui Actualizați()
metodă:
funcția publică (): void var distance: Number = Vector3D.distance (poziție, Game.mouse); hunter = getHunterWithinRange (poziție); dacă (vânător! = nulă) direcție. invadator (vânător); dacă (distanța <= 300 && hunter == null) steering.seek(Game.mouse, 30); else if(hunter == null) steering.wander(); steering.update(); x = position.x; y = position.y;
Rezultatul final (gri este rătăcit, verde este căutat, portocaliu este urmărit, roșu este evadare):
Un manager de mișcare este foarte util pentru controlul mai multor comportamente de direcție în același timp. Combinația de astfel de comportamente poate produce modele de mișcare foarte complexe, permițând unei entități de joc să caute un lucru în același timp evitând un alt.
Sper că ți-a plăcut sistemul de management discutat și implementat în acest tutorial și îl folosești în jocurile tale. Mulțumesc că ați citit! Nu uitați să țineți la curent urmând-ne pe Twitter, Facebook sau Google+.