问题:
我指挥一个机器人,我想消灭敌人。 再次面对相同类型的敌人很无聊。 我需要新的挑战,这意味着需要新型的敌人。 例如,在第一级中,我只想练习目标。 所以我需要一个漂亮的傻瓜敌人,不做太多事情,但会射击。 掌握了该技能(射击无助的机器人)之后,我需要挑战,并且希望敌方机器人进行反击,但是由于我仍然是初学者,所以我不想很快死去,所以我需要较弱的机器人。 和他们在一起之后,我想面对一个更艰巨的挑战。 我需要更好更强的机器人。 不仅更强大,而且在行为上也有所不同,而且它可能会无聊地一遍又一遍地杀死相同类型的敌人。
显而易见的解决方案:
为3种类型的敌方机器人创建3个类别。 为了简单起见,每个机器人都有2种能力:移动和攻击。 使用这两种方法创建Droid接口并让每个droid实现它们都是有意义的。
他们可以移动 射击 。 好吧,不是全部,但是我们可以为不做任何事情的人提供空的实现。
机器人类型:
- 诱饵机器人 -没有武器,无法移动。
- 侦察员Droid –武器较弱且动作Swift。
- 突击机器人 -将拥有重型武器并缓慢移动。
查看这三种类型,我们可以实现以下简单的类图:
该接口具有3种简单的方法,机器人需要实现这些方法:
public interface Droid {
// display some info of the droid
public void display();
// move the droid
public void move(int x, int y);
// attack position
public void shoot(int x, int y);
}
这三个类如下:
DecoyDroid.java
public class DecoyDroid implements Droid {
@Override
public void display() {
System.out.println("I am a DECOY droid");
}
@Override
public void move(int x, int y) {
System.out.println("Can't move.");
}
@Override
public void shoot(int x, int y) {
System.out.println("Have no weapon.");
}
}
ScoutDroid.java
public class ScoutDroid implements Droid {
private float damage = 0.5f;
@Override
public void display() {
System.out.println("I am a scout droid");
}
@Override
public void move(int x, int y) {
System.out.println("Moving QUICKLY to: " + x + "," + y + ".");
}
@Override
public void shoot(int x, int y) {
System.out.println("Light Laser Canon targeting: " + x + "," + y
+ ". Damage: " + damage);
}
}
AssaultDroid.java
public class AssaultDroid implements Droid {
private float damage = 2.5f;
private boolean loaded = true;
@Override
public void display() {
System.out.println("I am an ASSAULT droid");
}
@Override
public void move(int x, int y) {
System.out.println("Moving SLOWLY to: " + x + "," + y + ".");
}
@Override
public void shoot(int x, int y) {
if (loaded) {
System.out.println("Heavy laser targeting: " + x + "," + y
+ ". Damage: " + damage);
loaded = false;
} else {
System.out.println("Reloading...");
loaded = true;
}
}
}
无论ScoutDroid和AssaultDroid有争论的损害 。 这保留了他们造成的损害的价值。
为了给AssaultDroid带来重型武器并且重装时间很慢,我们添加了load变量。 这样,攻击机器人需要转两圈才能发射一次武器。
我为机器人创建了一个简单的模拟器来轮流移动和射击。
为此设计运行模拟器:
BadDroidSimulator.java
public class BadDroidSimulator {
public static void main(String[] args) {
// for generating random numbers
Random rand = new Random();
Droid scout = new ScoutDroid();
Droid assailant = new AssaultDroid();
Droid decoy = new DecoyDroid();
scout.display();
assailant.display();
decoy.display();
// shoot-out - each droid fires once per turn
for (int i = 1; i <= 5; i++) {
System.out.println("\n<=== BEGIN TURN " + i + " ===>");
scout.shoot(rand.nextInt(10), rand.nextInt(10)); // we assume this is an enemy position
scout.move(rand.nextInt(10), rand.nextInt(10));
System.out.println();
assailant.shoot(rand.nextInt(10), rand.nextInt(10));
assailant.move(rand.nextInt(10), rand.nextInt(10));
System.out.println();
decoy.shoot(rand.nextInt(10), rand.nextInt(10));
decoy.move(rand.nextInt(10), rand.nextInt(10));
System.out.println("<=== END TURN " + i + " ===>");
}
}
}
结果(控制台输出)将如下所示:
I am a scout droid
I am an ASSAULT droid
I am a DECOY droid
<=== BEGIN TURN 1 ===>
Light Laser Canon targeting: 9,0. Damage: 0.5
Moving QUICKLY to: 4,6.
Heavy laser targeting: 6,2. Damage: 2.5
Moving SLOWLY to: 9,1.
Have no weapon.
Can’t move.
<=== END TURN 1 ===>
<=== BEGIN TURN 2 ===>
Light Laser Canon targeting: 3,4. Damage: 0.5
Moving QUICKLY to: 6,5.
Reloading…
Moving SLOWLY to: 1,6.
Have no weapon.
Can’t move.
<=== END TURN 2 ===>
<=== BEGIN TURN 3 ===>
Light Laser Canon targeting: 6,7. Damage: 0.5
Moving QUICKLY to: 9,7.
Heavy laser targeting: 7,1. Damage: 2.5
Moving SLOWLY to: 2,0.
Have no weapon.
Can’t move.
<=== END TURN 3 ===>
<=== BEGIN TURN 4 ===>
Light Laser Canon targeting: 3,7. Damage: 0.5
Moving QUICKLY to: 1,4.
Reloading…
Moving SLOWLY to: 5,9.
Have no weapon.
Can’t move.
<=== END TURN 4 ===>
<=== BEGIN TURN 5 ===>
Light Laser Canon targeting: 0,8. Damage: 0.5
Moving QUICKLY to: 3,9.
Heavy laser targeting: 1,2. Damage: 2.5
Moving SLOWLY to: 3,2.
Have no weapon.
Can’t move.
<=== END TURN 5 ===>
扩展设计的挑战
机器人轮流移动并射击。 一切都很好,但是:
- 如果要创建混合机器人,该怎么办? 能像侦察兵一样快速移动但拥有重型武器的机器人? 您将必须创建一个新类,然后从Scout和Assault机器人中复制粘贴相应的方法,对吗?
- 还可以想象射击机制不是那么简单,它需要碰撞检测等等。 对于每个机器人,都需要重写相同的冗余代码。
- 如果可以通过通电来增强火力怎么办?
- 如果机器人获得了自觉性并找到了代替当前机器人使用它的武器怎么办?
我敢肯定,您对如何增强游戏玩法和扩展游戏世界有很多想法,但是最明显的解决方案(如上所述)似乎不适合这样做。 它需要创建新的droid类,并且每种droid类型都将分别实现其方法。 这些方法很多都是相同的。 当前的设计不允许您在运行时更改droid的内部结构,而无需花费大量精力。
设计Droid(正确)
一个非常简单的机器人是由放在底盘上的武器组成的。 第一个设计由“ 是 ”类型的关系组成。 ScoutDroid 是具有某些特性的通用Droid 。
组成基于“ 具有 ”关系。 机器人 有 底盘 。 机器人 拥有 武器 。 机器人具有哪种组件类型决定了它的类型。
例如,让我们分解Scout Droid。
侦察员 Droid的 是 具有 光 激光 佳能 有一组轮子来移动 的Droid。 我们想用轻武器使侦察员Swift行动。 另一方面, 突击 Droid 也是 Droid,但它具有 重型 激光 佳能 ,并且可以在Tracks上运行。 这使其功能非常强大,但速度有些慢。
从工厂的角度思考。 汽车生产线如何运作? 您可以在底盘上找到发动机,车轮,驱动轴,变速箱等的特定位置。
所有这些组件都是单独生产的。 产生它们的团队对其他部分一无所知。 它们必须满足一个条件:变速箱必须完全适合其位置并与发动机连接。不同的制造商具有不同的实现方式。 在这种情况下,连接器是interface 。 引擎也有类似的故事。 如果它很好地与车轮和变速箱连接,则可以安装它。 其内部设计,容量,功率,油耗可以完全不同。 发动机是汽车的组成部分之一。
我们的机器人也是如此。 但是为了简单起见,我们只有2个组件。 我们需要一个具有所有布线的通用机器人,以便其组件可以通过这些接口由机器人触发。 例如,机器人只需拉动武器的扳机,而不必关心它是什么类型的武器,只要它具有扳机即可。 机器人需要了解pullTrigger方法,并且需要实现该方法才能实现,以便我们为机器人提供武器使用。
位置的改变也是一样。 它需要触发运动。 车轮,履带或反重力推进器将把机器人带到那里。 机器人仅需要设置坐标。
为了实现这一点,我们需要一个带有实现方法而不是接口的类。
我们创建抽象的Droid类。 之所以将其抽象化,是因为我们实际上实现了触发武器,对移动机制进行动作的方法,但是我们没有将具体的武器和移动机制附加到机器人上。 具体机器人的组装将与description方法一起委托给类型构造函数。
public abstract class Droid {
protected Weapon weapon; // the weapon which will be used in fights
protected Chassis chassis; // the chassis on which the droid is placed
public void moveToPosition(int x, int y) {
System.out.print(id + " > " );
chassis.moveTo(x, y);
}
/**
* Engages the position on the screen whether it is occupied by
* an enemy or not. Each strategy should decide how to do it.
*/
public void attackPosition(int x, int y) {
System.out.print(id + " > ");
weapon.useWeapon(new Vector2f(x, y));
}
/**
* Displays some info on the droid
*/
public abstract void display();
}
如果检查该类,您将看到Droid有3种方法,从中实现2种方法。 它还具有两个组成部分 : 武器和底盘 。
组件是接口,因此droid在触发对它们的操作时不知道它在做什么。 所有这些都委托给实现。
接口如下:
Weapon.java
public interface Weapon {
/**
* The trigger to use the weapon.
* @param target - where on the map is the target
*/
public void useWeapon(Vector2f target);
/**
* Returns the description of the weapon
*/
public String getDescription();
}
底盘
public interface Chassis {
/**
* Delegates the movement to the supporting chassis and
* tries to move the unit to x,y
*/
public void moveTo(int x, int y);
/**
* Returns the description of the chassis
*/
public String getDescription();
}
我们将增强Droid基础类。 我们将为Weapon和Chassis添加setter和getter方法。 这将使我们能够在运行时更改droid的行为。 这就是战略模式的全部内容。 Droid具有行为:它可以使用武器并且可以移动 。 这两种策略(行为)需要实施。
我们还添加了一个ID ,该ID在我们的游戏中对于每个droid实例都是唯一的。 我使用一个非常简单的id生成策略。 我增加nextId静态字段,并将其附加到每种类型的构造函数中的具体droid类型前缀。
这是新的Droid类:
public abstract class Droid {
protected static int nextId = 0; // the next available ID
protected String id; // unique id
protected Weapon weapon; // the weapon which will be used in fights
protected Chassis chassis; // the chassis on which the droid is placed
// the unique ID of the droid in the game
public String getId() {
return id;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
public Chassis getChassis() {
return chassis;
}
public void setChassis(Chassis chassis) {
this.chassis = chassis;
}
public void moveToPosition(int x, int y) {
System.out.print(id + " > " );
chassis.moveTo(x, y);
}
/**
* Engages the position on the screen whether it is occupied by
* an enemy or not. Each strategy should decide how to do it.
*/
public void attackPosition(int x, int y) {
System.out.print(id + " > ");
weapon.useWeapon(new Vector2f(x, y));
}
/**
* Displays some info on the droid
*/
public abstract void display();
}
让我们制造一些武器
NoWeapon.java
/**
* This is a null object. A null object is a dummy that does nothing and it
* is a mere place-holder and eliminates the need to check for null.
* @author impaler
*
*/
public class NoWeapon implements Weapon {
@Override
public void useWeapon(Vector2f target) {
// We are doing nothing
System.out.println("No weapon equipped!");
}
@Override
public String getDescription() {
return "Nothing";
}
}
这是空对象。 类描述应该让您知道它是什么。
LightLaserCanon.java
/**
* This is a light laser cannon whit a quick reload time and high accuracy
*
* @author impaler
*
*/
public class LightLaserCanon implements Weapon {
private float damage = 0.5f; // the damage inflicted
@Override
public void useWeapon(Vector2f target) {
System.out.println("Shooting my laser canon to " + (int)target.getX() + ","
+ (int)target.getY() + ". Bang on! Done " + damage + " damage.");
}
@Override
public String getDescription() {
return "First generation laser canon. Street use only!";
}
}
HeavyLaserCanon.java
/**
* This is a heavy assault laser cannon with high accuracy but slow reload time.
* @author impaler
*/
public class HeavyLaserCanon implements Weapon {
private boolean loaded = true; // after fire needs to be reloaded
private float damage = 1.5f; // the damage is considerable
@Override
public void useWeapon(Vector2f target) {
if (loaded) {
// we fire the canon
System.out.println("Eat this! Laser beam hit target (" + (int)target.getX() + "," + (int)target.getY() + ") and dealt " + damage + " damage.");
// next time needs reloading
loaded = false;
} else {
System.out.println("Darn! Out of ammo! Reloading...");
loaded = true;
}
}
@Override
public String getDescription() {
return "DASS-5000 - The ultimate in siege weaponry provided by Obviam Enterprises.";
}
}
您可能会注意到Vector2f类。 这是一个非常基本的2D向量类,当前包含x和y坐标。 而已。 您可以在下载的源中找到它。
让我们建立一些底盘
getDescription()方法说明机箱的外观 。
NoChassis.java –空对象(请参阅武器)
public class NoChassis implements Chassis {
@Override
public void moveTo(int x, int y) {
System.out.println("It's just a frame. Can't move.");
}
@Override
public String getDescription() {
return "It's just a frame.";
}
}
SteelStand.java
public class SteelStand implements Chassis {
@Override
public void moveTo(int x, int y) {
System.out.println("Can't move.");
}
@Override
public String getDescription() {
return "Unmovable reinforced steel stand ideal for turrets and defensive units.";
}
}
Wheels.java
public class Wheels implements Chassis {
@Override
public void moveTo(int x, int y) {
System.out.println("Speeding to " + x + "," + y + " on my wheels!");
}
@Override
public String getDescription() {
return "4 wheel drive, very fast on flat terrain but struggling through obstacles.";
}
}
Track.java
public class Track implements Chassis {
@Override
public void moveTo(int x, int y) {
System.out.println("Don't get in my way! Moving slowly to: " + x + "," + y + ".");
}
@Override
public String getDescription() {
return "Slow moving tracks but able to go through many obstacles.";
}
}
现在我们可以组装机器人了
首先让我们创建一个DecoyDroid 。 该机器人没有武器,将被放置在钢架上。 是为了我们的目标练习,还记得吗?
DecoyDroid.java
public class DecoyDroid extends Droid {
public DecoyDroid() {
id = "DCY-" + (++Droid.nextId);
weapon = new NoWeapon();
chassis = new SteelStand();
}
@Override
public void display() {
System.out.println("+--------------------------------------------------------------------------------------------+");
System.out.println("| I am a DECOY droid.");
System.out.println("|\tID: " + id);
System.out.println("|\tWeapon: " + weapon.getDescription());
System.out.println("|\tChassis: " + chassis.getDescription());
System.out.println("+--------------------------------------------------------------------------------------------+");
}
}
检查默认构造函数。 它创建一个id并将NoWeapon和SteelStand的实例分配给该机器人。
display()方法比以前更复杂,但只是为了更好地描述droid。 它也利用了组件的描述。 如果实例化DecoyDroid并调用其显示方法,则将获得一个不错的描述。
+——————————————————————————————–+
| I am a DECOY droid.
| ID: DCY-3
| Weapon: Nothing
| Chassis: Unmovable reinforced steel stand ideal for turrets and defensive units.
+——————————————————————————————–+
让我们构建其余类型:
ScoutDroid.java
public class ScoutDroid extends Droid {
public ScoutDroid() {
id = "SCT-" + (++Droid.nextId);
weapon = new LightLaserCanon();
chassis = new Wheels();
}
@Override
public void display() {
System.out.println("+--------------------------------------------------------------------------------------------+");
System.out.println("| I am a SCOUT droid.");
System.out.println("|\tID: " + id);
System.out.println("|\tWeapon: " + weapon.getDescription());
System.out.println("|\tChassis: " + chassis.getDescription());
System.out.println("+--------------------------------------------------------------------------------------------+");
}
}
AssaultDroid.java
public class AssaultDroid extends Droid {
public AssaultDroid() {
id = "ASS-" + (++Droid.nextId);
weapon = new HeavyLaserCanon();
chassis = new Track();
}
@Override
public void display() {
System.out.println("+--------------------------------------------------------------------------------------------+");
System.out.println("| I am an ASSAULT droid.");
System.out.println("|\tID: " + id);
System.out.println("|\tWeapon: " + weapon.getDescription());
System.out.println("|\tChassis: " + chassis.getDescription());
System.out.println("+--------------------------------------------------------------------------------------------+");
}
}
您会注意到,唯一需要实现的就是构造函数(它增加了底盘和武器)以及display()方法。
下图显示了新的体系结构:
让我们为其创建一个测试脚本。 我们将模拟5个回合,每个机器人将使用其武器并移动到随机位置。 检查每种武器的行为,您会发现重型激光每2圈会发射一次。 为了使它有趣,我们依次在4 中将HeavyLaserCanon赋予DecoyDroid 。 看一下它如何改变机器人的行为并开始发射。 这是在运行时动态创建的混合机器人。
模拟器代码(DroidSimulator.java):
public class DroidSimulator {
public static void main(String[] args) {
// for generating random numbers
Random rand = new Random();
Droid scout = new ScoutDroid();
Droid assailant = new AssaultDroid();
Droid decoy = new DecoyDroid();
scout.display();
assailant.display();
decoy.display();
// shoot-out - each droid fires once per turn
for (int i = 1; i <= 5; i++) {
System.out.println("\n<=== BEGIN TURN " + i + " ===>");
// in turn 3 decoy droid is given an assault canon
if (i == 4) {
decoy.setWeapon(new HeavyLaserCanon());
System.out.println("* " + decoy.getId() + " acquired " + decoy.getWeapon().getDescription() + "\n");
}
scout.attackPosition(rand.nextInt(10), rand.nextInt(10)); // we assume this is an enemy position
scout.moveToPosition(rand.nextInt(10), rand.nextInt(10));
System.out.println();
assailant.attackPosition(rand.nextInt(10), rand.nextInt(10));
assailant.moveToPosition(rand.nextInt(10), rand.nextInt(10));
System.out.println();
decoy.attackPosition(rand.nextInt(10), rand.nextInt(10));
decoy.moveToPosition(rand.nextInt(10), rand.nextInt(10));
System.out.println("<=== END TURN " + i + " ===>");
}
}
}
输出:
+——————————————————————————————–+
| I am a SCOUT droid.
| ID: SCT-1
| Weapon: First generation laser canon. Street use only!
| Chassis: 4 wheel drive, very fast on flat terrain but struggling through obstacles.
+——————————————————————————————–+
+——————————————————————————————–+
| I am an ASSAULT droid.
| ID: ASS-2
| Weapon: DASS-5000 – The ultimate in siege weaponry provided by Obviam Enterprises.
| Chassis: Slow moving tracks but able to go through many obstacles.
+——————————————————————————————–+
+——————————————————————————————–+
| I am a DECOY droid.
| ID: DCY-3
| Weapon: Nothing
| Chassis: Unmovable reinforced steel stand ideal for turrets and defensive units.
+——————————————————————————————–+
<=== BEGIN TURN 1 ===>
SCT-1 > Shooting my laser canon to 0,3. Bang on! Done 0.5 damage.
SCT-1 > Speeding to 0,2 on my wheels!
ASS-2 > Eat this! Laser beam hit target (3,4) and dealt 1.5 damage.
ASS-2 > Don’t get in my way! Moving slowly to: 3,8.
DCY-3 > No weapon equipped!
DCY-3 > Can’t move.
<=== END TURN 1 ===>
<=== BEGIN TURN 2 ===>
SCT-1 > Shooting my laser canon to 4,0. Bang on! Done 0.5 damage.
SCT-1 > Speeding to 5,0 on my wheels!
ASS-2 > Darn! Out of ammo! Reloading…
ASS-2 > Don’t get in my way! Moving slowly to: 1,6.
DCY-3 > No weapon equipped!
DCY-3 > Can’t move.
<=== END TURN 2 ===>
<=== BEGIN TURN 3 ===>
SCT-1 > Shooting my laser canon to 3,0. Bang on! Done 0.5 damage.
SCT-1 > Speeding to 0,6 on my wheels!
ASS-2 > Eat this! Laser beam hit target (9,1) and dealt 1.5 damage.
ASS-2 > Don’t get in my way! Moving slowly to: 8,0.
DCY-3 > No weapon equipped!
DCY-3 > Can’t move.
<=== END TURN 3 ===>
<=== BEGIN TURN 4 ===>
* DCY-3 acquired DASS-5000 – The ultimate in siege weaponry provided by Obviam Enterprises.
SCT-1 > Shooting my laser canon to 8,6. Bang on! Done 0.5 damage.
SCT-1 > Speeding to 2,3 on my wheels!
ASS-2 > Darn! Out of ammo! Reloading…
ASS-2 > Don’t get in my way! Moving slowly to: 0,6.
DCY-3 > Eat this! Laser beam hit target (9,4) and dealt 1.5 damage.
DCY-3 > Can’t move.
<=== END TURN 4 ===>
<=== BEGIN TURN 5 ===>
SCT-1 > Shooting my laser canon to 1,7. Bang on! Done 0.5 damage.
SCT-1 > Speeding to 1,9 on my wheels!
ASS-2 > Eat this! Laser beam hit target (1,4) and dealt 1.5 damage.
ASS-2 > Don’t get in my way! Moving slowly to: 3,6.
DCY-3 > Darn! Out of ammo! Reloading…
DCY-3 > Can’t move.
<=== END TURN 5 ===>
依次注意4, DecoyDroid如何获得新武器并改变其行为(黄线)。 现在,您还应该了解空对象模式。
就目前而言,通过将代码保持在最低限度即可轻松创建新的武器,底盘和机器人。 在这些情况下,始终主张使用组合而不是继承。 您可以创建一个非常精致的机器人,其中包括盾牌,传感器,一系列武器以及一个AI 组件 ,该AI 组件可以根据情况决定使用哪种武器。 如果您已经注意到,我使用“使用”一词来代替火,因为一种武器也可以是近战武器,而不一定是远程武器。
可下载的项目仅包含策略模式实施。 遗漏了简单的继承方法。 尝试使用它,并掌握这个有用的模式,因为我将广泛使用它,并且它被认为是好的设计。 这是一个简单的Java应用程序,未启用Android。
在此处下载源(obviam.compositions.part1.tgz) 。
在以下各部分中,我将尝试添加一些AI功能以及如何根据其组件的图像组成最终的机器人。
参考: 设计游戏内实体。 对象组成策略。 第1部分–来自“ 反对谷物 ”博客的JCG合作伙伴Tamas Jano 的策略模式 。
- Android游戏开发教程简介
- Android游戏开发–游戏创意
- Android游戏开发–创建项目
- Android游戏开发–基本游戏架构
- Android游戏开发–基本游戏循环
- Android游戏开发–使用Android显示图像
- Android游戏开发–在屏幕上移动图像
- Android游戏开发–游戏循环
- Android游戏开发–测量FPS
- Android游戏开发–雪碧动画
- Android游戏开发–粒子爆炸
- Android游戏开发–使用位图字体
- Android游戏开发–从Canvas切换到OpenGL ES
- Android游戏开发–使用OpenGL ES显示图形元素(原语)
- Android游戏开发– OpenGL纹理映射
- Android游戏开发–设计游戏实体–状态模式
- Android游戏文章系列
翻译自: https://www.javacodegeeks.com/2011/08/android-game-development-design-in-game.html