文章目录
一、效果展示二、代码编写1. 素材准备2. 创建窗口类3. 创建常量类4. 创建动作类5. 创建关卡类6. 创建障碍物类7. 创建马里奥类8. 编写程序入口
一、效果展示
二、代码编写
1. 素材准备
首先创建一个基本的 java 项目,并将本游戏需要用到的图片素材 image 导入。
图片素材如下:
https://pan.baidu.com/s/1db_IcPvPKWKbVPtodPWO5Q?pwd=03kv
提取码:03kv
2. 创建窗口类
① Java 内部已经给我们封装了窗口的各种方法,我们只需创建一个窗口类并重写父类的方法,即可使用;
② Alt + Enter 键 → implement methods 可一键补全所有的重写方法;
③ 实现多线程有两种方法,分别是继承 Thread 类和实现 Runnable 接口,这里我们用 Runnable 方法,因为 Java 不支持多继承。
重写 paint 方法,实现场景、物体的绘制,使用多线程无限绘制窗口。
完整代码如下:
package com.zxe.beans;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 { //定义一个集合用于所有的关卡 private List<LevelMap> levelMaps = new ArrayList<>(); //定义一个变量,存放当前背景 private LevelMap levelMap = new LevelMap(); //定义变量,记录马里奥 private Mario mario; //重写paint方法,实现场景、物体的绘制 @Override public void paint(Graphics g) { //创建一张图片 Image image = createImage(1045, 500); //设置图片 Graphics graphics = image.getGraphics(); graphics.drawImage(levelMap.getBg(), 0, 0, 1045, 500, this); //绘制障碍物 for (Obstacle obstacle : levelMap.getObstacles()){ graphics.drawImage(obstacle.getObstacleImage(), obstacle.getX(), obstacle.getY(), this); } //绘制马里奥 graphics.drawImage(mario.getMarioImage(), mario.getX(), mario.getY(), this); //将图片描绘到当前窗口中 g.drawImage(image, 0, 0, this); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == 37) { mario.runLeft(); } else if (e.getKeyCode() == 39) { mario.runRight(); } else if (e.getKeyCode() == 38) { mario.jump(); } } public Mario getMario() { return mario; } public void setMario(Mario mario) { this.mario = mario; } @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == 37) { mario.runLeftStop(); } else if (e.getKeyCode() == 39) { mario.runRightStop(); } else if (e.getKeyCode() == 38) { mario.jumpDown(); } } @Override public void run() { //无限次绘制马里奥 while (true) { repaint(); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } //判断一下马里奥是否通关 if (mario.getX() > 1040) { levelMap = levelMaps.get(levelMap.getLevel()); mario.setLevelMap(levelMap); mario.setX(50); mario.setY(420); } } } public List<LevelMap> getLevelMaps() { return levelMaps; } public void setLevelMaps(List<LevelMap> levelMaps) { this.levelMaps = levelMaps; } public LevelMap getLevelMap() { return levelMap; } public void setLevelMap(LevelMap levelMap) { this.levelMap = levelMap; }}
3. 创建常量类
小游戏中的各种元素画面其实都是一张张的图片,而这些图片路径的定义都将放在常量类中完成。
package com.zxe.beans;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.List;/** * 常量类 */public class Constant { //给窗口定义一张图片 public static BufferedImage bg; //右跳图片 public static BufferedImage jumpR; //左跳图片 public static BufferedImage jumpL; //右边站立 public static BufferedImage standR; //左边站立 public static BufferedImage standL; //定义一个集合,存放右跑动作 public static List<BufferedImage> runR = new ArrayList<>(); //定义一个集合,存放左跑动作 public static List<BufferedImage> runL = new ArrayList<>(); //为障碍物定义一个集合 public static List<BufferedImage> onstacles = new ArrayList<>(); //定义一个变量,记录文件路径前缀 public static String prefix = "C:\\Users\\Lenovo\\Desktop\\demo\\file\\image\\"; //初始化图片到系统中 public static void initImage() { try { //加载图片 bg = ImageIO.read(new File(prefix + "bg2.jpeg")); jumpR = ImageIO.read(new File(prefix + "mario_jump_r.png")); jumpL = ImageIO.read(new File(prefix + "mario_jump_l.png")); standR = ImageIO.read(new File(prefix + "mario_stand_r.png")); standL = ImageIO.read(new File(prefix + "mario_stand_l.png")); runR.add(ImageIO.read(new File(prefix + "mario_run_r1.png"))); runR.add(ImageIO.read(new File(prefix + "mario_run_r2.png"))); runL.add(ImageIO.read(new File(prefix + "mario_run_l1.png"))); runL.add(ImageIO.read(new File(prefix + "mario_run_l2.png"))); for (int i = 1; i <= 6; i++) { onstacles.add(ImageIO.read(new File(prefix + "ob" + i + ".png"))); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } }}
常量用 static 修饰,外部可直接通过类名调用常量,而无需创建对象。
4. 创建动作类
记录玛丽的动作状态,具体的常量名与代码分离,可以降低代码的耦合度,更规范化。
5. 创建关卡类
每个关卡的障碍物是不一样的,这里需要外部传入关卡号,在关卡类中把不同的障碍物拼成自定义的关卡。
完整代码如下:
package com.zxe.beans;import com.zxe.utils.Constant;import java.awt.image.BufferedImage;import java.util.ArrayList;import java.util.List;/** * 关卡地图类 */public class LevelMap { //记录当前场景需要的图片 private BufferedImage bg; //记录当前关卡 private int level; //创建一个集合,用于存放障碍物 private List<Obstacle> obstacles = new ArrayList<>(); public LevelMap() { } public LevelMap(int level) { this.level = level; bg = Constant.bg; if (level == 1) { //绘制方块 obstacles.add(new Obstacle(100, 370, 0, this)); obstacles.add(new Obstacle(130, 370, 1, this)); obstacles.add(new Obstacle(160, 370, 0, this)); obstacles.add(new Obstacle(190, 370, 1, this)); obstacles.add(new Obstacle(300, 260, 0, this)); obstacles.add(new Obstacle(330, 260, 1, this)); obstacles.add(new Obstacle(360, 260, 1, this)); obstacles.add(new Obstacle(800, 300, 0, this)); obstacles.add(new Obstacle(830, 300, 0, this)); obstacles.add(new Obstacle(860, 300, 1, this)); obstacles.add(new Obstacle(890, 300, 1, this)); //绘制水管 obstacles.add(new Obstacle(420, 420, 4, this)); obstacles.add(new Obstacle(450, 420, 5, this)); obstacles.add(new Obstacle(415, 390, 2, this)); obstacles.add(new Obstacle(435, 390, 2, this)); obstacles.add(new Obstacle(455, 390, 3, this)); obstacles.add(new Obstacle(600, 420, 4, this)); obstacles.add(new Obstacle(630, 420, 5, this)); obstacles.add(new Obstacle(600, 390, 4, this)); obstacles.add(new Obstacle(630, 390, 5, this)); obstacles.add(new Obstacle(595, 360, 2, this)); obstacles.add(new Obstacle(615, 360, 2, this)); obstacles.add(new Obstacle(635, 360, 3, this)); } else if (level == 2) { int i = 0; for (int y = 420; y >= 300; y -= 30) { for (int x = 100; x <= 190 - 30 * i; x += 30) { obstacles.add(new Obstacle(x + 30 * i, y, 0, this)); } for (int x = 300; x <= 390 - 30 * i; x += 30) { obstacles.add(new Obstacle(x, y, 0, this)); } for (int x = 550; x <= 640 - 30 * i; x += 30) { obstacles.add(new Obstacle(x + 30 * i, y, 0, this)); } for (int x = 670; x <= 790 - 30 * i; x += 30) { obstacles.add(new Obstacle(x, y, 0, this)); } i++; } } } public BufferedImage getBg() { return bg; } public void setBg(BufferedImage bg) { this.bg = bg; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public List<Obstacle> getObstacles() { return obstacles; } public void setObstacles(List<Obstacle> obstacles) { this.obstacles = obstacles; }}
6. 创建障碍物类
障碍物的属性包括图片以及横纵坐标。
完整代码如下:
package com.zxe.beans;import com.zxe.utils.Constant;import java.awt.image.BufferedImage;/** * 障碍物类 */public class Obstacle { //记录障碍物的坐标 private int x; private int y; //定义一个变量,记录当前障碍物的图片信息 private BufferedImage obstacleImage; //定义障碍物类型 private int type; //定义变量存放当前的背景 private LevelMap bg; public Obstacle(int x, int y, int type, LevelMap bg) { this.x = x; this.y = y; this.type = type; this.bg = bg; //根据障碍物的编号,从常量中的障碍物集合中获取对应的图片 this.obstacleImage = Constant.onstacles.get(type); } 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 getObstacleImage() { return obstacleImage; } public void setObstacleImage(BufferedImage obstacleImage) { this.obstacleImage = obstacleImage; } public int getType() { return type; } public void setType(int type) { this.type = type; } public LevelMap getBg() { return bg; } public void setBg(LevelMap bg) { this.bg = bg; }}
7. 创建马里奥类
马里奥的无限行走动作由多线程实现,定义一个状态量status,用于标记马里奥当前的运动状态,以便进行不同动作的来回切换。
完整代码如下:
package com.zxe.beans;import com.zxe.utils.Action;import com.zxe.utils.Constant;import java.awt.image.BufferedImage;/** * 马里奥类 */public class Mario implements Runnable { //记录马里奥坐标信息 private int x; private int y; //记录马里奥状态 private String status; //定义一个变量,记录马里奥当前动作所对应的图片信息 private BufferedImage marioImage; //定义变量,记录当前的关卡地图,也可以获取障碍物的信息 private LevelMap levelMap = new LevelMap(); //创建线程执行马里奥的动作 private Thread thread; //定义变量,记录马里奥的移动速度 private int xSpeed; //定义变量,记录马里奥的跳跃速度 private int ySpeed; //定义变量,记录马里奥的上升状态 private int up; public Mario() {} public Mario(int x, int y) { this.x = x; this.y = y; //默认马里奥的动作是朝右站立 status = Action.STAND_RIGHT; marioImage = Constant.standR; thread = new Thread(this); thread.start(); } //马里奥向左移动的方法 public void runLeft() { //判断当前是否为跳跃状态,如果不是,就改变状态 if ( !status.contains("jump") ) { status = Action.RUN_LEFT; } else { status = Action.JUMP_LEFT; } xSpeed = -5; } //马里奥向右移动的方法 public void runRight() { //判断当前是否为跳跃状态,如果不是,就改变状态 if ( !status.contains("jump") ) { status = Action.RUN_RIGHT; } else { status = Action.JUMP_RIGHT; } xSpeed = 5; } public void jump() { if (status.contains("left")) { status = Action.JUMP_LEFT; } else { status = Action.JUMP_RIGHT; } ySpeed = -12; } public void jumpDown() { ySpeed = 12; } private void jumpStop() { if (status.contains("left")) { status = Action.STAND_LEFT; } else { status = Action.STAND_RIGHT; } ySpeed = 0; } //马里奥向左移动停止的方法 public void runLeftStop() { if (!status.contains("jump")) { status = Action.STAND_LEFT; } else { status = Action.JUMP_LEFT; } xSpeed = 0; } //马里奥向右移动停止的方法 public void runRightStop() { if (!status.contains("jump")) { status = Action.STAND_RIGHT; } else { status = Action.JUMP_RIGHT; } xSpeed = 0; } 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 String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public BufferedImage getMarioImage() { return marioImage; } public void setMarioImage(BufferedImage marioImage) { this.marioImage = marioImage; } public LevelMap getLevelMap() { return levelMap; } public void setLevelMap(LevelMap levelMap) { this.levelMap = levelMap; } public Thread getThread() { return thread; } public void setThread(Thread thread) { this.thread = thread; } public int getxSpeed() { return xSpeed; } public void setxSpeed(int xSpeed) { this.xSpeed = xSpeed; } public int getySpeed() { return ySpeed; } public void setySpeed(int ySpeed) { this.ySpeed = ySpeed; } public int getUp() { return up; } public void setUp(int up) { this.up = up; } @Override public void run() { int index = 0; //控制马里奥无限移动 while (true) { //判断当前是否移动,xSpeed<0左移动,xSpeed>0右移动 if (xSpeed < 0 || xSpeed > 0) { x += xSpeed; if (x < 0) { x = 0; } } if (ySpeed < 0 || ySpeed > 0) { y += ySpeed; if (y > 420) { y = 420; jumpStop(); } if (y < 280) { y = 280; } } //判断移动状态,跑步状态图片切换 if (status.contains("run")) { index = index == 0 ? 1 : 0; } //根据马里奥的状态切换不同的图片 if (Action.RUN_LEFT.equals(status)) { marioImage = Constant.runL.get(index); } if (Action.RUN_RIGHT.equals(status)) { marioImage = Constant.runR.get(index); } if (Action.STAND_LEFT.equals(status)) { marioImage = Constant.standL; } if (Action.STAND_RIGHT.equals(status)) { marioImage = Constant.standR; } if (Action.JUMP_LEFT.equals(status)) { marioImage = Constant.jumpL; } if (Action.JUMP_RIGHT.equals(status)) { marioImage = Constant.jumpR; } // 控制线程的速度 try { Thread.sleep(30); } catch (InterruptedException e) { throw new RuntimeException(e); } } }}
8. 编写程序入口
创建游戏窗口,并对窗口的基本属性进行设置,创建三个关卡,并调用 repaint 方法绘制场景。
package com.zxe;import com.zxe.beans.LevelMap;import com.zxe.beans.Mario;import com.zxe.utils.Constant;import com.zxe.beans.MyFrame;import javax.swing.*;public class Main { public static void main(String[] args) { //创建窗口对象 MyFrame myFrame = new MyFrame(); //设置窗口大小 myFrame.setSize(1045,500); //设置窗口居中 myFrame.setLocationRelativeTo(null); //设置窗口可见性 myFrame.setVisible(true); //设置窗口关闭程序 myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置键盘监听事件 myFrame.addKeyListener(myFrame); //设置窗口的大小不可改变 myFrame.setResizable(false); //设置窗口标题 myFrame.setTitle("超级玛丽"); //加载图片 Constant.initImage(); //创建三个关卡地图 for (int i = 1; i <= 3; i++) { myFrame.getLevelMaps().add(new LevelMap(i)); } //设置当前关卡地图 myFrame.setLevelMap(myFrame.getLevelMaps().get(0)); //创建马里奥 Mario mario = new Mario(50, 420); myFrame.setMario(mario); mario.setLevelMap(myFrame.getLevelMap()); //绘制场景 myFrame.repaint(); Thread thread = new Thread(myFrame); thread.start(); }}