"Lagi" w aplikacji sieciowej

0

Witam napisalem aplikacje w Javie. Mozna sie polaczyc do serwera i poruszac sie kwadratem razem z innymi graczami. Jesli sam sobie to testuje to dziala swietnie, bezproblemowo. Jednak problem zaczyna sie gdy ktos podlaczy sie ze mna z innego komputera i jednoczesnie bedzie poruszac kwadratami (jesli tylko 1 osoba bedzie sie poruszala a druga nic nie robila to wszystko bedzie w porzadku). Wtedy nie da sie normalnie sterowac kwadratem. Np. klikam strzalke w prawo by isc w prawo i nic sie nie dzieje, a jak wciskam jednoczesnie gore i prawo to np. idzie tylko w prawo. Wklejam kod serwera i klienta. (caly na wszelki wypadek jakby ktos chcial u siebie przetestowac). Sporo kodu ale na uwage zasluguje tylko pierwsza klasa bo problem lezy raczej po stronie serwera. Moze po prostu nie nadaza i powininem dodac sleep() w watku gracza? A moze cos innego?

SERWER

public class SquaresServer {

   public static void main(String[] args) {
      try {
         ServerSocket so = new ServerSocket(8189);
         final List<PrintWriter> writers = new LinkedList<>();
         final int[] nextId = {0};
         while (true) {
            Socket incoming = so.accept();
            final Scanner in = new Scanner(incoming.getInputStream());
            final PrintWriter out = new PrintWriter(incoming.getOutputStream(), true);
            //Nadanie graczowi id
            final int playerId = nextId[0]++;
            out.println(playerId);
            //Powiadomienie wszystkich graczy o nowym graczu
            writers.add(out);
            int red = (int) (Math.random() * 255);
            int green = (int) (Math.random() * 255);
            int blue = (int) (Math.random() * 255);
            for (PrintWriter writer : writers) {
               writer.println("new|0:0:" + red + ":" + green + ":" + blue + ":" + 
                       playerId + ":false:false:false:false");
            }
            new Thread(new Runnable() {
               @Override
               public void run() {
                  try {
                     //Pobranie pozycji graczy od innego gracza
                     if (writers.size() > 1) {
                        PrintWriter writer;
                        do {
                           writer = writers.get((int) (Math.random() * writers.size()));
                        } while (writer == out);
                        writer.println("get|update");
                     }
                     while (in.hasNextLine()) {
                        String line = in.nextLine();
                        //Pierwsza nadeslana linia jest pusta (nie wiem dlaczego tak sie dzieje ale to teraz nieistotne)
                        if (line.equals("")) {
                           continue;
                        }
                        String[] split = line.split("\\|");
                        String type = split[0];
                        String message = split[1];
                        switch (type) {
                           case "move":
                              for (PrintWriter writer : writers) {
                                 writer.println(line);
                              }
                              break;
                           case "update":
                              String[] squares = message.split("/");
                              for (String square : squares) {
                                 for (PrintWriter writer : writers) {
                                    writer.println("new|" + square);
                                 }
                              }
                              break;
                        }
                     }
                  } finally {
                     writers.remove(out);
                     for (PrintWriter writer : writers) {
                        writer.println("remove|" + playerId);
                     }
                  }
               }
            }).start();

         }
      } catch (IOException ex) {
         ex.printStackTrace();
      }
   }
}

KLIENT

public class Squares {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
           @Override
           public void run() {
              new MainFrame();
           }
        });
    }
}


public class MainFrame extends JFrame {
   public MainFrame() {
      final MainPanel mainPanel = new MainPanel();
      add(mainPanel);
      addWindowListener(new WindowAdapter() {
         @Override
         public void windowDeactivated(WindowEvent ev) {
            //Zeby nie stracic kontroli nad kwadratem
            mainPanel.stopSquare();
         }
      });
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setResizable(false);
      pack();
      setVisible(true);
   }
}


public class MainPanel extends JPanel {

   private final int WIDTH = 600;
   private final int HEIGHT = 400;
   private List<Square> squares = new LinkedList<>();
   private int id;
   private Scanner in;
   private PrintWriter out;

   public MainPanel() {
      setPreferredSize(new Dimension(WIDTH, HEIGHT));
      setFocusable(true);
      addKeyListener(new KeyHandler());
      try {
         Socket so = new Socket("xxx.xxx.xxx.xxx", 8189);
         in = new Scanner(so.getInputStream());
         out = new PrintWriter(so.getOutputStream(), true);
         id = in.nextInt();
         new Thread(new Runnable() {
            @Override
            public void run() {
               while (in.hasNextLine()) {
                  String line = in.nextLine();
                  //Pierwsza nadeslana linia jest pusta (nie wiem dlaczego tak sie dzieje ale to teraz nieistotne)
                  if (line.equals("")) {
                     continue;
                  }
                  String[] split = line.split("\\|");
                  String type = split[0];
                  String message = split[1];
                  switch (type) {
                     case "new":
                        String[] info = message.split(":");
                        int x = Integer.parseInt(info[0]);
                        int y = Integer.parseInt(info[1]);
                        int red = Integer.parseInt(info[2]);
                        int green = Integer.parseInt(info[3]);
                        int blue = Integer.parseInt(info[4]);
                        int id = Integer.parseInt(info[5]);
                        boolean up = Boolean.parseBoolean(info[6]);
                        boolean right = Boolean.parseBoolean(info[7]);
                        boolean down = Boolean.parseBoolean(info[8]);
                        boolean left = Boolean.parseBoolean(info[9]);
                        //Dodajemy kwadrat jesli jeszcze nie istnieje zaden o takim id
                        if (getSquareFromId(id) == null) {
                           squares.add(new Square(x, y, new Color(red, green, blue),
                                   id, up, right, down, left));
                        }
                        break;
                     case "get":
                        switch (message) {
                           case "update":
                              StringBuilder sb = new StringBuilder();
                              for (Square square : squares) {
                                 sb.append(square.toString());
                                 sb.append("/");
                              }
                              sb.delete(sb.length() - 1, sb.length());
                              out.println("update|" + sb.toString());
                              break;
                        }
                        break;
                     case "move":
                        info = message.split(":");
                        int squareId = Integer.parseInt(info[0]);
                        Square square = getSquareFromId(squareId);
                        String dir = info[1];
                        boolean state = (info[2].equals("on")) ? true : false;
                        switch (dir) {
                           case "up":
                              square.setUp(state);
                              break;
                           case "right":
                              square.setRight(state);
                              break;
                           case "down":
                              square.setDown(state);
                              break;
                           case "left":
                              square.setLeft(state);
                              break;
                        }
                        break;
                     case "remove":
                        squareId = Integer.parseInt(message);
                        squares.remove(getSquareFromId(squareId));
                        break;
                  }
               }
            }
         }).start();
      } catch (IOException ex) {
         ex.printStackTrace();
         JOptionPane.showMessageDialog(this, ex.getMessage(), "Error",
                 JOptionPane.ERROR_MESSAGE);
      }
      new Thread(new MoveRnn()).start();
   }
   
   public void stopSquare() {
      out.println("move|" + id + ":up:off");
      out.println("move|" + id + ":right:off");
      out.println("move|" + id + ":down:off");
      out.println("move|" + id + ":left:off");
   }

   private Square getSquareFromId(int id) {
      for (Square square : squares) {
         if (square.getId() == id) {
            return square;
         }
      }
      return null;
   }

   @Override
   public void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2D) g;
      g2.setColor(Color.DARK_GRAY);
      g2.fillRect(0, 0, WIDTH, HEIGHT);
      for (Square square : squares) {
         g2.setColor(square.getColor());
         g2.fillRect(square.getX(), square.getY(), Square.SIZE, Square.SIZE);
      }
   }

   private class MoveRnn implements Runnable {

      @Override
      public void run() {
         try {
            while (true) {
               for (Square square : squares) {
                  int oldX = square.getX();
                  int oldY = square.getY();
                  square.move();
                  if (square.getX() < 0 || square.getX() > WIDTH - Square.SIZE) {
                     square.setX(oldX);
                  }
                  if (square.getY() < 0 || square.getY() > HEIGHT - Square.SIZE) {
                     square.setY(oldY);
                  }
               }
               repaint();
               Thread.sleep(10);
            }
         } catch (InterruptedException ex) {
         }
      }
   }

   private class KeyHandler extends KeyAdapter {

      @Override
      public void keyPressed(KeyEvent ev) {
         switch (ev.getKeyCode()) {
            case KeyEvent.VK_UP:
               out.println("move|" + id + ":up:on");
               break;
            case KeyEvent.VK_RIGHT:
               out.println("move|" + id + ":right:on");
               break;
            case KeyEvent.VK_DOWN:
               out.println("move|" + id + ":down:on");
               break;
            case KeyEvent.VK_LEFT:
               out.println("move|" + id + ":left:on");
               break;
         }
      }

      @Override
      public void keyReleased(KeyEvent ev) {
         switch (ev.getKeyCode()) {
            case KeyEvent.VK_UP:
               out.println("move|" + id + ":up:off");
               break;
            case KeyEvent.VK_RIGHT:
               out.println("move|" + id + ":right:off");
               break;
            case KeyEvent.VK_DOWN:
               out.println("move|" + id + ":down:off");
               break;
            case KeyEvent.VK_LEFT:
               out.println("move|" + id + ":left:off");
               break;
         }
      }
   }
}


public class Square {

   private int x;
   private int y;
   private Color color;
   private int id;
   private boolean up;
   private boolean right;
   private boolean down;
   private boolean left;
   public static final int SIZE = 30;

   public Square(int x, int y, Color color, int id,
           boolean up, boolean right, boolean down, boolean left) {
      this.x = x;
      this.y = y;
      this.color = color;
      this.id = id;
      this.up = up;
      this.right = right;
      this.down = down;
      this.left = left;
   }

   public void setUp(boolean up) {
      this.up = up;
   }

   public void setRight(boolean right) {
      this.right = right;
   }

   public void setDown(boolean down) {
      this.down = down;
   }

   public void setLeft(boolean left) {
      this.left = left;
   }

   public void move() {
      if (up) {
         y--;
      }
      if (right) {
         x++;
      }
      if (down) {
         y++;
      }
      if (left) {
         x--;
      }
   }

   public void setX(int x) {
      this.x = x;
   }

   public void setY(int y) {
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public int getY() {
      return y;
   }

   public Color getColor() {
      return color;
   }

   public int getId() {
      return id;
   }

   @Override
   public String toString() {
      return x + ":" + y + ":" + color.getRed() + ":" + color.getGreen()
              + ":" + color.getBlue() + ":" + id + ":" + up + ":" + right
              + ":" + down + ":" + left;
   }
}
0

Ty wszystko wysyłasz za pomocą protokołu TCP a powinieneś także korzystać z UDP czyli Datagramów. W tym przypadku samą pozycje powinieneś wysyłać z pomocą UDP.

0

W UDP jedna rzecz mnie zastanawia. A wlasciwie dwie.

  1. Dane moga zostac utracone. Czyli to oznacza ze nie moglbym stosowac systemu przesylania informacji o puszczeniu lub wcisnieciu klawiszy? No bo jakby przypadkiem infromacjia o puszczeniu klawisza nie dotarla to zaburzona zostala by zgodnosc miedzy uzytkownikami jesli chodzi o pozycje innych graczy?
  2. Dane sa przesylane w dowolnej kolejnosci. Czyli ze musialbym dodawac jakies identyfikatory kiedy zostala dana wiadomosc wyslana (np. dopisywac czas w ms). Kwadrat mialby pole lastUpdate i w przypadku dostania wiadomosci sprawdzalbym czy czas jej nadania jest wczesniejszy niz ostatni update. Jesli tak to zmienialbym pozycje. Rozumiem ze jakos tak to trzeba by zrobic? Nie moge polegac na takiej samej kolejnosci dostarczenia jak wyslania?
0

Nie musisz wysyłać danych o aktualnym stanie klawisza gracza lecz pozycje jego kwadrata (x,y).
I potem renderujesz drugi kwadrat na podstawie pobranym danych.

0
michat34 napisał(a):

W UDP jedna rzecz mnie zastanawia. A wlasciwie dwie.

  1. Dane moga zostac utracone. Czyli to oznacza ze nie moglbym stosowac systemu przesylania informacji o puszczeniu lub wcisnieciu klawiszy? No bo jakby przypadkiem infromacjia o puszczeniu klawisza nie dotarla to zaburzona zostala by zgodnosc miedzy uzytkownikami jesli chodzi o pozycje innych graczy?
  2. Dane sa przesylane w dowolnej kolejnosci. Czyli ze musialbym dodawac jakies identyfikatory kiedy zostala dana wiadomosc wyslana (np. dopisywac czas w ms). Kwadrat mialby pole lastUpdate i w przypadku dostania wiadomosci sprawdzalbym czy czas jej nadania jest wczesniejszy niz ostatni update. Jesli tak to zmienialbym pozycje. Rozumiem ze jakos tak to trzeba by zrobic? Nie moge polegac na takiej samej kolejnosci dostarczenia jak wyslania?

Hm co do dowolnej kolejności.

Zrob sobie tak:

Pakiet (klasa która bd przechowywała dane które chcesz przesłać)

Wpisujesz w bufer id pakiety np 1 dla ruchu myszki a poznij 2 shorty x,y
dla pakietu który naciska przycisk dajesz inne id.

btw. Nie męcz się tylko użyj NETTY.

0

Zajrzyj na tę stronę: http://gafferongames.com/networking-for-game-programmers/
Tam jest kilka ciekawych artykułów dotyczących programowania sieciowego w grach. Powinno Ci się rozjaśnić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1