此项目是bilibili软帝学院的教学项目,由纯Java编写。本博客仅为记录自己遇到的一些错误,同时尽力帮助同时在做类似游戏的同学。
原项目链接:Java小游戏制作:三国战记\捕鱼达人\飞机大战\飞扬小鸟
创建并启动一个线程,控制游戏场景中活动的物体
固定格式
new Thread(){
public void run(){
线程需要做的事
}
}.start();
鼠标移动事件mouseMoved(MouseEvent e){
相对应函数
}
其他监听事件的格式类似
鼠标单击事件mouseClicked()
鼠标按下去的事件mousePressed()
鼠标移入游戏界面的事件mouseEntered()
鼠标移出游戏界面的事件mouseExited()
MouseAdapter adapter=new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
相应的方法
}
};
//将适配器加入到监听器中
addMouseListener(adapter);
addMouseMotionListener(adapter);
可用getKeyCode()函数获得按键的编号
KeyAdapter kd=new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
相应的方法
}
};
//同样,需要将适配器加入到监听器中
frame.addKeyListener(kd);
固定开头
@Override
public void paint(Graphics g) {
super.paint(g);
用画笔画图
g.drawImage(图片,位置(横坐标),位置(纵坐标),null)
设置字体颜色、粗体、大小等
g.setColor(Color.WHITE);
g.setFont(new Font(“楷体”,Font.BOLD,20));
用画笔写字
g.drawString(“字符串”,位置(横坐标),位置(纵坐标));
原因一:
1. 查看控制台错误提示信息
2. 检查App类中26行附近语句的路径是否正确
原因二:
1. 选中src文件夹后,点击顶部横栏中的build
2. 点击ReBuild ‘img’
3. 点击左上角File,点击Close Project。
4. 再次打开项目即可。
如果还不行,就重启电脑。
本人两种方法都试过,都可以。
package ui;
import javax.swing.*;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:23
**/
public class GameFrame extends JFrame{
public GameFrame(){
//设置窗体标题
setTitle("飞机大战");
//设置窗体大小
setSize(512,768);
//设置位置居中(null表示相对左上角居中)
setLocationRelativeTo(null);
//设置不允许玩家改变窗体大小
setResizable(false);
//设置窗口关闭时自动停止程序
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//主函数
public static void main(String[] args){
//创建窗体
GameFrame frame=new GameFrame();
//创建面板
GamePanel panel=new GamePanel(frame);
//将面板加入到窗体中
frame.add(panel);
panel.action();
//设置面板的可见性
frame.setVisible(true);
}
}
package ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:44
**/
//面板类
public class GamePanel extends JPanel {
BufferedImage bg; //背景
Hero hero=new Hero();
/**
* 使用集合代表 敌机大本营
* 不用数组,是因为不能提前知道大本营中敌机的数量
* 若用数组,还需要扩容
* 创建的敌机都会放到这个集合中
* 绘图时遍历此集合即可
*/
List eps=new ArrayList();
/**
* 英雄机的弹药库
* 与敌机大本营类似
*/
ArrayList fs=new ArrayList();
int score; //得分
int power=1; //火力
boolean gameover=false; //游戏开关
//创建面板
public GamePanel(GameFrame frame){
//设置背景颜色
setBackground(Color.black);
//App是工具类,专门用来获取图片的,参数是相对路径
bg=App.getImg("/img/bg1.jpg");
/**
* 鼠标监听事件
* 鼠标移动事件mouseMoved(MouseEvent e){
* 相对应函数
* }
* 其他监听事件的格式类似
* 鼠标单击事件mouseClicked()
* 鼠标按下去的事件mousePressed()
* 鼠标移入游戏界面的事件mouseEntered()
* 鼠标移出游戏界面的事件mouseExited()
*/
MouseAdapter adapter=new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
//获取鼠标的位置
int mx=e.getX();
int my=e.getY();
//游戏未结束时,调用函数,将英雄机移动到鼠标位置
if(!gameover)
hero.moveToMouse(mx,my);
//重画,刷新一下
repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
/**
* 游戏结束时,鼠标点击画面,重新开始新游戏
* 恢复hp
* 游戏结束标志置假
* 得分清零
* 敌机大本营清零
* 弹药库清零
* 刷新,重画
*/
if(gameover){
hero = new Hero();
gameover=false;
score=0;
eps.clear();
fs.clear();
power=1;
repaint();
}
}
};
//将适配器加入到监听器中
addMouseListener(adapter);
addMouseMotionListener(adapter);
//键盘监听事件
KeyAdapter kd=new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
/**
* 游戏未结束时,才可以调用相应函数移动
* getKeyCode()函数获得按键的编号
* KeyEvent.VK_UP、KeyEvent.VK_DOWN、KeyEvent.VK_LEFT、KeyEvent.VK_RIGHT
* 分别表示上下左右键的编号
* 移动之后需要重画,刷新页面
*/
if(!gameover){
if(e.getKeyCode()==KeyEvent.VK_UP){
hero.moveUp();
}else if(e.getKeyCode()==KeyEvent.VK_DOWN){
hero.moveDown();
}else if(e.getKeyCode()==KeyEvent.VK_LEFT){
hero.moveLeft();
}else if(e.getKeyCode()==KeyEvent.VK_RIGHT){
hero.moveRight();
}
}
repaint();
}
};
//同样,需要将适配器加入到监听器中
frame.addKeyListener(kd);
}
public void action(){
/**
* 创建并启动一个线程,控制游戏场景中活动的物体
* 固定格式
* new Thread(){public void run(){..线程需要做的事..}}.start();
*/
new Thread(){
public void run(){
while(true){
if(!gameover){
epEnter(); //敌机入场
epMove(); //敌机向下移动
shoot(); //英雄机发射子弹
firemove(); //子弹移动
shootEp(); //
hit();
repaint(); //刷新
}
try {
Thread.sleep(10); //暂停10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
//英雄机被敌机击中
private void hit() {
/**
* 遍历敌机大本营
* 若敌机与英雄机相撞
* 则将相撞的英雄机删除,
* 英雄机hp-1
* 得分-10(最低为0)
* 火力回归最初水平
* 若血量为0,则游戏结束
*/
for (int i = 0; i < eps.size(); i++) {
Ep ep = eps.get(i);
if(ep.hitBy(hero)){
eps.remove(ep);
hero.hp--;
score-=10;
if(score<0)
score=0;
power=1;
if(hero.hp<=0)
gameover=true;
}
}
}
//遍历敌机大本营
private void shootEp() {
for (int i = 0; i < fs.size(); i++) {
Fire fire=fs.get(i);
bang(fire);
}
}
//敌机被击中后
private void bang(Fire fire){
for (int i = 0; i < eps.size(); i++) {
Ep ep=eps.get(i);
//如果击中目标
if(ep.shootBy(fire)){
//敌机血量下降
ep.hp--;
//如果敌机的血量为零,则将此敌机从大本营删除,并加分
if(ep.hp==0){
//如果被击毁的是14号敌机,那么英雄机可以加血或者升级武器
if(ep.type==14){
if(hero.hp<=5)
hero.hp++;
else if(power<=3)
power++;
}
eps.remove(ep);
score+=10;
}
//将发生碰撞的子弹从子弹库中删去
fs.remove(fire);
}
}
}
//用于控制子弹发射频率
int fireindex;
//英雄机发射子弹
private void shoot() {
//GamePanel类action方法中的while循环10次,发射一次子弹
if(fireindex%5==0){
//火力为1,英雄机正中间发射子弹
if(power==1) {
Fire fire2 = new Fire(hero.x + 33, hero.y - 20); //中间弹道子弹的起始位置应在靠前方一点
fs.add(fire2);
}else if(power==2){
//火力为2,英雄机两侧发射子弹
Fire fire1=new Fire(hero.x+11,hero.y);
fs.add(fire1);
Fire fire3=new Fire(hero.x+58,hero.y);
fs.add(fire3);
}else{
//火力为3,三弹道一起发射子弹
Fire fire1=new Fire(hero.x+11,hero.y);
fs.add(fire1);
Fire fire2=new Fire(hero.x+33,hero.y-20);
fs.add(fire2);
Fire fire3=new Fire(hero.x+58,hero.y);
fs.add(fire3);
}
}
fireindex++;
}
//子弹移动
private void firemove(){
//获取子弹库中每发子弹,并调用move函数,进行向下移动
for (int i = 0; i < fs.size(); i++) {
Fire fire=fs.get(i);
fire.move();
}
}
//飞机移动(与子弹移动类似)
private void epMove() {
for (int i = 0; i < eps.size(); i++) {
Ep ep=eps.get(i);
ep.move();
}
}
int index; //用于控制敌机进场频率(与子弹发射频率类似)
//飞机进场
private void epEnter(){
if(index%20==0){
Ep ep=new Ep();
eps.add(ep);
}
index++;
}
/**
* 绘图
* 画笔Graphics
* super.paint(g);
*/
@Override
public void paint(Graphics g) {
//Graphics g 画笔
super.paint(g);
//用画笔画图g.drawImage(图片,位置(横坐标),位置(纵坐标),null)
//画背景及英雄机
g.drawImage(bg,0,0,null);
g.drawImage(hero.img,hero.x,hero.y,hero.h,hero.w,null);
//设置字体颜色、大小等
g.setColor(Color.WHITE);
g.setFont(new Font("楷体",Font.BOLD,20));
//用画笔写字
g.drawString("分数:"+score,10,30);
g.drawString("血量:",240,30);
//绘画英雄机
for(int i = 0; i < hero.hp; i++) {
g.drawImage(hero.img,300+35*i,5,30,30,null);
}
//绘画敌机
for(int i=0;i
package ui;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:56
**/
public class App {
/**
* static 方法 特点
* 所有对象都共用
* 并且,不需要创建对象就可以直接调用
*/
public static BufferedImage getImg(String path){
try{
/**
* Java的IO流(输入输出流)
* App.class获取App类的路径
* getResource()方法获取资源
*/
BufferedImage img = ImageIO.read(App.class.getResource(path));
return img;
} catch (IOException e){
//找不到图片则输出将异常情况输出
e.printStackTrace();
}
return null;
}
}
package ui;
import java.awt.image.BufferedImage;
import java.io.File;
import java.security.PublicKey;
import java.util.ArrayList;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 20:23
**/
public class Hero extends FlyObject{
int hp; //英雄机的血量
public Hero(){
img=App.getImg("/img/hero.png");
//英雄机的坐标(x,y),此处是英雄机图片的左上角的坐标
x=200;
y=500;
hp=5;
//确定所绘制的英雄机的宽、高(此处分别等于图片的宽和高)
w=img.getWidth();
h=img.getHeight();
}
// 让英雄机移动到“鼠标位置“
public void moveToMouse(int mx,int my){
x=mx-w/2; //让英雄机的中心点(而不是左上角)位于鼠标位置
y=my-w/2;
}
/**
* 键盘控制时所调用的函数
* 其中的if条件判断是为了确保英雄机始终处于屏幕内
*/
public void moveUp(){
y-=20;
if(y<0)
y=0;
}
public void moveDown(){
y+=20;
if(y>768)
y=768;
}
public void moveLeft(){
x-=20;
if(x<0)
x=0;
}
public void moveRight(){
x+=20;
if(x>512)
x=512;
}
}
package ui;
import java.util.Random;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 21:54
**/
public class Ep extends FlyObject{
int speed; //敌机的移动速度
int hp=6; //敌机的血量
int type; //敌机的类型(用于确定Boss机,奖品等)
public Ep(){
//获取随机数
Random rd=new Random();
//将随机数取整(rd.nextInt()范围是[0,15)),所以index范围是[1,16)即[1,15]
int index=rd.nextInt(15)+1;
//确定路径,如果index在1~9,则在index前加0,否则不加
String path="/img/ep"+(index<10?"0":"")+index+".png";
//通过工具类App 获得图片
img=App.getImg(path);
//绘制大小和图片的大小一致
w=img.getWidth();
h=img.getHeight();
//位置横坐标范围为[0,512-w)
//纵坐标为-h ,让其开始位置在屏幕外
x=rd.nextInt(512-w);
y=-h;
/**
* 图片的编号与大小正相关
* 此处想让大飞机速度慢
* 小飞机速度块
* 因此,让敌机速度与编号成反相关
*/
speed=17-index;
//记录飞机型号
type=index;
}
//敌飞向下移动
public void move() {
y+=speed;
}
//判断敌机是否被击中
public boolean shootBy(Fire fire) {
//画图即可判断清楚条件
boolean shoot = x<=fire.x+fire.w &&
x>=fire.x-w &&
y<=fire.y+fire.h &&
y>=fire.y-h;
return shoot;
}
public boolean hitBy(Hero hero) {
//与ShootBy(Fire fire)类似
boolean hit = x<=hero.x+hero.w &&
x>=hero.x-w &&
y<=hero.y+hero.h &&
y>=hero.y-h;
return hit;
}
}
package ui;
import java.awt.image.BufferedImage;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 21:59
**/
public class FlyObject {
BufferedImage img;
//定义飞行物的横坐标
int x;
//定义飞行物的纵坐标
int y;
//定义飞行物的宽
int w;
//定义飞行物的高
int h;
}
package ui;
/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/13
* Time: 11:25
**/
public class Fire extends FlyObject{
//英雄机的横纵坐标(hx,hy)
public Fire(int hx,int hy){
//通过工具类App 获得子弹的图片
img=App.getImg("/img/fire.png");
//绘制的大小为图片大小的1/4
w=img.getWidth()/4;
h=img.getHeight()/4;
//子弹的起始位置在英雄机的位置
x=hx;
y=hy;
}
public void move() {
y-=10;
}
}