8.3 动态小蛇
8.3.1 监听空格(开始游戏)
这次我们采取 将键盘事件 被 我们 GamePanel 实现的方式,就不 创建个 新的 事件类了。
package com.muquanyu.snake;import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;public class GamePanel extends JPanel implements KeyListener {
//蛇的长度int length;//蛇头的 X坐标 和 Y坐标int[] snakeX = new int[900];int[] snakeY = new int[900];//蛇头方向String snakeDirection = null;//游戏当前的状态:开始、停止 默认为 "停止"boolean isStart = false;//初始化public void init() {
//初始化蛇的长度length = 3;//初始化蛇头 坐标snakeX[0] = 125;snakeY[0] = 100;//初始化蛇身 坐标snakeX[1] = 100;snakeY[1] = 100;snakeX[2] = 75;snakeY[2] = 100;//蛇头方向snakeDirection = "right";}//绘制面板,我们游戏中的所有东西,都是用画笔来 画@Overridepublic void paintComponent(Graphics g) {
//清屏,不会出现闪烁。super.paintComponent(g);//绘制静态的面板this.setBackground(Color.BLACK);//第一个参数是画到哪个设备上,那肯定是我们 创建的 面板上呀!Data.header.paintIcon(this, g, 25, 11);//画游戏区域g.fillRect(25, 75, 850, 600);//画静态小蛇switch(snakeDirection){
case "right":Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);break;case "left":Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);break;case "up":Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);break;case "down":Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);break;default:break;}for(int i = 1;i<length;++i){
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);}//游戏状态if(!isStart){
g.setColor(Color.white);//设置字体Font 微软雅黑 = new Font("微软雅黑", Font.BOLD, 40);g.setFont(微软雅黑);g.drawString("按下空格开始游戏",300,300);}}public GamePanel() {
init();//获得焦点和键盘事件this.setFocusable(true);//获得焦点事件this.addKeyListener(this);//获得键盘监听事件}//键盘监听事件@Overridepublic void keyTyped(KeyEvent e) {
}@Overridepublic void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();if(keyCode == KeyEvent.VK_SPACE){
System.out.println("按下了空格!");isStart = !isStart;repaint();//开始重画}}@Overridepublic void keyReleased(KeyEvent e) {
}
}
我们知道,只要我们 把 isStart 设定为 true,在绘画 的时候,它就不会 去 绘画 提示的文字。那么 我们 通过 监听 空格键,把 isStart 取反,再进行 重画,更新所有的画面!不就实现了 游戏开始 和 停止的操作吗?
这里如果 懂的话,其实 让小蛇 动起来的操作,理解起来就不是很难了。无非就是 更新 小蛇的数据然后用 Timer 一直进行重画!!!
8.3.2 Timer 定时器(时钟)
使用定时器之前,就必须 先 继承或作为 Actionlistener 的实现。这是 为 了 Timer 做前奏准备。因为 我们 需要 用 Actionlistener 做最基本的监听事件!
- 创建 Timer 对象(定时器)
//创建一个时钟 进行 监听Timer timer = new Timer(33,监听事件);
它是以毫秒为单位,进行监听的!1000ms = 1s。
所以要想模仿出 30 帧,那就得 33 ms 监听一次!
需要注意的是,Timer 开启后,默认 会去 监听 你一开始创建的时候,设定的那个 监听事件!这个监听事件可以是 很多组件的监听事件。
8.3.2.1 自动识别内容是否正确(不需要 按 回车键)
package com.muquanyu.snake;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;public class TimerDemo extends JFrame {
MyAction m = new MyAction();Timer t = new Timer(300,m);JTextField jTextField;public TimerDemo(){
setBounds(200,200,500,500);setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);jTextField = new JTextField("请输入密钥,我们会一直判定!");jTextField.addActionListener(m);add(jTextField);t.start();setVisible(true);}public static void main(String[] args) {
new TimerDemo();}class MyAction implements ActionListener {
@Overridepublic void actionPerformed(ActionEvent e) {
if(jTextField.getText().equals("helloworld")){
System.out.println("输入密钥正确!");t.stop();}else{
System.out.println("输入密钥错误!");}}}
}
这个 案例,是为了让你明不白,Timer 对象 一旦被创造 出来,就可以 捆绑 一个 事件。然后 一旦开启,就会一直 执行那个 事件的监听。
如果是这样的话,我们是否可以 把 GamePanel 的 action 事件 绑定 在 上面呢?? 完全是可以的!
这样就能在 Action 事件里,进行 数据的刷新和重画了。
8.3.2.2 实现动态小蛇
@Overridepublic void actionPerformed(ActionEvent e) {
//如果游戏开始的话if(isStart){
//宽度:850 高度:600//右移//头移动switch(snakeDirection){
case "right":{
if(snakeX[0] == 850){
//那就直接让它 从左边 穿过来就行了snakeX[0] = 25;}else{
//编程思想的写法,并不是什么高深算法!//我们通过自己画图,也会发现这个 现象//就是 蛇身的每一块 在移动的时候,恰好移动到了 前一个块的坐标!!!//你们 自己画图 观察,就能看出来!那么我们只需要 从 最后一块 开始遍历//然后 让它每次刷新 都 变成 前一块的 坐标不就完事了!!for(int i = length-1;i>0;i--){
snakeX[i] = snakeX[i-1];snakeY[i] = snakeY[i-1];}snakeX[0] = snakeX[0] + 25;}repaint();break;}case "left":{
if(snakeX[0] == 25){
snakeX[0] = 850;}else{
for(int i = length-1;i>0;i--){
snakeX[i] = snakeX[i-1];snakeY[i] = snakeY[i-1];}snakeX[0] = snakeX[0] - 25;}repaint();break;}case "up":{
if(snakeY[0] == 75){
snakeY[0] = 600;}else{
for(int i = length-1;i>0;i--){
snakeX[i] = snakeX[i-1];snakeY[i] = snakeY[i-1];}snakeY[0] = snakeY[0] - 25;}repaint();break;}case "down":{
if(snakeY[0] == 650){
snakeY[0] = 75;}else{
for(int i = length-1;i>0;i--){
snakeX[i] = snakeX[i-1];snakeY[i] = snakeY[i-1];}snakeY[0] = snakeY[0] + 25;}repaint();break;}default:break;}
编程思想的写法,并不是什么高深算法!
我们通过自己画图,也会发现这个 现象
就是 蛇身的每一块 在移动的时候,恰好移动到了 前一个块的坐标!!!你们 自己画图 观察,就能看出来!那么我们只需要 从 最后一块 开始遍历
然后 让它每次刷新 都 变成 前一块的 坐标不就完事了!!
在每次 进行刷新判定的时候,都要 重画哟!
8.3.3 实现方向控制
- 实现方向控制
@Overridepublic void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();if(keyCode == KeyEvent.VK_SPACE){
System.out.println("按下了空格!");isStart = !isStart;repaint();//开始重画}if(keyCode == KeyEvent.VK_UP){
System.out.println("上方向键!");snakeDirection = "up";}if(keyCode == KeyEvent.VK_DOWN){
System.out.println("下方向键!");snakeDirection = "down";}if(keyCode == KeyEvent.VK_LEFT){
System.out.println("左方向键!");snakeDirection = "left";}if(keyCode == KeyEvent.VK_RIGHT){
System.out.println("右方向键!");snakeDirection = "right";}}
直接改变 snakeDirection 的值,就完事了,因为你改变了这个值,我们的 action 事件在 更新数据的时候 就会 不一样。