JSR 184 - tutorial 3 Přidávání, ubírání a kopírování objekt ů v JSR-184 Cíl tutorialu: - nau čit vás p řidávat/ ubírat objekty ze scény a kopírovat je Tak jdeme na to: V p ředešlém tutorialu jsme si popsali základní metody pro zm ěnu polohy, rotace a velikosti objekt ů. Dnes bych vám cht ěl popsat pár metod, pomocí kterých se do scény p řidávají nebo ubírají objekty a pomocí kterých m ůžete objekty kopírovat. Na záv ěru tohoto tutorialu se op ět vrátíme k naší h ře „packman“ a ukážeme si, jak lze vytvá řet úrovně do takovýchto her za pomoci nau čených metod. T řída Group obsahuje metody na p řidávání a odebírání objekt ů ze skupin. A protože je t řída World odvozena od třídy Group, tak všechny tyto metody m ůžeme aplikovat i na „World“ (což je vlastn ě také taková větší skupina). Pokud do nějaké skupiny něco přidáváme (odebíráme), musí to být odvozeno od t řídy Node. To znamená, že m ůžeme p řidávat (odebírat) objekty, jako jsou „Camera“ (kamera), „Group“ (skupina), „Light“ (sv ětlo), „Mesh“ (model) a „Sprite3D“ (sprit). Jak vidíte, do jedné skupiny m ůžeme p řidat další skupinu, do které m ůžeme p řidat další atd… P řidávání a ubírání P řidávání a ubírání objekt ů je velmi jednoduchá záležitost. P řidání n ějakého objektu do nějaké skupiny (světa) se provádí pomocí metody „addChild(Node node)“. Možná vám není jasné, k čemu vám je, když máte nějaký objekt ve skupin ě. Tak například, kdybyste nem ěli objekty ve vykreslovaném ...
JSR 184 - tutorial 3 P ř idávání, ubírání a kopírování objekt ů v JSR-184 Cíl tutorialu: - nau č it vás p ř idávat/ ubírat objekty ze scény a kopírovat je Tak jdeme na to: V p ř edelém tutorialu jsme si popsali základní metody pro zm ě nu polohy, rotace a velikosti objekt ů . Dnes bych vám cht ě l popsat pár metod, pomocí kterých se do scény p ř idávají nebo ubírají objekty a pomocí kterých m ů ete objekty kopírovat. Na záv ě ru tohoto tutorialu se op ě t vrátíme k naí h ř e packman a ukáeme si, jak lze vytvá ř et úrovn ě do takovýchto her za pomoci nau č ených metod. T ř ída Group obsahuje metody na p ř idávání a odebírání objekt ů ze skupin. A protoe je t ř ída World odvozena od t ř ídy Group, tak vechny tyto metody m ů eme aplikovat i na World (co je vlastn ě také taková v ě tí skupina). Pokud do n ě jaké skupiny n ě co p ř idáváme (odebíráme), musí to být odvozeno od t ř ídy Node. To znamená, e m ů eme p ř idávat (odebírat) objekty, jako jsou Camera (kamera), Group (skupina), Light (sv ě tlo), Mesh (model) a Sprite3D (sprit). Jak vidíte, do jedné skupiny m ů eme p ř idat dalí skupinu, do které m ů eme p ř idat dalí atd P ř idávání a ubírání P ř idávání a ubírání objekt ů je velmi jednoduchá záleitost. P ř idání n ě jakého objektu do n ě jaké skupiny (sv ě ta) se provádí pomocí metody addChild(Node node). Moná vám není jasné, k č emu vám je, kdy máte n ě jaký objekt ve skupin ě . Tak nap ř íklad, kdybyste nem ě li objekty ve vykreslovaném sv ě t ě , tak by tam samoz ř ejm ě nebyli vid ě t (nebo by v p ř ípad ě sv ě tel neovliv ň ovali okolí atp.). Take pro č mít objekty ve sv ě t ě je snad jasné. Ale pro č je dávat do oby č ejných skupin? D ů vod ů je hned n ě kolik. Nap ř íklad, kdy chci, aby se spolu pohybovalo více objekt ů , dám si je do skupiny a pohybuji celou skupinou hýbou se vechny objekty závisle na skupin ě (samoz ř ejm ě m ů ete pohybovat i kadým zvlá ť ). Ale nejspí hlavním d ů vodem, pro č p ř i ř azovat objekty skupinám je ten, e kontrola kolize (které se nau č íme v jednom z p ř ítích tutorial ů ) se vdy provádí na skupin ě a ne na jednotlivých objektech. Prakticky stejným zp ů sobem, jako se objekty do skupin p ř idávají, se z nich mohou i odebírat. To se d ě lá pomocí metody removeChild(Node node). Poté u objekt do skupiny nepat ř í, tedy skrze tuto skupinu s ním nelze manipulovat ani provád ě t kontroly kolizí. Kadá skupina má také monost zjistit, kolik pod ní spadá objekt ů . To se d ě lá pomocí metody getChildCount(), která vrací hodnotu typu int, udávající po č et objekt ů spadajících pod tuto skupinu. Jet ě bych cht ě l poznamenat, e jeden objekt nem ů e spadat pod více skupin.
Kopírování Kopírování se provádí metodou z t ř ídy Object3D a to metodou duplicate(). Takto se vytvo ř í p ř esná kopie dané v ě ci. Vimn ě te si, e kopírovat (duplikovat) nemusíme pouze v ě ci odvozené od t ř ídy Node, ale m ů eme kopírovat ve, co je odvozené od t ř ídy Object3D. To znamená, e m ů eme kopírovat t ř eba i celý sv ě t. Jen nezapome ň te do závorek p ř ed tím napsat, co vlastn ě kopírujete. Nap ř íklad, kopírujeme-li sv ě tlo, zapíeme to takto: Light light2 = (Light)light1.duplicate(). Zp ě t k naemu packmanovi Jak jsem ji ř íkal na za č átku tohoto tutorialu, dnes si ukáeme, jak lze jednodue vytvá ř et úrovn ě do takových her, jako je ná packman. Budeme to d ě lat tak, e si vytvo ř íme pole, které bude p ř edstavovat ná level a podle hodnot v n ě m budeme kopírovat na pozice (které si vypo č teme) polí č ka, po kterých se bude packman pohybovat. 0 bude p ř edstavovat prázdné pole, 1 bude pole, na kterém bude ná packman za č ínat, 2 bude p ř edstavovat pole, na kterém budou za č ínat monstra (a bude na nich 10 bodové jídlo), 3 bude tvo ř it pole, nad kterým bude p ř i na č tení úrovn ě jídlo (nebo jak tomu chcete ř íkat) s bodovou hodnotou 10 a 4 bude pole s jídlem za 50 bod ů umo ň ující do č asnou pr ů chodnost monster. Take si do vaeho *.m3g souboru p ř idejte zmín ě né modely (polí č ko po kterém bude packman chodit, model monstra a modely 10 a 50 bodového jídla). Na obrázku jsou tyto modely vid ě t vedle sebe a popsané, abyste v ě d ě li, co bude jaký model p ř edstavovat:
Zjist ě te si ID t ě chto model ů a zapite je takto (jen nejspí s jinými hodnotami): int floorID = 45; int monsterID = 36; int point10ID = 11; int point50ID = 19;
Te ď si vytvo ř íme novou t ř ídu s názvem levely, do které budeme ukládat námi vytvo ř ené úrovn ě . Jet ě ne vytvo ř íme samotnou první úrove ň , ř ekneme si, kolik bude mít polí č ek. Pro uleh č ení budeme d ě lat č tvercové úrovn ě ř ekn ě me o velikosti 15x15 polí. Toto č íslo si zapíeme jako prom ě nnou typu int s názvem rad (jako ř ad). static int rad = 15; A te ď si ud ě láme level podle toho, jak jsme si to napsali výe. Bude to pole typu int s 15x15 hodnotami (s 225 hodnotami). Nazveme si ho level1: static int[] level1 = new int[] { 2, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 2, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 3, 0, 4, 3, 3, 3, 0, 3, 0, 3, 3, 3, 4, 0, 3, 3, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 0, 3, 3, 1, 3, 3, 0, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 0, 3, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 0 }; Celá tato t ř ída tedy bude vypadat jednodue takto: public class levely { static int rad = 15; static int[] level1 = new int[] { 2, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 2, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 3, 0, 4, 3, 3, 3, 0, 3, 0, 3, 3, 3, 4, 0, 3, 3, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 0, 3, 3, 1, 3, 3, 0, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 0, 3, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 0 }; } Nyní se vrátíme k t ř íd ě s packmanem. Deklarujeme Meshe vytvo ř ených model ů , kadý s po č áte č ní nulovou hodnotou: Mesh floorMesh = null; Mesh monsterMesh = null; Mesh point10Mesh = null; Mesh point50Mesh = null;
Dále budeme pot ř ebovat pole t ě chto Mesh ů , kam budeme ukládat zkopírované Meshe naich model ů . Hodnoty zde nastavené jsou nejvyím moným po č tem kadého objektu (i kdy t ř eba monster asi 225 mít nebudete): Mesh[] floor = new Mesh[levely.level1.length]; Mesh[] monster = new Mesh[levely.level1.length]; Mesh[] point10 = new Mesh[levely.level1.length]; Mesh[] point50 = new Mesh[levely.level1.length]; A jet ě si (trochu do budoucna) p ř ipravíme skupiny, do kterých budeme vkládat také tyto Meshe, hlavn ě kv ů li kolizím: Group floorGroup = new Group(); Group monsterGroup = new Group(); Group point10Group = new Group(); Group point50Group = new Group(); Do funkce na na č tení sv ě ta (loadWorld()) si za kód pro na č tení vloíme kód, který p ř idá tyto skupiny do sv ě ta: world.addChild(floorGroup);world.addChild(monsterGroup);world.addChild(point10Group);world.addChild(point50Group); A samoz ř ejm ě si n ě kde musíme ve sv ě t ě vyhledat objekty, které budeme kopírovat. Od toho máme nai funkci loadObjects(): public void loadObjects() { cam = world.getActiveCamera(); packman = (Mesh)world.find(packmanID); floorMesh = (Mesh)world.find(floorID); monsterMesh = (Mesh)world.find(monsterID); point10Mesh = (Mesh)world.find(point10ID); point50Mesh = (Mesh)world.find(point50ID); world.removeChild(floorMesh); world.removeChild(monsterMesh); world.removeChild(point10Mesh); world.removeChild(point50Mesh); } Pro správné umíst ě ní objekt ů jet ě pot ř ebujeme v ě d ě t, jak velká bude nae hrací plocha. A protoe máme h ř it ě 15x15 polí a 1 pole má rozm ě ry 10x10, tak velikost hrací plochy bude 150: int poleVelikost = 150; Jet ě budeme do budoucna pot ř ebovat v ě d ě t, kolik je v úrovni monster (aby nemusel kód probíhat v cyklu 225 krát kdy bude sta č it jenom 2x). Proto si vytvo ř íme prom ě nnou (int) s názvem pocetMonster a po č áte č ní hodnotou 0. Jak bude kód procházet naí úrovní a narazí na pole s monstry tak se hodnota této prom ě nné o 1 zvýí. Int pocetMonster = 0;
Z konstruktoru zavoláme metodu (kterou si nyní vytvo ř íme) pro na č tení levelu s parametrem pole typu int (námi vytvo ř ené úrovn ě ze t ř ídy levely): loadRoom(levely.level1); A nyní ta slibovaná metoda na postavení naeho levelu.. Nejt ě í pro vás z ř ejm ě bude se zorientovat v metod ě setTranslation, protoe jsem tam zahrnul troku sloit ě jí výpo č ty pro správné umíst ě ní daného objektu na správné sou ř adnice (ale kdy se jim budete chvíli v ě novat tak se v nich snat zorientujete): public void loadRoom(int[] level) { for(int i = 0; i < level.length; i++) { if(level[i] == 1) { floor[i] = (Mesh)floorMesh.duplicate(); floor[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); floorGroup.addChild(floor[i]); packman.setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); } else if(level[i] == 2) { floor[i] = (Mesh)floorMesh.duplicate(); floor[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); floorGroup.addChild(floor[i]); monster[pocetMonster] = (Mesh)monsterMesh.duplicate(); monster[pocetMonster].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); monsterGroup.addChild(monster[pocetMonster]); pocetMonster++; point10[i] = (Mesh)point10Mesh.duplicate(); point10[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); point10Group.addChild(point10[i]); } else if(level[i] == 3) { floor[i] = (Mesh)floorMesh.duplicate(); floor[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); floorGroup.addChild(floor[i]); point10[i] = (Mesh)point10Mesh.duplicate(); point10[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); point10Group.addChild(point10[i]); } else if(level[i] == 4) {
floor[i] = (Mesh)floorMesh.duplicate(); floor[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); floorGroup.addChild(floor[i]); point50[i] = (Mesh)point50Mesh.duplicate(); point50[i].setTranslation(((-10*(int)(i/levely.rad))+(poleVelikost/2))-5, ((-10*(int)(i-(i/levely.rad)*levely.rad))+(poleVelikost/2))-5, 0); point50Group.addChild(point50[i]); } } } Nyní se u ná packman docela obstojn ě pohybuje po úrovni (nepo č ítaje to, e se m ů e ohybovat i mimo cestu), kde jsou rozmíst ě ná jak pole, tak body a monstra, ale kamera je stále na jednom míst ě , co je pro takovouto hru vcelku nepraktické. Proto si jet ě p ř ed koncem dneního tutorialu ukáeme, jak ud ě lat pohled (a pohyb) kamery z t ř etí osoby, u kterého vyuijeme to, co jsme se dnes nau č ili. Nejprve si vytvo ř íme 2 pomocné skupiny (camG a camGG), které nám usnadní manipulaci s kamerou: Group camGG = new Group(); Group camG = new Group(); Pak budeme pot ř ebovat prom ě nnou (float) s aktuální rotací kamery (zpo č atku 0): Float camRotZ = 0.0f; Na konci metody loadWorld si do sv ě ta p ř idáme skupinu camGG: world.addChild(camGG); A ve funkci loadObjects si hned za na č tením kamery tuto kameru odebereme ze sv ě ta, protoe ji budeme dávat do jiné skupiny (pro snaí manipulaci) a jak jsme si ji ř íkali, 1 objekt nem ů e být ve více skupinách. Kameru dáme pod skupinu camG a tuto skupinu dáme pod camGG: world.removeChild(cam);camGG.addChild(camG); camG.addChild(cam); P ř i grafickém znázorn ě ní by to vypadalo asi takto: World world Group camGG -----> -----> Group camG -----> Camera cam
Take budeme-li pohybovat (rotovat) skupinou camGG, p ř emíst ě ní (rotace) se aplikuje i na camG i cam. Pro pohyb kamery budeme pot ř ebovat znát sou ř adnice packmana, take si vytvo ř íme nové pole typu float s názvem packmanPos o 3 hodnotách: float[] packmanPos = new float[3]; Polohu naeho packmana zjistíme (jak jsme si ř íkali v 2. Tutorialu) metodou getTranslation(float[] xyz), tak si do metody pohybPackmana tuto metodu p ř idáme: packman.getTranslation(packmanPos);Nyní si do metody run() p ř idáme volání metody pohybKamery(), která bude vypadat docela jednodue: public void pohybKamery() { camGG.setTranslation(packmanPos[0], packmanPos[1], packmanPos[2]); camRotZ -= camRotZ/6; camG.setOrientation(camRotZ, 0, 0, 1); } Skupina camGG se v této metod ě stará o to, aby se pohybovala spole č n ě s packmanem (tím pádem se tak pohybuje i skupina camG i nae kamera) a skupina camG se natá č í podle prom ě nné camRotZ, která se plynule p ř ibliuje nule (tedy vyrovnává se za packmana). Ovem aby to bylo takto moné, musíme si p ř i zm ě n ě sm ě ru packmana nastavit rotaci camGG stejnou jako rotaci packmana a s prom ě nnou camRotZ tedy musíme ud ě lat opa č nou operaci (aby byla nato č ena jako p ř edtím pouze aby bylo moné hodnotu op ě t vyrovnávat na 0). Takto bude vypadat upravená metoda pro kontrolu stisku kláves: protected void keyPressed(int keyCode) { if(keyCode == KEY_NUM4 || keyCode == getKeyCode(Canvas.LEFT)) { if(smerPackmana <= 4) { smerPackmana = smerPackmana>1 ? smerPackmana-1 : 4; } else { smerPackmana = smerPackmana==10 ? 4 : (smerPackmana-10)/10 ; } packman.preRotate(90, 0, 0, 1); camGG.preRotate(90, 0, 0, 1); camRotZ -= 90; camG.setOrientation(camRotZ, 0, 0, 1); } else if(keyCode == KEY_NUM6 || keyCode == getKeyCode(Canvas.RIGHT)) { if(smerPackmana <= 4) { smerPackmana = smerPackmana<4 ? smerPackmana+1 : 1; } else {