在分论(一)讨论了贪吃蛇和随机点的设计,挡在窗体中定义好贪吃蛇和随机点,那么,最关键的问题是如何定义贪吃蛇的移动,以及当碰到随机点以后会发生什么?
贪吃蛇的移动说白了是坐标的变化,如何控制坐标变化呢?有两个关键点需要考虑,方向和速度。第一,移动的控制,即外界通过什么方式来控制贪吃蛇的移动方向,常用的是键盘或者鼠标。第二,贪吃蛇移动的速度,贪吃蛇在键盘控制下,以怎么样的速度来移动。 通过鼠标控制或者键盘控制贪吃蛇的移动,移动方式的时间监听也有两种方式,通过Timer类或者线程类监听,在总论二中介绍两种移动方式,由Timer()类控制速度键盘控制方向的移动方式和多线程控制速度和鼠标控制方向的移动方式.在源代码中我利用的是多线程控制速度和鼠标控制方向的移动方式,不过可以在窗体中加入按钮,用于选择键盘操作或者鼠标操作,这算一个彩蛋吧,后面介绍华容道小游戏就有这个功能,即通过鼠标或者键盘操作的选择,可以参考。
(一)Timer()类控制速度键盘控制方向的移动方式
蛇的移动进行控制 ,键盘上的“上下左右”来控制蛇身的移动变化。 对于整个面板加入监听器事件,对整个面板增加一个键盘监听器,用来监听自己在键盘上的动作。这里我们统一一下,用”↑↓←→”来控制方向。当我们使用键盘捕捉到相应的动作后,该如何继续呢?该如何编写事件的处理?
我们来翻阅一下API。查看API中的KeyListener,我们可以查到KeyEvent,他有静态的常量用来表示键盘上相应的点触。VK_UP代表上箭头,VK_DOWN代表下箭头,VK_LEFT代表左箭头,VK_RIGHT代表右箭头。我们马上可以联想到:通过getKeyCode方法获取到键盘事件,和四个常量进行比较,如果符合,就可以按照对应的方向调用方法,来移动蛇身。我们可以定义一个Move()方法,并且定义一个变量direction代表方向,通过对direction不同的赋值传递给Move(),来对蛇身产生不同的移动效果。接下来贴代码:
采用键盘控制的控制模式,利用键盘的上下左右键,来实现让·direction的变化,从而使贪吃蛇能够按照键盘的控制来实现移动
this.addKeyListener(new KeyAdapter() {//捕捉键盘的按键事件 设置监听器
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_UP://按下向上,返回1
direction = 1;
break;
case KeyEvent.VK_DOWN://按下向下,返回-1
direction = -1;
break;
case KeyEvent.VK_LEFT://按下相左,返回2
direction = 2;
break;
case KeyEvent.VK_RIGHT://按下向右,返回-2
direction = -2;
break;
default:
break;
}
if(direction + Direction !=0) {//不能反向运动
Direction = direction;
Move(direction);
repaint();
}
}
});
在键盘上按钮上下左右键以后,通过键盘监听器获得传入的方向,然后将不同的方向定义为不同的Diretion值,不同的Direction值反映不同的坐标值的变化,按照不同的方向移动的过程中,需要考虑的问题有:撞到随机点,撞到墙,撞到自己,贪吃蛇不断移动的定义方式 。
当输入不同的方向时,需要贪吃蛇蛇头的坐标,蛇头方向的变化就是贪吃蛇移动方向的变化,向上,向下,向左,向右,每一个方向的移动过程中x y值坐标的变化不同,
int FirstX = snake.get(0).getX(); //获取蛇第一个点的横坐标
int FirstY = snake.get(0).getY(); //获取蛇第一个点的纵坐标
if(!startFlag)
return ;
//方向控制
switch(direction) {
case 1: //向上
FirstY--;
break;
case -1: //向下
FirstY++;
break;
case 2: //向左
FirstX--;
break;
case -2:
FirstX++; //向右
break;
default:
break;
}
当贪吃蛇撞到随机点时,定义一个撞到随机点的方法。
//当碰到随机点时
if(FirstX == newNode.getX()&&FirstY == newNode.getY()) {
new eatFoodMusic();
getNode();
return;
}
撞到随机点的方法,贪吃蛇的长度增加一,贪吃蛇从前向后遍历,然后重画随机点。
//获取随机点
public void getNode() {
snake.add(new SnakeNode());
Length++;
for(int x = Length-1; x >0; x--) {
snake.get(x).setX(snake.get(x-1).getX());
snake.get(x).setY(snake.get(x-1).getY());
snake.get(x).setColor(snake.get(x-1).getColor());
}
snakeScore.setText( ""+( Length )); //定义蛇的长度
snake.get(0).setX(newNode.getX());
snake.get(0).setY(newNode.getY());
snake.get(0).setColor(newNode.getColor());
CreateNode1();//产生随机点
// CreateNode2();
repaint();
//当长度超过10的时候,产生鼓掌声
if(Length==10) {
new applauseMusic();
}
}
当贪吃蛇 撞到墙,撞到自己时,游戏结束,转入到重启界面。
//当碰到蛇身自己时
for(int x = 0; x < Length; x++) {
if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) {
startFlag=false;
new DeadMusic();
new Restart();
christmas.stop ();
}
}
//当贪吃蛇撞到边界
if(FirstX < 1 || FirstX >29 || FirstY < 1 || FirstY >18) {
startFlag=false;
new DeadMusic();
new Restart();
christmas.stop ();
// new Test();
}
贪吃蛇不断移动的定义方式 ,从前向后的移动是蛇身的遍历,也就是在方向和时间监听器的控制下,贪吃蛇从前向后移动,实现方式就是从蛇头向蛇尾从前向后向前赋值,逐次遍历。
//定义循环,使得贪吃蛇从前向后移动
for(int x = Length - 1; x > 0; x--) {
snake.get(x).setX(snake.get(x-1).getX());
snake.get(x).setY(snake.get(x-1).getY());
}
snake.get(0).setX(FirstX);
snake.get(0).setY(FirstY);
repaint();
}
定义一个Move()方法,来实现在贪吃蛇移动过程中这些问题处理。
有一个自有的固定的DIRECTION,之后随着我们的操控Direction也不断发生改变,借此来改变它自身不断移动的方向。用代码来体现,就是在成员变量处定义一个Direction,我们将其初始化为1,这样在Timer的事件触发后,Move()的参数为1,就会不断的向上移动。在键盘的监听事件中,将direction的值赋值给Direction,那么随着我们上下左右的控制,Direction的值也不断发生改变,贪吃蛇的自身移动方向就会发生变化。 贪吃蛇在移动的过程中会发生的问题有:撞到随机点,撞到墙,撞到自己,调用相应的方法即可。
/*定义蛇移动的方法
* 贪吃蛇的移动方法主要包括方向控制,碰到随机点,碰到自己,碰到边界以及设计贪吃蛇从前向后的移动
*
*/
public void Move(int direction) {
int FirstX = snake.get(0).getX(); //获取蛇第一个点的横坐标
int FirstY = snake.get(0).getY(); //获取蛇第一个点的纵坐标
if(!startFlag)
return ;
//方向控制
switch(direction) {
case 1:
FirstY--;
break;
case -1:
FirstY++;
break;
case 2:
FirstX--;
break;
case -2:
FirstX++;
break;
default:
break;
}
//当碰到随机点时
if(FirstX == newNode.getX()&&FirstY == newNode.getY()) {
new eatFoodMusic();
getNode();
return;
}
//当碰到蛇身自己时
for(int x = 0; x < Length; x++) {
if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) {
startFlag=false;
new DeadMusic();
new Restart();
christmas.stop ();
}
}
//当贪吃蛇撞到边界
if(FirstX < 1 || FirstX >29 || FirstY < 1 || FirstY >18) {
startFlag=false;
new DeadMusic();
new Restart();
christmas.stop ();
// new Test();
}
//定义循环,使得贪吃蛇从前向后移动
for(int x = Length - 1; x > 0; x--) {
snake.get(x).setX(snake.get(x-1).getX());
snake.get(x).setY(snake.get(x-1).getY());
}
snake.get(0).setX(FirstX);
snake.get(0).setY(FirstY);
repaint();
}
那么,上面的分析解决了贪吃蛇移动中的方向问题,那么,如何解决速度问题呢·?也就是说,当采用时间监听器来让贪吃蛇每隔一定的时间来移动,如何如何实现呢?
查阅API,我们发现了一个TIMER类。API中的描述是:在指定时间间隔触发一个或多个ActionEvent,一个实例用法就是动画对象,它将Timer用作绘制其帧 的触发器。Timer的构造方法是Timer(int delay, ActionListner listener)通俗的说就是创建一个每 delay秒触发一次动作的计时器,每隔特定的时间就会触发特定的事件。可以使用start方法启动计时器。这个Timer类可以完全满足我们的需要。我们只要定义一个Timer类,设置好间隔时间与触发事件就可以了。这里要注意,我们要定义的触发事件是蛇自身的移动。
//蛇的移动控制,利用线程来实现用鼠标控制,利用计时器来实现用键盘控制。
Timer time = new Timer(1000, new ThingsListener1());//定义一个定时器对象,这里我们还要创建一个ThingsListener事件
其中,1000是1000ms的意思,也就是贪吃蛇每隔一秒收到时间监听,判断是否移动。new ThingsListener1()是定义的一个内部类。
//定义内部类,贪吃蛇不断移动
public class ThingsListener1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
Move(direction);
}
}//
实现移动的方式就是在构造方法中加入Time.start();
(二)多线程控制速度和鼠标控制方向的移动方式.
采用多线程和鼠标控制主要原因在于时间的控制,也就是如何实现贪吃蛇移动速度的可控,Timer()我尝试了很多次没有成功,最后采用多线程的方法实现了速度的可控,对于贪吃蛇速度的控制是利用时间监听器来实现。
鼠标控制方向和键盘控制方向的原理基本上相同,不同的在于前者需要对鼠标进行监听,后者需要对于鼠标监听器时间监听,实现的Move()方法是一样的,主要差别在于监听器方式的设计。
在设计中,将贪吃蛇的移动区域按钮对角线划分为上下左右四个部分,如下图:
那么,鼠标控制的监听在于监听鼠标在窗体中的位置,点击鼠标返回鼠标在窗体中的坐标,在监听鼠标事件的时候需要考虑点击鼠标的坐标在上下左右哪个区域,然后将方向值direction改掉,并定义一个标签,在窗体的左上角,用来说明点击鼠标是上下左右那个方向,源代码如下:
//采用 鼠标控制的控制模式 通过监听鼠标在容器中的位置,点击上下左右区域,改变direction的值,即可实现贪吃蛇的移动,
this.addMouseListener(new MouseAdapter(){ //匿名内部类,鼠标事件
public void mousePressed(MouseEvent e){
int a=0;//鼠标完成点击事件
//e.getButton就会返回点鼠标的那个键,左键还是右健,3代表右键
mousex = e.getX(); //得到鼠标x坐标
mousey = e.getY(); //得到鼠标y坐标
double k=0.6; //直线斜率
double Y1=0.6*mousex;
double Y2=-0.6*mousex+810;
double X1=1.6*mousey;
double X2=-1.6*mousey+1350;
if(mousex > X1&&mousex<X2&&mousey>0&&mousey<405) { //第一象限 向上
label4.setText( "向上" );
a=1;
}
if(mousex>X2&&mousex<X1&&mousey>405&&mousey<810) { // 第二象限 向下
label4.setText( " 向下" );
a=2;
}
if(mousex>0&&mousex<675&&mousey>Y1&&mousey<Y2) { //第三象限 向左
label4.setText( " 向左" );
a=3;
}
if(mousex>675&&mousex<1350&&mousey>Y2&&mousey<Y1) { //第四象限 向右
label4.setText( " 向右" );
a=4;
}
//将不同的a值定义为不同的direction值,
switch( a) {
case 1://按下向上,返回1
direction = 1;
break;
case 2://按下向下,返回-1
direction = -1;
break;
case 3://按下相左,返回2
direction = 2;
break;
case 4://按下向右,返回-2
direction = -2;
break;
default:
break;
}
if(direction + Direction !=0) {//不能反向运动
Direction = direction;
Move(direction);
repaint();
}
}
});
}
贪吃蛇移动速度的控制是利用多线程来实现的。 在构造方法中加入tr.start();即可实现贪吃蛇移动的控制,
//蛇的移动控制,利用线程来实现用鼠标控制,利用计时器来实现用键盘控制。
Thread tr= new Thread(new ThingsListener());
new ThingsListener()是新定义的一个内部类,用来实现速度的可控,
/*
* 当startflag为真的时候,贪吃蛇在线程时间的脉冲下继续移动,这个过程包含在if语句块中,当程序启动时,每隔1.2s就有一个响应,
*上一个方法采用Timer, Timer的构造方法是Timer(int delay, ActionListner listener)通俗的说就是创建一个每 delay秒触发一次动作的计时器,
* 每隔特定的时间就会触发特定的事件。可以使用start方法启动计时器。
* 优点在于形式简单,缺点在于当采用速度控制的时候不易控制,而同样作为时间触发作用的线程控制可以实现这个目的,即通过控制时间来控制贪吃蛇的移动速度
* 之所以之前的设计有错误,在于while后面没有用if进行startflag的检验,即startflag只有在真的条件下才可以移动,时间脉冲触发下才可以移动
*/
//定义线程类,使得贪吃蛇能够在线程的控制下不断移动
class ThingsListener implements Runnable {
@Override
public void run() {
// TODO 自动生成的方法存根
while( true) {
if(startFlag) {
Move(Direction);
repaint();
}
try {
Thread.sleep(1200/Difficult_Degree);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}//设置一个监听器事件,用来控制蛇的不断移动
}
Difficult_Degree是速度控制的实现,在不同的按钮的监听器作用下输入不同的值,就可以到达速度控制的目的。
//按速度一键
if(e.getSource() == FspeedButton) {
new speedButtonMusic ();
Difficult_Degree= 2;
}//按速度二键
if(e.getSource() == SspeedButton) {
new speedButtonMusic ();
Difficult_Degree= 3;
}//按速度三键
if(e.getSource() == TspeedButton) {
new speedButtonMusic ();
Difficult_Degree= 4;
}//按速度四键
if(e.getSource() == THspeedButton) {
new speedButtonMusic ();
Difficult_Degree= 5;
}
这样的话Timer()类控制时间键盘控制方向和多线程类控制时间鼠标控制速度两种移动方式说完了,那么,如何实现在窗体中两种方式的可选择呢?我想可以定义两个按钮,然后在按钮的监听器事件分别加入键盘监听和鼠标监听的部分,即可实现移动方式的可控。