Diskuze: pomoc s rozhýbáním obrázku (2D transformace)
V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.
Fugiczek:12.8.2012 14:37
No já bych to vyřešil přes proměnnou delta a určení rychlosti
panáčka.
Delta je doba která proběhla od poslední zobrazení snímku. Takže nejdřív
bych si napsal metodu která bude vracet hodnotu delta. K tomu bude potřeba si
někde deklarovat proměnnou která bude mít v sobě čas posledního
snímku.
Takže někde si deklaruješ proměnou:
long lastFrame = System.getCurrentTime();
A pak si napišeš metodu která bude vracet deltu:
private int getDelta(){
long time=System.getCurrentTime();
int delta=(int)(time-lastFrame);
lastFrame=time;
return delta;
}
Pak si někde stanovíš rychlost panáčka:
float velocity = 0.2f;
Dále bych doporučil si udělat pro panáčka objekt, kde bych dal informace
o té rychlost, souřadnicích a jeho cíl v souřadnicích. Budu předpokládat
že takovej objekt již je a má metody jako getVelocity(), get/set X(),
get/setY() a souřadnice typu float tak jako rychlost.
Takže teď upravíme tu metodu paint:
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(pozadi, 20, 20, this);
g2.drawImage(panak, Math.round(getX()), Math.round(getY()), this);
updateLogic();
}
A teď metodu která bude počítat nové souřadnice:
private void updateLogic(){
int delta = getDelta();
panak.setX(panak.getX()+((float)delta*velocity));
panak.setY(panak.getY()+((float)delta*velocity));}
Samozřejmě updateLogic() se upraví tak aby postupně šel za danými souřadnice pomocí nějakého algoritmu. Pokud se bude panák pohybovat moc rychle stačí změnšit rychlost, nebo deltu vydělit třeba 1000 aby se čas převedl na sekundy. Snad tenhle vyčerpávající příspěvek bude k něčemu nápomocný
zdravíčko .. Tak už jsem to vyřešil trošku jinak přesto díky za odpověď. Jelikož mám dalších tisíc otázek, tak tady ještě nějaký vytasím. přes metodu
public void keyPressed(KeyEvent e)
vždy odchytím tlačítko a podle něj volím směr pohybu. Když chci ale odchytit dvě najednou (např. doprava+nahoru)? mám zvolit jinou metodu, nebo volit nějaké proměnné do kterých si dám požadované tlačítka a podle nich zvolím směr?
Dál jsem se chtěl zeptat, jak to chodí když chci ohraničit prostředí ( aby se mi panáček nedostal tam kde ho nevidím ). Mám to udělat stylem když je na xxx-tém pixelu, metoda jdi() nefunguje? ... nebo se to dělá jinak?
Za odpovědi/typy díky
Takovej směr jsem zatím neřešil. Napadají mě dvě řešení. První že
když se vyvolá metoda keyPressed tak se ten směr nastaví na true a když se
vyvolá metoda keyReleased tak se nastaví zpět na false. Pak jen budeš
ověřovat jestli jsou obě dvě proměnné true. Druhá možnost že si
nastavíš proměnou typu float a tam budeš mít směr jako u hodin. 0-sever
0.125-severovýchod 0.25-vychod, ... No při stisknutí klávesy by se to
přičítalo a odečítalo a cesta by se počítala přes uhel nebo nějak
podobně.
K tomu druhému dotazu. Já si obvykle udělám mapu do textovýho souboru,
0-pruchozí 1-zeď. Udělám si to třeba ty čísla 20x20 a pak si
propočítám podle herní plochy kolik zabírá místa jakoby jedno číslo. A
pak si jen počítám jestli na tom místě není 1 Tvým stylem by to šlo udělat
taky. Můj styl není nějak univerzální. Kdyby jsi chtěl udělat herní
plochu která půjde ničit tak bych mapu nahrával jako obrázek a mapu
postupně střelami "gumoval" zdi bych ověřoval metodou jakou jsi popisoval
jestli je tam daná barva tak jdi
Fugiczek:12.8.2012 21:24
Ještě k těm aby se nedostal tam kde ho nevidíš tak prostě jen
nedovolíš pohyb pokud jeho souřadnice nejsou menší než 0 a větší než
velikost okna.
Mohlo by to vypadat nějak takhle:
if(panak.getX()>0 &&panak.getX()<getWidth()){
if(panak.getY()>0 &&panak.getX()<getHeight()){
//neco delej
}
}
metody getWidth() a getHeight() jsou normálně u komponenty JFrame a možná i u JPanel ale si nejsem jistý.
Fugiczek:12.8.2012 23:54
Já se vše učím z různých článků co najdu na netu v angličtině.
Každý to dělá jinými způsoby a já si vyberu ten co se mi ke které hře
hodí.
Tady ti postnu pár zdrojáků z jedné hry.
Toto je třída s mapou:
package objects;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
public class Map {
private int[][]mapa;
private int poradiMapy;
public Map(){
mapa=new int[21][21];
poradiMapy=0;
}
/**
*
* @return <ul>
* <li>0-když je vše v pořádku</li>
* <li>-1-když mapa není k dispozici</li>
* <li>-2-když nastane chyba při čtení</li>
* </ul>
*/
public short nactiMapu(){
poradiMapy++;
mapa=new int[21][21];
try {
BufferedReader br=new BufferedReader(new InputStreamReader(new
FileInputStream(poradiMapy + ".txt"), "UTF-8"));
String radka;
char[] radkaChar;
int radek=0;
while((radka=br.readLine())!=null){
radkaChar=new char[21];
radka.getChars(0, 21, radkaChar, 0);
for(int i=0;i<21;i++){
mapa[radek][i]=Character.digit(radkaChar[i],10);
if(!(mapa[radek][i]==0 || mapa[radek][i]==1)){
br.close();
prepis();
return -2;
}
}
radek++;
}
br.close();
} catch (FileNotFoundException e) {
return -1;
} catch (IOException e) {
prepis();
return -2;
}
return 0;
}
public int[][] getMap(){
int [][] mapaCopy=new int[21][21];
System.arraycopy(mapa, 0, mapaCopy, 0, 21);
return mapaCopy;
}
public int getPoradiMapy(){
return poradiMapy;
}
private void prepis(){
mapa=new int[21][21];
for(int[]a:mapa){
for(@SuppressWarnings("unused") int i:a){
i=0;
}
}
}
}
A tady je metoda ze jedné třídy, počítá se tu pohyb a cesta.
public void move(Map mapa) throws ArrayIndexOutOfBoundsException{
map=mapa.getMap();
int probehlo=0;
for(Point p:player){
if(p.isSelected() && (targetX!=0 || targetY!=0)){
if(targetX>p.getX() && map[(int)(p.getY())/20][(int)(p.getX()+p.getVelocity()+6)/20]!=1){
p.setX(p.getX()+p.getVelocity());
}else if(targetX<p.getX() && map[(int)(p.getY())/20][(int)(p.getX()-p.getVelocity())/20]!=1){
p.setX(p.getX()-p.getVelocity());
}else if(targetY>p.getY() && map[(int)(p.getY()+p.getVelocity()+6)/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()+p.getVelocity());
}else if(targetY<p.getY() && map[(int)(p.getY()-p.getVelocity())/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()-p.getVelocity());
}
if(p.getX()==targetX && p.getY()==targetY){
p.setSelected(false);
}
probehlo++;
}else{
switch(rand.nextInt(4)){
case 0:
if(p.getY()<widthWindow && map[(int)(p.getY()+p.getVelocity()+5)/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()+p.getVelocity());
}
break;
case 1:
if(p.getX()<heightWindow && map[(int)(p.getY())/20][(int)(p.getX()+p.getVelocity()+5)/20]!=1){
p.setX(p.getX()+p.getVelocity());
}
break;
case 2:
if(p.getY()>0 && map[(int)(p.getY()-p.getVelocity())/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()-p.getVelocity());
}
break;
case 3:
if(p.getX()>0 && map[(int)(p.getY())/20][(int)(p.getX()-p.getVelocity())/20]!=1){
p.setX(p.getX()-p.getVelocity());
}
break;
default:
try {
throw new Exception("Random se zbláznil");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
for(Point p:enemy){
switch(rand.nextInt(4)){
case 0:
if(p.getY()<widthWindow && map[(int)(p.getY()+p.getVelocity()+5)/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()+p.getVelocity());
}
break;
case 1:
if(p.getX()<heightWindow && map[(int)(p.getY())/20][(int)(p.getX()+p.getVelocity()+5)/20]!=1){
p.setX(p.getX()+p.getVelocity());
}
break;
case 2:
if(p.getY()>0 && map[(int)(p.getY()-p.getVelocity())/20][(int)(p.getX())/20]!=1){
p.setY(p.getY()-p.getVelocity());
}
break;
case 3:
if(p.getX()>0 && map[(int)(p.getY())/20][(int) (p.getX()-p.getVelocity())/20]!=1){
p.setX(p.getX()-p.getVelocity());
}
break;
default:
try {
throw new Exception("Random se zbláznil");
} catch (Exception e) {
e.printStackTrace();
}
}
}
if(probehlo==0){
targetX=0;
targetY=0;
}
}
Tohle jsou zdrojaky z jedné hry kterou jsem samozřejmě nedokončil, takže ani ten pohyb není dokonalý ale snad z toho něco pochytíš. Čísla jako 5 a 6 se tam přičítají z důvodu velikosti toho objektu co se vykresluje. Pak to dělení 20 je abych se dostal do indexu těch polí a zjistil jesli v tom indexu je 0 nebo 1.
David Hartinger:13.8.2012 0:02
Můžeš se podívat na algoritmy pohybu:
Po přímce: http://www.itnetwork.cz/…yb-po-primce
V bludišti (vyhýbání se překážkám): http://www.itnetwork.cz/…y-v-bludisti
opět musím poděkovat za odpovědi a nadhodit další dotaz. Řešili jste už někdy pohyb o určitý úhel? tzn. když mačkám nahoru jedu podle hlavy, když dám šipku doleva, objekt zatáčí o určitý kousek (úhel) doleva. Prostě jako v této hře :
Dík moc za pomoc
Fugiczek:14.8.2012 9:05
Tenhle pohyb jsem zatím neřešil ale bude mě to čekat teď v multiplayer
hře kterou plánuji. Nemůžu ti poradit nic kontrétního, ale vím že se to
dělá přes trigonometrii. Podívej se na toto: http://en.wikipedia.org/…/Unit_circle
Já to budu řešit stejně...
David Hartinger:14.8.2012 10:58
Přesně tak, nastuduj si goniometrické funkce Budeš mít uložený úhel, ten budeš šipkami zvyšovat nebo snižovat. Podle úhlu si vypočítáš další souřadnici.
Koukni na tohle, jistě si potom hravě napíšeš svůj kód: http://www.itnetwork.cz/…-po-kruznici
David Hartinger:14.8.2012 11:05
Bude to něco jako (píšu z hlavy):
x += Math.Round(Math.Cos(uhel) * rychlost);
y += Math.Round(Math.Sin(uhel) * rychlost);
Fugiczek:14.8.2012 14:21
No trošku bych to upravil na toto:
x+=Math.round(Math.cos(Math.toRadians(uhelVeStupnich))*(0.1*delta));
y+=Math.round(Math.sin(Math.toRadians(uhelVeStupnich))*(0.1*delta));
Mě osobně se líp dělá se stupni než s radiány.
0° - doprava
90° - dolu
180° - doleva
270° - nahoru
360° stejný jako 0°
Takhle ty směry platí jen když to přičítáš, u odečítání by to bylo
naopak.
Udělal bych si jen metodu která ti ten uhel bude upravovat. Třeba nějak
tak:
public void modifyAngle(int count){ //count musí být ve stupních
if(angle+count>360){
angle=+count-360;
}else if(angle+count<0){
angle=(angle+count)+360;
}else{
angle+=count;
}
}
Když dáš count záporný úhel se bude odečítat. Jinak pokud se ti
líbí víc radiány použij to co napsal David Hartinger.
U radiánů by to bylo:
PI*2 || 0 -> doprava
PI/2 ->dolu
PI -> doleva
PI+PI/2 -> nahoru
A na metoda na úpravu úhlu by se upravila z úhlů na radiány nějak
takto:
public void modifyAngle(int count){ //count musí být v radiánech
if(angle+count>(Math.PI*2)){
angle=+count-(Math.PI*2);
}else if(angle+count<0){
angle=(angle+count)+(Math.PI*2);
}else{
angle+=count;
}
}
Místo výrazu
p.setX(p.getX()+p.getVelocity());
bych raději udělal
p.addX(p.getVelocity());
nebo rovnou
p.addVelocity(1,0);
p.addVelocity(0,-1);
apod.
Také je možné použít polární souřadnice
p.addVelocity(rychlost, kurs);
a v metodě addVelocity():
X+=rychlost*cos(kurs);
Y+=rychlost*sin(kurs);
tak pohyb o úhly mám zdárně za sebou, teď bych potřeboval rotaci samotného objektu. tzn. když zahnu doleva o 15° aby se mi objekt o 15° natočil ... to znamená, že kdyby byl objekt šipka, tak ukazuje furt směrem kterým jedu ... neznáte prosím někdo princip řešení?
Fugiczek:16.8.2012 10:47
Pro obrázek:
AffineTransform affineTransform = new AffineTransform();
affineTransform.rotate(Math.toRadians(uhel_ve_stupnich));
g2.drawImage(image, affineTransform, null);
Pokud nevykresluješ obrázek tak:
g2.rotate(Math.toRadians(uhel_ve_stupnich));
Fugiczek:16.8.2012 11:14
Pro podrobnější informace o AffineTransform si můžeš přečíst v
dokumentaci zde: http://docs.oracle.com/…ansform.html
Nastavování pozice pro vykreslování se dělá myslím takhle:
affineTransform.setToTranslation(x,y);
Fugiczek:16.8.2012 12:10
Já si většinou dělám interface Drawable, který obsahuje metodu
public void draw(Graphics2D g2);
A pak ho implementuji do objektů který se mi vyklesujou. Takže přímo v
objektu si napíšeš jak se ten tvůj panáček bude vykreslovat. Kód je pak
přehlednější když zavolám na plátně pouze panak.draw(g2);
Místo paint() bych radši použival paintComponent(Graphics
g); která je lepší a rychlejší.
Rozdělení kódu je otázkou vkusu. Já osobně bych to napsal úplně jinak,
ignoroval metody paint() a paintComponent(),
vytvořil si BufferStrategy(pokud by bylo k dispozici BufferCapatabilities
tak bych využíval i toho), pokud by to byl nějakej velkej projekt udělal
bych si vykreslovaní a renderování přes VolatileImage.
O tom rozdělení kódy fakt nevím co bych ti k tomu řekl, akorát tak
rozdělit a rozlišovat kód kde je herní logika a kde se jen vykresluje. V
tvé třídě Platno by mělo být minimum počítání nějaké logiky,
všechno by se mělo zpracovávat v jiných třídách. Měl by jsi tam měl
jít prostě jen sestavení okna, nějakou smyčku na vykreslování, metodu na
vykreslení, kde budeš vykreslovat to pozadi jedním řádkem a pak druhým
řádkem budeš volat vykreslení panáčka a na nakonec nějaký listenery na
myš/klávesnici (přes soukromou třídu která bude dědit od
MouseInputAdapter nebo KeyAdapter, nebo
používat implementaci těch tříd), ale to taky jednoduchý zpracování aby
prostě nebylo v třídě která má za úkol zobrazovat a zachytávat různý
vstupy moc počítání, aby to bylo přehledný.
Ale všechno co ti tu píšu každej si dělá po svým. Je jen na
programátorovi jestli to bude psát "prasácky" nebo na úrovni s OOP.
jo jo díky moc za nákop .. OOP jsem samozřejmě měl, ale s grafikou teprve začínám a mám v tom docela bordel. Někde jsem dokonce četl (evidentně kravina) že veškerý vykreslování by se mělo dít jen v jedné třídě v metodě paint. Tak jsem se toho asi zbytečně snažil držet
Fugiczek:16.8.2012 12:42
Vykreslování hlavně u her bych doporučil dělat následovně:
1. Jak už jsem psal, ignorovat metody na překreslení
setIgnoreRepaint(true);
2. Vytvořit si bufferstrategy a uložit si to do nějaké proměnné
BufferStrategy bs = null; //deklarace někde na začátku třídy
...
createBufferStrategy(2); //vytvoření BS se 2 buffery (je to technika dvojího vykreslování)
bs=getBufferStrategy()
3. Udělat si svoji metodu na kreslení, třeba pod názvem updateGUI
public void updateGUI(){
Graphics2D g2 = (Graphics2D)bs.getDrawGraphics();
//tady různé vykreslování třeba to pozadí + panáček
g2.dispose();
bs.show(); //zobrazení
Toolkit.getDefaultToolkit().sync(); //užitečná funkce zvláště když děláš animace/hry
}
Tak rotace je funkční bohužel jen ke středu souřadnic [0,0] v levém horním rohu plátna. teď bych potřeboval přehodit střed okolo kterého panáček rotuje na roh panáčkova obrázku, aby se mi to točilo na místě.
Zatím sem vygooglil změnu měřítka následně.
AffineTransform af = new AffineTransform();
af.translate(x,y);
af.rotate(uhel)
Jenže se mi to ještě nepodařilo implementovat. Kde mám změnu měřítka provést? ve třídě panáček nebo plátno? ... Zkoušel jsem oboje a ani jedno mi nechodilo
Fugiczek:16.8.2012 18:15
Takhle to vykreslis doprostřed s úhlem otočení 45°
AffineTransform at = new AffineTransform();
at.setToTranslation((getWidth()-i.getWidth(null))/2,(getHeight()-i.getHeight(null))/2);
at.rotate(Math.toRadians(45),i.getWidth(null)/2,i.getHeight(null)/2);
g2.drawImage(i, at, null);
Proměnná i je objekt typu Image. Metody getWidht() a
getHeight() jsou metody z JFrame. Ty hodnoty si můžeš dát na plátně třeba
jako public static final int HEIGHT/WIDTH a pak k nim
pristupovat Platno.HEIGHT/WIDTH.
Tenhle kód jsem teď testoval na JFrame. První metoda
setToTranslation() nastavíš pozici obrázku. Druhá metoda
rotate() ti nastaví o kolik obrázek otočíš + si můžeš
přidat bod na obrázku podle kterýho se to bude otáčet.
Kit:16.8.2012 18:38
Uvnitř objektu si udržuj souřadnice X a Y. K tomu Kurs a Rychlost. Když nebudeš měnit kurs ani rychlost, stačí zavolat
p.step()
Metoda step() provede výpočet
X += Math.Cos(Kurs) * Rychlost);
Y += Math.Sin(Kurs) * Rychlost);
K tomu si dopíšeš metody zmenaKursu() pro změnu směru a zmenaRychlosti() pro změnu rychlosti pohybu. Tyto metody se mohou volat na základě události od kurzorových šipek. Při malé rychlosti budeš zatáčet ostřeji. Může být i stanovena maximální rychlost (rychlost se ani při dalším požadavku nezvýší nad limit).
Pro vykreslení máš k dispozici vše co potřebuješ uvnitř objektu, stačí mu jen dopsat další metodu, abys to nemusel tahat přes nesmyslné gettery.
Není nějaká metoda, která mi dá dohromady transformaci a pohyb? Myslím tím metody:
g2.drawImage(i, at, null);
a
g2.drawImage(i, i.x, i.y, null);
protože pohyb mi funguje rotace taky, ale nejde mi to dát dohromady (vykreslujou se mi dva obrázky).
Fugiczek:17.8.2012 17:08
Jak dohromady? Trochu specifikuj, poskytni nějaký zdrojový kód. Takhle se blbě radí.
ok tak mám metodu na rotování s panákem:
private void draw_rotace(Graphics2D g2){
af.setToTranslation((getWidth()-panak.getWidth(null))/2,(getHeight()-panak.getHeight(null))/2);
af.rotate(uhel,panak.getWidth(null)/2,panak.getHeight(null)/2);
g2.setTransform(af);
g2.drawImage(panak, af, null);
}
Tahle metoda zařídí rotaci o úhel.
pak mám druhou metodu na posunutí panáka:
private void draw_posun(Graphics2D g2) {
g2.drawImage(panak, this.getX(), this.getY(),null);
}
(obě metody jsou ve třídě panák)
Jenže já oboje potřebuju napsat v jedné metodě, protože když je zavolám obě, tak se mi na plátno vykreslí dva panáci, jeden rotuje a druhej jede . Takže potřebuju nastavit rotaci a mít jen jednu metodu g2.drawImage() (pokud to teda takhle dobře chápu). A s tím si furt nemůžu poradit
Fugiczek:17.8.2012 18:11
Kdyby jsi pořádně četl, tak jsem psal že pozice se nastavuje přes tuto metodu:
af.setToTranslation(x,y);
Zobrazeno 31 zpráv z 31.