当前位置: 首页 > news >正文

游戏中的设计模式及杂项

概述

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

  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,并调用其中的导出函数。

相关文章:

游戏中的设计模式及杂项

概述 如果要做以下游戏功能会用到哪些设计模式。比如创建一个人物角色&#xff0c;这个角色可以装备刀&#xff0c;然后角色可以用刀砍怪物&#xff0c;造成流血。 对于这个游戏功能&#xff0c;可以使用以下设计模式&#xff1a; 工厂模式&#xff08;Factory Pattern&#x…...

Docker网络和overlay的基础讲解

本人发现了两篇写的不错的文章&#xff1a;Docker网络 - docker network详解-CSDN博客&#xff0c;Docker 容器跨主机通信 overlay_docker overlay 网络-CSDN博客 因为这两篇文章中含有大量的例子&#xff0c;新手看起来毫不费力。于是我偷了个小懒&#xff0c;在本篇文章中没有…...

分布式数据库:深入探讨架构、挑战与未来趋势

引言 在数字化时代&#xff0c;数据已成为企业的核心资产。随着数据量的爆炸性增长和业务需求的多样化&#xff0c;传统的集中式数据库已难以满足现代应用对于高可用性、可扩展性和性能的需求。分布式数据库以其独特的优势&#xff0c;如数据的高可用性、容错性和可扩展性&…...

基于Springboot+Vue的仓库管理系统 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 这个系…...

基于立体连接与开源链动 2+1 模式的新商业路径探索

摘要&#xff1a;本文深入剖析了立体连接的内涵&#xff0c;包括其核心关键词、连接路径与主体&#xff0c;同时详细阐述了开源链动 2 1 模式、AI 智能名片和 S2B2C 商城小程序源码的特点与功能。在此基础上&#xff0c;深入研究这些要素的融合方式及其在商业实践中的应用&…...

开启鸿蒙开发之旅:核心组件及其各项属性介绍——布局容器组件

写在前面 组件的结构 rkTS通过装饰器 Component 和 Entry 装饰 struct 关键字声明的数据结构&#xff0c;构成一个自定义组件。 自定义组件中提供了一个 build 函数&#xff0c;开发者需在该函数内以链式调用的方式进行基本的 UI 描述 今天我们要学习的就是写在build 函数里的系…...

RabbitMQ 全面解析:语法与其他消息中间件的对比分析

1. 引言 在分布式系统和微服务架构中&#xff0c;消息中间件扮演着重要的角色。它们能够解耦服务、平衡负载、提高系统的可扩展性和可靠性。RabbitMQ 是其中广受欢迎的一种。本文将从 RabbitMQ 的基础概念、语法介绍、以及与其他消息中间件的对比角度&#xff0c;全面剖析其在…...

Three.js 搭建3D隧道监测

Three.js 搭建3D隧道监测 Three.js 基础元素场景scene相机carema网络模型Mesh光源light渲染器renderer控制器controls 实现3d隧道监测基础实现道路实现隧道实现多个摄像头点击模型进行属性操作实现点击模型发光效果 性能监视器stats引入使用 总结完整代码 我们将通过three.js技…...

「IDE」集成开发环境专栏目录大纲

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「IDE」集成开发环境&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定…...

MySQL-初识数据库

目录 一、数据库基础概念 1、SQL 2、数据&#xff08;Data&#xff09; 3、数据库&#xff08;DB&#xff09; 4、数据库管理系统DBMS 5、数据库系统DBS 6、关系模型&#xff08;Relational Model&#xff09; 7、E-R图 8、常见的数据库 9、数据库基本操作 一、数据库…...

初始 html

html 文件结构 html 标签是整个 html 文件的根标签(最顶层标签) head 标签中写页面的属性. body 标签中写的是页面上显示的内容 title 标签中写的是页面的标题 <html><head><title>这是一个标题</title></head><body></body> <…...

前端 call、bind、apply的实际使用

目录 一、call 1、继承的子类可以使用父类的方法 2、可以接收任意参数 二、call、apply、bind比较 1、案例一 2、案例二 三、总结 这个三个方法都是改变函数的this指向的方法。 一、call 代码&#xff1a; const obj{uname:"pink"}function fn(){console.log…...

非关系型数据库NoSQL的类型与优缺点对比

NoSQL数据库根据数据模型和应用场景主要分为四种类型&#xff1a;键值型、列族型、文档型和图形型。以下是对每种类型的详细描述&#xff0c;包括其应用场景、优缺点的比较&#xff1a; 1. 键值型数据库 (Key-Value Store) 典型代表 RedisMemcachedAmazon DynamoDB 应用场景…...

面试击穿mysql

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

PyQt5超详细教程终篇

PyQt5超详细教程 前言 接&#xff1a; [【Python篇】PyQt5 超详细教程——由入门到精通&#xff08;序篇&#xff09;](【Python篇】PyQt5 超详细教程——由入门到精通&#xff08;序篇&#xff09;-CSDN博客) 建议把代码复制到pycahrm等IDE上面看实际效果&#xff0c;方便理…...

Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别

目录 一、概念 1、纹理过滤 2、邻近过滤 3、线性过滤 二、邻近过滤和线性过滤的区别 三、源码下载 一、概念 1、纹理过滤 当纹理被应用到三维物体上时&#xff0c;随着物体表面的形状和相机视角的变化&#xff0c;会导致纹理在渲染过程中出现一些问题&#xff0c;如锯齿…...

Elasticsearch实战应用:从入门到精通

在当今这个数据爆炸的时代&#xff0c;如何快速、有效地从海量数据中检索信息&#xff0c;已经成为了许多企业和开发者面临的挑战。Elasticsearch&#xff0c;作为一个基于Lucene的搜索引擎&#xff0c;以其强大的全文搜索能力、分布式特性以及易用性&#xff0c;成为了解决这一…...

axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)

fetch 是啥&#xff1f; fetch 函数是 JavaScript 中用于发送网络请求的内置 API&#xff0c;可以替代传统的 XMLHttpRequest。它可以发送 HTTP 请求&#xff08;如 GET、POST 等&#xff09;&#xff0c;并返回一个 Promise&#xff0c;从而简化异步操作 基本用法 /* 下面是…...

【优选算法 — 滑动窗口】水果成篮 找到字符串中所有字母异位词

水果成篮 水果成篮 题目描述 因为只有两个篮子&#xff0c;每个篮子装的水果种类相同&#xff0c;如果从 0 开始摘&#xff0c;则只能摘 0 和 1 两个种类 &#xff1b; 因为当我们在两个果篮都装有水果的情况下&#xff0c;如果再走到下一颗果树&#xff0c;果树的水果种类…...

Go 数据库查询与结构体映射

下面是关于如何使用 Go 进行数据库查询并映射数据到结构体的教程&#xff0c;重点讲解 结构体字段导出 和 db 标签 的使用。 Go 数据库查询与结构体映射教程 在 Go 中&#xff0c;我们可以使用 database/sql 或 sqlx 等库与数据库进行交互。为了方便地将数据库查询结果映射到结…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...