游戏中的设计模式及杂项

概述

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

对于这个游戏功能,可以使用以下设计模式:

  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. 定义角色接口和基本实现

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

java 复制代码
// 角色接口
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;
    }

    @Override
    public void display() {
        System.out.println(name + " 准备战斗!");
    }

    @Override
    public int getAttackPower() {
        return attackPower;
    }

    @Override
    public void equip(Weapon weapon) {
        throw new UnsupportedOperationException("基础角色不能直接装备武器");
    }
}

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

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

java 复制代码
// 武器接口
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;
    }

    @Override
    public void display() {
        System.out.println("装备了普通刀!");
    }

    @Override
    public int getAdditionalAttackPower() {
        return additionalAttackPower;
    }
}

// 长剑
public class LongSword implements Weapon {
    private int additionalAttackPower;

    public LongSword(int additionalAttackPower) {
        this.additionalAttackPower = additionalAttackPower;
    }

    @Override
    public void display() {
        System.out.println("装备了长剑!");
    }

    @Override
    public 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;
    }

    @Override
    public void display() {
        System.out.println("装备了魔法刀!");
    }

    @Override
    public int getAdditionalAttackPower() {
        return additionalAttackPower;
    }

    @Override
    public int getAdditionalMagicPower() {
        return additionalMagicPower;
    }
}

3. 定义装饰者基类

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

java 复制代码
// 装饰者基类
public abstract class CharacterDecorator implements Character {
    protected Character character;
    protected Weapon weapon;

    public CharacterDecorator(Character character) {
        this.character = character;
    }

    @Override
    public void display() {
        character.display();
        if (weapon != null) {
            weapon.display();
        }
    }

    @Override
    public int getAttackPower() {
        if (weapon != null) {
            return character.getAttackPower() + weapon.getAdditionalAttackPower();
        }
        return character.getAttackPower();
    }

    @Override
    public int getMagicPower() {
        if (weapon instanceof MagicKnife) {
            return character.getMagicPower() + ((MagicKnife) weapon).getAdditionalMagicPower();
        }
        return character.getMagicPower();
    }

    @Override
    public void equip(Weapon weapon) {
        this.weapon = weapon;
    }
}

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

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

更新角色接口
java 复制代码
public interface Character {
    void display();
    int getAttackPower();
    int getMagicPower();
    void equip(Weapon weapon);
}
更新基本角色实现
java 复制代码
public class BasicCharacter implements Character {
    private String name;
    private int attackPower;

    public BasicCharacter(String name, int attackPower) {
        this.name = name;
        this.attackPower = attackPower;
    }

    @Override
    public void display() {
        System.out.println(name + " 准备战斗!");
    }

    @Override
    public int getAttackPower() {
        return attackPower;
    }

    @Override
    public int getMagicPower() {
        return 0; // 基本角色默认没有魔法值
    }

    @Override
    public void equip(Weapon weapon) {
        throw new UnsupportedOperationException("基础角色不能直接装备武器");
    }
}

5. 更新测试代码

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

java 复制代码
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抽象类。

java 复制代码
public abstract class Knife {
    public abstract void attack(); // 定义攻击行为
}

public class Sword extends Knife {
    @Override
    public void attack() {
        System.out.println("挥剑攻击!");
    }
}

public class Dagger extends Knife {
    @Override
    public void attack() {
        System.out.println("匕首快速刺击!");
    }
}

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

java 复制代码
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接口,然后为每种攻击方式创建一个具体实现类。

java 复制代码
public interface AttackStrategy {
    void performAttack();
}

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

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

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

java 复制代码
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("没有设置攻击策略!");
        }
    }
}

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

示例应用

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

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

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

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

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

观察者模式+状态模式

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

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

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

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

java 复制代码
// 状态接口
interface State {
    void takeDamage(Character character, float damage);
    void attack(Character character, Character target);
    void heal(Character character, float amount);
}

// 活着状态
class AliveState implements State {
    @Override
    public 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());
        }
    }

    @Override
    public void attack(Character character, Character target) {
        System.out.println(character.getName() + " attacks " + target.getName());
        target.takeDamage(10.0f); // 假设每次攻击造成固定伤害
    }

    @Override
    public 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 {
    @Override
    public 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());
        }
    }

    @Override
    public void attack(Character character, Character target) {
        System.out.println(character.getName() + " is too weak to attack.");
    }

    @Override
    public 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 {
    @Override
    public void takeDamage(Character character, float damage) {
        System.out.println(character.getName() + " is already dead.");
    }

    @Override
    public void attack(Character character, Character target) {
        System.out.println(character.getName() + " cannot attack because they are dead.");
    }

    @Override
    public void heal(Character character, float amount) {
        System.out.println(character.getName() + " cannot be healed because they are dead.");
    }
}

修改 Character 类以支持状态模式

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

java 复制代码
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(); // 初始状态为活着
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public 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;
    }

    @Override
    public void update(float damage) {
        System.out.println(name + " observes that someone took damage: " + damage);
    }
}

测试代码

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

java 复制代码
public class Main {
    public static void main(String[] args) {
        Character hero = new Character("Hero");
        Character monster = new Character("Monster");

        // 让monster观察hero
        hero.registerObserver(monster);

        // Hero 攻击 Monster
        hero.attack(monster);

        // Monster 反击 Hero
        monster.attack(hero);

        // Hero 再次攻击 Monster
        hero.attack(monster);

        // 尝试治愈死亡的Monster
        monster.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

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

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

java 复制代码
// 命令接口
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;
    }

    @Override
    public void execute() {
        attacker.attack(target);
    }
}

修改 Character 类以支持命令模式

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

java 复制代码
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(); // 初始状态为活着
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public 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;
    }

    @Override
    public 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; // 返回固定的伤害值
    }
}

测试代码

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

java 复制代码
public class Main {
    public static void main(String[] args) {
        Character hero = new Character("Hero");
        Character monster = new Character("Monster");

        // 让monster观察hero
        hero.registerObserver(monster);

        // 创建攻击命令
        Command attackHero = new AttackCommand(monster, hero);
        Command attackMonster = new AttackCommand(hero, monster);

        // 执行命令
        hero.executeCommand(attackMonster); // Hero 攻击 Monster
        monster.executeCommand(attackHero); // Monster 反击 Hero
        hero.executeCommand(attackMonster); // Hero 再次攻击 Monster

        // 尝试治愈死亡的Monster
        monster.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. 支持命令队列:可以将多个命令放入队列中,按顺序执行。

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

示例代码

定义命令接口和具体命令类
java 复制代码
// 命令接口
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;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public void execute() {
        character.useSkill(skillName);
    }
}
修改 Character 类以支持命令模式
java 复制代码
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(); // 初始状态为活着
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public 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;
    }

    @Override
    public 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; // 返回固定的伤害值
    }
}
测试代码
java 复制代码
public class Main {
    public static void main(String[] args) {
        Character hero = new Character("Hero");
        Character monster = new Character("Monster");

        // 让monster观察hero
        hero.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 攻击 Monster
        monster.executeCommand(attackHero); // Monster 反击 Hero
        hero.executeCommand(moveHero);      // Hero 向北移动
        monster.executeCommand(moveMonster); // Monster 向南移动
        hero.executeCommand(useSkillHero);  // Hero 使用技能 Fireball
        monster.executeCommand(useSkillMonster); // Monster 使用技能 Poison Breath

        // 尝试治愈死亡的Monster
        monster.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)关键字来导出函数。
    cpp 复制代码
    // 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文件和导入库文件。
    sh 复制代码
    cl /LD MyDll.cpp

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

使用DLL
  1. 链接导入库

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

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

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

动态加载DLL

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

cpp 复制代码
#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,并调用其中的导出函数。
相关推荐
单音GG21 分钟前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
马剑威(威哥爱编程)31 分钟前
读写锁分离设计模式详解
java·设计模式·java-ee
修道-032333 分钟前
【JAVA】二、设计模式之策略模式
java·设计模式·策略模式
G皮T4 小时前
【设计模式】结构型模式(四):组合模式、享元模式
java·设计模式·组合模式·享元模式·composite·flyweight
W_Meng_H4 小时前
设计模式-组合模式
设计模式·组合模式
吾与谁归in14 小时前
【C#设计模式(8)——过滤器模式(Adapter Pattern)】
设计模式·c#·过滤器模式
G皮T15 小时前
【设计模式】行为型模式(一):模板方法模式、观察者模式
java·观察者模式·设计模式·模板方法模式·template method·行为型模式·observer
erxij16 小时前
【游戏引擎之路】登神长阶(十三)——Vulkan教程:讲个笑话:离开舒适区
c++·经验分享·游戏·3d·游戏引擎
iFlyCai18 小时前
23种设计模式的Flutter实现第一篇创建型模式(一)
flutter·设计模式·dart
zhouzhihao_0718 小时前
程序代码设计模式之模板方法模式(1)
java·设计模式·模板方法模式