潜艇游戏需求:
- 所参与的角色:
- 战舰、深水炸弹、侦察潜艇、鱼雷潜艇、水雷潜艇、水雷
- 角色间的关系:
- 战舰发射深水炸弹
- 深水炸弹可以打潜艇(侦察潜艇、鱼雷潜艇、水雷潜艇),若打中:
- 潜艇消失、深水炸弹消失
- 得东西:
- 打掉侦察潜艇,玩家得10分
- 打掉鱼雷潜艇,玩家得40分
- 打掉水雷潜艇,战舰得1条命
- 水雷潜艇可以发射水雷
- 水雷可以击打战舰,若击中:
- 水雷消失
- 战舰减1条命(命数为0时游戏结束)
一、day01
- 创建6个类,创建World类并测试
二、day02
- 给6个类设计构造方法,并测试
三、day03
- 设计侦察潜艇数组、鱼雷潜艇数组、水雷潜艇数组、水雷数组、炸弹数组,并测试
- 设计SeaObject超类,设计6个类继承SeaObject
- 给SeaObject设计两个构造方法,6个类中分别调用
- 将侦察潜艇数组、鱼雷潜艇数组、水雷潜艇数组统一组合为SeaObject数组,并测试
四、day04
-
给6个类重写 move() 使其坐标点移动,并测试
- 侦察潜艇,鱼雷潜艇,水雷潜艇
- 由于潜艇是从左往右移动,速度为speed,y 坐标不改变,改变的为 x 坐标
- 方法实现为
x+=speed
- 水雷
- 由于水雷是从下往上移动,速度为speed,x 坐标不改变,改变的为 y 坐标
- 方法实现为
y-=speed
- 炸弹
- 由于炸弹是从上往下移动,速度为speed,x 坐标不改变,改变的为 y 坐标
- 方法实现为
y+=speed
- 战舰
- 由于它的移动方式和其他的不一致,方法只需要重写,具体实现先搁置不写
- 测试
- World
- main方法
-
分别创建 5 个对象
-
分别输出每个对象的 x,y,speed 数值
-
分别调用每个对象的 move() 方法
-
在动用每个对象的 move() 方法后,再次对对象的 x,y,speed 值进行输出
-
测试可参考以下示例:
ObserveSubmarine o1 = new ObserveSubmarine(); System.out.println("侦察潜艇初始数据-----x:"+o1.x+",y:"+o1.y+",speed:"+o1.speed); o1.move(); System.out.println("侦察潜艇移动后数据---x:"+o1.x+",y:"+o1.y+",speed:"+o1.speed);在这里插入代码片
-
- main方法
- World
- 侦察潜艇,鱼雷潜艇,水雷潜艇
-
给类中成员添加访问控制修饰符
- 访问控制修饰符是为了控制访问权限的,适当的减小访问权限可以使得代码更加安全
- 通常我们会将属性(成员变量)私有化
private
,行为(方法)公开化public
- 但是由于私有的属性不能被继承,这里我们还需要将 SeaObject 中的属性更改为
protected
-
将图片拷贝到项目中
- 在项目下创建
img
文件夹/目录 - 将8张图片拷贝到
img
文件夹中 - 最好将图片拷贝之后,进行一个 Build -> Rebuild Project 操作
- 在项目下创建
-
设计Images图片类
- 在项目的包下创建类
Images
- 目的:
- 这个类是为了封装我们的 图片对象而设计的
- 思路:
- 由于我们为了减少内存消耗,想让图片只有一份,这里使用
static
关键字对图片对象进行修饰 - 由于我们想项目一起动就对图片进行加载,使用
static
关键字修饰静态代码块,为图片对象进行赋值
- 由于我们为了减少内存消耗,想让图片只有一份,这里使用
- 创建8个图片对象,
public static ImageIcon xxx;
- 静态代码块对图片对象进行赋值操作
static{xxx = new ImageIcon("img/xxx.png");}
- 测试
- Images 类中创建main方法
- 在main方法中分别调用
System.out.println(xxx.getImageLoadStatus());
进行测试 - 测试结果为 8 ,表示正确
- 在main方法中分别调用
- Images 类中创建main方法
- 在项目的包下创建类
五、day05
今日目标:
-
设计窗口的宽和高为常量,在适当的地方做修改
- World 类中设计宽WIDTH = 641,高HEIGHT = 479
- main方法里内容删除
-
画窗口
- World类 继承 JPanel
- main方法里内容复制
public static void main(String[] args) { JFrame frame = new JFrame(); //3. World world = new World(); //会创建窗口中的那一堆对象 world.setFocusable(true); frame.add(world); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(WIDTH+16, HEIGHT+40); frame.setLocationRelativeTo(null); frame.setResizable(false); frame.setVisible(true); //自动调用paint()方法 }
-
画海洋图、至少准备6个对象并画出来
-
SeaObject
- 修改 SeaObject 为抽象类
- 修改 move 为抽象方法
- 创建 getImage 抽象方法
- 返回值类型为 ImageIcon 类型
- 定义两个状态常量(LIVE,DEAD)和一个变量 表示当前状态(state)
- LIVE = 0 表示活着
- DEAD = 1 表示死了
- state = LIVE 表示默认为活着
- 创建 isLive 方法
- 返回值类型:boolean 类型
- 业务:判断当前状态是否为活着
- 创建 isDead 方法
- 返回值类型:boolean 类型
- 业务:判断当前状态是否为死了
- 创建 paintImage 方法
-
返回值类型 void
-
参数 Graphics g
-
业务:如果当前状态为活着,画图片
/** 画对象 g:画笔 */ public void paintImage(Graphics g){ if(this.isLive()){ //若活着的 this.getImage().paintIcon(null,g,this.x,this.y); //----不要求掌握 } }
-
-
Battleship
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
ObserveSubmarine
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
TorpedoSubmarine
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
MineSubmarine
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
Bomb
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
Mine
- 重写 getImage 方法,返回对应的 ImageIcon 对象
-
World
-
创建战舰(Battleship对象,SeaObject数组对象,Mine数组对象,Bomb数组对象),并赋值
-
重画
/** 重写paint()画 g:系统自带的画笔 */ public void paint(Graphics g){ Images.sea.paintIcon(null,g,0,0); //画海洋图 ship.paintImage(g); //画战舰 for(int i=0;i<submarines.length;i++){ //遍历所有潜艇 submarines[i].paintImage(g); //画潜艇 } for(int i=0;i<mines.length;i++){ //遍历所有水雷 mines[i].paintImage(g); //画水雷 } for(int i=0;i<bombs.length;i++){ //遍历所有炸弹 bombs[i].paintImage(g); //画炸弹 } }
-
-
-
测试
-
修改 SeaObject 类,2个参数的构造方法,将负号去掉,为了能在屏幕上显示已经画好的对象
-
运行测试(海洋,战舰,潜艇[侦查潜艇,鱼雷潜艇,水雷潜艇],水雷,炸弹等对象是否显示在窗口里)
-
六、day06
今日目标:
深海杀手_day06效果视频
- 清空成员变量数组中的元素
- 潜艇数组 submarines
- 水雷数组 mines
- 炸弹数组 bombs
- 定时做某件事:
- 定义 action 方法,作为游戏的启动执行方法
- 由于游戏是要求定时做某件事,所以创建定时器对象 Timer,util 包下的 Timer
- 调用 Timer对象中 schedule 方法 – 3个参数的方法
- 在方法中将定时器中调用需要定时执行的方法,定时间隔,每10毫秒走一次
- 潜艇入场
- 水雷入场
- 海洋对象移动
- 重画
- 在 main 方法中调用 action 方法
- 定义 action 方法,作为游戏的启动执行方法
- 潜艇入场:
- 生成潜艇对象方法(侦察潜艇、鱼雷潜艇、水雷潜艇)
- 方法名 nextSubmarine
- 返回值类型 SeaObject
- 要求随机返回某一潜艇对象,为了统一返回的类型,所以使用父类 SeaObject 作为返回值类型
- 方法具体实现
- 业务:按照一定比例随机生成潜艇对象
- 侦查潜艇:50%
- 鱼雷潜艇:30%
- 水雷潜艇:20%
- 思路:
- 通过随机数的形式,加上if…else,返回对应的潜艇对象
- 业务:按照一定比例随机生成潜艇对象
- 潜艇入场方法
- 方法名 submarineEnterAction
- 返回值类型 void
- 方法具体实现
- 业务:每400毫秒调用nextSubmarine方法生成一个潜艇对象
- 每10豪秒走一次方法submarineEnterAction,现在想每400毫秒走一次/调用一个 nextSubmarine 生成潜艇对象
- 之前我们已经清空了潜艇数组(为的就是想让潜艇对象从无到有)
- 将生成的潜艇对象添加到World类中,成员变量(submarines)的潜艇数组中
- 思路:添加一个计数的变量subEnterIndex,通过整除的方式(计数变量自增,取余),if判断等方式达到目的
- 业务:每400毫秒调用nextSubmarine方法生成一个潜艇对象
- 生成潜艇对象方法(侦察潜艇、鱼雷潜艇、水雷潜艇)
- 水雷入场(上):
- 水雷入场方法
- 方法名:mineEnterAction
- 返回值类型:void
- 方法具体实现
- 业务:
- 每1000毫秒方法生成一个水雷对象
- 每10豪秒走一次方法mineEnterAction,现在想每1000毫秒走一次/调用一个 xxx方法(当日未实现) 生成潜艇对象
- 思路:添加一个计数的变量mineEnterIndex,通过整除的方式(计数变量自增,取余),if判断等方式达到目的
- 业务:
- 水雷入场方法
- 海洋对象移动:
- 方法名:moveAction
- 返回值类型:void
- 方法具体实现:
- 业务:
- 每10豪秒走一次方法moveAction
- 分别循环遍历海洋对象(潜艇对象,水雷对象,炸弹)
- 循环中调用每个子类中的重写的move方法
- 业务:
七、day07
-
深水炸弹入场:
- 业务:
- 深水炸弹是由战舰发出的
- 我们深水炸弹是通过键盘进行触发的
- 将深水炸弹存放进炸弹数组
- 思路:
- 通过监听键盘,去调用创建深水炸弹的方法
- 将深水炸弹存放进炸弹数组
- 由于深水炸弹是由战舰发出的,坐标点为战舰的坐标点,所以这个生成深水炸弹对象的方法需要写在战舰类上(Battleship)
// 可以参考,不需要必须会写 KeyAdapter k = new KeyAdapter(){ public void keyPressed(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_SPACE){ // 深水炸弹入场 } } }; this.addKeyListener(k);
- 业务:
-
战舰移动:
- 业务:
- 通过键盘进行触发战舰的左右移动
- 思路
- 和上述空格键触发深水炸弹一样,写在键盘出发的方法里
- 通过不同的按键触发,调用不同的方法
-
通过按 ← 箭头,触发 Battleship 中的 moveLeft 战舰左移方法
-
通过按 → 箭头,触发 Battleship 中的 moveRight 战舰右移方法
if(e.getKeyCode()==KeyEvent.VK_LEFT){ //不要求掌握--若抬起的是左箭头 // 战舰左移 } if(e.getKeyCode()==KeyEvent.VK_RIGHT){ //不要求掌握--若抬起的是右箭头 //战舰右移 }
-
- moveLeft 和 moveRight 实现,移动 Battleship 的 x 坐标
- 业务:
-
删除越界的海洋对象:
- 方法名:outOfBoundsAction
- 返回值类型:void
- 业务:
- 由于超越边界的潜艇,深水炸弹,鱼雷就没有作用了,并且占用内存资源,所以我们需要删除它们
- 潜艇是超出右边的边界删除
- 深水炸弹是超出底下的边界删除
- 鱼雷是超过海平面伤处
- 由于超越边界的潜艇,深水炸弹,鱼雷就没有作用了,并且占用内存资源,所以我们需要删除它们
- 思路
- 分别循环所有的海洋对象,判断是否越界,如果越界,从数组中删除
- 判断是否越界
- SeaObject(潜艇越界)
- 定义 isOutOfBounds 方法,返回值类型为boolean类型
- 潜艇的x坐标 >= 世界的WIDTH
- 定义 isOutOfBounds 方法,返回值类型为boolean类型
- Bomb(深水炸弹越界)
- 重写 isOutOfBounds 方法
- 深水炸弹的y坐标 >= 世界的WEIGHT
- Mine(鱼雷越界)
- 重写 isOutOfBounds 方法
- 鱼雷的y坐标 <= 150(海平面的y坐标)- 鱼雷的高height
- SeaObject(潜艇越界)
- 数组删除
- 将数组的最后一个元素赋值给想要删除的元素
- 数组缩容
- 判断是否越界
- 分别循环所有的海洋对象,判断是否越界,如果越界,从数组中删除
-
设计EnemyScore得分接口、EnemyLife得命接口,侦察潜艇与鱼雷潜艇实现EnemyScore接口,水雷潜艇实现EnemyLife接口
- EnemyScore 得分接口
- 声明得分的抽象方法
- 方法名:getScore
- 返回值类型:int
- 侦察潜艇和鱼类潜艇实现得分接口,重写 getScore 方法
- 侦察潜艇得10分
- 鱼类潜艇得40分
- 声明得分的抽象方法
- EnemyLife 得命接口
- 声明得名的抽象方法
- 方法名:getLife
- 返回值类型:int
- 水雷潜艇实现得命接口,重写 getLife 方法
- 水雷潜艇得1条命
- 声明得名的抽象方法
- EnemyScore 得分接口
八、day08
-
水雷入场(下):
- 水雷入场之前我们已经将方法创建完成,mineEnterAction,但是具体的生成水雷对象及将水雷对象添加到水雷数组中未做,我们需要的就是根据之前的水雷入场继续做即可
- 业务:
- 生成水雷对象,并将水雷对象添加到水雷数组中
- 由于是定时出现水雷,之后我们需要将 mineEnterAction 方法在定时器中调用
- 思路:
- 由于水雷对象是由水雷潜艇产生,水雷对象的初始坐标点与水雷潜艇的坐标点有关,所以我们应该在水雷潜艇类中创建生成水雷对象的方法 shootMine
- 我们需要先遍历潜艇数组中所有的潜艇对象,再判断这些潜艇对象是否是水雷潜艇(instanceof),如果是水雷潜艇(MineSubmarine),调用创建水雷对象方法(shootMine),并且对水雷数组(mines)进行扩容,之后将水雷对象(Mine)添加到水雷数组中
-
炸弹与潜艇的碰撞:
-
在 SeaObject 中设计 isHit() 检测碰撞方法
- 由于碰撞的两者都是 SeaObject 类型,所以将方法写在 SeaObject 类中,
- 由于碰撞是两个对象的事,所以我们在设计这个方法的时候,需要提供一个参数 (SeaObject other)
- 由于这个方法是判断是否碰撞,所以返回值类型为 boolean 类型
- 碰撞逻辑如下图所示:
-
参考代码:
/** * 检测碰撞 * @param other 另一个对象 this表示一个对象 * @return 若撞上了则返回true,否则返回false */ public boolean isHit(SeaObject other){ //假设:this为潜艇,other为炸弹 int x1 = this.x-other.width; //x1:潜艇的x-炸弹的宽 int x2 = this.x+this.width; //x2:潜艇的x+潜艇的宽 int y1 = this.y-other.height; //y1:潜艇的y-炸弹的高 int y2 = this.y+this.height; //y2:潜艇的y+潜艇的高 int x = other.x; //x:炸弹的x int y = other.y; //y:炸弹的y //练习-----------2:34继续 return x>=x1 && x<=x2 && y>=y1 && y<=y2; //x在x1与x2之间,并且,y在y1与y2之间,即为撞上了 }
-
在 SeaObject 中设计 goDead() 去死方法 – 变更状态
- 业务:
- 变更状态,将海洋对象的状态 state 从 LIVE 变为 DEAD
- 方法名: goDead
- 返回值: void
- 业务:
-
在 Battleship 中设计 addLife() 增命方法
- 业务:设计增命方法
- 思路:给 Battleship 的 life 属性累加
- 方法名:addLife
- 参数:int num
-
在 World 中设计 bombBangAction() 炸弹与潜艇的碰撞方法
- 业务:
- 判断炸弹与潜艇是否碰撞,如果碰撞,让炸弹和潜艇一起去死
- 如果摧毁的是水雷潜艇得命
- 如果摧毁的是侦察潜艇得10分
- 如果摧毁的是鱼雷潜艇得40分
- 思路:
- 判断所有炸弹与潜艇是否碰撞
- 首先需要嵌套循环两个数组(bombs 炸弹,submarines 潜艇)
- 判断炸弹是否活着(isLive())
- 判断潜艇是否活着(isLive())
- 判断炸弹与潜艇是否碰撞(isHit())
- 如果都满足则代表碰撞上了
- 让炸弹去死(goDead),让潜艇去死(goDead)
- 判断摧毁的是得分的潜艇(侦察/鱼雷)
- 向下转型(引用类型强转)
- 判断这个对象是否实现了得分接口 (instanceof)
- 调用 getScore 方法
- 判断摧毁的是得命的潜艇
- 向下转型(引用类型强转)
- 判断这个对象是否实现了得命接口 (instanceof)
- 调用 getLife 获取命数的方法 和 addLife 增加命数的方法
- 在定时器中调用 bombBangAction() 方法
- 判断所有炸弹与潜艇是否碰撞
- 业务:
-
-
海洋对象死亡删除
- 在 outOfBoundsAction 删除越界的海洋对象的方法中添加是否死了的判断
- 将方法改为 越界和死亡 都删除删除对象
- 参考代码:
-
画分和画命:
- 在 Battleship 中设计 getLife() 获取命数
- 方法名:getLife
- 返回值类型:int – 获取 Battleship 中的 life 属性
- 在World类的 paint() 中:画分和画命————不要求掌握
g.drawString("SCORE: "+score,200,50); //画分----不要求掌握 g.drawString("LIFE: "+ship.getLife(),400,50); //画命----不要求掌握
- 在 Battleship 中设计 getLife() 获取命数
九、day09
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/107634.html