目录
一.实现马里奥的移动:
实现思路分析:
1.//水平方向移动就是移动速度,垂直方向移动就是跳跃速度
//马里奥的移动速度
private int xSpeed;
//马里奥的跳跃速度
private int ySpeed;
2.判断马里奥是否处于空中
由于在空中时,马里奥不能移动,所以要判断一下马里奥此时的状态是否在跳跃中
如果马里奥正在跳跃,就把马里奥的状态改为jump_left,如果马里奥不在空中,就把马里奥的状态修改为move_left,正在向左移动
//马里奥向左移动
public void leftMove(){
xSpeed =-5;
//判断马里奥是否处于空中
if(status.indexOf("jump") != -1){
status="jump_left";
}else{ //马里奥向左移动时
status="move_left";
}
}
以此类推,马里奥是否在向右移动,是否向左停止,是否向右停止
//马里奥向右移动
public void rightMove(){
xSpeed =5;
if(status.indexOf("jump") != -1){
status="jump_right";
}else{
status="move_right";
}
}
//马里奥向左停止
public void leftStop(){
xSpeed =0;
if(status.indexOf("jump") != -1){ //判断是否在空中
status="jump_left";
}else{
status="stop_left";
}
}
//马里奥向右停止
public void rightStop(){
xSpeed =0;
if(status.indexOf("jump") != -1){ //判断是否在空中
status="jump_right";
}else{
status="stop_right";
}
}
3.定义一个index索引,来帮助我们获取马里奥的当前状态的图像
其实是为了让马里奥能够在奔跑的两张图片中来回切换,马里奥只有跑的动作和停下来的两个动作
@Override
public void run() {
while(true){
if(xSpeed <0 ||xSpeed>0){
x+=xSpeed;
//判断马里奥是否运动到了最左边
x=Math.max(0,x);
}
//判断当前是否是移动状态
if(status.contains("move")){
index=index==0?1:0;
}
//判断是否是向左跑
if("move_left".equals(status)){
show =StaticValue.run_L.get(index);
}
//判断是否向右移动
if("move_right".equals(status)){
show =StaticValue.run_R.get(index);
}
//判断是否向左停止移动
if("stop_left".equals(status)){
show =StaticValue.stand_L;
}
//判断是否向右停止移动
if("stop_right".equals(status)){
show =StaticValue.stand_R;
}
}
}
4.由于是键盘来控制马里奥的跳跃,移动,所以我们写完这几个方法之后,需要用键盘监听函数进行监听
按下时向右移动,松开时停止
//当键盘按下按键时调用
@Override
public void keyPressed(KeyEvent e) {
//向右移动
if(e.getKeyCode()==KeyEvent.VK_D){
mario.rightMove();
}
//向左移动
if(e.getKeyCode()==KeyEvent.VK_A){
mario.leftMove();
}
}
//当键盘松开按键时调用
@Override
public void keyReleased(KeyEvent e) {
//向右停止
if(e.getKeyCode()==KeyEvent.VK_D){
mario.rightStop();
}
//向左停止
if(e.getKeyCode()==KeyEvent.VK_A){
mario.leftStop();
}
}
二.实现马里奥的跳跃和下落:
实现思路分析:
1.在马里奥创建一个int变量表示马里奥的上升时间
2.马里奥跳跃函数和马里奥下落函数
//马里奥跳跃
public void jump(){
if(status.indexOf("jump") == -1){ //表示不是跳跃状态
if(status.indexOf("left")!=-1){ //判断方向
status="jump_left"; //改变为向左跳跃
}else {
status="jump_right";
}
ySpeed = -10; //为什么是负数,因为我们在垂直方向上时,越往上坐标大小越小
upTime =7; // 说明跳跃高度为70 但是其实这里应该要变成重力的那个公式
}
}
//马里奥下落
public void fail(){
if(status.indexOf("left")!=-1){
status="jump_left";
}else{
status="jump_right";
}
ySpeed =10;
}
但是有一点困惑的是如果马里奥从障碍物落下的话,这里还能表示么,好像可以,应为fail只判断了方向,也就是说,但是按键监听的时候估计还是得控制一下,
还有一个困惑的点是马里奥为啥自己要开一个线程,然后窗口也要控制一个线程,而且我咋感觉这个线程有点像嵌入式里边的那个loop循环
3.在run方法里边写这个代码:
//判断是否是向左跳跃
if("jump_left".equals(status)){
show=StaticValue.jump_L;
}
//判断是否是向右跳跃
if("jump_right".equals(status)){
show=StaticValue.jump_R;
}
4.判断马里奥是否在障碍物上
5.实现障碍物的阻挡
马里奥向上跳被阻挡
障碍物的阻挡为什么是这样的???什么意思
马里奥往右走被阻挡
马里奥往左走被阻挡
砖块高度是420,马里奥人物高度是25,这个坐标是以哪里为原点的啊?
马里奥是以左上角为坐标原点
障碍物是以左上角,都是以左上角作为每一个图片的坐标原点
三.马里奥掉落地面的优化:
由于最开始马里奥是否在障碍物上的bool值是false,而在下一步才开始进行循环判断马里奥是否真正在每一个障碍物上,所以这其中有一个时间空缺,我们可以将马里奥出生位置往上挪一点点,这样使马里奥掉落到地面之前有一个时间段可以让for循环判断,不至于一开始马里奥掉到了地面的下一层
引起掉落到地面的下一层的原因位置代码:
//还要时刻判断马里奥是否处于障碍物上
boolean onObstacle=false;
//遍历当前场景里所有的障碍物 所以这个时候background派上用场了,需要用background对象来获取当前的障碍物个数和位置
for(int i=0;i<background.getObstacleList().size();i++){
Obstacle ob = background.getObstacleList().get(i);
//判断马里奥是否在障碍物上方
if(ob.getY() ==this.y +25 && (ob.getX() >this.x -30 && ob.getX() <this.x + 25)){
//水平位置左右两边保证了,垂直方向上也要保证
onObstacle=true;
}
}
可以在MyFrame窗口的这两个地方进行修改代码:-----主要是修改马里奥初始化的高度
@Override
public void run() {
while(true){
repaint(); //用于重新绘制我们的图像
try {
Thread.sleep(50);
//判断马里奥是否移动到了场景最右边,移动到了最右边就应该切换场景
if(mario.getX()>=775){
nowBg =allBg.get(nowBg.getSort());
//将当前的场景对象传递给我们马里奥类
mario.setBackground(nowBg);
//重置马里奥的坐标
mario.setX(10);
mario.setY(355);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//初始化马里奥
mario =new Mario(10,355);
//创建全部的场景
for(int i=1;i<=3;i++){
allBg.add(new BackGround(i, i==3? true:false));
}
四.实现游戏结束:
这里需要先做后几关的场景界面,还要实现碰到旗杆才会游戏结束。。
五、当前代码展示:
马里奥类:
package com.game;
import com.Constants.StaticValue;
import com.View.BackGround;
import java.awt.image.BufferedImage;
public class Mario implements Runnable{
//用于表示横纵坐标
private int x;
private int y;
//用于表示当前的状态
private String status;
//用于显示当前状态对应的图像
private BufferedImage show=null;
//定义一个BackGround对象,用来获取障碍物的信息!!!
private BackGround background=new BackGround();
//用于实现马里奥的动作
private Thread thread=null;
//马里奥的移动速度 xspeed不等于0说明在移动,负的为往左走,正的为往右走
private int xSpeed;
//马里奥的跳跃速度
private int ySpeed; //水平方向移动就是移动速度,垂直方向移动就是跳跃速度
//定义一个索引 用于取得马里奥的运动图像
private int index;
//表示马里奥的上升时间
private int upTime = 0;
public Mario() {
}
public Mario(int x, int y) {
this.x = x;
this.y = y;
show = StaticValue.stand_R; //初始动作是向右站立
this.status="stand-right";
thread=new Thread(this); //创建线程
thread.start();//启动线程
}
//马里奥向左移动
public void leftMove(){
xSpeed =-5;
//判断马里奥是否处于空中
if(status.indexOf("jump") != -1){
status="jump_left";
}else{ //马里奥向左移动时
status="move_left";
}
}
//马里奥向右移动
public void rightMove(){
xSpeed =5;
if(status.indexOf("jump") != -1){
status="jump_right";
}else{
status="move_right";
}
}
//马里奥向左停止
public void leftStop(){
xSpeed =0;
if(status.indexOf("jump") != -1){ //判断是否在空中
status="jump_left";
}else{
status="stop_left";
}
}
//马里奥向右停止
public void rightStop(){
xSpeed =0;
if(status.indexOf("jump") != -1){ //判断是否在空中
status="jump_right";
}else{
status="stop_right";
}
}
//马里奥跳跃
public void jump(){
if(status.indexOf("jump") == -1){ //表示不是跳跃状态
if(status.indexOf("left")!=-1){ //判断方向
status="jump_left"; //改变为向左跳跃
}else {
status="jump_right";
}
ySpeed = -10; //为什么是负数,因为我们在垂直方向上时,越往上坐标大小越小
upTime =7; // TODO说明跳跃高度为70 但是其实这里应该要变成重力的那个公式
}
}
//马里奥下落
public void fail(){
if(status.indexOf("left")!=-1){
status="jump_left";
}else{
status="jump_right";
}
ySpeed =10;
}
@Override
public void run() {
while(true){
//还要时刻判断马里奥是否处于障碍物上
boolean onObstacle=false;
//判断是否可以往右走
boolean canRight =true;
//判断是否可以往左走
boolean canLeft =true;
//遍历当前场景里所有的障碍物 所以这个时候background派上用场了,需要用background对象来获取当前的障碍物个数和位置
for(int i=0;i<background.getObstacleList().size();i++){
Obstacle ob = background.getObstacleList().get(i);
//判断马里奥是否在障碍物上方
if(ob.getY() ==this.y +25 && (ob.getX() >this.x -30 && ob.getX() <this.x + 25)){
//水平位置左右两边保证了,垂直方向上也要保证
onObstacle=true;
}
//判断是否跳起来顶到砖块
if(ob.getY()>=this.y -30 &&ob.getY()<=this.y -20 &&(ob.getX() >this.x-30 && ob.getX() <this.x+25)) {
//砖块高度是420,马里奥人物高度是25,这个坐标是以哪里为原点的啊
//还需要判断是普通砖块还是可破坏砖块
if(ob.getType()==0){
//用background来调用obstaclelist
background.getObstacleList().remove(ob);
}
upTime=0; //顶到砖块后立刻下落
}
//判断是否可以往右走
if(this.x+25==ob.getX()&&(this.y-30<ob.getY() && this.y+25>ob.getY())){
canRight=false;
//xSpeed=0; //如果在这里把速度变成0,惹,,这个判断就一直成立,xspeed就一直等于0,阿哲,所以你就一动不了了
}
//判断是否可以往左走
if(this.x==ob.getX()+30&&(this.y-30<ob.getY() && this.y+25>ob.getY())){
canLeft=false;
//xSpeed=0;
}
}
//进行马里奥跳跃的操作 表示在障碍物上而且在障碍物上没有跳??因该是这个意思
if(onObstacle && upTime == 0){
if(status.indexOf("left")!=-1){
if(xSpeed!=0){
//表示向左移动
status="move_left";
}else{
status="stop_left";
}
}else{
if(xSpeed!=0){
//表示向左移动
status="move_right";
}else{
status="stop_right";
}
}
}else{
//处于上升状态
if(upTime !=0){
upTime--;
}else{
fail();
}
y+=ySpeed;
}
//这是移动函数 所以只有速度不等于0而且没有遇到障碍物才能移动
if(canLeft && canRight &&(xSpeed <0 ||xSpeed>0)){
x+=xSpeed;
//判断马里奥是否运动到了最左边
x=Math.max(0,x);
}
//判断当前是否是移动状态
if(status.contains("move")){
index=index==0?1:0;
}
//判断是否是向左跑
if("move_left".equals(status)){
show =StaticValue.run_L.get(index);
}
//判断是否向右移动
if("move_right".equals(status)){
show =StaticValue.run_R.get(index);
}
//判断是否向左停止移动
if("stop_left".equals(status)){
show =StaticValue.stand_L;
}
//判断是否向右停止移动
if("stop_right".equals(status)){
show =StaticValue.stand_R;
}
//判断是否是向左跳跃
if("jump_left".equals(status)){
show=StaticValue.jump_L;
}
//判断是否是向右跳跃
if("jump_right".equals(status)){
show=StaticValue.jump_R;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public BufferedImage getShow() {
return show;
}
public void setShow(BufferedImage show) {
this.show = show;
}
public void setBackground(BackGround background) {
this.background = background;
}
}
当前的窗口类:
package com.View;
import com.Constants.StaticValue;
import com.game.Mario;
import com.game.Obstacle;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
public class MyFrame extends JFrame implements KeyListener,Runnable{
//为什么要存储场景 TODO惹??
//BackGround类的list,里边全部是background类,不是buffered图片,要区别开来
//用于存储 所有的背景
private List<BackGround> allBg =new ArrayList<>();
//用于存储 当前的背景
private BackGround nowBg =new BackGround();
//用于双缓存
//定义一个变量用于双缓存 TODO????????
private Image offScreenImage =null;
//马里奥对象
private Mario mario = new Mario();
//定义一个线程对象,用于实现马里奥的运动
private Thread thread =new Thread(this);
//无参构造
public MyFrame() {
//设置窗口的大小为800*600
this.setSize(800,600);
//设置窗口的居中显示
this.setLocationRelativeTo(null);
//设置窗口的可见性
this.setVisible(true);
//设置点击窗口上的关闭键,结束程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口大小不可变
this.setResizable(false);
//向窗口对象添加键盘监听器
this.addKeyListener(this);
//设置窗口名称
this.setTitle("超级玛丽");
//初始化图片
StaticValue.init();
//初始化马里奥
mario =new Mario(10,355);
//创建全部的场景
for(int i=1;i<=3;i++){
allBg.add(new BackGround(i, i==3? true:false));
}
//将第一个场景设置为当前的场景
nowBg =allBg.get(0);
mario.setBackground(nowBg); //当前场景的背景对象传递给马里奥
//绘制图像 进行图像的绘制
repaint();
thread.start(); //启动线程
}
@Override
public void paint(Graphics g) {
if(offScreenImage ==null){
offScreenImage =createImage(800,600);
}
Graphics graphics = offScreenImage.getGraphics();
graphics.fillRect(0,0,800,600);
//fillRect 用于在指定的矩形内绘制一个填充的矩形
//绘制背景 将图像绘制到了缓冲区上
graphics.drawImage(nowBg.getBgImage(), 0,0,this);
//绘制障碍物
for(Obstacle ob : nowBg.getObstacleList()){
graphics.drawImage(ob.getShow(),ob.getX(),ob.getY(),this);
}
//绘制马里奥
graphics.drawImage(mario.getShow(),mario.getX(),mario.getY(),this); //注意马里奥绘制要在g.drawImage之前
//将缓冲区的图片绘制到窗口中
g.drawImage(offScreenImage,0,0,this);
}
@Override
public void keyTyped(KeyEvent e) {
}
//当键盘按下按键时调用
@Override
public void keyPressed(KeyEvent e) {
//向右移动
if(e.getKeyCode()==KeyEvent.VK_D){
mario.rightMove();
}
//向左移动
if(e.getKeyCode()==KeyEvent.VK_A){
mario.leftMove();
}
if (e.getKeyCode()==KeyEvent.VK_W){
mario.jump();
}
}
//当键盘松开按键时调用
@Override
public void keyReleased(KeyEvent e) {
//向右停止
if(e.getKeyCode()==KeyEvent.VK_D){
mario.rightStop();
}
//向左停止
if(e.getKeyCode()==KeyEvent.VK_A){
mario.leftStop();
}
}
public static void main(String[] args) {
new MyFrame();
}
@Override
public void run() {
while(true){
repaint(); //用于重新绘制我们的图像
try {
Thread.sleep(50);
//判断马里奥是否移动到了场景最右边,移动到了最右边就应该切换场景
if(mario.getX()>=775){
nowBg =allBg.get(nowBg.getSort());
//将当前的场景对象传递给我们马里奥类
mario.setBackground(nowBg);
//重置马里奥的坐标
mario.setX(10);
mario.setY(355);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}