欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

命令模式--在魔兽世界中的运用

程序员文章站 2022-05-03 23:19:28
...

魔兽世界中的命令场景

 

笔者以前是个普通的魔兽世界玩家,每个魔兽世界玩家心中都比别人多一个世界。但同时笔者是一名程序员,经常又会在程序员的世界去思考游戏中各种场景是怎么实现的。今天心血来潮,准备使用“命令模式”为魔兽世界设计一套技能释放系统,包括:命令设计、宏命令、游戏外挂等具体实现过程。

 

在讲解命令模式之前,首先让我们来回味下魔兽世界中法师职业的技能:寒冰箭、火球术、奥术强化、气定神闲 等等,由于技能太多这里只列出4个技能。这些技能与普通游戏没什么两样,但魔兽世界的强大之处在于 可以让玩家根据自己的按键习惯,随意设置技能释放的快捷键。比如我个人的设置:数字键1--寒冰箭、数字键2--气定神闲、数字键3--奥术强化、数字键4--火球术等等。但另外一个玩家快捷键可能是:数字键1--寒冰箭、数字键2--气定神闲、字母键Q--奥术强化、字母键E--火球术等等。

 

这说明玩家的键盘和技能释放动作是完全解耦的,他们之间的桥梁就是每个技能释放动作都被封装为一个个命令,可以根据个人按键习惯随意设置。这其实就是命令模式的典型运用场景。今天我们就来自己设计下魔兽世界的命令体系,在此之前首先看下什么是命令模式。

 

命令模式介绍

 

命令模式的定义:将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象;命令模式也支持撤销操作。

 

以魔兽世界的命令体系为参照,定义中的其他对象就是键盘,将请求封装成对象这个对象就是命令对象,“请求”就是具体的技能释放动作。“命令对象”将“键盘”和“具体的技能释放动作”解耦。命令模式的类图如下:

 
命令模式--在魔兽世界中的运用
            
    
    博客分类: 设计模式 命令模式 
 

如果把Client去掉,可以发现跟上一章讲的适配器模式类图是一样的,但二者的目的不一样导致最终的实现方式也不一样。命令模式的作用是把命令发出者和命令执行者的责任分开(解耦),而适配器模式的作用是转换接口。以魔兽世界的命令体系为例,命令把“键盘”和“具体的技能释放动作”分开,键盘不会去实现具体的技能。

 

魔兽世界的命令体系实现

 

在开始代码实现环节之前,首先以上述类图定义角色:

请求者角色:类图中的Invoker对应键盘类Keyboard

命令接口角色:新建一个Command类与类图中的Command类对应;

具体的命令角色:新建一系列的命令实现类与类图中ConcreteCommand类对应:

接收着角色:新建一系列的“技能实现类”与类图中的Receiver对应。

 

下面是开始实现各个角色。

 

1、接受者角色

 

首先来看接收着角色,这里只模拟实现魔兽世界里法师技能列表中的4个技能:寒冰箭、气定神闲、奥术强化、火球术。

 

寒冰箭实现类Frostbolt,法师施放寒冰箭需要两秒的时间吟唱咒语,说得简单点就是读条,可以用一个线程来模拟。在释放技能读条期间,可以被打断(撤销)。具体实现如下:

/**
 * 寒冰箭操作类
 * Created by gantianxing on 2017/11/6.
 */
public class Frostbolt {
 
    //释法线程
    private Thread thread;
 
    //释放寒冰箭,具体实现
    public void releaseSkill(){
        if(this.getThread()!=null && this.getThread().isAlive()){
            System.out.println("上一个释法正在进行中");
            return;
        }
 
        //寒冰箭释放时长2秒
        Thread thread = new Thread(new CastFrostbolt(2));
        thread.start();
        this.setThread(thread);
    }
 
    //打断 技能释放
    public void cansel(){
        if(this.getThread() !=null && this.getThread().isAlive()){
            this.getThread().interrupt();//中断释法
        }else {
            System.out.println("目前没有释放寒冰箭");
        }
    }
 
 
    public Thread getThread() {
        return thread;
    }
 
    public void setThread(Thread thread) {
        this.thread = thread;
    }
}
 
/**
 * 模拟寒冰箭释放过程
 * Created by gantianxing on 2017/11/6.
 */
public class CastFrostbolt implements Runnable{
    private int time;//释法时长
 
    public CastFrostbolt(int time) {
        this.time = time;
    }
 
    public void run() {
        System.out.println("+++开始释放寒冰箭+++");
        System.out.println(time+"秒读条ing");
        try {
            Thread.sleep(time*1000);
            System.out.println("+++完成释放寒冰箭+++");
            System.out.println("                   ");
        } catch (InterruptedException e) {
            //技能被取消
            System.out.println("+++取消释放寒冰箭,读条结束+++");
            System.out.println("                   ");
        }
    }
}
 

 

火球术实现类Fireball,法师施放火球术需要5秒的读条时间(记不太清了),也可以用一个线程来模拟。在释放技能读条期间,同样可以被打断(撤销)。具体实现与寒冰箭的实现类似,这里就不贴代码了,大家可以自行实现。

 

奥术强化实现类ArcanePower,该技能是瞬发技能,不需要读条,所有不会被打断(撤销)。实现比较简单:

public class ArcanePower {
    //释放奥术强化,具体实现
    public void releaseSkill(){
        System.out.println("释放:奥术强化,接下来30秒内的攻击 暴击率提高30%");
    }
}

气定神闲实现类PresenceOfMind,该技能同样是瞬发技能,不需要读条,所有也不会被打断(撤销)。该技能释放后的下一次读条技能 改为瞬发,所以需要记录状态:

public class PresenceOfMind {
 
    //是已经释放 气定神闲
    private boolean isReleaseed = false;
 
    //释放气定神闲,具体实现
    public void releaseSkill(){
        this.setIsReleaseed(true);
        System.out.println("释放:气定神闲,接下来的一次读条技能变为瞬发");
    }
 
    public boolean isReleaseed() {
        return isReleaseed;
    }
 
    public void setIsReleaseed(boolean isReleaseed) {
        this.isReleaseed = isReleaseed;
    }
}

 

到这里,4个技能实现完毕。

 

2、命令接口角色

 

命令接口Command,只定义了一些统一的方法:

public interface Command {
    void execute();//执行命令
    void undo();//撤销命令
}

 

3、具体的命令角色

 

具体命令角色实现了接口Command,是对上述4个具体的法师技能的封装。

 

寒冰箭命令类FrostboltCommand,封装寒冰箭技能施放:

public class FrostboltCommand implements Command {
 
    private Frostbolt frostbolt;
 
    public FrostboltCommand(Frostbolt frostbolt) {
        this.frostbolt = frostbolt;
    }
 
    public void execute() {
        frostbolt.releaseSkill();
    }
 
    public void undo() {
        frostbolt.cansel();
    }
}
 

 

火球术命令类FireballCommand,与寒冰箭命令类 实现类似,这里就不贴出代码了。

 

奥术强化命令类ArcanePowerCommand,奥术强化技能cd时间为30秒,在技能cd期间无法使用该技能,这里使用一个线程模拟技能cd中,具体实现为:

public class ArcanePowerCommand implements Command {
 
    private static boolean cd = false;//技能是否进入cd
 
    private ArcanePower arcanePower;
 
    public ArcanePowerCommand(ArcanePower arcanePower) {
        this.arcanePower = arcanePower;
    }
 
    public void execute() {
        if(this.cd == false){
            this.cd = true;
            arcanePower.releaseSkill();
 
            //定时清除技能cd
            optCd();
        }else {
            System.out.println("奥术强化技能cd中");
        }
    }
 
    public void undo() {
        System.out.println("奥术强化 是瞬发技能,不能撤销");
    }
 
    private void optCd(){
        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(30*1000);// 奥术强化cd时间 30秒
                    System.out.println("奥术强化cd结束");
                    ArcanePowerCommand.cd = false;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        thread.start();
    }
}
 

 

气定神闲命令类PresenceOfMindCommand,该技能与奥术强化类似,技能cd时间同样为30秒,实现方式与ArcanePowerCommand类型,这里就不再贴出代码。

 

另外还有一个空命令实现类NoCommand,什么都不做,用于赋值给键盘上没有设置快捷键的按键。具体实现如下:

public class NoCommand implements Command{
    public void execute() {
    }
 
    public void undo() {
    }
}

 

4、请求者角色

 

这里的请求者角色,就是键盘Keyboard 。键盘的实现 主要完成上述5个命令的绑定,具体实现如下:

public class Keyboard {
    Command[] commands; //对应键盘上的10个数字键 0-9
    Command undoCommand; //撤销按键,对应键盘上的空格键
 
    public Keyboard() {
        commands = new Command[10];
        //游戏刚开始没有设置快捷键,键盘上的这10个按键默认是空命令
        Command noCommand = new NoCommand();
        for (int i=0;i<10;i++){
            commands[i] = noCommand;
        }
    }
 
    //为键盘上的指定按键 绑定命令
    public void setCommand(int keyNum,Command command){
        commands[keyNum] = command;
    }
 
    public void pushKeyNum(int keyNum){
        commands[keyNum].execute();
        undoCommand = commands[keyNum];//记录最近一次操作
    }
 
    public void pushSpacekey(){
        if(undoCommand != null){
            undoCommand.undo();
        }
    }
}

到这里魔兽世界的命令体系设计和开发完成,下面可以开始玩游戏了。

 

游戏时间

 

在游戏开始前,玩家还需要按照各自习惯绑定下技能命令(当然真实的游戏中有默认按键设置),然后按下键盘上的指定按键,就可以开始打怪了。

 

public class Main {
 
    public static void main(String[] args) throws Exception{
        //Step1 初始化技能
        ArcanePower arcanePower = new ArcanePower();//奥术强化
        PresenceOfMind presenceOfMind = new PresenceOfMind();//气定神闲
        Frostbolt frostbolt = new Frostbolt();//寒冰箭
        Fireball fireball = new Fireball(presenceOfMind);//火球术
 
        //step2 初始化命令,对4个技能进行封装
        ArcanePowerCommand arcanePowerCommand = new ArcanePowerCommand(arcanePower);
        PresenceOfMindCommand presenceOfMindCommand = new PresenceOfMindCommand(presenceOfMind);
        FrostboltCommand frostboltCommand = new FrostboltCommand(frostbolt);
        FireballCommand fireballCommand = new FireballCommand(fireball);
 
        //step3 初始化键盘,并为键盘绑定命令,玩家可以根据自己的喜好调整 命令位置
        Keyboard keyboard = new Keyboard();
        keyboard.setCommand(1,frostboltCommand);//按键1设置为寒冰箭命令
        keyboard.setCommand(2,presenceOfMindCommand);//按键2设置为气定神闲命令
        keyboard.setCommand(3,arcanePowerCommand);//按键3设置为奥术强化命令
        keyboard.setCommand(4,fireballCommand);//按键4设置为火球术命令
        //准备完毕,目前你我们只设置了4个快捷键,其他的就交个玩家自己去设置吧
 
        //开始打boss啦
        // 按1键 释放寒冰箭
        keyboard.pushKeyNum(1);
        Thread.sleep(1999);//寒冰箭释法时长2秒
        keyboard.pushKeyNum(1);//上一个释放还没有完成,再次释法无效
        Thread.sleep(500);//按下一个键 人的反应时间,0.5秒
 
        //按4键 释放火球术
        keyboard.pushKeyNum(4);
        Thread.sleep(5000);//火球术释法时长5秒
        Thread.sleep(500);//按下一个键 人的反应时间,0.5秒
}
}

 

执行main方法,打印信息如下:

+++开始释放寒冰箭+++
2秒读条ing
上一个释法正在进行中
+++完成释放寒冰箭+++
                  
---开始释放火球术---
5秒读条ing
---完成火球术释放---
 

 

这次模拟分别施放了两次寒冰箭和一次次火球术,但有由于第一次寒冰箭,还没有施放结束,第二次施放没有成功。

 

火球术的伤害被寒冰箭高,但施法时间太长(读条),一般不会直接使用。但我们技能栏里,还有“气定神闲”技能。先施放气定神闲,再施法“火球术”就是瞬发(不用读条了),再加上奥术强化就更配了,这就是传说中的气定 奥强 大火球。具体操作如下(代码加在main方法后面):

 
//按1键 再次释放寒冰箭
        keyboard.pushKeyNum(1);
        //0.5秒后 气定神闲和奥术强化cd时间到
        Thread.sleep(500);
        keyboard.pushSpacekey();//取消寒冰箭
        Thread.sleep(500);
        keyboard.pushKeyNum(2);//释放气定神闲
        Thread.sleep(500);
        keyboard.pushKeyNum(3);//释放奥术强化
        Thread.sleep(500);
        keyboard.pushKeyNum(4);//瞬发大火球
        Thread.sleep(500);
        keyboard.pushKeyNum(2);//想再释放“气定神闲”还得等30秒cd
 

 

执行mian方法,打印信息如下:

+++开始释放寒冰箭+++
2秒读条ing
+++取消释放寒冰箭,读条结束+++
                  
释放:气定神闲,接下来的一次读条技能变为瞬发
释放:奥术强化,接下来30秒内的攻击 暴击率提高30%
瞬发火球术完成
            
气定神闲技能cd中
气定神闲cd结束
奥术强化cd结束
 

 

玩家在释放寒冰箭读条的过程中发现,气定神闲的cd结束,立即取消寒冰箭技能,开始释放气定 奥强 大火球组合技能。

 

这时玩家的操作顺序是,先按空格键 再依次按下234键。一次操作需要按4个键,对玩家手速是个巨大的考验,怎么办?别紧张“命令模式”中还有宏命令

 

宏命令

 

所谓宏命令,就是把多个具体的动作放在一个命令中,动作发起者只需一个操作,就可以引发多个接收者角色执行多个动作。

 

在魔兽世界的场景中,就是玩家只需按下一个键,就可以完成气定 奥强 大火球的技能释放。下面来看宏命令的实现:

public class MacroCommand implements Command{
 
    List<Command> commands = new ArrayList<Command>();
   
    //添加命令
    public void add(Command command){
        commands.add(command);
    }
   
    //移除命令
    public void remove(Command command){
        commands.remove(command);
    }
 
    //批量执行命令
    public void execute() {
        for(Command cmd : commands){
            cmd.execute();
        }
    }
 
    public void undo() {
        System.out.println("宏命令 暂不提供撤销功能");
    }
}

 

 

main方法中,初始化宏命令,并放绑定到“数字键5”:

//宏命令初始化
        MacroCommand macroCommand = new MacroCommand();
        macroCommand.add(presenceOfMindCommand);//气定
        macroCommand.add(arcanePowerCommand);//奥强
        macroCommand.add(fireballCommand);//瞬发大火球
        keyboard.setCommand(5,macroCommand);
 

 

 

气定、奥强的cd结束,玩家立即取消当前施法,再按下数字键5

//按1键 再次释放寒冰箭
        keyboard.pushKeyNum(1);
        //0.5秒后 气定神闲和奥术强化cd时间到
        Thread.sleep(500);
        keyboard.pushSpacekey();//取消寒冰箭
        keyboard.pushKeyNum(5);
 

 

执行mian方法,打印结果为:

+++开始释放寒冰箭+++
2秒读条ing
+++取消释放寒冰箭,读条结束+++
                  
释放:气定神闲,接下来的一次读条技能变为瞬发
释放:奥术强化,接下来30秒内的攻击 暴击率提高30%
瞬发火球术完成

 

 

游戏外挂

 

boss的时候 一打就是好几分钟,一直不停的按键是件很累的事情。别紧张,我们可以开发一个游戏外挂(不影响游戏平衡的外挂)。针对某个boss的外挂需求是这样的:我们可以一直释放寒冰箭,有气定”+“奥强”cd就使用瞬发火球术,在释放寒冰箭期间夹杂一些火球术,可以加深伤害。我们的外挂实现就是这样的:

 
int boss_blood = 10000;//boss初始血量
        System.out.println("战斗开始");
 
        //一次循环大约31秒
        while (true){
            //气定 奥强 cd结束,就使用宏命令"气定 奥强 大火球"
            keyboard.pushKeyNum(5);
 
            //一次循环 15.5秒,两次31秒。气定和奥强cd时间都是30秒
            for(int j=0;j<2;j++){
                for (int i = 0;i<4;i++){//4次寒冰箭 10秒
                    keyboard.pushKeyNum(1);
                    Thread.sleep(2000);//寒冰箭释法时长2秒
                    Thread.sleep(500);//按下一个键 人的反应时间,0.5秒
                }
                keyboard.pushKeyNum(4);//火球术释法时长5秒
                Thread.sleep(5000);//寒冰箭释法时长2秒
                Thread.sleep(500);//按下一个键 人的反应时间,0.5秒
            }
 
            boss_blood = boss_blood - 2000;
            if(boss_blood <0){
                System.out.println("boss被消灭,开始分装备啦");
                break;
            }
        }
 
}
 

 

执行main方法,打印信息如下:

战斗开始
释放:气定神闲,接下来的一次读条技能变为瞬发
释放:奥术强化,接下来30秒内的攻击 暴击率提高30%
瞬发火球术完成
            
+++开始释放寒冰箭+++
2秒读条ing
+++完成释放寒冰箭+++
 
//此处省略约3分钟的战斗画面
 
---开始释放火球术---
5秒读条ing
气定神闲cd结束
奥术强化cd结束
---完成火球术释放---
                  
boss被消灭,开始分装备啦
 

 

整个过程由外挂执行,玩家可以上个厕所休息下,3分钟后回来分装备就可以啦。

 

小结

 

各大游戏里的命令体系设计,就是命令模式的典型应用场景之一。学会了魔兽世界的命令体系设计,相信你也可以设计英雄联盟的命令了。

 

结合命令模式中的undo(撤销)操作,此模式还可以广泛用于其他场景:日志记录和恢复、以及事务处理等。具体就是在执行execute方法时 记录操作顺序和日志,在出现问题后,执行undo操作进行恢复。这里就不再展开讲解,可以自行实现。

 

 

本次总结 内容涉及太多魔兽世界游戏元素,对于魔兽世界玩家是一个福利。但对于没有玩过魔兽世界的朋友,要说声抱歉了。

 

出处:

 

http://moon-walker.iteye.com/blog/2398844

  • 命令模式--在魔兽世界中的运用
            
    
    博客分类: 设计模式 命令模式 
  • 大小: 15.1 KB
相关标签: 命令模式