• JAVA 实现《泡泡堂对战版》游戏


    前言

    《泡泡堂对战版》是一个基于java的自制游戏,使用了MVC模式,分离了模型、视图和控制器,使得项目结构清晰易于扩展,使用配置文件来设置游戏基本配置,扩展地图人物道具等。同时,该程序编写期间用了单例模式、工厂模式、模板模式等设计模式。为了游戏的可玩性,特意设计了平滑碰撞以及机器人。

    主要设计

    1. 设计游戏界面,用swing实现

    2. 绘制游戏启动界面、结束界面、地图、主角、道具

    3. 实现泡泡爆炸

    4. 为了尽量复原泡泡堂游戏,初步实现了机器人功能。该机器人可以判断障碍物释放炸弹、规避炸弹、攻击玩家。

    5. 实现道具掉落和相应属性加成

    6. 实现游戏音效和背景音乐

    7. 平滑碰撞:人物在拐角处移动的时候经常不是刚好对齐的状态,程序会判定玩家碰撞了障碍物所以导致玩家无法拐弯。所以我们在处理这种情况的时候,会让玩家进行平滑的移动使得玩家看上去是滑进去的,增强玩家游戏体验

    8. 设计单/双人模式

    功能截图

    游戏启动界面:

    如图

    道具说明:

    如图

    游戏开始:

    如图

    释放炸弹·:

    如图

    炸弹爆炸效果:

    如图

    代码实现

    游戏启动类

    
    public class GameStart {
    	private static GameFrame gameFrame;
    
    	//游戏启动入口
    	public static void main(String[] args) {
    		// 资源加载
    		try {
    			ElementLoader.getElementLoader().readGamePro();
    			ElementLoader.getElementLoader().readImagePro();
    			ElementLoader.getElementLoader().readCharactorsPro();
    			ElementLoader.getElementLoader().readBubblePro();
    			ElementLoader.getElementLoader().readSquarePro();
    		} catch (IOException e) {
    			System.out.println("资源加载失败");
    			e.printStackTrace();
    		}
    		//初始化
    		gameFrame = new GameFrame();
    		//界面显示
    		gameFrame.setVisible(true);
    		//音乐播放
    		GameMusicPlayer musicPlayer = new GameMusicPlayer();
    		musicPlayer.start();
    	}
    	
    	/**
    	 * 界面切换
    	 * @param panelName 界面名称
    	 */
    	public static void changeJPanel(String panelName){
    		if(panelName == "game") {
    			GameController.setGameRunning(true);
    			gameFrame.addListener();
    		} else {
    			GameController.setGameRunning(false);
    			gameFrame.removeListener();
    		}
    		gameFrame.changePanel(panelName);
    	
    		//强制刷新,否则监听无效
    		gameFrame.setVisible(false);
    		gameFrame.setVisible(true);
    	}
    	
    	public static void startNewGame() {
    		GameController.setGameRunning(true);
    		gameFrame.startGame();
    		changeJPanel("game");
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    核心监听类

    
    public class GameThread extends Thread{
    	private boolean running; //表示当前关卡是否在进行
    	private boolean over = false; //表示游戏是否结束,结束返回开始菜单
    	private static int sleepTime = 20; //runGame刷新时间
    	//倒计时变量
    	private static int allTime = 600*1000; //10分钟
    
    	
    	@Override
    	public void run() {
    		while(!over) {
    			running = true;//当前关卡正在进行
    			//加载元素
    			loadElement();
    			//显示人物,流程,自动化
    			runGame();
    			//结束当前关
    			overGame(over);
    		}
    		GameStart.changeJPanel("over");
    	}
    	
    	//加载元素
    	private void loadElement() {
    		ElementManager.getManager().loadMap();//加载地图及其元素
    	}
    	
    	/**
    	 * 关卡结束
    	 * 如果over为真则游戏失败返回界面,否则进入下一关
    	 * @param over
    	 */
    	private void overGame(Boolean over) {
    		ElementManager.getManager().overGame(over);
    	}
    	
    	//显示人物,游戏流程,自动化
    	private void runGame() {
    		allTime = 600*1000;
    		while(running) {
    			Map<String, List<SuperElement>> map = ElementManager.getManager().getMap();
    			Set<String> set = map.keySet();
    			for(String key:set) {
    				List<SuperElement> list = map.get(key);
    				for(int i=list.size()-1; i>=0; i--) {
    					list.get(i).update();
    					if(!list.get(i).isAlive())
    						list.remove(i);
    				}
    			}
    			
    			//添加游戏的流程控制linkGame()?
    			
    			//玩家与炸弹碰撞死亡
    			playerBoom();
    			//可破坏物与炸弹碰撞
    			fragilityBoom();
    			//电脑与炸弹碰撞死亡
    			npcBoom();
    			//电脑与道具碰撞效果,暂时不开启
    			//npcMagicBox();
    			//玩家与道具碰撞效果
    			playerMagicBox();
    			//检测是否玩家全部死亡
    			defeat();
    			
    			//控制runGame进程
    			allTime = allTime - sleepTime;
    			try {	
    				sleep(20);
    			} catch (InterruptedException e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	private void defeat() {
    		boolean allDead = true;
    		int surviveP = 0;
    		int winner = 2;//0为玩家1,1为玩家2,2为电脑获胜
    		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
    		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
    		for(SuperElement se:playerList) {
    			if(!((Player)se).isDead()) {
    				surviveP++;
    			}
    		}
    		for(SuperElement npc:npcList) {
    			if(!((Npc)npc).isDead()) {
    				allDead = false;
    			}
    		}
    		
    		//玩家失败
    		if(surviveP==0||(allTime<=0 && !allDead)) {
    			running = false;
    			over = true;
    			OverJPanel.getResult().setText("defeated");
    		}
    		//玩家胜利
    		if(allDead&&surviveP==1) {
    			running = false;
    			over = true;
    			for(SuperElement se:playerList) {
    				if(!((Player)se).isDead()) {
    					surviveP++;
    					winner = ((Player)se).getPlayerNum();
    				}
    			}
    			OverJPanel.getResult().setText("player "+(winner+1)+" win");
    		}
    		
    		//时间到,两个玩家都活着
    		if(allTime<=0&&surviveP==2&&allDead) {
    			running = false;
    			over = true;
    			int score1 = ((Player)playerList.get(0)).score;
    			int score2 = ((Player)playerList.get(0)).score;
    			if(score1==score2) {
    				OverJPanel.getResult().setText("defeated");
    			}
    			else if(score1>score2)
    			{
    				OverJPanel.getResult().setText("player 1 win");
    			}
    			else {
    				OverJPanel.getResult().setText("player 2 win");
    			}
    		}
    	}
    	
    	//玩家与炸弹碰撞判断
    	private void playerBoom() {
    		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
    		List<SuperElement> explodeList = ElementManager.getManager().getElementList("explode");
    		for(int i=0; i<playerList.size(); i++) {
    			for(int j=0; j<explodeList.size(); j++) {
    				if(explodeList.get(j).crash(playerList.get(i))){
    					Player player = (Player) playerList.get(i);
    					player.setHealthPoint(-1);//生命值-1
    				}
    			}
    		}
    		
    	}
    	//npc与炸弹碰撞判断
    	private void npcBoom() {
    		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
    		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
    		List<SuperElement> explodeList = ElementManager.getManager().getElementList("explode");
    		for(int i=0; i<npcList.size(); i++) {
    			for(int j=0; j<explodeList.size(); j++) {
    				if(explodeList.get(j).crash(npcList.get(i))){
    					Npc npc = (Npc) npcList.get(i);
    					npc.setDead(true);
    					npc.setX(-100);
    					npc.setY(-100);
    					BubbleExplode e = (BubbleExplode)explodeList.get(j);
    					if(e.getPlayerNum()<2)//目前只有玩家计分
    						((Player)playerList.get(e.getPlayerNum())).setScore(((Player)playerList.get(e.getPlayerNum())).getScore()+50);
    				}
    			}
    		}
    	}
    	
    	//障碍物与炸弹碰撞判断
    	private void fragilityBoom() {
    		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
    		List<SuperElement> explodes = ElementManager.getManager().getElementList("explode");
    		List<SuperElement> fragility = ElementManager.getManager().getElementList("fragility");
    		for(int i=0; i<fragility.size(); i++) {
    			for(int j=0; j<explodes.size(); j++) {
    				if(explodes.get(j).crash(fragility.get(i))) {
    					MapFragility mapFragility = (MapFragility)fragility.get(i);
    					mapFragility.setDestoried(true);
    					BubbleExplode e = (BubbleExplode)explodes.get(j);
    					if(e.getPlayerNum()<2)//目前只有玩家计分
    						((Player)playerList.get(e.getPlayerNum())).setScore(((Player)playerList.get(e.getPlayerNum())).getScore()+10);
    				}
    			}
    		}
    	}
    	
    	//玩家与道具碰撞判断
    	private void playerMagicBox() {
    		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
    		List<SuperElement> magicBoxList = ElementManager.getManager().getElementList("magicBox");
    		for(int i=0; i<playerList.size(); i++) {
    			for(int j=magicBoxList.size()-1; j>=0; j--) {
    				if(magicBoxList.get(j).crash(playerList.get(i))){
    					MagicBox magicBox = (MagicBox) magicBoxList.get(j);
    					magicBox.setCharacterIndex(i);//谁吃方块
    					magicBox.setEaten(true);//方块被吃
    					((Player)playerList.get(i)).setScore(((Player)playerList.get(i)).getScore()+30);
    				}
    				
    			}
    		}
    	}
    	
    	//玩家与道具碰撞判断
    	private void npcMagicBox() {
    		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
    		List<SuperElement> magicBoxList = ElementManager.getManager().getElementList("magicBox");
    		for(int i=0; i<npcList.size(); i++) {
    			for(int j=magicBoxList.size()-1; j>=0; j--) {
    				if(magicBoxList.get(j).crash(npcList.get(i))){
    					MagicBox magicBox = (MagicBox) magicBoxList.get(j);
    					magicBox.setCharacterIndex(i+2);//谁吃方块
    					magicBox.setEaten(true);//方块被吃
    				}
    			}
    		}
    	}
    		
    	//runGame调用,加入拓展
    	public void linkGame() {}
    	
    
    	public static int getAllTime() {
    		return allTime;
    	}
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228

    核心线程类

    
    
    public class GameKeyListener implements KeyListener{
    	/**
    	 * 用栈来解决按键冲突
    	 * 每个栈用来存放不同用户的按键,通过判断按键的code来设置移动方向或者攻击
    	 * 
    	 */
    
    	private List<?> list;
    	private Stack<Integer> p1PressStack = new Stack<>();
    	private Stack<Integer> p2PressStack = new Stack<>();
    	
    	@Override
    	public void keyPressed(KeyEvent e) {
    		list = ElementManager.getManager().getElementList("player");
    		Player player1 = (Player) list.get(0);
    		
    		int code = e.getKeyCode();
    		switch (code) {
    		case 10://炸弹键
    			if(player1.isKeepAttack())//不允许一直按着炸弹键,每次只能放一个炸弹
    				player1.setAttack(false);
    			else {
    				player1.setKeepAttack(true);
    				player1.setAttack(true);
    			}
    			break;
    		case 37://左右上下
    		case 38:
    		case 39:
    		case 40:
    			if(!p1PressStack.contains(code)) {
    				p1PressStack.push(code);
    			}
    			player1.setMoveType(MoveTypeEnum.codeToMoveType(code));
    			break;
    		default://其它按键无视
    			break;
    		}
    		if(GameController.isTwoPlayer()) {
    			Player player2 = (Player) list.get(1);
    			switch (code) {
    			case 32:
    				if(player2.isKeepAttack())
    					player2.setAttack(false);
    				else {
    					player2.setKeepAttack(true);
    					player2.setAttack(true);
    				}
    				break;
    			case 65:
    			case 87:
    			case 68:
    			case 83:
    				if(!p2PressStack.contains(code)) {
    					p2PressStack.push(code);
    				}
    				player2.setMoveType(MoveTypeEnum.codeToMoveType(code));
    				break;
    			
    			default:
    				break;
    			}
    		}
    	}
    
    	
    	@Override
    	public void keyReleased(KeyEvent e) {
    		List<?> list = ElementManager.getManager().getElementList("player");
    		int code = e.getKeyCode();
    		Player player1 = (Player) list.get(0);
    		if(!player1.isDead()) {
    			switch (code) {
    			case 10:
    				player1.setAttack(false);
    				player1.setKeepAttack(false);
    				break;
    			case 37:
    			case 38:
    			case 39:
    			case 40:
    				if(p1PressStack.peek()!=code) {
    					p1PressStack.remove(new Integer(code));
    				} else {
    					p1PressStack.pop();
    					if(p1PressStack.size()==0) {
    						player1.setMoveType(MoveTypeEnum.STOP);
    					} else {
    						player1.setMoveType(MoveTypeEnum.codeToMoveType(p1PressStack.peek()));
    					}
    				}
    				break;
    			default:
    				break;
    			}
    		}
    		if(GameController.isTwoPlayer()) {
    			Player player2 = (Player) list.get(1);
    			if(!player2.isDead()) {
    				switch (code) {
    				case 32:
    					player2.setAttack(false);
    					player2.setKeepAttack(false);
    					break;
    				case 65:
    				case 87:
    				case 68:
    				case 83:
    					if(p2PressStack.peek()!=code) {
    						p2PressStack.remove(new Integer(code));
    					} else {
    						p2PressStack.pop();
    						if(p2PressStack.size()==0) {
    							player2.setMoveType(MoveTypeEnum.STOP);
    						} else {
    							player2.setMoveType(MoveTypeEnum.codeToMoveType(p2PressStack.peek()));
    						}
    					}
    					break;
    				default:
    					break;
    				}
    			}
    			
    		}
    	}
    
    	@Override
    	public void keyTyped(KeyEvent arg0) {
    		// TODO 自动生成的方法存根
    		
    	}
    	
    	public void clearKeyStatcks() {
    		p1PressStack.clear();
    		p2PressStack.clear();
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142

    总结

    通过此次的《泡泡堂对战版》实现,让我对JAVA的相关知识有了进一步的了解,对java这门语言也有了比以前更深刻的认识。

    java的一些基本语法,比如数据类型、运算符、程序流程控制和数组等,理解更加透彻。java最核心的核心就是面向对象思想,对于这一个概念,终于悟到了一些。

    源码获取

    涉及《泡泡堂》版权,无法托管!

    点赞,关注博主后,私聊博主免费获取

    今天是持续写作的第 27 / 100 天。
    可以关注我,点赞我、评论我、收藏我啦。

  • 相关阅读:
    【LeetCode: 2578. 最小和分割 | 贪心】
    SpringCloud微服务技术栈(黑马)学习笔记DAY1
    Java--MyBatis传入参数parameterType
    C语言:用递归函数求n的阶乘
    python程序,放入docker容器中, 生成镜像
    快速安装JDK以及配置环境变量
    周少剑,很少见
    SCI一区 | Matlab实现POA-TCN-BiGRU-Attention鹈鹕算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测
    CI/CD docker compose 部署 humpback - single mode
    JAVA面试题整理《微服务篇》一
  • 原文地址:https://blog.csdn.net/qq_40869977/article/details/125247956