实现功能:← → ↑ 键控制马里奥的向左向右和跳跃
效果
目录
(1)马里奥类
要绘制马里奥角色,需要知道角色的位置、此时的图片以及一些有关运动的信息,马里奥角色还需要有向左向右移动和跳跃的方法,因此我们将角色封装成一个类
①首先我们需要角色的位置坐标,为了不让角色跑到窗口外面去还需要知道窗口的大小
②马里奥的不同的动作会对应不同的动作集合(向左、向右、原地跳跃、向左跳跃、向右跳跃、向左停止、向右停止),我们用一个List来存储,用index表示此时应该显示第几张图片
public static int posx,posy,screenWidth,screenHeight;//马里奥的位置和窗口的宽度和高度
public static List<BufferedImage> images;//马里奥动画图片的集合
public static int index;//第index张图片
对应的,将StaticValue里面的马里奥角色图片分成不同的动作集合:
public static List<BufferedImage> marioImgsRight = new ArrayList<BufferedImage> ();//马里奥向右
public static List<BufferedImage> marioImgsLeft = new ArrayList<BufferedImage> ();//马里奥向左
public static List<BufferedImage> marioImgsJump = new ArrayList<BufferedImage> ();//马里奥跳跃
public static BufferedImage marioImgsDead = null;//马里奥死亡
③在界面上画出马里奥的时候需要获得马里奥此时的图片:
考虑如果此时马里奥在空中,向右跳跃过程中得到向左跳跃过程中得到,其他时刻得到向右移动的动作集合 向左移动的动作集合 里面的index索引处的图片。
/*
* 得到图片
* */
public static BufferedImage getImage(){
if(overHead !=0){//在空中
if(overHead == 1)//向右飞行中
return StaticValue.marioImgsJump.get(0);
else if(overHead == 2)//向左飞行中
return StaticValue.marioImgsJump.get(1);
}
return images.get(index);//其他动作集合中第index张图片
}
④接下来实现马里奥的向右向左移动和跳跃的方法:
明确一点,我们使用的是双缓冲画图,也就是在界面类里面每隔一段时间会将当时的图片状态画出来,因此在其他改变图像某部分的方法里面,我们只需要考虑正确改变物体的状态,修改对应的Image
A.向左向右移动:
- 注意的是当角色处于跳跃中,或者相应方向处于边界,向左向右行走的方法不执行
- 需要为马里奥确定一个步长step=7
- 会发现一个问题,就是动作图片切换的太快了,这里我的处理方式是每调用两次切换一次动作图片switchN=2
/*
* 水平左行走
* */
public static void moveL( ){
if(overHead==0&&posx>0){
images = StaticValue.marioImgsLeft;//向左行走的动作集合
posx -= step;
switchN--;
if(switchN == 0){
index = (index+1)%4;//每调用两次切换对应的动作突破
switchN = 2;
}
isMoving = 2;//向左移动标记
if(posx<0)//不超过窗口边界
posx=0;
}
}
/*
* 水平右行走
* */
public static void moveR( ){
if(overHead==0&&posx<screenWidth-50){
images = StaticValue.marioImgsRight;//向右行走的动作集合
posx += step;
switchN--;
if(switchN == 0){
index = (index+1)%4;//每调用两次切换对应的动作突破
switchN = 2;
}
isMoving = 1;//向右移动标记
if(posx>screenWidth-50)//不超过窗口边界
posx = screenWidth-50;
}
}
B.跳跃
- 跳跃分为两个阶段: 上升和下降,包装在两个方法内
- 当不在空中的时候jump才执行,跳跃分为三种:原地、在向左移动过程中、在向右移动过程中,用一个属性isMoving来标记移动的状态:0静止、1向右移动、2向左移动,overHead来标记跳跃状态:0不处于跳跃中,1向右跳跃中、2向左跳跃中
- 要处理的是,跳跃时的位置不能切换太快,我的处理是Thread.sleep(20),让线程休眠20ms
/*
* 向上
* @param int dx 表示水平的步长,负数向左,正数向右
* */
public static void moveU(int dx){
posx += dx;
posy -= 2*step;
if(posx<0) posx=0;
if(posx>screenWidth-50)
posx = screenWidth-50;
}
/*
* 向下
* @param int dx 表示水平的步长,负数向左,正数向右
* */
public static void moveD(int dx){
posx += dx;
posy += 2*step;
if(posx<0) posx=0;
if(posx>screenWidth-50)
posx = screenWidth-50;
}
/*
* 跳跃
* */
public static void jump(){
if(overHead==0){
//是否在移动过程中跳跃
int dx = 0;
if (isMoving == 1 ) {dx = step/2;overHead = 1;}
else if(isMoving == 2 ){dx = -step/2;overHead = 2;}
for(int i = 0 ; i< jumpN/2 ; i++){//上升
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
moveU(dx);
}
for(int i = jumpN/2 ; i< jumpN ; i++){//下降
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
moveD(dx);
}
overHead = 0;
}
}
上面的跳跃方法还会有一些问题,当长按↑键时,松开键后角色会持续跳跃很长一段时间。
我们应该希望监听器检测到↑键按下,如果在时间上距离上一次跳跃的时间间隔小于某个值,则不执行此次跳跃。
这里运用:System.currentTimeMillis();(System.currentTimeMillis()产生一个当前的毫秒,这个毫秒其实就是自1970年1月1日0时起的毫秒数)
/*
* 按钮按下控制马里奥的移动
* */
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int code = e.getKeyCode();
System.out.println("pressed "+code);
switch(code){
case 37://向左
Mario.moveL();break;
case 38://跳跃
t2 = System.currentTimeMillis();
if(t2-t1>500){//当时间间隔小于500ms时不执行
Mario.jump();
t1 = t2;
}
break;
case 39: //向右
Mario.moveR();break;
// else if(code == 32){//攻击
//
}
}
C.最后不要忘了初始化方法:
public static void init(int screenWidth,int screenHeight){
Mario.screenWidth = screenWidth;
Mario.screenHeight = screenHeight;
images = StaticValue.marioImgsRight;
step = 7;
jumpN = 20;
isMoving = 0;
overHead = 0;
posx = 0;
posy = 400;
index = 0;
switchN = 2;
}
D.最终的Mario类的属性:
public static int posx,posy,screenWidth,screenHeight;//马里奥的位置和窗口的宽度和高度
public static int step,switchN;//速度
public static int jumpN;//跳跃切换次数
public static int index;//第index张图片
public static List<BufferedImage> images;//马里奥动画图片的集合
public static int isMoving;//是否在移动0没有移动,1向右,2向左
public static int overHead;//是否在空中
(2)MFrame类
- 要通过键盘控制角色,窗口必须实现键盘监听器keyListener接口
- 双缓冲图片需要不停地绘画,需要运用线程,这里通过实现Runnable接口实现
①实现键盘监听器
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
/*
* 按钮按下控制马里奥的移动
* */
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int code = e.getKeyCode();
if( code == 37){//向左
Mario.moveL();
}
else if(code == 38){//跳跃
Mario.jump();
}
else if(code == 39){//向右
Mario.moveR();
}
// else if(code == 32){//攻击
//
// }
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
int code = e.getKeyCode();
if(code == 37||code == 39){
Mario.isMoving = 0;//放开鼠标停了下来
Mario.index = 0;//动作图片中恢复静止的图片索引
}
else if(code == 38){
}
}
②实现Runnable接口:
/*
* 在线程里面绘制图片
* */
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//缓冲图片上绘制
Image bgimg = new ImageIcon(StaticValue.bgImg1).getImage();//背景图片
buffg.drawImage(bgimg, 0,0, null);
buffg.drawImage(Mario.getImage(), Mario.posx, Mario.posy, 50, 100, null);//马里奥图片
//界面上绘制缓冲图片
g.drawImage(buffimg, 0, 0, null);
}
}
③给MFrame类添加属性:
private Thread marioThread ;//线程
在构造方法里面初始化:
最后不要忘了添加键盘监听器和启动线程
(3)MFrame的重绘
/*
* 重写paint方法
* */
public void paint(Graphics g){
super.repaint();
if(marioDead)
g.drawImage(buffimg,0,0, null);
}