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

魔兽世界中的命令场景

 

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

 

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

 

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

 

命令模式介绍

 

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

扫描二维码关注公众号,回复: 277040 查看本文章

 

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

 

 

如果把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

猜你喜欢

转载自moon-walker.iteye.com/blog/2398844