实验4: 多线程
1. 所使用的工具软件及环境
环境:Windows 8.1;jdk 1.8;
工具:eclipse
2. 实验目的
掌握JAVA多线程结构和开发过程。
了解多线程生命周期和运行机制。
3. 实验内容
使用多线程机制实现动画。
用继承Thread类实现多线程;
用实现Runnable接口实现多线程。
4. 源程序
(1)MyFrame类
package com.sxt;
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<BackGround> allBg = new ArrayList<>(); //所有的背景
private BackGround nowBg = new BackGround(); //当前的背景
private Image offScreenImage = null; //用于双缓存
private Redbean redbean = new Redbean(); //红小豆对象
private Thread thread = new Thread(this); //线程对象实现红小豆的运动
public MyFrame() {
this.setSize(800,600);
this.setLocationRelativeTo(null);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("红小豆历险记");
this.setResizable(false); //窗口大小不可变
this.addKeyListener(this); //添加键盘监听器
//初始化
StaticValue.init();
redbean = new Redbean(10,355);
//创建全部的场景
for (int i = 1;i <= 3;i++) {
allBg.add(new BackGround(i, i == 3 ? true : false));
}
//设置第一场景为当前场景
nowBg = allBg.get(0);
redbean.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);
//绘制背景
graphics.drawImage(nowBg.getBgImage(),0,0,this);
//绘制敌人
for (Enemy e : nowBg.getEnemyList()) {
graphics.drawImage(e.getShow(),e.getX(),e.getY(),this);
}
//绘制障碍物
for (Obstacle ob : nowBg.getObstacleList()) {
graphics.drawImage(ob.getShow(),ob.getX(),ob.getY(),this);
}
//绘制城堡
graphics.drawImage(nowBg.getTower(),620,270,this);
//绘制旗杆
graphics.drawImage(nowBg.getGan(),500,220,this);
//绘制红小豆
graphics.drawImage(redbean.getShow(), redbean.getX(), redbean.getY(),this);
//提示
Color c = graphics.getColor();
graphics.setColor(Color.BLACK);
graphics.setFont(new Font("黑体",Font.BOLD,23));
graphics.drawString("关卡: " + nowBg.getSort(),55,75);
graphics.drawString("当前得分: " + redbean.getScore(),320,110);
graphics.setColor(c);
//将图像绘制到窗口中
g.drawImage(offScreenImage,0,0,this);
}
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
}
//键盘操控设置
@Override
public void keyTyped(KeyEvent e) {}
//当键盘按下时调用
@Override
public void keyPressed(KeyEvent e) {
//向右移动
if (e.getKeyCode() == 39) {
redbean.rightMove();
}
//向左移动
if (e.getKeyCode() == 37) {
redbean.leftMove();
}
//跳跃
if (e.getKeyCode() == 38) {
redbean.jump();
}
}
//当键盘松开时调用
@Override
public void keyReleased(KeyEvent e) {
//向左停止
if (e.getKeyCode() == 37) {
redbean.leftStop();
}
//向右停止
if (e.getKeyCode() == 39) {
redbean.rightStop();
}
}
@Override
public void run() {
while (true) {
repaint();
try {
Thread.sleep(50);
//切换到下一场景
if (redbean.getX() >= 775) {
nowBg = allBg.get(nowBg.getSort());
redbean.setBackGround(nowBg);
redbean.setX(10);
redbean.setY(355);
}
//红小豆死亡
if (redbean.isDeath()) {
JOptionPane.showMessageDialog(this,"红小豆被吃掉了!!!");
System.exit(0);
}
//游戏成功
if (redbean.isOK()) {
JOptionPane.showMessageDialog(this,"恭喜你!通关成功!");
System.exit(0);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(2)StaticValue类
package com.sxt;
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 StaticValue {
//背景
public static BufferedImage bg1 = null;
public static BufferedImage bg2 = null;
public static BufferedImage bg3 = null;
public static BufferedImage jump_L = null; //向左跳跃
public static BufferedImage jump_R = null; //向右跳跃
public static BufferedImage stand_L = null; //向左站立
public static BufferedImage stand_R = null; //向右站立
public static List<BufferedImage> run_L = new ArrayList<>(); //向左跑
public static List<BufferedImage> run_R = new ArrayList<>(); //向右跑
public static BufferedImage tower = null; //城堡
public static BufferedImage gan = null; //旗杆
public static List<BufferedImage> obstacle = new ArrayList<>(); //障碍物
public static List<BufferedImage> snowman = new ArrayList<>(); //雪人
public static List<BufferedImage> flower = new ArrayList<>(); //食人花
public static String path = System.getProperty("user.dir") + "/src/images/"; //路径前缀,方便后续调用
//初始化
public static void init() {
try {
bg1 = ImageIO.read(new File(path + "bg1.png"));
bg2 = ImageIO.read(new File(path + "bg2.png"));
bg3= ImageIO.read(new File(path + "bg3.png"));
stand_L = ImageIO.read(new File(path + "redbean_standL.png"));
stand_R = ImageIO.read(new File(path + "redbean_standR.png"));
jump_L = ImageIO.read(new File(path + "redbean_jumpL.png"));
jump_R = ImageIO.read(new File(path + "redbean_jumpR.png"));
tower = ImageIO.read(new File(path + "tower.png"));
gan = ImageIO.read(new File(path + "gan.png"));
} catch (IOException e) {
e.printStackTrace();
}
//加载红小豆向左跑
for (int i = 1;i <= 2;i++) {
try {
run_L.add(ImageIO.read(new File(path + "redbean_runL"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载红小豆向右跑
for (int i = 1;i <= 2;i++) {
try {
run_R.add(ImageIO.read(new File(path + "redbean_runR"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
try {
//加载障碍物
obstacle.add(ImageIO.read(new File(path + "brick.png")));
obstacle.add(ImageIO.read(new File(path + "soil_up.png")));
obstacle.add(ImageIO.read(new File(path + "soil_base.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载水管
for (int i = 1;i <= 4;i++) {
try {
obstacle.add(ImageIO.read(new File(path + "pipe"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载不可破坏的砖块和旗子
try {
obstacle.add(ImageIO.read(new File(path + "brick2.png")));
obstacle.add(ImageIO.read(new File(path + "flag.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载雪人
for (int i = 1;i <= 4;i++) {
try {
snowman.add(ImageIO.read(new File(path + "snow"+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载食人花
for (int i = 1;i <= 2;i++) {
try {
flower.add(ImageIO.read(new File(path + "flower1."+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3)Background类
package com.sxt;
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 StaticValue {
//背景
public static BufferedImage bg1 = null;
public static BufferedImage bg2 = null;
public static BufferedImage bg3 = null;
public static BufferedImage jump_L = null; //向左跳跃
public static BufferedImage jump_R = null; //向右跳跃
public static BufferedImage stand_L = null; //向左站立
public static BufferedImage stand_R = null; //向右站立
public static List<BufferedImage> run_L = new ArrayList<>(); //向左跑
public static List<BufferedImage> run_R = new ArrayList<>(); //向右跑
public static BufferedImage tower = null; //城堡
public static BufferedImage gan = null; //旗杆
public static List<BufferedImage> obstacle = new ArrayList<>(); //障碍物
public static List<BufferedImage> snowman = new ArrayList<>(); //雪人
public static List<BufferedImage> flower = new ArrayList<>(); //食人花
public static String path = System.getProperty("user.dir") + "/src/images/"; //路径前缀,方便后续调用
//初始化
public static void init() {
try {
bg1 = ImageIO.read(new File(path + "bg1.png"));
bg2 = ImageIO.read(new File(path + "bg2.png"));
bg3= ImageIO.read(new File(path + "bg3.png"));
stand_L = ImageIO.read(new File(path + "redbean_standL.png"));
stand_R = ImageIO.read(new File(path + "redbean_standR.png"));
jump_L = ImageIO.read(new File(path + "redbean_jumpL.png"));
jump_R = ImageIO.read(new File(path + "redbean_jumpR.png"));
tower = ImageIO.read(new File(path + "tower.png"));
gan = ImageIO.read(new File(path + "gan.png"));
} catch (IOException e) {
e.printStackTrace();
}
//加载红小豆向左跑
for (int i = 1;i <= 2;i++) {
try {
run_L.add(ImageIO.read(new File(path + "redbean_runL"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载红小豆向右跑
for (int i = 1;i <= 2;i++) {
try {
run_R.add(ImageIO.read(new File(path + "redbean_runR"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
try {
//加载障碍物
obstacle.add(ImageIO.read(new File(path + "brick.png")));
obstacle.add(ImageIO.read(new File(path + "soil_up.png")));
obstacle.add(ImageIO.read(new File(path + "soil_base.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载水管
for (int i = 1;i <= 4;i++) {
try {
obstacle.add(ImageIO.read(new File(path + "pipe"+ i +".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载不可破坏的砖块和旗子
try {
obstacle.add(ImageIO.read(new File(path + "brick2.png")));
obstacle.add(ImageIO.read(new File(path + "flag.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载雪人
for (int i = 1;i <= 4;i++) {
try {
snowman.add(ImageIO.read(new File(path + "snow"+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载食人花
for (int i = 1;i <= 2;i++) {
try {
flower.add(ImageIO.read(new File(path + "flower1."+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(4)Obstacle类
package com.sxt;
import java.awt.image.BufferedImage;
public class Obstacle implements Runnable{
private int x;
private int y;
private int type; //用于记录障碍物类型
private BufferedImage show = null; //显示图像
private BackGround bg = null; //当前的场景对象
private Thread thread = new Thread(this); //线程对象
public Obstacle(int x,int y,int type,BackGround bg) {
this.x = x;
this.y = y;
this.type = type;
this.bg = bg;
show = StaticValue.obstacle.get(type);
//旗子启动线程
if (type == 8) {
thread.start();
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getType() {
return type;
}
public BufferedImage getShow() {
return show;
}
@Override
public void run() {
while (true) {
if (this.bg.isReachGan()) {
if (this.y < 374) {
this.y += 5;
}else {
this.bg.setBase(true);
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(5)Redbean类
package com.sxt;
import java.awt.image.BufferedImage;
public class Redbean implements Runnable{
private int x; //横坐标
private int y; //纵坐标
private String status; //当前的状态
private BufferedImage show = null; //当前状态对应的图像
private BackGround backGround = new BackGround(); //定义一个BackGround对象,用来获取障碍物的信息
private Thread thread = null; //实现动作
private int xSpeed; //移动速度
private int ySpeed; //跳跃速度
private int index; //索引
private int upTime = 0; //上升的时间
private boolean isOK; //是否走到终点
private boolean isDeath = false; //是否死亡
private int score = 0; //得分
public Redbean() {}
public Redbean(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 death() {
isDeath = true;
}
//向左移动
public void leftMove() {
//改变速度
xSpeed = -5;
//判断是否碰到旗子
if (backGround.isReachGan()) {
xSpeed = 0;
}
//判断是否处于空中
if (status.indexOf("jump") != -1) {
status = "jump--left";
}else {
status = "move--left";
}
}
//向右移动
public void rightMove() {
xSpeed = 5;
if (backGround.isReachGan()) {
xSpeed = 0;
}
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;
}
//是否碰到旗子
if (backGround.isReachGan()) {
ySpeed = 0;
}
}
//下落
public void fall() {
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; //是否可以往左走
//是否到达旗杆位置
if (backGround.isFlag() && this.x >= 500) {
this.backGround.setReachGan(true);
//旗子是否下落完成
if (this.backGround.isBase()) {
status = "move--right";
if (x < 690) {
x += 5;
}else {
isOK = true;
}
}else {
if (y < 395) {
xSpeed = 0;
this.y += 5;
status = "jump--right";
}
if (y > 395) {
this.y = 395;
status = "stop--right";
}
}
} else{
//遍历当前场景里所有的障碍物
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)) {
if (ob.getType() == 0) {
backGround.getObstacleList().remove(ob);
score += 1;
}
upTime = 0;
}
//判断是否可以往右走
if (ob.getX() == this.x + 25 && (ob.getY() > this.y - 30 && ob.getY() < this.y + 25)) {
canRight = false;
}
//判断是否可以往左走
if (ob.getX() == this.x - 30 && (ob.getY() > this.y - 30 && ob.getY() < this.y + 25)) {
canLeft = false;
}
}
//判断红小豆是否碰到敌人死亡或者踩死雪人
for (int i = 0;i < backGround.getEnemyList().size();i++) {
Enemy e = backGround.getEnemyList().get(i);
if (e.getY() == this.y + 20 && (e.getX() - 25 <= this.x && e.getX() + 35 >= this.x)) {
if (e.getType() == 1) {
e.death();
score += 2;
upTime = 3;
ySpeed = -10;
}else if (e.getType() == 2) {
death();
}
}
if ((e.getX() + 35 > this.x && e.getX() - 25 < this.x) && (e.getY() + 35 > this.y && e.getY() - 20 < this.y)) {
death();
}
}
//进行红小豆跳跃操作
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 {
fall();
}
y += ySpeed;
}
}
//左移
if ((canLeft && xSpeed < 0) || (canRight && xSpeed > 0)) {
x += xSpeed;
//判断红小豆是否到了最左边
if (x < 0) {
x = 0;
}
}
//判断当前是否是移动状态
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 setBackGround(BackGround backGround) {
this.backGround = backGround;
}
public boolean isOK() {
return isOK;
}
public boolean isDeath() {
return isDeath;
}
public int getScore() {
return score;
}
}
(6)Enemy类
package com.sxt;
import java.awt.image.BufferedImage;
public class Enemy implements Runnable{
private int x;
private int y;
private int type; //敌人类型
private boolean face_to = true; //判断敌人运动方向
private BufferedImage show; //显示敌人当前图像
private BackGround bg; //背景对象
private int max_up = 0; //食人花上升极限
private int max_down = 0; //食人花下降极限
private Thread thread = new Thread(this); //线程对象
private int image_type = 0; //当前的图片的状态
//雪人敌人的构造函数
public Enemy(int x,int y,boolean face_to,int type,BackGround bg) {
this.x = x;
this.y = y;
this.face_to = face_to;
this.type = type;
this.bg = bg;
show = StaticValue.snowman.get(0);
thread.start();
}
//食人花敌人的构造函数
public Enemy(int x,int y,boolean face_to,int type,int max_up,int max_down,BackGround bg) {
this.x = x;
this.y = y;
this.face_to = face_to;
this.type = type;
this.max_up = max_up;
this.max_down = max_down;
this.bg = bg;
show = StaticValue.flower.get(0);
thread.start();
}
//雪人敌人死亡方法
public void death(){
show = StaticValue.snowman.get(3);
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
show = StaticValue.snowman.get(3);
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.bg.getEnemyList().remove(this);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public BufferedImage getShow() {
return show;
}
public int getType() {
return type;
}
@Override
public void run() {
while (true) {
//判断是否是蘑菇敌人
if (type == 1) {
if (face_to) {
this.x -= 2;
}else {
this.x += 2;
}
image_type = image_type == 1 ? 0 : 1;
show = StaticValue.snowman.get(image_type);
}
//判断是否可以右走和左走
boolean canLeft = true;
boolean canRight = true;
for (int i = 0;i < bg.getObstacleList().size();i++) {
Obstacle ob1 = bg.getObstacleList().get(i);
if (ob1.getX() == this.x + 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canRight = false;
}
if (ob1.getX() == this.x - 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canLeft = false;
}
}
if (face_to && !canLeft || this.x == 0) {
face_to = false;
}
else if ((!face_to) && (!canRight) || this.x == 764) {
face_to = true;
}
//判断是否是食人花敌人
if (type == 2) {
if (face_to) {
this.y -= 2;
}else {
this.y += 2;
}
image_type = image_type == 1 ? 0 : 1;
//食人花是否到达极限位置
if (face_to && (this.y == max_up)) {
face_to = false;
}
if ((!face_to) && (this.y == max_down)) {
face_to = true;
}
show = StaticValue.flower.get(image_type);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5. 实验结果
6. 体会
本次实验,我通过java多线程机制制作了一款简单的小游戏——红小豆历险记。游戏一共设有三个关卡。红小豆会在途中遇到雪人和食人花两种敌人,他需要成功躲避敌人,并且踩死一个雪人可以获得2分。途中还设有未知砖块,触碰它们会奖励相应积分。当红小豆到达第三关卡,成功将旗子放下并到达城堡,游戏成功。
游戏的操作是通过键盘的上键实现红小豆的跳跃,键盘左键实现左移,键盘右键实现右移。在代码实现中,我通过调用Keylistener监听器接口,实现键盘的按下和弹起操作与游戏主角红小豆的移动和静止函数相对应。
其中,红小豆与未知砖块、雪人、食人花、旗子、背景之间均存在多线程。我设计了Redbean、Obstacle、Enemy、MyFrame四个类,对四个类分别调用Runnable接口并覆写其run()方法,再依次实例化Runnable子类对象和Thread类对象,最后启动线程,实现多对象的同步运行。以红小豆与雪人之间的碰撞为例:当红小豆踩到雪人时,雪人死亡并且得分加2;当红小豆没有踩到雪人,红小豆死亡。首先,实现Redbean类的Runnable接口。然后,覆写它的run()方法中:遍历当前场景中所有敌人并记录其横纵坐标,当敌人类型为雪人时,判断红小豆的横坐标是否在雪人横坐标左侧25个像素点到右侧35个像素点范围内,纵坐标在雪人纵坐标上方20个像素,若在此范围内则成功踩死雪人,调用雪人死亡方式函数并得分加2;若不在此范围内,则红小豆死亡,调用红小豆死亡方式函数。其后,对Redbean这一Runnable子类对象进行实例化。接着,通过thread = new Thread(this);对Thread对象进行实例化。最后,利用thread.start();启动多线程。
因此,实现Runnable接口创建和执行多线程需要以下五个步骤:①定义一个类实现Runnable接口;②覆写其中的run()方法;③创建Runnable接口实现类的对象;④以Runnable子类对象为构造方法参数,创建Thread类的对象;⑤用start()方法启动线程。
对于线程的控制,我直接使用Thread对象的sleep()方法,使线程在指定毫秒数内暂时进入睡眠状态,从而留出这部分时间给其他线程执行。如在实现红小豆移动时,我通过sleep()方法,每睡眠50ms后对红小豆位置进行更新,从而达到其移动效果。
总之,通过这次实验,我掌握了Java多线程结构和开发过程,了解了多线程的生命周期和运行机制,也成功实现了利用多线程进行简单的游戏开发。将自己喜欢的卡通人物做进小游戏中,让我在编程中不再感觉枯燥无聊,特别是最后可以成功运行游戏时,有一种神奇的感觉。