Java и Swing с цикъл на игра

(Не толкова) кратък въпрос тук. Работя върху създаването на прост roguelike, използвайки Swing като потребителски интерфейс, а не конзола (което улеснява работата в Eclipse, наред с други неща), но изглежда съм попаднал на пречка.

Това, с което имам проблем е, че когато вляза в цикъла на играта, потребителският интерфейс няма да се покаже правилно. Получавам грозна рамка на прозореца, която ми създава ефекта на „пасианса“ по време на цялото нещо и докато работи, бързо нараства използването на RAM.

Пропускам ли нещо критично за Swing тук? Задължително ли е да използвам настройката за едновременност на Swing, за да направя това? Ако е така, какъв би бил най-добрият подход?

Пълният код е по-долу:

package roguelike;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.LinkedList;

import javax.swing.*;
import javax.swing.text.*;

public class roguelike {
    Color c_black = new Color(0x000000);
    Color c_white = new Color(0xffffff);
    Color c_red = new Color(0xff0000);
    Color c_blue = new Color(0x0000ff);
    Color c_green = new Color(0x00ff00);

    UI ui = null;
    Player me = null;
    Map gamemap = null;
    LinkedList<Character> keyqueue = new LinkedList<Character>();

    int charheight = 20;
    int charwidth = 80;

    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                roguelike game = new roguelike();
                game.run();
            }
        });
    }
    public roguelike(){
        gamemap = new Map();
        ui = new UI(charheight,charwidth,"Monospaced");
        me = new Player();
    }
    private class UI extends JFrame implements KeyListener{
        private static final long serialVersionUID = 9065411532125953842L;
        JPanel disp_screen = null;
        JTextPane disp_screen_text = null;
        StyledDocument disp_doc = null;
        Font mono_norm = null;
        int pxheight = 0;
        int pxwidth = 0;
        String[][] ctemp = new String[charheight][charwidth];
        String[][] stemp = new String[charheight][charwidth];


        public UI(int h,int w,String fontname){
            setVisible(true);
            charheight = h;
            charwidth = w;
            addKeyListener(this);

            this.setResizable(false);
            mono_norm = new Font(fontname,0,25);
            initScreen(h,w);
            add(disp_screen);
            makeStyles();
            try {
                disp_doc.insertString(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", disp_doc.getStyle("Default"));
            } catch (BadLocationException e) {
                e.printStackTrace();
            }

            Dimension temp = disp_screen.getSize();
            this.setSize(temp);
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);           


            pack();
            try {
                disp_doc.remove(0, disp_doc.getLength());
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
            renderMap(gamemap.getText(),gamemap.getStyles());

        }
        @Override
        public void keyPressed(KeyEvent e) {
            // TODO Auto-generated method stub

        }
        @Override
        public void keyReleased(KeyEvent e) {
            // TODO Auto-generated method stub

        }
        @Override
        public void keyTyped(KeyEvent e) {
            keyqueue.add(e.getKeyChar());
        }
        private void initScreen(int height, int width){
            FontMetrics metric = getFontMetrics(mono_norm);
            String temp = null;

            //generate a string of the right width from which to grab metrics
            for(int x=0;x<=width;x++){
                temp += '.';
            }
            pxwidth = SwingUtilities.computeStringWidth(metric, temp);
            pxheight = (metric.getHeight())*charheight;

            disp_screen = new JPanel();
            disp_screen_text = new JTextPane();
            disp_screen_text.setEditable(false);
            disp_screen_text.setAlignmentX(LEFT_ALIGNMENT);
            disp_screen_text.setAlignmentY(TOP_ALIGNMENT);
            disp_screen_text.setFont(mono_norm);
            disp_screen_text.setBackground(c_black);
            disp_screen_text.setForeground(c_green);
            disp_doc = disp_screen_text.getStyledDocument();


            disp_screen.add(disp_screen_text);
            disp_screen.setAlignmentX(LEFT_ALIGNMENT);
            disp_screen.setAlignmentY(TOP_ALIGNMENT);
            disp_screen.setLayout(new BoxLayout(disp_screen,BoxLayout.Y_AXIS));
        }
        private void makeStyles(){
            //The default style removes all special formatting and returns the text to standard nonsense
            Style sty_default = disp_doc.addStyle("Default", null);
            StyleConstants.setFontFamily(sty_default, "Monospaced");
            StyleConstants.setFontSize(sty_default, 18);
            StyleConstants.setForeground(sty_default, c_green);
            StyleConstants.setBackground(sty_default, c_black);
            StyleConstants.setItalic(sty_default, false);
            StyleConstants.setBold(sty_default, false);
            //StyleConstants.setSpaceAbove(sty_default, 0);
            //StyleConstants.setSpaceBelow(sty_default, 0);

            //The following styles apply certain effects. They are meant to be set without replacing styles
            Style sty_bold = disp_doc.addStyle("Bold", disp_doc.getStyle("Default"));
            StyleConstants.setBold(sty_bold,true);

            Style sty_ital = disp_doc.addStyle("Italic", disp_doc.getStyle("Default"));
            StyleConstants.setItalic(sty_ital, true);

        }
        private void clearMap(){
            try {
                disp_doc.remove(0, disp_doc.getLength());
            } catch (BadLocationException e1) {
                e1.printStackTrace();
            }
            try {
                //CLEAR THE MAP
                //For every row...
                for(int y=0;y<charheight;y++){
                    //For every column location in a row...
                    for(int x=0;x<charwidth;x++){
                        disp_doc.insertString(disp_doc.getLength(),".", disp_doc.getStyle("Default"));
                    }
                    disp_doc.insertString(disp_doc.getLength(),"\n", disp_doc.getStyle("Default"));
                }
            } catch (BadLocationException e){
                    e.printStackTrace();
            }
        }
        public void renderMap(String[][] chars, String[][] styles){
            System.out.print("Rendering map...");
            clearMap();
            ctemp = chars;
            stemp = styles;

            try {

                //For every row...
                for(int y=0;y<charheight;y++){
                    //For every column location in a row...
                    for(int x=0;x<charwidth;x++){
                        if(ctemp[y][x] != null){
                            if(stemp[y][x] == "D"){
                                disp_doc.remove((y*charwidth)+x, 1);
                                disp_doc.insertString((y*charwidth)+x,ctemp[y][x], disp_doc.getStyle("Default"));
                            } else if(stemp[y][x] == "B"){
                                disp_doc.remove((y*charwidth)+x, 1);
                                disp_doc.insertString((y*charwidth)+x,ctemp[y][x], disp_doc.getStyle("Bold"));
                            } else if(stemp[y][x] == "I"){
                                disp_doc.remove((y*charwidth)+x, 1);
                                disp_doc.insertString((y*charwidth)+x,ctemp[y][x], disp_doc.getStyle("Italic"));
                            } else{
                                disp_doc.remove((y*charwidth)+x, 1);
                                disp_doc.insertString((y*charwidth)+x,ctemp[y][x], disp_doc.getStyle("Default"));
                            }
                        }
                    }
                }
            } catch (BadLocationException e){
                e.printStackTrace();
                System.err.print(e.getCause());
            }
        }
    }

    //Handles the virtualized information of characters on the map. Does NOT handle display of map on screen
    //Displaying the screen happens by passing required data to the ui member
    private class Map{
        //Holds an array of map characters
        String[][] text = new String[charheight][charwidth];
        //Holds an array of styles associated with each map character
        String[][] styles = new String[charheight][charwidth];


        public Map(){
        }
        public String[][] getText(){

            return text;
        }
        public String[][] getStyles(){
            return styles;
        }
        public void putch(String thing, String styledef,int y, int x){
            text[y][x] = thing;
            styles[y][x] = styledef;
        }
    }

    private class Player{
        //Player location, [y][x]
        int[] location = {0,0};
        String sym = "@";
        public Player(){
        }
        public int[] getLocation(){
            return location;
        }
        public String getSymbol(){
            return sym;
        }
        public void setLocation(int y, int x){
            location[0]=y;
            location[1]=x;
        }
        public void setSymbol(String newsym){
            sym = newsym;
        }
        public void move(int dir){
            //Movement will be handled in an 8 directional fashion
            //North is 1, like below
            //     812
            //     703
            //     654
            //////////////////////
            switch(dir){
                case 0:
                    break;
                case 1:
                    location[0] += 1;
                    break;
                case 2:
                    location[0] += 1;
                    location[1] += 1;
                    break;
                case 3:
                    location[1] += 1;
                    break;
                case 4:
                    location[0] -= 1;
                    location[1] += 1;
                    break;
                case 5:
                    location[0] -= 1;
                    break;
                case 6:
                    location[0] -= 1;
                    location[1] -= 1;
                    break;
                case 7:
                    location[1] -= 1;
                    break;
                case 8:
                    location[0] += 1;
                    location[1] -= 1;
                default:
                    System.err.print("ERROR! "+dir+" is not a valid direction!");
                    break;
            }
        }
    }

    /////////////////////////////////////////////////////
    /////////////FUNCTIONS///////////////////////////////
    /////////////////////////////////////////////////////
    public void run(){
        boolean running = true;
        while(running){
            //Render Map
            gamemap.putch(me.getSymbol(), "D", me.getLocation()[0], me.getLocation()[1]);
            ui.renderMap(gamemap.getText(), gamemap.getStyles());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Wait for player input
            if(keyqueue.isEmpty()){
                continue;
            }
            //Act on input
            else{
                char input = keyqueue.getFirst();
                keyqueue.removeFirst();
                if(input == 'w'){
                    me.move(1);
                }
                if(input =='a'){
                    me.move(7);
                }
                if(input == 's'){
                    me.move(5);
                }
                if(input == 'd'){
                    me.move(3);
                }
            }
        }

    }
}

person Arialth    schedule 19.07.2012    source източник


Отговори (1)


Приложенията на Swing трябва да се управляват от събития - вашето game.run() повикване ще блокира Swing да прави нещо друго полезно. Обърнете внимание, че „събитията“ могат да бъдат потребителски събития, таймери или вероятно други неща.

Ако вашият дизайн се нуждае от game.run() или нещо подобно, за да работи и никога да не се върне, това може да се направи в друга нишка (или дори фонова нишка на SwingWorker), но всички компоненти на потребителския интерфейс трябва да бъдат достъпни в нишката Swing.

Препоръка: работата с вашата клавиатура може лесно да се извърши с KeyListener, който пишете, прикрепен към манипулатор на събития в прозореца или друг компонент и след това ще бъде извикан всеки път, когато потребителят натисне клавиша - т.е. не е необходимо да анкетирате с този дизайн.

person Rob I    schedule 19.07.2012
comment
Добре. Така че бих имал ВСИЧКИ компоненти на Swing под първоначалния оператор InvokeLater() и бих използвал периодичен таймер или потребителско входно събитие за обработка на цикъла, вместо да го правя автоматично? Как бихте предложили да внедря таймер, който да замени цикъл на изпълнение? - person Arialth; 20.07.2012
comment
Редактирах, за да добавя малко повече информация. Работата е там, че Swing има собствен цикъл, но също така има поддръжка за обработка на потребителски събития и уведомяване на вашия код, когато се случат, така че не виждам вашия цикъл да е необходим. Надяваме се, че е толкова лесно, колкото извикването на addKeyListener() към компонентите във вашата рамка. - person Rob I; 20.07.2012
comment
О, разбирам какво става - направихте рамката си KeyListener и извикахте addKeyListener(), така че бих си представил, че премахването на собствения ви цикъл на играта ще бъде 99% от решението! - person Rob I; 20.07.2012
comment
addKeyListener() ще работи добре. Вече имам UI клас, който слуша сам себе си и обработва свои собствени ключови събития, така че това не трябва да е твърде трудно. РЕДАКТИРАНЕ: Да, имам класа JFrame, внедряващ keylistener. Това беше чудесен малък трик, който видях в урок по Swing, който хванах. Мога просто да свържа функцията gamemap.render() към събитието с keytyped. От любопитство обаче, какво ще стане, ако искам да е периодично? Какъв слушател би бил това? - person Arialth; 20.07.2012
comment
Имайте предвид, че както сочи този въпрос, може да се наложи да се обадите addKeyListener() на множество JComponents. Вероятно зависи от това кои могат да фокусират? - person Rob I; 20.07.2012
comment
По-добре е да използвате ключови свързвания, а не конструкция от ниско ниво, като например KeyListener. - person Hovercraft Full Of Eels; 20.07.2012
comment
Страхотна точка, @Hovercraft, определено по-добра. Ще се радвам да подкрепя отговор с това - това е, което питащият искаше (добър съвет). - person Rob I; 20.07.2012
comment
Не, просто редактирайте отговора си. Също така той трябва да обмисли използването на Swing Timer за обикновена игра. - person Hovercraft Full Of Eels; 20.07.2012