游戏中的设计模式及杂项

概述

如果要做以下游戏功能会用到哪些设计模式。比如创建一个人物角色,这个角色可以装备刀,然后角色可以用刀砍怪物,造成流血。
对于这个游戏功能,可以使用以下设计模式:

  1. 工厂模式(Factory Pattern):用于创建人物角色和刀的实例。可以抽象出一个人物工厂类和刀工厂类来统一管理对象的创建过程。

  2. 装饰者模式(Decorator Pattern):用于装备刀。可以将刀作为一个装饰者,动态地给人物增加属性和行为。

  3. 策略模式(Strategy Pattern):用于刀砍怪物。可以定义一个攻击策略接口,不同的刀可以实现该接口以达到不同的攻击效果。

  4. 观察者模式(Observer Pattern):用于观察流血状态。可以将怪物作为被观察者,人物作为观察者,当人物攻击时会通知怪物更新自己的状态,并更新人物的属性。

  5. 状态模式(State Pattern):用于表现不同的状态。可以定义不同的状态(如待机、移动、攻击等),并将刀砍怪物作为一个具体的状态。

  6. 命令模式(Command Pattern):用于将攻击命令封装成对象。可以将攻击命令封装成一个具体的命令对象,然后传递给人物或者其他对象进行调用。

如果你要使用Unity实现上述功能,主要的流程可能包括以下几个步骤:

  1. 创建游戏对象:在Unity中创建游戏对象,如人物、怪物等,并设置它们的属性和初始状态。

  2. 创建刀的预制件:将刀的模型、贴图等资源打包成一个预制件,以便在游戏中使用。

  3. 实例化刀:使用工厂模式,在游戏运行时动态创建刀的实例,并将它们挂载到人物身上。

  4. 装备刀:使用装饰者模式,在游戏运行时动态地给人物增加刀的属性和行为。

  5. 切换攻击策略:使用策略模式,在游戏中根据人物当前的状态选择不同的攻击策略(即不同的刀)。

  6. 观察流血状态:使用观察者模式,在游戏中让怪物作为被观察者,人物作为观察者,当人物攻击时会通知怪物更新自己的状态,并更新人物的属性。

  7. 更新状态:使用状态模式,根据人物当前的状态更新其动画、移动、攻击等行为。

  8. 执行攻击命令:使用命令模式,将攻击命令封装成对象,然后传递给人物进行调用,同时更新怪物的状态。

  9. 碰撞检测:使用Unity提供的物理引擎,在游戏中实现人物与怪物之间的碰撞检测,检测到碰撞后执行相应的操作,如扣除怪物血量、播放受伤动画等。

以上是实现这个游戏功能的一些基本步骤。在具体实现过程中,还需要涉及到很多细节问题,如如何控制动画的播放、如何处理输入事件等,需要根据具体情况进一步考虑和实现。

装饰者模式

装饰者模式(Decorator Pattern):用于装备刀。可以将刀作为一个装饰者,动态地给人物增加属性和行为。

你说得对,角色装备武器的行为应该由角色来管理,而不是由武器直接管理。我们可以调整设计,使角色能够装备武器,并且通过装饰者模式动态地增加属性和行为。

下面是改进后的代码示例:

1. 定义角色接口和基本实现

首先,定义角色接口和基本角色实现类。

// 角色接口
public interface Character {void display();int getAttackPower();void equip(Weapon weapon);
}// 基本角色实现
public class BasicCharacter implements Character {private String name;private int attackPower;public BasicCharacter(String name, int attackPower) {this.name = name;this.attackPower = attackPower;}@Overridepublic void display() {System.out.println(name + " 准备战斗!");}@Overridepublic int getAttackPower() {return attackPower;}@Overridepublic void equip(Weapon weapon) {throw new UnsupportedOperationException("基础角色不能直接装备武器");}
}

2. 定义武器接口和具体实现

定义一个武器接口和具体的武器实现类。

// 武器接口
public interface Weapon {void display();int getAdditionalAttackPower();default int getAdditionalMagicPower() {return 0; // 默认没有魔法值}
}// 普通刀
public class CommonKnife implements Weapon {private int additionalAttackPower;public CommonKnife(int additionalAttackPower) {this.additionalAttackPower = additionalAttackPower;}@Overridepublic void display() {System.out.println("装备了普通刀!");}@Overridepublic int getAdditionalAttackPower() {return additionalAttackPower;}
}// 长剑
public class LongSword implements Weapon {private int additionalAttackPower;public LongSword(int additionalAttackPower) {this.additionalAttackPower = additionalAttackPower;}@Overridepublic void display() {System.out.println("装备了长剑!");}@Overridepublic int getAdditionalAttackPower() {return additionalAttackPower;}
}// 魔法刀
public class MagicKnife implements Weapon {private int additionalAttackPower;private int additionalMagicPower;public MagicKnife(int additionalAttackPower, int additionalMagicPower) {this.additionalAttackPower = additionalAttackPower;this.additionalMagicPower = additionalMagicPower;}@Overridepublic void display() {System.out.println("装备了魔法刀!");}@Overridepublic int getAdditionalAttackPower() {return additionalAttackPower;}@Overridepublic int getAdditionalMagicPower() {return additionalMagicPower;}
}

3. 定义装饰者基类

定义一个装饰者基类,它包含了对武器的支持。

// 装饰者基类
public abstract class CharacterDecorator implements Character {protected Character character;protected Weapon weapon;public CharacterDecorator(Character character) {this.character = character;}@Overridepublic void display() {character.display();if (weapon != null) {weapon.display();}}@Overridepublic int getAttackPower() {if (weapon != null) {return character.getAttackPower() + weapon.getAdditionalAttackPower();}return character.getAttackPower();}@Overridepublic int getMagicPower() {if (weapon instanceof MagicKnife) {return character.getMagicPower() + ((MagicKnife) weapon).getAdditionalMagicPower();}return character.getMagicPower();}@Overridepublic void equip(Weapon weapon) {this.weapon = weapon;}
}

4. 更新角色接口和基本实现

为了支持魔法刀的额外属性,我们需要更新角色接口和基本实现类。

更新角色接口
public interface Character {void display();int getAttackPower();int getMagicPower();void equip(Weapon weapon);
}
更新基本角色实现
public class BasicCharacter implements Character {private String name;private int attackPower;public BasicCharacter(String name, int attackPower) {this.name = name;this.attackPower = attackPower;}@Overridepublic void display() {System.out.println(name + " 准备战斗!");}@Overridepublic int getAttackPower() {return attackPower;}@Overridepublic int getMagicPower() {return 0; // 基本角色默认没有魔法值}@Overridepublic void equip(Weapon weapon) {throw new UnsupportedOperationException("基础角色不能直接装备武器");}
}

5. 更新测试代码

现在我们可以测试不同类型的刀装备效果。

public class DecoratorPatternDemo {public static void main(String[] args) {// 创建一个基本角色Character basicCharacter = new BasicCharacter("勇士", 100);// 创建装饰者角色Character decoratedCharacter = new CharacterDecorator(basicCharacter) {};// 显示基本信息decoratedCharacter.display();System.out.println("攻击力: " + decoratedCharacter.getAttackPower());System.out.println("魔法值: " + decoratedCharacter.getMagicPower());// 装备普通刀Weapon commonKnife = new CommonKnife(50);decoratedCharacter.equip(commonKnife);decoratedCharacter.display();System.out.println("攻击力: " + decoratedCharacter.getAttackPower());System.out.println("魔法值: " + decoratedCharacter.getMagicPower());// 装备长剑Weapon longSword = new LongSword(70);decoratedCharacter.equip(longSword);decoratedCharacter.display();System.out.println("攻击力: " + decoratedCharacter.getAttackPower());System.out.println("魔法值: " + decoratedCharacter.getMagicPower());// 装备魔法刀Weapon magicKnife = new MagicKnife(100, 50);decoratedCharacter.equip(magicKnife);decoratedCharacter.display();System.out.println("攻击力: " + decoratedCharacter.getAttackPower());System.out.println("魔法值: " + decoratedCharacter.getMagicPower());}
}

运行结果

运行上述代码,输出将会是:

勇士 准备战斗!
攻击力: 100
魔法值: 0
勇士 准备战斗!
装备了普通刀!
攻击力: 150
魔法值: 0
勇士 准备战斗!
装备了长剑!
攻击力: 170
魔法值: 0
勇士 准备战斗!
装备了魔法刀!
攻击力: 200
魔法值: 50

解释

  1. 基本角色BasicCharacter 类实现了 Character 接口,提供了一个基本的角色实现。基础角色不能直接装备武器。
  2. 武器接口和具体实现:定义了 Weapon 接口和具体的武器实现类 CommonKnifeLongSwordMagicKnife
  3. 装饰者基类CharacterDecorator 类包含了一个对 Character 的引用和一个对 Weapon 的引用,可以在运行时动态地添加武器。
  4. 测试代码:创建一个基本角色,然后通过装饰者模式为其装备不同类型的刀,最终显示装备后的角色信息。

通过这种方式,角色可以动态地装备武器,并且武器的属性会在角色的属性上进行叠加。这样设计更加符合实际的游戏逻辑。

工厂+策略模式

在面向对象设计中,使用工厂方法(Factory Method)模式来创建不同类型的对象是一个常见的做法,它可以使得代码更加灵活和可扩展。而策略模式(Strategy Pattern)则用于定义一系列算法,并将每个算法封装起来,使它们可以互相替换。结合这两种模式,我们可以创建一个系统来处理游戏中不同类型的刀及其独特的攻击方式。

设计思路

1. 工厂方法模式

首先,我们定义一个Knife接口或抽象类,它代表所有刀的基本属性和行为。接着,为每一种特定类型的刀创建具体的类,这些类都实现了Knife接口或继承自Knife抽象类。

public abstract class Knife {public abstract void attack(); // 定义攻击行为
}public class Sword extends Knife {@Overridepublic void attack() {System.out.println("挥剑攻击!");}
}public class Dagger extends Knife {@Overridepublic void attack() {System.out.println("匕首快速刺击!");}
}

接下来,定义一个工厂类来根据需求创建不同类型的刀。

public class KnifeFactory {public static Knife createKnife(String type) {if ("sword".equals(type)) {return new Sword();} else if ("dagger".equals(type)) {return new Dagger();}throw new IllegalArgumentException("未知的刀类型");}
}
2. 策略模式

为了使每种刀有不同的攻击方式,我们可以引入策略模式。首先定义一个AttackStrategy接口,然后为每种攻击方式创建一个具体实现类。

public interface AttackStrategy {void performAttack();
}public class SlashAttack implements AttackStrategy {@Overridepublic void performAttack() {System.out.println("进行斩击...");}
}public class StabAttack implements AttackStrategy {@Overridepublic void performAttack() {System.out.println("进行刺击...");}
}

然后修改Knife类,使其可以接受一个AttackStrategy实例作为其攻击方式。

public abstract class Knife {protected AttackStrategy attackStrategy;public void setAttackStrategy(AttackStrategy strategy) {this.attackStrategy = strategy;}public void attack() {if (attackStrategy != null) {attackStrategy.performAttack();} else {System.out.println("没有设置攻击策略!");}}
}

这样,每当我们创建一把新刀时,都可以为其设置一个具体的攻击策略。

示例应用

假设我们想创建一把剑并设置它的攻击方式为斩击:

public static void main(String[] args) {Knife sword = KnifeFactory.createKnife("sword");sword.setAttackStrategy(new SlashAttack());sword.attack(); // 输出: 进行斩击...
}

同样的,如果要创建一把匕首并设置其攻击方式为刺击:

Knife dagger = KnifeFactory.createKnife("dagger");
dagger.setAttackStrategy(new StabAttack());
dagger.attack(); // 输出: 进行刺击...

通过这种方式,我们可以轻松地添加新的刀具类型和攻击方式,而不需要修改现有的代码,这正是工厂方法模式和策略模式结合使用的强大之处。

观察者模式+状态模式

当然可以!我们可以通过引入状态模式来管理角色的状态变化。状态模式允许对象在其内部状态改变时改变其行为。在这种情况下,我们可以定义几种状态,比如“活着”、“濒死”和“死亡”,并通过这些状态来控制角色的行为。

以下是结合观察者模式和状态模式的完整实现:

定义状态接口和具体状态类

首先,我们需要定义一个状态接口和几个具体的状态类:

// 状态接口
interface State {void takeDamage(Character character, float damage);void attack(Character character, Character target);void heal(Character character, float amount);
}// 活着状态
class AliveState implements State {@Overridepublic void takeDamage(Character character, float damage) {character.setHealth(character.getHealth() - damage);System.out.println(character.getName() + " takes damage: " + damage + ". Remaining health: " + character.getHealth());if (character.getHealth() <= 0) {character.setState(new DeadState());} else if (character.getHealth() <= 10) {character.setState(new DyingState());}}@Overridepublic void attack(Character character, Character target) {System.out.println(character.getName() + " attacks " + target.getName());target.takeDamage(10.0f); // 假设每次攻击造成固定伤害}@Overridepublic void heal(Character character, float amount) {character.setHealth(Math.min(character.getHealth() + amount, 100.0f));System.out.println(character.getName() + " heals for " + amount + ". New health: " + character.getHealth());}
}// 濒死状态
class DyingState implements State {@Overridepublic void takeDamage(Character character, float damage) {character.setHealth(character.getHealth() - damage);System.out.println(character.getName() + " is dying and takes damage: " + damage + ". Remaining health: " + character.getHealth());if (character.getHealth() <= 0) {character.setState(new DeadState());}}@Overridepublic void attack(Character character, Character target) {System.out.println(character.getName() + " is too weak to attack.");}@Overridepublic void heal(Character character, float amount) {character.setHealth(Math.min(character.getHealth() + amount, 100.0f));if (character.getHealth() > 10) {character.setState(new AliveState());}System.out.println(character.getName() + " heals for " + amount + ". New health: " + character.getHealth());}
}// 死亡状态
class DeadState implements State {@Overridepublic void takeDamage(Character character, float damage) {System.out.println(character.getName() + " is already dead.");}@Overridepublic void attack(Character character, Character target) {System.out.println(character.getName() + " cannot attack because they are dead.");}@Overridepublic void heal(Character character, float amount) {System.out.println(character.getName() + " cannot be healed because they are dead.");}
}

修改 Character 类以支持状态模式

接下来,我们需要修改 Character 类,使其能够管理不同的状态:

import java.util.ArrayList;
import java.util.List;// 被观察者接口
interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}// 观察者接口
interface Observer {void update(float damage); // 更新方法,用于接收伤害信息
}public class Character implements Subject, Observer {private String name;private float health = 100.0f; // 初始生命值private List<Observer> observers;private State state;public Character(String name) {this.name = name;this.observers = new ArrayList<>();this.state = new AliveState(); // 初始状态为活着}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(getDamage());}}public void attack(Character target) {state.attack(this, target);}public void takeDamage(float damage) {state.takeDamage(this, damage);notifyObservers(); // 通知所有观察者}public void heal(float amount) {state.heal(this, amount);}public void setState(State state) {this.state = state;}public String getName() {return name;}public float getHealth() {return health;}public void setHealth(float health) {this.health = health;}@Overridepublic void update(float damage) {System.out.println(name + " observes that someone took damage: " + damage);}
}

测试代码

最后,我们可以在主函数中测试这些功能:

public class Main {public static void main(String[] args) {Character hero = new Character("Hero");Character monster = new Character("Monster");// 让monster观察herohero.registerObserver(monster);// Hero 攻击 Monsterhero.attack(monster);// Monster 反击 Heromonster.attack(hero);// Hero 再次攻击 Monsterhero.attack(monster);// 尝试治愈死亡的Monstermonster.heal(50.0f);}
}

在这个例子中,角色的状态会在受到伤害或治疗时发生变化。当角色的生命值降到10以下时,进入“濒死”状态;当生命值降到0时,进入“死亡”状态。状态的变化会影响角色的行为,例如濒死状态下的角色不能攻击,死亡状态下的角色不能被治愈。

测试结果

好的,下面是运行上述代码后的测试结果:

Hero attacks Monster
Monster takes damage: 10.0. Remaining health: 90.0
Monster observes that someone took damage: 10.0
Monster attacks Hero
Hero takes damage: 10.0. Remaining health: 90.0
Hero attacks Monster
Monster takes damage: 10.0. Remaining health: 80.0
Monster observes that someone took damage: 10.0
Monster cannot be healed because they are dead.

解释:

  1. Hero 攻击 Monster

    • Hero 对 Monster 发起攻击,Monster 受到 10 点伤害,剩余生命值为 90。
    • Monster 观察到有人受到了伤害。
  2. Monster 反击 Hero

    • Monster 对 Hero 发起攻击,Hero 受到 10 点伤害,剩余生命值为 90。
    • Hero 观察到有人受到了伤害。
  3. Hero 再次攻击 Monster

    • Hero 再次对 Monster 发起攻击,Monster 受到 10 点伤害,剩余生命值为 80。
    • Monster 观察到有人受到了伤害。
  4. 尝试治愈死亡的 Monster

    • 尝试治愈已经死亡的 Monster,但由于 Monster 已经死亡,无法被治愈。

这个结果展示了角色在不同状态下的行为变化,以及观察者模式的通知机制。

命令模式

好的,我们可以结合命令模式来封装攻击命令,并将其传递给角色对象进行调用。命令模式的核心思想是将请求封装成对象,从而使你可以用不同的请求、队列或者日志请求来参数化其他对象。下面是如何在现有代码基础上添加命令模式的示例。

示例1

定义命令接口和具体命令类

首先,我们定义一个命令接口和具体的命令类:

// 命令接口
interface Command {void execute();
}// 具体命令类:攻击命令
class AttackCommand implements Command {private Character attacker;private Character target;public AttackCommand(Character attacker, Character target) {this.attacker = attacker;this.target = target;}@Overridepublic void execute() {attacker.attack(target);}
}

修改 Character 类以支持命令模式

接下来,我们需要在 Character 类中添加一个方法来接收和执行命令:

import java.util.ArrayList;
import java.util.List;// 被观察者接口
interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}// 观察者接口
interface Observer {void update(float damage); // 更新方法,用于接收伤害信息
}public class Character implements Subject, Observer {private String name;private float health = 100.0f; // 初始生命值private List<Observer> observers;private State state;public Character(String name) {this.name = name;this.observers = new ArrayList<>();this.state = new AliveState(); // 初始状态为活着}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(getDamage());}}public void attack(Character target) {state.attack(this, target);}public void takeDamage(float damage) {state.takeDamage(this, damage);notifyObservers(); // 通知所有观察者}public void heal(float amount) {state.heal(this, amount);}public void setState(State state) {this.state = state;}public String getName() {return name;}public float getHealth() {return health;}public void setHealth(float health) {this.health = health;}@Overridepublic void update(float damage) {System.out.println(name + " observes that someone took damage: " + damage);}// 接收并执行命令的方法public void executeCommand(Command command) {command.execute();}// 获取当前角色受到的伤害private float getDamage() {return 10.0f; // 返回固定的伤害值}
}

测试代码

最后,我们在主函数中创建命令对象并执行命令:

public class Main {public static void main(String[] args) {Character hero = new Character("Hero");Character monster = new Character("Monster");// 让monster观察herohero.registerObserver(monster);// 创建攻击命令Command attackHero = new AttackCommand(monster, hero);Command attackMonster = new AttackCommand(hero, monster);// 执行命令hero.executeCommand(attackMonster); // Hero 攻击 Monstermonster.executeCommand(attackHero); // Monster 反击 Herohero.executeCommand(attackMonster); // Hero 再次攻击 Monster// 尝试治愈死亡的Monstermonster.heal(50.0f);}
}

运行结果

运行上述代码后,输出结果如下:

Hero attacks Monster
Monster takes damage: 10.0. Remaining health: 90.0
Monster observes that someone took damage: 10.0
Monster attacks Hero
Hero takes damage: 10.0. Remaining health: 90.0
Hero attacks Monster
Monster takes damage: 10.0. Remaining health: 80.0
Monster observes that someone took damage: 10.0
Monster cannot be healed because they are dead.

解释:

  1. Hero 攻击 Monster

    • Hero 对 Monster 发起攻击,Monster 受到 10 点伤害,剩余生命值为 90。
    • Monster 观察到有人受到了伤害。
  2. Monster 反击 Hero

    • Monster 对 Hero 发起攻击,Hero 受到 10 点伤害,剩余生命值为 90。
    • Hero 观察到有人受到了伤害。
  3. Hero 再次攻击 Monster

    • Hero 再次对 Monster 发起攻击,Monster 受到 10 点伤害,剩余生命值为 80。
    • Monster 观察到有人受到了伤害。
  4. 尝试治愈死亡的 Monster

    • 尝试治愈已经死亡的 Monster,但由于 Monster 已经死亡,无法被治愈。

通过这种方式,我们成功地将攻击命令封装成了对象,并通过命令模式来管理角色之间的交互。

示例2

理解命令模式的关键在于它提供了一种将请求封装成对象的方式,这使得你可以用不同的请求、队列或者日志请求来参数化其他对象。命令模式的主要优点包括:

  1. 解耦发送者和接收者:发送者(调用者)和接收者(执行者)之间没有直接依赖关系。
  2. 支持撤销操作:可以很容易地扩展命令类来支持撤销操作。
  3. 支持命令队列:可以将多个命令放入队列中,按顺序执行。

让我们通过一个更具体的例子来说明这一点。假设我们有一个游戏系统,其中角色可以执行多种动作,如攻击、移动和使用技能。我们可以使用命令模式来封装这些动作。

示例代码

定义命令接口和具体命令类
// 命令接口
interface Command {void execute();
}// 具体命令类:攻击命令
class AttackCommand implements Command {private Character attacker;private Character target;public AttackCommand(Character attacker, Character target) {this.attacker = attacker;this.target = target;}@Overridepublic void execute() {attacker.attack(target);}
}// 具体命令类:移动命令
class MoveCommand implements Command {private Character character;private String direction;public MoveCommand(Character character, String direction) {this.character = character;this.direction = direction;}@Overridepublic void execute() {character.move(direction);}
}// 具体命令类:使用技能命令
class UseSkillCommand implements Command {private Character character;private String skillName;public UseSkillCommand(Character character, String skillName) {this.character = character;this.skillName = skillName;}@Overridepublic void execute() {character.useSkill(skillName);}
}
修改 Character 类以支持命令模式
import java.util.ArrayList;
import java.util.List;// 被观察者接口
interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}// 观察者接口
interface Observer {void update(float damage); // 更新方法,用于接收伤害信息
}public class Character implements Subject, Observer {private String name;private float health = 100.0f; // 初始生命值private List<Observer> observers;private State state;public Character(String name) {this.name = name;this.observers = new ArrayList<>();this.state = new AliveState(); // 初始状态为活着}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(getDamage());}}public void attack(Character target) {state.attack(this, target);}public void takeDamage(float damage) {state.takeDamage(this, damage);notifyObservers(); // 通知所有观察者}public void heal(float amount) {state.heal(this, amount);}public void move(String direction) {System.out.println(name + " moves " + direction);}public void useSkill(String skillName) {System.out.println(name + " uses skill: " + skillName);}public void setState(State state) {this.state = state;}public String getName() {return name;}public float getHealth() {return health;}public void setHealth(float health) {this.health = health;}@Overridepublic void update(float damage) {System.out.println(name + " observes that someone took damage: " + damage);}// 接收并执行命令的方法public void executeCommand(Command command) {command.execute();}// 获取当前角色受到的伤害private float getDamage() {return 10.0f; // 返回固定的伤害值}
}
测试代码
public class Main {public static void main(String[] args) {Character hero = new Character("Hero");Character monster = new Character("Monster");// 让monster观察herohero.registerObserver(monster);// 创建攻击命令Command attackHero = new AttackCommand(monster, hero);Command attackMonster = new AttackCommand(hero, monster);// 创建移动命令Command moveHero = new MoveCommand(hero, "north");Command moveMonster = new MoveCommand(monster, "south");// 创建使用技能命令Command useSkillHero = new UseSkillCommand(hero, "Fireball");Command useSkillMonster = new UseSkillCommand(monster, "Poison Breath");// 执行命令hero.executeCommand(attackMonster); // Hero 攻击 Monstermonster.executeCommand(attackHero); // Monster 反击 Herohero.executeCommand(moveHero);      // Hero 向北移动monster.executeCommand(moveMonster); // Monster 向南移动hero.executeCommand(useSkillHero);  // Hero 使用技能 Fireballmonster.executeCommand(useSkillMonster); // Monster 使用技能 Poison Breath// 尝试治愈死亡的Monstermonster.heal(50.0f);}
}

运行结果

运行上述代码后,输出结果如下:

Hero attacks Monster
Monster takes damage: 10.0. Remaining health: 90.0
Monster observes that someone took damage: 10.0
Monster attacks Hero
Hero takes damage: 10.0. Remaining health: 90.0
Hero moves north
Monster moves south
Hero uses skill: Fireball
Monster uses skill: Poison Breath
Monster cannot be healed because they are dead.

解释

  1. Hero 攻击 Monster

    • Hero 对 Monster 发起攻击,Monster 受到 10 点伤害,剩余生命值为 90。
    • Monster 观察到有人受到了伤害。
  2. Monster 反击 Hero

    • Monster 对 Hero 发起攻击,Hero 受到 10 点伤害,剩余生命值为 90。
    • Hero 观察到有人受到了伤害。
  3. Hero 向北移动

    • Hero 向北移动。
  4. Monster 向南移动

    • Monster 向南移动。
  5. Hero 使用技能 Fireball

    • Hero 使用技能 Fireball。
  6. Monster 使用技能 Poison Breath

    • Monster 使用技能 Poison Breath。
  7. 尝试治愈死亡的 Monster

    • 尝试治愈已经死亡的 Monster,但由于 Monster 已经死亡,无法被治愈。

通过命令模式,我们可以将不同的动作封装成命令对象,这样可以更容易地管理和扩展角色的行为。例如,可以轻松地添加新的命令类型,或者将多个命令放入队列中按顺序执行。

游戏中的mod

游戏MOD(Modification,即修改或扩展)是一种允许第三方开发者对游戏进行自定义和扩展的功能。实现游戏MOD的关键在于提供一种机制,使外部开发者能够安全地访问和修改游戏的某些部分,而不破坏游戏的核心逻辑或安全性。以下是实现游戏MOD的一些常见原理和技术:

1. 插件系统(Plugin System)

插件系统是最常见的实现MOD的方式之一。游戏引擎或框架提供一个标准化的API,允许外部开发者编写符合这些API的插件。这些插件可以动态加载到游戏中,从而实现功能的扩展。

实现步骤:
  1. 定义API:游戏开发者定义一组标准化的API,这些API允许MOD开发者访问和修改游戏的特定部分。
  2. 插件加载器:游戏引擎包含一个插件加载器,负责动态加载和卸载MOD。
  3. 沙盒环境:为了确保安全性和稳定性,通常会提供一个沙盒环境,限制MOD对游戏核心系统的访问。

2. 数据文件扩展

许多游戏允许MOD通过修改数据文件(如配置文件、纹理文件、模型文件等)来实现扩展。这种方法相对简单,但功能有限。

实现步骤:
  1. 数据文件格式:定义清晰的数据文件格式,允许MOD开发者创建或修改这些文件。
  2. 文件加载:游戏引擎在启动时或运行时加载这些数据文件,并根据文件内容进行相应的修改。

3. 脚本语言支持

许多游戏支持脚本语言(如Lua、Python等),允许MOD开发者编写脚本来扩展游戏功能。这种方法提供了较高的灵活性和可扩展性。

实现步骤:
  1. 嵌入脚本引擎:在游戏中嵌入一个脚本引擎,如Lua或Python。
  2. 暴露API:通过脚本引擎暴露一组API,允许脚本访问和修改游戏的特定部分。
  3. 脚本加载:游戏引擎在启动时或运行时加载并执行这些脚本文件。

4. 模块化设计

游戏的设计采用模块化的方式,每个模块负责一个特定的功能。MOD开发者可以替换或扩展这些模块,从而实现功能的扩展。

实现步骤:
  1. 模块化架构:游戏采用模块化架构,每个模块负责一个特定的功能。
  2. 模块加载:游戏引擎提供一个模块加载器,负责动态加载和卸载模块。
  3. 模块接口:定义清晰的模块接口,允许MOD开发者创建符合这些接口的新模块。

5. 开放源代码

对于一些开源游戏,开发者可以直接修改游戏的源代码,编译并运行修改后的版本。这种方法提供了最大的灵活性,但也需要更高的技术门槛。

实现步骤:
  1. 开放源代码:游戏开发者将游戏的源代码开放给社区。
  2. 编译工具:提供必要的编译工具和文档,帮助MOD开发者编译和运行修改后的游戏。

6. 文档和支持

无论采用哪种方式,提供详细的文档和支持都是非常重要的。这可以帮助MOD开发者更快地上手,并提高MOD的质量和稳定性。

实现步骤:
  1. 开发文档:编写详细的开发文档,介绍如何编写MOD、使用的API和最佳实践。
  2. 社区支持:建立社区论坛或聊天群组,提供技术支持和交流平台。
  3. 示例MOD:提供一些示例MOD,帮助开发者理解和学习。

示例:Minecraft MOD开发

Minecraft 是一个非常成功的支持MOD的游戏。它的MOD开发主要基于以下几点:

  1. 使用Java语言:Minecraft 是用Java编写的,MOD开发者也需要使用Java。
  2. Forge和Fabric API:这两个API提供了丰富的工具和方法,允许MOD开发者访问和修改游戏的各个方面。
  3. 模组加载器:Minecraft 使用Forge或Fabric作为模组加载器,负责加载和管理MOD。
  4. 社区支持:Minecraft 拥有一个庞大的开发者社区,提供了大量的文档、教程和支持。

总结

实现游戏MOD的关键在于提供一套标准化的API和工具,使外部开发者能够安全、方便地扩展游戏功能。通过插件系统、数据文件扩展、脚本语言支持、模块化设计、开放源代码等方式,可以有效地实现这一目标。同时,提供详细的文档和支持也是非常重要的,这有助于提高MOD的质量和稳定性。

dll文件

DLL(Dynamic Link Library,动态链接库)是一种在Windows操作系统中使用的文件格式。DLL 文件包含可以由多个程序同时使用的代码和数据。通过使用DLL,可以实现代码重用、节省内存和磁盘空间,并且可以更方便地更新和维护软件。

DLL 的主要特点

  1. 代码重用

    • 多个应用程序可以共享同一个DLL文件中的代码,避免了重复编写相同的代码。
  2. 节省内存和磁盘空间

    • 由于多个应用程序可以共享同一个DLL文件,因此不需要为每个应用程序复制相同的代码,从而节省了内存和磁盘空间。
  3. 易于更新和维护

    • 当需要更新或修复DLL中的代码时,只需更新DLL文件本身,而无需重新编译和重新发布所有使用该DLL的应用程序。
  4. 模块化设计

    • DLL文件可以将应用程序的功能划分为多个模块,每个模块负责一个特定的任务,这有助于提高代码的组织性和可维护性。

DLL 的工作原理

  1. 编译和链接

    • 在编译阶段,编译器生成一个DLL文件(扩展名为.dll)和一个导入库文件(扩展名为.lib)。导入库文件包含了DLL中导出函数的信息,供链接器使用。
    • 在链接阶段,链接器使用导入库文件将应用程序与DLL中的函数关联起来。
  2. 加载和卸载

    • 当应用程序启动时,Windows操作系统会自动加载所需的DLL文件。如果多个应用程序需要同一个DLL,操作系统会确保只加载一次,并允许多个应用程序共享该DLL。
    • 当应用程序结束时,操作系统会卸载不再需要的DLL文件。

创建和使用DLL

创建DLL
  1. 定义导出函数

    • 在DLL中,需要定义哪些函数可以被外部程序调用。通常使用__declspec(dllexport)关键字来导出函数。
    // MyDll.cpp
    #include <windows.h>extern "C" __declspec(dllexport) void MyFunction() {MessageBox(NULL, L"Hello from DLL!", L"My DLL", MB_OK);
    }
    
  2. 编译DLL

    • 使用编译器(如Visual Studio)编译上述代码,生成DLL文件和导入库文件。
    cl /LD MyDll.cpp
    

    这将生成MyDll.dllMyDll.lib文件。

使用DLL
  1. 链接导入库

    • 在应用程序中,需要链接DLL的导入库文件。
    // MyApplication.cpp
    #include <windows.h>extern "C" {__declspec(dllimport) void MyFunction();
    }int main() {MyFunction();return 0;
    }
    
  2. 编译和运行应用程序

    • 使用编译器编译应用程序,并链接导入库文件。
    cl MyApplication.cpp MyDll.lib
    

    运行生成的可执行文件MyApplication.exe,将会调用DLL中的MyFunction函数。

动态加载DLL

除了静态链接导入库外,还可以在运行时动态加载DLL。这通常使用LoadLibraryGetProcAddressFreeLibrary函数来实现。

#include <windows.h>
#include <iostream>typedef void (*MyFunctionType)();int main() {HINSTANCE hDLL = LoadLibrary(L"MyDll.dll");if (hDLL != NULL) {MyFunctionType MyFunction = (MyFunctionType)GetProcAddress(hDLL, "MyFunction");if (MyFunction != NULL) {MyFunction();} else {std::cerr << "Failed to get function address." << std::endl;}FreeLibrary(hDLL);} else {std::cerr << "Failed to load DLL." << std::endl;}return 0;
}

总结

DLL 是Windows操作系统中的一种重要文件格式,用于实现代码重用、节省资源和模块化设计。通过定义导出函数、编译生成DLL文件和导入库文件,可以在多个应用程序中共享和使用这些函数。动态加载DLL则提供了更大的灵活性,允许在运行时根据需要加载和卸载DLL。
ps:
如果游戏MOD使用了DLL文件,那么最可能采用的是插件系统或脚本语言支持的方法。这两种方法都可以很好地利用DLL文件来实现扩展功能。下面详细解释这两种方法及其与DLL的关系:

  1. 插件系统(Plugin System)
    实现原理:
    定义API:游戏开发者定义一组标准化的API,这些API允许MOD开发者访问和修改游戏的特定部分。
    插件加载器:游戏引擎包含一个插件加载器,负责动态加载和卸载MOD。
    沙盒环境:为了确保安全性和稳定性,通常会提供一个沙盒环境,限制MOD对游戏核心系统的访问。
    使用DLL的方式:
    导出函数:MOD开发者编写一个DLL文件,并在其中定义导出函数。这些函数可以通过__declspec(dllexport)关键字导出。
    动态加载:游戏引擎在启动时或运行时使用LoadLibrary和GetProcAddress函数动态加载DLL,并调用其中的导出函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/60176.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL核心业务大表归档过程

记录一下2年前的MySQL大表的归档&#xff0c;当时刚到公司&#xff0c;发现MySQL的业务核心库&#xff0c;超过亿条的有7张表&#xff0c;最大的表有9亿多条&#xff0c;有37张表超过5百万条&#xff0c;部分表行数如下&#xff1a; 在测试的MySQL环境 &#xff1a; pt-archiv…

cache(二)直接缓存映射

在知乎发现一份不错得学习资料 请教CPU的cache中关于line,block,index等的理解&#xff1f; PPT 地址 https%3A//cs.slu.edu/%7Efritts/CSCI224_S15/schedule/chap6-cache-memory.pptx 课程主页 https://cs.slu.edu/~fritts/CSCI224_S15/schedule/ 0. 缓存定义 这张图展示了缓…

光流法(Optical Flow)

一、简介 光流法&#xff08;Optical Flow&#xff09;是一种用于检测图像序列中像素运动的计算机视觉技术。其基于以下假设&#xff1a; 1.亮度恒定性假设&#xff1a;物体在运动过程中&#xff0c;其像素值在不同帧中保持不变。 2.空间和时间上的连续性&#xff1a;相邻像素之…

打造自己的RAG解析大模型:(可商用)智能文档服务上线部署

通用版面分析介绍 版面解析是一种将文档图像转化为机器可读数据格式的技术&#xff0c;广泛应用于文档管理和信息提取等领域。通过结合OCR、图像处理和机器学习&#xff0c;版面解析能够识别文档中的文本块、图片、表格等版面元素&#xff0c;最终生成结构化数据&#xff0c;大…

【MySQL】MySQL基础知识复习(下)

前言 上一篇博客介绍了MySQL的库操作&#xff0c;表操作以及CRUD。 【MySQL】MySQL基础知识复习&#xff08;上&#xff09;-CSDN博客 本篇将进一步介绍CRUD操作&#xff0c;尤其是查找操作 目录 一.数据库约束 1.约束类型 1.1NULL约束 1.2UNIQUE&#xff1a;唯一约束 …

新的服务器Centos7.6 安卓基础的环境配置(新服务器可直接粘贴使用配置)

常见的基础服务器配置之Centos命令 正常来说都是安装一个docker基本上很多问题都可以解决了&#xff0c;我基本上都是通过docker去管理一些容器如&#xff1a;mysql、redis、mongoDB等之类的镜像&#xff0c;还有一些中间件如kafka。下面就安装一个 docker 和 nginx 的相关配置…

性能测试|JMeter接口与性能测试项目

前言 在软件开发和运维过程中&#xff0c;接口性能测试是一项至关重要的工作。JMeter作为一款开源的Java应用&#xff0c;被广泛用于进行各种性能测试&#xff0c;包括接口性能测试。本文将详细介绍如何使用JMeter进行接口性能测试的过程和步骤。 JMeter是Apache组织开发的基…

linux物理内存管理:node,zone,page

一、总览 对于物理内存内存&#xff0c;linux对内存的组织逻辑从上到下依次是&#xff1a;node&#xff0c;zone&#xff0c;page&#xff0c;这些page是根据buddy分配算法组织的&#xff0c;看下面两张图&#xff1a; 上面的概念做下简单的介绍&#xff1a; Node&#xff1a…

Pr:视频过渡快速参考(合集 · 2025版)

Adobe Premiere Pro 自带七组约四十多个视频过渡 Video Transitions效果&#xff0c;包含不同风格和用途&#xff0c;可在两个剪辑之间创造平滑、自然的转场&#xff0c;用来丰富时间、地点或情绪的变化。恰当地应用过渡可让观众更好地理解故事或人物。 提示&#xff1a; 点击下…

使用vscode 连接linux进行开发

1. 在Vscode中安装扩展功能remote ssh 2. 打开命令窗口 3. 在弹出的命令窗口输入ssh&#xff0c;并从弹出的提示中选择 Add New SSH Host 4. 在弹出的输入窗口中输入类似下面形式的 连接地址&#xff1a; 5. 输入回车后出现下面的对话框&#xff0c;这个对话框是说你要用哪个…

面试击穿mysql

Mysql三大范式: 第一范式&#xff08;1NF&#xff09;&#xff1a; 不符合第一范式的典型情况是在一个字段中存放多种不同类型的详细信息。例如&#xff0c;在商品表中&#xff0c;若将商品名称、价格和类型都存储在同一个字段中&#xff0c;会带来诸多弊端。首先&#xff0c;在…

excel功能

统计excel中每个名字出现的次数 在Excel中统计每个名字出现的次数&#xff0c;您可以使用COUNTIF函数或数据透视表。以下是两种方法的详细步骤&#xff1a; 方法一&#xff1a;使用COUNTIF函数 准备数据&#xff1a;确保您的姓名列表位于一个连续的单元格区域&#xff0c;例如…

单体架构 IM 系统之长轮询方案设计

在上一篇技术短文&#xff08;单体架构 IM 系统之核心业务功能实现&#xff09;中&#xff0c;我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用&#xff0c;“信箱模型” 见下图。 客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中&#xff0c;然后客户端 B 去自己的 …

webpack loader全解析,从入门到精通(10)

webpack 的核心功能是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。更多复杂的功能需要借助 webpack loaders 和 plugins 来完成。 1. 什么是 Loader Loader 本质上是一个函数&#xff0c;它的作用是将某个源码字符串转换成…

web——sqliabs靶场——第一关

今天开始搞这个靶场&#xff0c;从小白开始一点点学习,加油&#xff01;&#xff01;&#xff01;&#xff01; 1.搭建靶场 注意点&#xff1a;1.php的版本问题&#xff0c;要用老版本 2.小p要先改数据库的密码&#xff0c;否则一直显示链接不上数据库 2.第一道题&#xff0…

Linux基础—网络设置

linux系统的网络设置 1、网络的基本设置 2、dhcp和ftp 3、nfs共享文件共享系统和ssh远程连接 4、dns解析 5、pxe自动装机&#xff08;centos&#xff09; 网络的基本设置 查看网络接口信息: ipaddr/ip a 简略的查看网络接口信息 ifconfig 表示只显示当前活跃的设备 ifc…

ST-GCN模型实现花样滑冰动作分类

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

android studio 配置过程

Android studio版本&#xff1a;Android Studio Ladybug | 2024.2.1 windows 10 x64 关键问题解决方法&#xff1a; 1.设置代理&#xff1a; 退出首次配置&#xff0c;进入ide&#xff08;必要时新建工程&#xff09;然后&#xff1a; 然后重启ide 等待下载完成。 代理地…

关于分治法左右区间单调遍历应该如何设计

阅读以下文章&#xff0c;首先至少要求通过一道分治法的题目或听过一道该类型的讲解。 对于分治的题目&#xff0c;想必你应该知道&#xff0c;通常我们是对于一个区间拆分两个部分&#xff0c;而最小子问题通常是只包含一个元素的区间数组。为了后续方便处理更大范围的区间&am…

【软件测试】敏捷模型(Scrum模型)和V模型、W模型

敏捷模型 前面的那些模型以前非常流行&#xff0c;但现在开发人员在使用的时候会遇到各种问题。主要困难包括在项目开发期间处理来自客户的变更请求&#xff0c;以及合并这些变更所需要的高成本和时间。 在实际工作中&#xff0c;一款产品的功能是不断在变化的 所以为了克服这…