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";
}
//绘制面板,我们游戏中的所有东西,都是用画笔来 画
@Override
public 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);//获得键盘监听事件
}
//键盘监听事件
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_SPACE)
{
System.out.println("按下了空格!");
isStart = !isStart;
repaint();//开始重画
}
}
@Override
public 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 {
@Override
public 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 实现动态小蛇
@Override
public 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 实现方向控制
- 实现方向控制
@Override
public 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 事件在 更新数据的时候 就会 不一样。