一、在游戏中加入接口
在项目包下创建两个接口:
package cn.tedu.submarine;
/**
* 加分的接口 : 目前 让侦察潜艇 和 鱼雷潜艇 来实现
*/
public interface EnemyScore {
/**
* 返回值为int类型 代表就是若被打到时 需要提要提供的分数
*/
int getScore();
}
侦察潜艇,鱼雷潜艇实现:
@Override 侦察潜艇类
public int getScore() {
return 10;
}
@Override 鱼雷潜艇类
public int getScore() {
return 40;
}
加命接口:
package cn.tedu.submarine;
/**
* 加命的接口
*/
public interface EnemyLife {
/**
*加命的接口,主要提供加命的行为,供实现类来进行实现并重写具体的细节。
*/
int getLife();
}
水雷潜艇类实现:
@Override
public int getLife() {
return 1;
}
总结设计规则:
1.将所有概念统一的类,它们共性属性和行为 提取到一个父类中.过程叫做泛化.也是优化代码的冗余.
2.提取到父类中的共性行为里面的实现逻辑,若子类都能够或多个子类可以使用,则可以设计为普通方法.
若所有子类不能够使用父类提供的行为内的实现逻辑时,则应该设计为抽象方法!
3.接口:不同类别或同类别中的部分类别存在行为相同,我们可以使用接口来进行优化和复用。
二、编程中错误的一些分类
1.编译错误:
编译期间产生的错误 ----------全部都是语法的错误(练得少,多练!)
2.运行异常:
运行期间产生的错误 -----------空指针异常/数组下标越界异常..
3.程序没报错,但是现象就很奇怪.
解决方法:
3.1:锁定错误范围,通过现在来决定当前需要筛查的代码模块。
3.2:打桩, 就是用System.out.println(1);打印语句,检测该方法有没有执行。
------------------------------------------------------------------------------------------
面向对象的三大特性:封装,继承 ,多态。
比如:人类在睡觉的行为上有哪些多态:右侧睡,左侧睡,平躺睡,趴着睡...
人类在形体上有哪些多态:有的人高,有的人矮,有的人胖,有的人瘦.....
-------------------------------------------------------------------------------------------
程序上的多态 -----------行为的多态,一个父类型中的行为,子类有不同的实现。
引用类型的变量能打点调出什么,要看变量的类型!比如说下面调用p.cut()
也就是调用父类执行子类
举例:
人 p = new 理发师(); //声明父 new 子 向上造型
人 p1 = new 医生();
人 p2 = new 园丁();
p.cut();//p在调用的时候,用的是人类中cut,但是具体运行时执行p里面存储的对象的cut行为
p1.cut();//p在调用的时候,用的是人类中cut,但是具体运行时执行p里面存储的对象的cut行为
p2.cut();//p在调用的时候,用的是人类中cut,但是具体运行时执行p里面存储的对象的cut行为
class Person{
abstract void cut();
}
class 理发师 extends Person{
void cut(){
System.out.println("剪发..");
}
}
class 医生 extends Person{
void cut(){
System.out.println("做手术..");
}
}
class 园丁 extends Person{
void cut(){
System.out.println("修剪树木..");
}
}
当一个对象 被造型为不同的类型时,具有不同的行为
我 me = new 我();
me.授课();
me.互相卷();
me.孝顺父母();
--------------------------------------
讲师 o1 = new 我();
//这里是向上造型,变成讲师。
o1.授课();
公司员工 o2 = new 我();
o2.互相卷();
父母的儿子 o3 = new 我();
o3.孝顺父母();
interface 讲师{
void 授课();
}
interface 公司员工{
void 互相卷();
}
interface 父母的儿子{
void 孝顺父母();
}
class 我 implements 讲师,公司员工,父母儿子{
void 授课(){ }
void 互相卷(){ }
void 孝顺父母(){ }
}
向上造型/引用类型中的自动类型转换
1.父 大 子 小 -----声明父 new 子(这时子类会被自动转换为大类型(父类))
2.能够向上造型成功, 父 new 子 , 接口 new 实现类
都是左边大(父类),右边小(子类)
----------------------------------------------------------------------------------
向下转型/引用类型中的强制类型转换
条件二:要强转的引用类型变量中的对象, 实现了要转换的这个接口,或者继承了该类。
需要引用类型的强转时,一定要在强制的语法前用if 通过instanceof来判断是否可以转换成功。
仔细看下面的代码例子,试着理解一下:
Aoo a1 = new Boo(); //向上造型。
Boo b1 = (Boo)a1;//可以完成强转操作,符合条件一:
Inter1 i1 = (Inter1)a1;//可以完成强转操作,符合条件二
Coo c1 = (Coo)a1; //强制转换失败的异常:class Cast Exception
interface Inter1{
}
class Aoo{
}
class Boo extends Aoo implements Inter1{
}
class Coo extends Aoo{
}
package oo.day05;
/**
* 类型转换的使用演示类:
*/
public class ClassCastDemo {
public static void main(String[] args) {
Aoo1 o1 = new Boo();//向上造型
Boo b1 = (Boo) o1;//符合条件一:要强转的引用类型变量中的对象 就是转换的这个类型。
Inter i1 = (Inter) o1;//符合条件二:要强转的引用类型变量中的对象, 实现了要转换的这个接口
//
if(o1 instanceof Coo) { //instanceof :判断 o对象 是否可以转换为 Coo类型
Coo c1 = (Coo) o1;
System.out.println("123");
}
}
}
interface Inter{
}
class Aoo1{
}
class Boo extends Aoo1 implements Inter{
}
class Coo extends Aoo1{
}
项目目前欠缺的:
深水炸弹要与潜艇碰撞:
因为碰撞的行为子类都有,所以要提取到父类中,做普通方法。根据碰撞的图可以计算出,雷和这些战舰的边界。所以以潜艇为参照物来进行坐标的设定。this代表潜艇、other代表深水炸弹
public boolean isHit(SeaObject other){
int x1 = this.x - other.width;
int x2 = this.x + this.width;
int y1 = this.y - other.height;
int y2 = this.y + this.height;
int x = other.x;//传入碰撞对象的x
int y = other.y;//传入碰撞对象的y
//返回 并判断 x是否在 x1 和x2 之间 并且 y是否在 y1 和y2 之间。
return (x >= x1 && x <= x2) &&(y >= y1 && y <= y2);
//这里相当于规定出潜艇的领空
}
-------------------------------------------
isHit方法的逻辑其实都是一样的,所以不管谁碰谁:
1)潜艇对象.isHit(深水炸弹)--------------------this指代的是潜艇对象 other指代的是深水炸弹
2)深水炸弹.isHit(潜艇对象)--------------------this指代的是深水炸弹 other指代的是潜艇对象
3)雷.isHit(战舰) --------------------this指代的是雷对象 other指代的是战舰
4)战舰.isHit(雷) --------------------this指代的是战舰 other指代的是雷
因为碰撞的行为是自动发生的,所以我们要在GameWorld类写一个方法bombBangAction,主要实现的是深水炸弹与潜艇的碰撞。
逻辑就是,把每个深水炸弹和每个潜艇的领空边界进行比较,看有没有碰上。
代码如下:
/**
* 深水炸弹与潜艇碰撞检测的逻辑实现。 -放run中调用
*/
public void bombBangAction(){
for (int i = 0; i < bombs.length; i++) {
Bomb b = bombs[i];//获取当前深水炸弹数组中的对象
for (int j = 0; j < submarines.length; j++) {
SeaObject obj = submarines[j];//获取当前潜艇数组中的对象
if(b.isLive() && obj.isLive()&&b.isHit(obj)){//深水炸弹.isHit(潜艇对象)
b.goDead(); //标记
obj.goDead();//标记
}
}
}
}
因为深水炸弹打到潜艇,两个都要消失,所以我们可以在SeaObject写一个goDead方法。哪个对象打点调用了goDead方法,哪个对象当前状态则被标记为死亡状态。
/**
* 哪个对象打点调用goDead方法,则被标记为死亡状态。
*/
public void goDead(){
this.currentState = DEAD;//将当前对象设置为死亡状态。
}
并且,被标记为死亡状态的对象
也需要被删除掉。所以这个时候补充到原来的deleteOutOfBounds方法中就好。
public SeaObject[] deleteOutOfBounds(SeaObject[] objects) {
for (int i = 0; i < objects.length; i++) {
//2.判断数组中的每个对象是否越界
if (objects[i].isOutBounds() || objects[i].isDead() ) {
objects[i] = objects[objects.length - 1];//将数组最后一个对象赋值覆盖当前越界的对象空间里
objects = Arrays.copyOf(objects, objects.length - 1);//删除最后一行
}
}
return objects;//需要将当前缩容后的数组对象 返回出去
}
如何实现雷与战舰的碰撞检测
在GameWorld类中,定义一个方法,方法命名为thunderBangAction,并放到run中调用。
内部逻辑: 遍历雷数组对象,依次与战舰对象去检测,如果碰到了雷对象 就goDead
/**
* 雷与战舰碰撞的逻辑。
*/
public void thunderBangAction() {
for (int i = 0; i < thunders.length; i++) {
if (thunders[i].isLive() && thunders[i].isHit(ship)) {
thunders[i].goDead();
}
}
}
所以到这一步之后,已经实现了炸弹攻击到潜艇后两者的消失。接下来就是加分逻辑和加命逻辑的写法了。
引入g.drawString用法(在paint方法里的)
用来将字符串显示在屏幕上
接受四个参数g.drawString("字符串"+参数值, x,y)
其中x和y表示字符串的坐标。
private int score = 0; //默认游戏分为0 分
g.drawString("Score:" + score, 200, 50);
g.drawString("Life:" + ship.getLife(), 400, 50);
这个时候在潜艇类里加上life的setter和getter方法,方法公开化。
加上去的字体样式可以调。
g.setFont/g.setColor等等
g.setFont(new Font("", Font.BOLD, 20));
双引号里写的是要选择的字体样式
20是字体大小
加分逻辑和加命逻辑写在哪里?
因为bombBangAction是炸弹爆炸的逻辑,炸弹爆炸就会让潜艇加分或加命,所以加在这里。
但是因为obj是父类,侦查潜艇加分,鱼类潜艇要加分,只有剩下的那个ship要加命,所以要用if-else来进行分类。
/**
* 深水炸弹与潜艇碰撞检测的逻辑实现。 -放run中调用
*/
public void bombBangAction() {
for (int i = 0; i < bombs.length; i++) {
Bomb b = bombs[i];//获取当前深水炸弹数组中的对象
for (int j = 0; j < submarines.length; j++) {
SeaObject obj = submarines[j];//获取当前潜艇数组中的对象
if (b.isLive() && obj.isLive() && b.isHit(obj)) {//深水炸弹.isHit(潜艇对象)
b.goDead(); //标记
obj.goDead();//标记
if (obj instanceof EnemyScore) {//判断obj对象是否是 可以强转为 EnemyScore 接口
score += ((EnemyScore) obj).getScore();
} else if (obj instanceof EnemyLife) {
EnemyLife e = (EnemyLife) obj;
ship.setLife(e.getLife());
}
//加分加命的操作
//侦察潜艇要加分
//以下代码 复用性差,扩展性差,维护性也差 ----垃圾代码
// if (obj instanceof ObserverSubmarine) { //判断 obj 是否是 侦察潜艇类型
// ObserverSubmarine os = (ObserverSubmarine) obj;//强制转换
// score += os.getScore();//调用给分行为 获取分数
// } else if (obj instanceof TorpedoSubmarine) { //鱼雷潜艇要加分
// TorpedoSubmarine ts = (TorpedoSubmarine) obj;
// score += ts.getScore();
// } else if(obj instanceof MineSubmarine){ //水雷潜艇要加命
// MineSubmarine ms= (MineSubmarine)obj;
// ship.setLife(ms.getLife());
// }
}
}
}
}
战舰类提供减命的行为:
public void subtractLife(){//提供减命的方法
this.life--; //自减
}
至此,完成了炸弹范围,攻击行为后潜艇和炸弹同样消失、生命、分数的显示及自增自减。