oyjnjy
「已注销」
2019-06-05 13:05
采纳率: 100%
浏览 964
已结题

JVAV写贪吃蛇小游戏,用线程实现窗口重绘,然后用键盘指令改变小蛇的方向,请问怎么让小蛇只根据指令移动?

图片说明

现在的程序能够实现通每隔200ms小蛇自动往前跑一格,通过键盘指令能够改变方向,但是我不想让它自动跑,只想实现给它指令它才移动,请问怎么实现?
这是蛇的活动类

package com.tcs.cn;

import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.Timer;

/**
 * 这个类代表贪吃蛇的活动场所
 * @author 
 * @version 
 */
public class Yard extends Frame {
    /*
     * 画图的线程
     * */
    Timer timer = new Timer();
    PaintThread paintThread = new PaintThread();
    private boolean gameOver = false; //游戏是否结束

    /**
     * 行数
     */
    public static final int ROWS = 30;
    /*
     * 列数
     * */
    public static final int COLS = 30;
    /*
     * 活动区域大小
     * */
    public static final int BLOCK_SIZE = 15;

    //设置显示字属性
    private Font fontGameOver = new Font("宋体", Font.BOLD, 50);

    //分数
    private int score = 0;
    /*
     * 记录开始时候的时间
     * */
    private long beginTime=0;
    /*
     *实例化Snake和Egg的对象
     * */
    Snake s = new Snake(this);
    Egg e = new Egg();
    /*
     * 抽象类 Image 是表示图形图像的所有类的超类。
     * 必须以特定于平台的方式获取图像。
     * */
    Image offScreenImage = null;

    /*
     * 此函数的功能是:设置窗口的大小、位置、可见,以及点击事件和键盘事件,最后开启了绘图线程
     * */
    public void launch() {
        /*
         * 指定窗口的位置,窗口的左上角的位置为(90,10).是相对父窗口的相对位置
         * */
        this.setLocation(90, 10);
        /*
         * 设定窗口的大小
         * 宽度width:COLS*BLOCK_SIZE
         * 高度hight:ROWS*BLOCK_SIZE
         * */
        this.setSize(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        /*
         * 为窗口添加监听器
         * */
        this.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }

        });
        /*
         * 将窗口设置可见
         * */
        this.setVisible(true);
        /*
         * 为窗口添加键盘事件
         * */
        this.addKeyListener(new KeyMonitor());

        new Thread(paintThread).start();
    }

    public static void main(String[] args) {
        Yard y=new Yard();
        y.beginTime=System.currentTimeMillis();
        y.launch();
    }
    /*
     * 将变量gameOver设置为true,使得在paint()函数中将使得线程停止
     * */
    public void stop() {
        gameOver = true;
    }

    @Override
    public void paint(Graphics g) {
        /*
         * 获取此图形上下文的颜色
         * */
        Color c = g.getColor();
        /*
         * 指定图形上下文的颜色
         * */
        g.setColor(Color.GRAY);
        /*
         * 用当前的颜色来填充指定的区域
         * */
        g.fillRect(0, 0, COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        /*
         * 再一次的指定颜色为:黑灰色???????为什么要再一次的设定???
         * 原因在于:我们想将绘图的颜色与文字显示的不一样
         * */
        g.setColor(Color.DARK_GRAY);
        //画出横线
        /*
         * drawLine(int x1, int y1, int x2, int y2) 
         *  函数的功能为:
         * 在此图形上下文的坐标系中,使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。
         * 通过下面的两个for循环就会在图形化对象上画出表格
         * */
        for(int i=1; i<ROWS; i++) {
            g.drawLine(0, BLOCK_SIZE * i, COLS * BLOCK_SIZE, BLOCK_SIZE * i);
        }
        for(int i=1; i<COLS; i++) {
            g.drawLine(BLOCK_SIZE * i, 0, BLOCK_SIZE * i, BLOCK_SIZE * ROWS);
        }


        g.setColor(Color.YELLOW);//设定颜色,为下面显示文字信息做准备
        /*
         * drawString(String str, int x, int y) 
         *使用此图形上下文的当前字体和颜色绘制由指定 string 给定的文本。
         * */
        g.drawString("使用说明:使用方向键控制方向,F1--停止,F2--停止后恢复,F5--重新开始" , 10, 40);
        g.drawString("目前分数:" + score, 10, 60);
        g.drawString("加分规则:每吃一个加5分,加油!" , 10, 80);
        g.drawString("已经使用的时间:"+(System.currentTimeMillis()-beginTime)/1000 , 10, 100);
        /*
         * 检测游戏是否结束,当游戏结束时,则提示游戏“game over”,而且将界面恢复到初始界面的状态,且终止绘图线程
         * */
        if(gameOver) {
            g.setFont(fontGameOver);
            g.drawString("game over!", 90, 170);
            g.drawString("在玩一次,请按F5", 10, 250);
            g.drawString(" ", 200, 230);//???这个用意何在??

            paintThread.pause();
        }
        if(score>100) {
            g.drawString("好棒!!!", 90, 170);
            g.drawString("你已经超过"+score+",继续加油", 10, 230);


        }

        /*
         * 将图形界面设置为刚开始的颜色
         * */
        g.setColor(c);

        s.eat(e);
        e.draw(g);
        s.draw(g);


    }

    @Override
    public void update(Graphics g) {
        if(offScreenImage == null) {
            /*
             * public Image createImage(int width,
             *           int height)
             * 创建一幅用于双缓冲的、可在屏幕外绘制的图像
             * */
            offScreenImage = this.createImage(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        }
        /*
         * getGraphics() 
         *创建供绘制闭屏图像(off-screen image)使用的图形上下文。
         * */
        Graphics gOff = offScreenImage.getGraphics();
        paint(gOff);
        /*
         * drawImage(Image img, int x, int y, ImageObserver observer) 
         *      绘制指定图像中当前可用的图像。
         * */
        g.drawImage(offScreenImage, 0, 0,  null);
    }

    private class PaintThread implements Runnable {
        private boolean running = true;
        private boolean pause = false;
        public void run() {
            while(running) {//线程将一直处于运行当中,只有在游戏结束的时候
                if(pause) continue; 
                else repaint();//如果组件是轻量级组件,则会尽快调用paint()方法或者是调用update()

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public void pause() {
            this.pause = true;
        }

        public void reStart() {
            this.pause = false;
            s = new Snake(Yard.this);
            gameOver = false;
            score=0;
        }

        public void gameOver() {
            running = false;
        }

    }
    /*
     * 此函数的功能为:检测我们是否按下F2,若按下,则重新启动线程,即重新开始游戏
     * */
    private class KeyMonitor extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {
            int key = e.getKeyCode();
            if(key == KeyEvent.VK_F5) {
                paintThread.reStart();//重新开始游戏
            }
            else if(key==KeyEvent.VK_F1){
                paintThread.pause=true;//暂停
            }
            else if(key==KeyEvent.VK_F2){
                paintThread.pause=false;//从暂停中恢复
            }
            s.keyPressed(e);
        }

    }

    /**
     * 拿到所得的分数
     * @return 分数
     */

    public int getScore() {
        return score;
    }

    /**
     * 设置所得的分数
     * @param score 分数
     */

    public void setScore(int score) {
        this.score = score;
    }

}

这是小蛇类

package com.tcs.cn;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
/**
 * 代表蛇
 * @author 
 *
 */

public class Snake {
    /*
     * 头结点
     * */
    private Node head = null;
    /*
     * 尾结点
     * */
    private Node tail = null;
    /*
     * 大小
     * */
    private int size = 0;
    /*
     * 开始游戏时:
     * 初始位置:(20,30)
     * 初始运动方向:Dir.L
     * */
    private Node n = new Node(20, 30, Dir.L);
    /*
     * Yard的对象属性;
     * */
    private Yard y;
    /*
     * 构造函数
     * */
    public Snake(Yard y) {
        /*
         * 将初始结点给头结点和尾结点,size初始化为 1,
         * */
        head = n;
        tail = n;
        size = 1;
        this.y = y;
    }
    /*
     * 节点类
     * */
    private class Node {
        int w = Yard.BLOCK_SIZE;
        int h = Yard.BLOCK_SIZE;
        int row , col;
        Dir dir = Dir.L;
        Node next = null;
        Node prev = null;

        Node(int row, int col, Dir dir) {
            this.row = row;
            this.col = col;
            this.dir = dir;
        }

        void draw(Graphics g) {
            Color c = g.getColor();
            g.setColor(Color.BLACK);
            g.fillRect(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h);
            g.setColor(c);
        }
    }


    // 从尾加,代码与下面一个函数的代码功能相似,这里不再分析
    public void addToTail() {
        Node node = null;
        switch(tail.dir) {
        case L :
            node = new Node(tail.row, tail.col + 1, tail.dir);
            break;
        case U :
            node = new Node(tail.row + 1, tail.col, tail.dir);
            break;
        case R :
            node = new Node(tail.row, tail.col - 1, tail.dir);
            break;
        case D :
            node = new Node(tail.row - 1, tail.col, tail.dir);
            break;
        }
        tail.next = node;
        node.prev = tail;
        tail = node;
        size ++;
    }

    // 从头加,下面的代码比较简单,相信大家应该都能理解
    public void addToHead() {
        Node node = null;
        switch(head.dir) {
        case L :
            node = new Node(head.row, head.col - 1, head.dir);
            break;
        case U :
            node = new Node(head.row - 1, head.col, head.dir);
            break;
        case R :
            node = new Node(head.row, head.col + 1, head.dir);
            break;
        case D :
            node = new Node(head.row + 1, head.col, head.dir);
            break;
        }
//      node.next = head;
//      head.prev = node;
//      head = node;
        /*
         * 上面这种写法与下面这种写法一致,不过下面这种写法我更容易理解
         * */
        head.prev=node;
        node.next=head;
        head=node;
        size ++;
    }

    public void draw(Graphics g) {
        if(size <= 0) return;
        move();
        for(Node n = head; n != null; n = n.next) {
            n.draw(g);
        }
    }
    /*
     * 移动过程所要做的操作:在运动方向增加一个节点,在尾部减去一个节点,并且检测是否已经死亡
     * */
    private void move() {
        addToHead();
        deleteFromTail();
        checkDead();
    }

    private void checkDead() {
        if(head.row < 2 || head.col < 0 || head.row > Yard.ROWS || head.col > Yard.COLS)  {
            y.stop();
        }
        /*
         * 头节点与身体的某一个节点相撞,也标志着结束
         * */
        for(Node n = head.next; n != null; n = n.next) {
            if(head.row == n.row && head.col == n.col) {
                y.stop();
            }
        }
    }
    /*
     * 删除尾节点
     * */
    private void deleteFromTail() {
        if(size == 0) return;
        tail = tail.prev;
        tail.next = null;

    }

    public void eat(Egg e) {
        /*
         * boolean intersects(Rectangle r) 
         *  确定此 Rectangle 是否与指定的 Rectangle 相交。
         * 若相交,表示我们吃到了一个点 ,则导致蛇的长度变长并且在出现一个点,并且加5分,否则什么也不做
         * */
        if(this.getRect().intersects(e.getRect())) {
            e.reAppear();
            this.addToHead();
            //吃了加5分
            y.setScore(y.getScore() + 5);
        }
    }

    private Rectangle getRect() {
        /*
         * 构造了一个格子大小的区域
         * */
        return new Rectangle(Yard.BLOCK_SIZE * head.col, Yard.BLOCK_SIZE * head.row, head.w, head.h);
    }
    /*
     * 接收从键盘的按键事件,然后采取相应的解决方案
     * */
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        switch(key) {
        case KeyEvent.VK_LEFT  :
            /*
             * 当按键为左的时候,只要前进方向不是右,即可转向
             * */
            if(head.dir != Dir.R)
                head.dir = Dir.L;
            break;
        case KeyEvent.VK_UP  :
            /*
             * 当按键为"上",只要前进方向不是"下",就可以转向
             * */
            if(head.dir != Dir.D)
                head.dir = Dir.U;
            break;
        case KeyEvent.VK_RIGHT  :
            /*
             * 当按键为"右"的时候,只要前进方向不是"左",就可以转向
             * */
            if(head.dir != Dir.L)
                head.dir = Dir.R;
            break;
        case KeyEvent.VK_DOWN :
            /*
             * 当按键为"下"的时候,只要前进方向不是"上",就可以转向
             * */
            if(head.dir != Dir.U)
                head.dir = Dir.D;
            break;
        }
    }
}

求大神解答 hen'ji

  • 点赞
  • 收藏

1条回答 默认 最新

  • weixin_43807553
    野指针丶 2019-06-05 13:32

    大致说一下我的思路吧,不知道你这里适不适用
    设置x,y变量,用来控制方向,按上下左右让其等于1,0或-1,
    在绘蛇的时候通过+x,+y来实现蛇的方向移动

    点赞 评论

相关推荐