View Full Version : [JAVA]applicazione 2d: conviene usare i thread?
DesmoColo
07-04-2006, 09:21
In una semplice applicazione 2d, dove ho un player che si muove tramite input e 3 nemici che sonon mossi dal pc, conviene usare i thread per implementare il giocatore e i nemici?
sottovento
07-04-2006, 10:57
In una semplice applicazione 2d, dove ho un player che si muove tramite input e 3 nemici che sonon mossi dal pc, conviene usare i thread per implementare il giocatore e i nemici?
Direi proprio di si. Ovviamente quando il numero di nemici si incrementa di molto, dovrai cercare altre soluzioni
High Flying
Sottovento
Se ti serve usare i thread allora li usi ;) E' una risposta banale, ma nemmeno tanto :)
DesmoColo
07-04-2006, 14:23
Potrei anche usare un array con dentro i nemici e le loro posizioni all'interno della finestra, ma forse usare i thread è meglio per ragioni di efficienza....
Per fare gli attori come thread avrei bisogno di un consiglio sull'architettura da seguire:
La classe attori ha un metodo
public void draw(Graphics g,Window w){
g.drawImage(loadImage(),getX(),getY(),w);
}
che disegna gli attori.
per far diventare gli attori thread mi serve il metodo run() che va ad eseguire la draw().
Cisi come ho strutturato la classe è un po problematica, come mi consigliate di procedere? Modifico la draw()? Come?
Altra domanda...hai bisogno di efficienza ?
Se vai a vedere nella sottosezione Diamonds, abbiamo fatto tutto il gioco senza thread...
Il problema dei thread in una situazione come questa è che dovrai comunque sincronizzare il tutto e molto strettamente (farai un refresh dello schermo ogni tot milisecondi), quindi se ci arrivi senza thread, perchè usarli ?
DesmoColo
07-04-2006, 15:17
Uso i thread....perchè il prof con faccio il progetto mi ha chiesto di usarli!!! :D
Se mi confermi che posso eveitarli però, mi togli un peso, perchè infatto sta riscontrando numerosi problemi di soncronizzazione, per cui forse un array è meglio. Cerco di spiegrami meglio partendo dall'inizio
Devo appunto fare una piccola applicazione grafica 2d, in cui vi è un giocatore che muovo io e tre spring che deve muovere la macchina: tanto per vedere se funziona mi basta che una figurina parta dalle coordinate (0,0) e ad ogni aggiornamento del buffer di 5 pixel, finchè le coordinate non sono (400,400).
A gestire la figurina che muovo tramite input non ho nessun problema: ogni 5 millisecondi il double buffering disegna le coordinate x,y aggiornate (se appunto sono cambiate a causa di un evento da tastiera). Il problema nasce nel disegnare e spostare gli altri spring mossi dall'intelligenza artificiale.
Il metodo render della mia finestra è fatto in queto modo:
public boolean render (Graphics g){
g.setColor (backgroundColor) ;
g.fillRect (0,0,getWidth(),getHeight());
for(int i = 0; i < iterator+1;i++){
attori[i].draw(g,this);
}
return true;
}
Quindi gli attori vengono disegnati dal rendering invocando il metodo draw() di un attore.
Come faccio dunque a simulare lo spostamento degli altri 3 attori non mossi da input? Io ed il mio prof avevamo pensato di far diventare gli attori dei thread affinche tramite l'invocazione del metodo start() avvenisse lo spostamento.
Come posso fare senza starvolgere tutto il codice trasformando gli attri in thread?
Sprite ;)
Un metodo molto semplice:
Timer timer = new Timer();
int timeStamp = timer.getTime();
while(true)
{
if(timeStamp + 20 <= timer.getTime())
{
timeStamp += 20;
giocatore.processInput();
giocatore.doAction();
for(int i = 0; i < numeroNemici; ++i)
nemico[i].move();
engine.render(g);
//qui ci vuole anche un Thread.Sleep(1); per non occupare tutta la CPU
}
}
Ti posto anche timer, giusto per farti capire:
public class Timer
{
private long time;
public Timer()
{
time = System.nanoTime();
}
public long getTime()
{
return (System.nanoTime() - time) / 1000000;
}
}
Con il valore 20 (che puoi mettere in una cosatante) specifichi che ogni 20 ms (50fps) venga effettuato un loop del gioco... move esegue il movimento logico dei nemici (sposta il loro valore interno di x e y)... render deve fare il rendering dei nemici e del giocatore...
DesmoColo
07-04-2006, 16:37
Purtroppo mi sa che come è strutturato il mio codice non funziona il metodo da te suggerito.
Non so come spiegarmi, così allego a questo post un file zip contenente i file java che ho scritto e che ti possono far capire meglio la situazione. Per motivi implementativi successivi, preferirei non modificare la struttura della classe Test, ovvero il main deve per forza terminare con l'invocazione del metodo gameInit();
Inoltre vedi come è strutturato il render della finestra? così com'è non so proprio come applicare quanto mi hai postato prima...
Il fatto che il main termini con l'invocazione di gameInit non è un problema... GameInit può benissimo contenere il codice sopra...
Provo a darti un'idea ma concedimi le attenuanti dell'aver scritto al volo.
Hai un proiettore fatto all'incirca così:
Renderer.java
import java.awt.*;
import java.awt.image.*;
import java.util.*;
public class Renderer {
private BufferStrategy bs;
private java.util.List<Attore> attori;
private final Rectangle viewRect;
public Renderer(BufferStrategy bs, Rectangle viewArea, Attore...a) {
this.bs = bs;
viewRect = (Rectangle)viewArea.clone();
attori = Collections.unmodifiableList(Arrays.asList(a));
}
public boolean render() {
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, viewRect.width, viewRect.height);
for(Attore a : attori) {
a.draw(g);
}
bs.show();
return true;
}
}
Con un attore di questa specie:
Attore.java
import java.awt.*;
import java.awt.image.*;
public class Attore {
private Point location = new Point();
private BufferedImage image;
public Attore(BufferedImage image) {
this.image = image;
}
public Point getLocation() {
return new Point(location);
}
public void setLocation(int x, int y) {
location.move(x, y);
}
public void move(int dx, int dy) {
location.translate(dx, dy);
}
public void draw(Graphics g) {
g.drawImage(image, location.x, location.y, null);
}
}
A questo punto vuoi trasformare alcuni attori in personaggi la cui logica sia gestita attraverso dei Thread autonomi dal Thread del proiettore.
A me risulta una cosa di questo tipo. Separo la definizione di "intelligenza" da quella di personaggio intelligente, giusto per non affastellare troppo nei "personaggi concorrenti":
AI.java
public interface AI {
void apply(Attore a);
long getTick();
}
Qui getTick è il periodo del ciclo della "intelligenza artificiale".
Il Thread che gestisce un singolo personaggio non giocante applica un AI. AI può alterare gli stati da cui dipende la proiezione del personaggio e, dunque, esiste un problema di accesso condiviso agli stati del personaggio. In particolare, il proiettore (Renderer) legge alcuni valori conservati da un Attore (posizione e figura) che un AI concorrente può modificare.
Per rendere il proiettore consistente con il singolo attore concorrente direi che basti rendere atomiche e mutualmente esclusive le operazioni di lettura e scrittura degli stati di un attore. Usando il composition and forwarding possiamo creare un secondo tipo di attori che risponda a queste caratteristiche:
import java.awt.*;
import java.util.concurrent.locks.*;
public class NPC extends Attore implements Runnable {
private final Attore attore;
private Lock lock = new ReentrantLock();
private volatile boolean running;
private long sleepTime;
private AI ai;
public NPC(Attore a, AI ai) {
super(null);
attore = a;
sleepTime = ai.getTick() / 1000000;
this.ai = ai;
}
public Point getLocation() {
lock.lock();
try {
return attore.getLocation();
} finally {
lock.unlock();
}
}
public void setLocation(int x, int y) {
lock.lock();
try {
attore.setLocation(x, y);
} finally {
lock.unlock();
}
}
public void move(int dx, int dy) {
lock.lock();
try {
attore.move(dx, dy);
} finally {
lock.unlock();
}
}
public void start() {
lock.lock();
try {
if(!running) {
running = true;
new Thread(this).start();
}
} finally {
lock.unlock();
}
}
public void stop() {
lock.lock();
try {
running = false;
} finally {
lock.unlock();
}
}
public void draw(Graphics g) {
lock.lock();
try {
attore.draw(g);
} finally {
lock.unlock();
}
}
public void run() {
long time0 = System.nanoTime();
while(running) {
long time1 = System.nanoTime();
long dTime = time1 - time0;
while(dTime >= ai.getTick()) {
dTime -= ai.getTick();
ai.apply(this);
}
time0 = time1 - dTime;
try {
Thread.sleep(sleepTime);
} catch(InterruptedException ex) {
running = false;
return;
}
}
}
}
A questo punto devi però modificare il ciclo di gioco in modo tale che l'avvio causi avvio anche delle entità concorrenti. La cosa più sentitica che sono riuscito a scrivere al riguardo è:
GameLoop.java
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;
public class GameLoop implements Runnable {
private long tick = 10000000;
private volatile boolean loop;
private Renderer renderer;
private java.util.List<Attore> attori;
public GameLoop(Renderer r, Attore...a) {
attori = Collections.unmodifiableList(Arrays.asList(a));
renderer = r;
}
public void start() {
if(!loop) {
loop = true;
new Thread(this).start();
}
}
public void stop() {
loop = false;
for(Attore a : attori) {
if(a instanceof NPC) ((NPC)a).stop();
}
}
public void run() {
for(Attore a : attori) {
if(a instanceof NPC) ((NPC)a).start();
}
long time0 = System.nanoTime();
while(loop) {
long time1 = System.nanoTime();
long dTime = time1 - time0;
while(dTime >= tick) {
dTime -= tick;
renderer.render();
}
long sleepTime = (tick - dTime) / 1000000;
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch(InterruptedException ex) {
loop = false;
}
}
time0 = time1 - dTime;
}
}
}
Qui l'avvio del motore di gioco causa (prima istruzione del metodo run()) l'avvio delle entità-attore che siano degli NPC. Specularmente, al termine dell'esecuzione (stop()) GameLoop si occupa anche di fermare tutti gli NPC.
Nel mucchio, Attore e Renderer sono rimasti invariati. GameLoop cambia solo per quanto riguarda l'avvio e l'arresto degli NPC.
Giusto per provare ad incollare i vari pezzi:
Main.java
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;
public class Main {
/** Movimento orizzontale a badalucco */
private AI horizontalMove = new AI() {
public long getTick() {
return 100000000; //100 ms
}
public void apply(Attore a) {
a.move(1, 0);
}
};
/** Movimento verticale a badalucco */
private AI verticalMove = new AI() {
public long getTick() {
return 50000000; //50 ms
}
public void apply(Attore a) {
a.move(0, 1);
}
};
private Renderer renderer;
private GameLoop gameLoop;
private void startApp() {
BufferedImage white, blue;
try {
white = ImageIO.read(Main.class.getResource("/white.png"));
blue = ImageIO.read(Main.class.getResource("/blue.png"));
} catch(Exception ex) {
throw new RuntimeException("cannot load images");
}
final NPC whiteNPC = new NPC(new Attore(white), horizontalMove);
final NPC blueNPC = new NPC(new Attore(blue), verticalMove);
final JFrame window = new JFrame("Sample") {
{ enableEvents(AWTEvent.KEY_EVENT_MASK); }
protected void processKeyEvent(KeyEvent e) {
manageKey(e);
}
};
window.setSize(640, 480);
window.setIgnoreRepaint(true);
window.setUndecorated(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
window.setVisible(true);
window.createBufferStrategy(2);
GraphicsConfiguration cfg = window.getGraphicsConfiguration();
GraphicsDevice device = cfg.getDevice();
if(device.isFullScreenSupported()) {
device.setFullScreenWindow(window);
} else {
Rectangle bounds = cfg.getBounds();
window.setSize(bounds.width, bounds.height);
}
BufferStrategy bs = window.getBufferStrategy();
renderer = new Renderer(bs,
new Rectangle(0, 0, window.getWidth(), window.getHeight()),
whiteNPC, blueNPC);
gameLoop = new GameLoop(renderer, whiteNPC, blueNPC);
gameLoop.start();
}
});
}
private void manageKey(KeyEvent e) {
gameLoop.stop();
Window w = JOptionPane.getFrameForComponent((Component)e.getSource());
GraphicsConfiguration cfg = w.getGraphicsConfiguration();
GraphicsDevice dev = cfg.getDevice();
if(dev.isFullScreenSupported()) {
dev.setFullScreenWindow(null);
}
w.dispose();
}
public static void main(String...args) {
new Main().startApp();
}
}
Diciamo che questo è un paio di maniche a cui occorre attaccare il resto della camicia. Manca, infatti, la gestione delle collisioni: qui hai un milione di entità di cui non puoi predire l'ordine di movimento. In particolare, Pippo potrebbe rilevare che la posizione (x,y) sia libera e trovarsi, nonostante ciò, accavallato a Mario. Occorre fare in modo che la lettura di una posizione ai fini del rilevamento di collisioni impedisca il movimento fintantochè non sia terminato il computo delle restrizioni allo spostamento. Il che va un po' oltre la semplice sincronizzazione della lettura/scrittura degli stati.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.