Minecraft 的多人游戏是如何发展起来的?

作者:Tiger Tang 2019-02-22


这个问题太想自问自答了,因为这段血泪史完全可以写成精彩纷呈的长篇小说!作为在Minecraft业界打滚多年的人,必须得给大家侃侃背后的故事!


//Survival Multiplayer时代(2010)

让时光回溯到五年前的8月9日的凌晨。我们的故事主角兼Minecraft创始人,Markus'Notch'Persson,正二十四个小时宅在家里,撑着双眼死瞪电脑屏幕,双手则迅速地敲着键盘,废寝忘食地调试着程序。再过一个小时就是8月10日了,Minecraft生存多人游戏(Survival Multiplayer,SMP)正式发布的日子。

你看Notch这妖魅的小眼神

Minecraft SMP的名字听上去很高大上,但其实就是一个叫做minecraft_server.jar的文件罢了,小巧绿色又便携。使用方法也非常简单,双击打开,它就会自动在默认端口上设置好一个Minecraft的服务器,别人只需凭你的IP即可进入。理所当然地,一些基本的命令也包含在其中:/kick用来踢人,/gamemode用来从生存转创造...

现在看来,第一个版本的SMP相当简陋,但玩家们正沉醉于和朋友一起玩生存的乐趣里,再简陋也赞不绝口。


SMP的发布,正是迎合Minecraft迅猛上升的用户注册量。截至2010年5月,Minecraft的付费用户已经达到两万,YouTube上以Minecraft作为关键字的视频日益增长,而此时,这个游戏还只是在Alpha阶段!

而SMP的出现,更让Minecraft的知名度登上又一巅峰:什么!?可以和好基友在开放式的LEGO世界里生存、探险、搞基(?);还可以开创造起个斗兽场战个痛或者堆满TNT然后炸地图;最给力的是神似编程的红石系统,直接令Minecraft一跃成为游戏开发工具!哪个AAA级游戏有这么爽的体验?!

即使从五年后看来,SMP的第一个版本也有相当高的游戏性

在SMP发布仅仅两个月后,Minecraft的付费用户就翻了个1.5倍,两个月就赚了一百多万!SMP的巨大成功并没有让Notch怠慢,没过多久就向玩家们宣布了Beta版本的到来。而Notch也正式注册了Mojang AB的商标,为之后发行游戏铺路。

//hMod时代(2010~2011)

SMP好玩归好玩,可是不能在上面装mod这一点让不少玩家很苦恼。当然了,可以通过反编译minecraft_server.jar修改里面的代码,比如调整一下玩家的默认速度什么的,然后每个玩家一走起路来就跑十公里远,上天入海不是梦。毕竟Mojang也没有做什么签名验证,也没什么坑爹的全程联网验证(育碧:...),要修改几个变量然后重新编译,理论上来讲不难啊。


可行归可行,问题是修改起来太麻烦:代码全部被混淆(obfuscated)了!

什么叫代码混淆呢?举个栗子,比如说原本的代码是这样的:

private String playerName = "你爸爸"; // 定义玩家名称
private double health = 20.0D; // 定义玩家血量
private float walkSpeed = 1.2F; // 定义玩家速度


public void chat(String message) { // 定义一个说话的函数
    Server.broadcastMessage(this, message); // 向服务器里的函数传递参数
}

没学过Java是不是也很清晰明了?这修改起来还不容易,简直就是填空嘛,小学生都会。


问题是在编译的时候,代码被Mojang事先混淆了,可能到你手里的时候就变成这样了:

String a = Base64.decodeFromBase64("5L2g54i454i4");
double b = 20.0D;
float c = 1.2F;

public void d(String a) {
    bl.aE(this, a);
}

尼玛这叫一个狠哪,若是没有原本的代码,你看得懂吗?


你或许说,上面这几行,我也能猜出个大概吧?嗯,b是血量,因为玩家血量最高就是20,然后c是...bl是...aE是...


别忙着翻桌,我们再来看看真实个例,下面是Minecraft 1.8里面的aap类:

public class aap extends um {
    private static final Logger b = ;
    public float a = (float) (Math.random() * 3.141592653589793D * 2.0D);
    private int c;
    private int d;
    private int e = 5;
    private String f;
    private String g;


    public aap(amp paramamp, double paramDouble1, double paramDouble2, double paramDouble3) {
        super(paramamp);
        a(0.25F, 0.25F);
        b(paramDouble1, paramDouble2, paramDouble3);


        this.y = ((float) (Math.random() * 360.0D));


        this.v = ((float) (Math.random() * 0.2000000029802322D - 0.1000000014901161D));
        this.w = 0.2000000029802322D;
        this.x = ((float) (Math.random() * 0.2000000029802322D - 0.1000000014901161D));
    }


    public aap(amp paramamp, double paramDouble1, double paramDouble2, double paramDouble3, aio paramaio) {
        this(paramamp, paramDouble1, paramDouble2, paramDouble3);
        a(paramaio);
    }


    public aap(amp paramamp) {
        super(paramamp);
        a(0.25F, 0.25F);
        a(new aio(apg.a, 0));
    }


    protected boolean q_() {
        return false;
    }


    protected void g() {
        F().a(10, 5);
    }


    public void j() {
        if (k() == null) {
            H();
            return;
        }
        super.j();
        if ((this.d > 0) && (this.d != 32767)) {
            this.d -= 1;
        }
        this.p = this.s;
        this.q = this.t;
        this.r = this.u;


        this.w -= 0.03999999910593033D;
        this.T = j(this.s, (aL().b + aL().e) / 2.0D, this.u);
        d(this.v, this.w, this.x);


        int i = ((int) this.p != (int) this.s) || ((int) this.q != (int) this.t) || ((int) this.r != (int) this.u) ? 1 : 0;
        if ((i != 0) || (this.W % 25 == 0)) {
            if (this.o.p(new dl(this)).c().r() == big.i) {
                this.w = 0.2000000029802322D;
                this.v = ((this.V.nextFloat() - this.V.nextFloat()) * 0.2F);
                this.x = ((this.V.nextFloat() - this.V.nextFloat()) * 0.2F);
                a("random.fizz", 0.4F, 2.0F + this.V.nextFloat() * 0.4F);
            }
            if (!this.o.C) {
                v();
            }
        }
        float f1 = 0.98F;
        if (this.C) {
            f1 = this.o.p(new dl(sr.c(this.s), sr.c(aL().b) - 1, sr.c(this.u))).c().K * 0.98F;
        }
        this.v *= f1;
        this.w *= 0.9800000190734863D;
        this.x *= f1;
        if (this.C) {
            this.w *= -0.5D;
        }
        if (this.c != -32768) {
            this.c += 1;
        }
        if ((!this.o.C) && (this.c >= 6000)) {
            H();
        }
    }


    private void v() {
        for (aap localaap : this.o.a(aap.class, aL().b(0.5D, 0.0D, 0.5D))) {
            a(localaap);
        }
    }


    private boolean a(aap paramaap) {
        if (paramaap == this) {
            return false;
        }
        if ((!paramaap.ad()) || (!ad())) {
            return false;
        }
        aio localaio1 = k();
        aio localaio2 = paramaap.k();
        if ((this.d == 32767) || (paramaap.d == 32767)) {
            return false;
        }
        if ((this.c == -32768) || (paramaap.c == -32768)) {
            return false;
        }
        if (localaio2.b() != localaio1.b()) {
            return false;
        }
        if ((localaio2.n() ^ localaio1.n())) {
            return false;
        }
        if ((localaio2.n()) && (!localaio2.o().equals(localaio1.o()))) {
            return false;
        }
        if (localaio2.b() == null) {
            return false;
        }
        if ((localaio2.b().k()) && (localaio2.i() != localaio1.i())) {
            return false;
        }
        if (localaio2.b < localaio1.b) {
            return paramaap.a(this);
        }
        if (localaio2.b + localaio1.b > localaio2.c()) {
            return false;
        }
        localaio2.b += localaio1.b;
        paramaap.d = Math.max(paramaap.d, this.d);
        paramaap.c = Math.min(paramaap.c, this.c);
        paramaap.a(localaio2);
        H();


        return true;
    }


    public void i() {
        this.c = 4800;
    }


    public boolean T() {
        return this.o.a(aL(), big.h, this);
    }


    protected void f(int paramInt) {
        a(ua.a, paramInt);
    }


    public boolean a(ua paramua, float paramFloat) {
        if (b(paramua)) {
            return false;
        }
        if ((k() != null) && (k().b() == aip.bU) && (paramua.c())) {
            return false;
        }
        X();
        this.e = ((int) (this.e - paramFloat));
        if (this.e <= 0) {
            H();
        }
        return false;
    }


    public void b(eu parameu) {
        parameu.a("Health", (short) (byte) this.e);
        parameu.a("Age", (short) this.c);
        parameu.a("PickupDelay", (short) this.d);
        if (m() != null) {
            parameu.a("Thrower", this.f);
        }
        if (l() != null) {
            parameu.a("Owner", this.g);
        }
        if (k() != null) {
            parameu.a("Item", k().b(new eu()));
        }
    }


    public void a(eu parameu) {
        this.e = (parameu.e("Health") & 0xFF);
        this.c = parameu.e("Age");
        if (parameu.c("PickupDelay")) {
            this.d = parameu.e("PickupDelay");
        }
        if (parameu.c("Owner")) {
            this.g = parameu.j("Owner");
        }
        if (parameu.c("Thrower")) {
            this.f = parameu.j("Thrower");
        }
        eu localeu = parameu.m("Item");
        a(aio.a(localeu));
        if (k() == null) {
            H();
        }
    }


    public void d(adq paramadq) {
        if (this.o.C) {
            return;
        }
        aio localaio = k();
        int i = localaio.b;
        if ((this.d == 0) && ((this.g == null) || (6000 - this.c <= 200) || (this.g.equals(paramadq.b_()))) && (paramadq.bg.a(localaio))) {
            if (localaio.b() == ahw.a(apg.r)) {
                paramadq.b(rl.g);
            }
            if (localaio.b() == ahw.a(apg.s)) {
                paramadq.b(rl.g);
            }
            if (localaio.b() == aip.aA) {
                paramadq.b(rl.t);
            }
            if (localaio.b() == aip.i) {
                paramadq.b(rl.w);
            }
            if (localaio.b() == aip.bq) {
                paramadq.b(rl.A);
            }
            if ((localaio.b() == aip.i) && (m() != null)) {
                adq localadq = this.o.a(m());
                if ((localadq != null) && (localadq != paramadq)) {
                    localadq.b(rl.x);
                }
            }
            this.o.a(paramadq, "random.pop", 0.2F, ((this.V.nextFloat() - this.V.nextFloat()) * 0.7F + 1.0F) * 2.0F);
            paramadq.a(this, i);
            if (localaio.b <= 0) {
                H();
            }
        }
    }


    public String b_() {
        if (i_()) {
            return aG();
        }
        return eq.a("item." + k().a());
    }


    public boolean az() {
        return false;
    }


    public void c(int paramInt) {
        super.c(paramInt);
        if (!this.o.C) {
            v();
        }
    }


    public aio k() {
        aio localaio = F().f(10);
        if (localaio == null) {
            if (this.o != null) {
                b.error("Item entity " + D() + " has no item?!");
            }
            return new aio(apg.b);
        }
        return localaio;
    }


    public void a(aio paramaio) {
        F().b(10, paramaio);
        F().h(10);
    }


    public String l() {
        return this.g;
    }


    public void a(String paramString) {
        this.g = paramString;
    }


    public String m() {
        return this.f;
    }


    public void c(String paramString) {
        this.f = paramString;
    }


    public void o() {
        this.d = 10;
    }


    public void p() {
        this.d = 0;
    }


    public void q() {
        this.d = 32767;
    }


    public void a(int paramInt) {
        this.d = paramInt;
    }


    public boolean r() {
        return this.d > 0;
    }


    public void t() {
        this.c = -6000;
    }


    public void u() {
        q();
        this.c = 5999;
    }
}


能猜得出来算你狠。

于是,虽然SMP的第三方修改成为可能,但基本没有服主会闲的蛋疼去玩这个。除了代码被混淆之外,由于Minecraft长期都是Notch一个人开发,所以内部的业务逻辑也写得很乱,或者说实在太有Notch特立独行的代码风格了,窝们实在猜不粗来呀!


Notch表示:“你丫反编译我的代码还瞎逼逼”(设计对白)

不过就是有些人点错天赋了,就在SMP发布后没多久的2010年年底,一位叫hey0的大神在自己的个人网站上发布了hMod。hMod一出,激起千层浪,众人纷纷惊呼:民间奇才!

hMod是个什么玩意儿?我尽量简单地解释一下。以往的SMP modding模式(也就是上面提到的,直接修改源代码),我们画个流程图出来:


hMod的原理,就是将那些不可读的代码,通过hey0君敏锐的观察能力,“翻译”成可读而清晰明了的东西。


还记得刚才那堆乱七八糟的代码吗?有兴趣的同学可以自行阅读“翻译”过后的代码。

(“翻译”这词实际上并不准确,实际上hMod是对SMP的半封装,详细的技术细节在此略过。)


这实在太伟大了!要在服务器上加入自己原创的内容,顿时简单了起来。


不过如此伟大的hMod更新了几个月,原作者就突然潜水,小道消息是说回老家结婚去了,然后由另一位现已就职Mojang的大神Dinnerbone继续填坑。还没填到一半Dinnerbone就不干了:靠,代码真乱!于是拉上几个志同道合的同志一起推翻重做,扛起“翻译”的任务,Bukkit计划就这么诞生了。

//Bukkit时代(2011~2014)

Bukkit计划实际上分为两部分:Bukkit API和CraftBukkit。废话不多说,我们再画个流程图:


原理和hMod是一样的,但Bukkit API写得更好之余,最重要的成就就是加入了事件系统,不过这个话题说下去完全可以另起炉灶了,所以咱们暂且跳过。


好了我知道你们都在吐槽上面的魔法是什么鬼,那么我尽量简单讲一讲,没有面向对象编程基础的同学可以跳过下面这几段。

Bukkit API里全部都是抽象的类与方法,打个比方有个方法叫getOnlinePlayers(),返回当前玩家数量。

为什么要抽象?为什么我们不直接整合实现(implementation)?比如我发现下面这行代码就可以返回当前玩家数量,这不搞定了吗,分两步干嘛。

aJ.e();

问题是我们的这行代码的基础,是通过破解Minecraft SMP的源代码对吧?更准确的说,是通过破解Minecraft SMP当前版本的源代码作为基础。而代码混淆这个过程,是每个版本都会重新进行一次的。上面那行代码或许在Minecraft SMP 1.7能用,但到1.8,可能就完全报错了。因为或许在1.8里,要获取当前玩家数量的代码是这样的:

b.aX();

所以,在Bukkit API的部分里,这个方法是抽象的,留给相应版本的CraftBukkit去实现。并且这么一来,有了抽象的接口作为参考,新版本的SMP发布时,Bukkit团队也能更方便地更新CraftBukkit。

在这里也顺便吐槽一下,常常见到有人说用Bukkit开服,其实是错的——Bukkit里全是抽象的接口而已,开个鬼啊。正确的说法是用CraftBukkit开服(其他服务器端另计)。所以下次你见到谁跟你炫耀说“我会用Bukkit开服务器你造吗”,记得高大上的回他一句:“乖,那个叫CraftBukkit。跟我读,科阿哇夫特巴可以特。”


好了话题扯远了,那么有了Bukkit能做些什么呢?能做的事太多了!比如用Bukkit API的自定义命令功能,加个叫/launch的命令,然后输入/launch<谁谁谁>就将目标玩家喷上天,这无论在原生Minecraft里或者SMP里都是做不到的!

效果请参见左下角~这些基于Bukkit API的小程序被统称为插件(plugin)

好了,我知道你们又要吐槽了。


当然不是!这种插件实在太肤浅了,Bukkit API真正最广泛的应用是用来开发小游戏(minigame)。你没听错,在游戏里开发游戏!只要有足够的人力物力,依靠着Bukkit API,要弄出个Minecraft版《无主之地》或者《使命召唤》是绝对可行的!



国外知名服务器Hypixel近日推出的新游戏Warlords,武器到装备的模型都是完全自制的。Warlords的核心玩法其实就是抢旗,但又加入了武器收集,附魔系统和角色系统等等,目前平均在线玩家2000+,称其为小型PvP网游也绝不为过



1毫不夸张地说,Warlords甚至要比Steam上不少免费的FPS好玩;光是收集要素就足够吸引了!



另一知名大服Wynncraft,则主打RPG玩法,照搬了当今网络上MMORPG的很多元素:饶有趣味的任务,广阔宏伟的地图,专门刷经验升级的地城...倒也弄得趣味横生

我们还是先回到2011年,回到Bukkit刚刚发展起来的时候吧:那时大众对Minecraft多人游戏的概念,还只是停留在与好基友一起玩生存的程度。真正将Bukkit计划推向大众视野的,当属在2011年发展起来的MCSG(Minecraft Survival Games)服务器。听过《饥饿游戏》吧?熟悉里面的设定吧?而MCSG,就是饥饿游戏在Minecraft的翻版:24个玩家在开放式的地图里生存,到处开箱寻找物资,谁生存到最后就赢。SMP可能也弄得出来,但是想做复杂一点,将箱子的物品完全随机化,或者将玩家数据保存在mySQL数据库里,又或者加入一堆炫目的技能,那是SMP绝不可及的。而以上这一切,利用Bukkit API,小case啦。


当然Survival Games远远没有如今的Warlords吸引,但是在当年却可谓掀起了一阵Minecraft潮。在YouTube上实况大型服务器里游戏的实况主越来越多,即使是如今已经超过千万订阅的SkyDoesMinecraft——就是那个玩了Flappy Bird的小伙——也是玩Minecraft发家的(好吧,这个看名字就知道)。

好了,我知道你看到这里有点无聊了。所以下面重点来了!!v(。・ω・。)ィェィ♪

因为也是在2011年,一个名为Buycraft的东东进入了服主们的视野。Buycraft是一个CMS系统,服务器的玩家可以通过在前端用Paypal或者信用卡购买东西,来获得服务器上的增值服务。用Minecraft赚钱不再是梦!

真是爆炸性的大新闻。

......

(超燃BGM响起)

这岂止是爆炸性,简直是历史性!!!这不正是现在手游最喜欢加入的课金系统吗?!!

你嫌你的装备太差吗??

你觉得打怪升级太慢吗???

你羡慕那些满身神装的高富帅吗????

快来买VIP会员吧!!!!!!


一个月只需10美刀,即可让你得到最尊贵的享受!!!!!!!!!!!


双倍金钱!!!

三倍经验!!!!


换装系统!!!!!


宠物陪伴!!!!!!


房间防踢!!!!!!!


非VIP说话全是灰色的!!!!买了VIP你就算骂脏话我们也帮你加个白色高亮!!!!!!还有高端洋气上档次VIP字样的前缀!!!!!


别人死了最多就一句死亡信息!!!!!!!!你死掉我们在你死亡的地点放个七彩烟花!!!!!!!!!让整个世界都知道你死了!!!!!!!!!!!


于是,Minecraft的多人游戏到这里已经完全发展起轻工业来了,首先是开发难度低兼成本小,插件还不一定要自己开发,网上现成的一堆,实在不行开价让别人来做;然后就等着收钱吧,五十美刀一个月的VIP照样有人买!

Bukkit计划也从此声名大噪,越来越多的热心人士加入了开发行列,每一个更新都是无数服主欢呼的时刻。和SMP一样简单的开服流程也让服务器越来越多,保守估计也有几十万——甚至有了Minecraft Server List,Minecraft Servers这样的网站,只是列出互联网上公开的服务器地址,同时暗中通过竞价为某些服务器提升排名,一个月就能赚上万。


吃惊吧?更恐怖的在后头呢。刚才提到过的,2012年迅速崛起的小游戏服务器Hypixel,也是通过增值内容付费的方式,赚了个盆满钵满!根据我一位认识Hypixel开发者的外国朋友的可信消息,高峰期的Hypixel,日均30000+玩家,一年的净利润$1000000+。除以12,一个月九万多美元,也即五十七万人民币。

一个月五十七万。

一个月五十七万。

一个月五十七万。

一个月五十七万。

一个月五十七万。



可能有人会质疑以上的数字,那么我晒晒亲身经历吧:学生党一枚,有多年编程基础,闲着没事也写写插件。我曾经在2013年9月在一家名为Minecade属下的SkyDoesMinecraft服务器工作过三个月,月薪一千。2014年年尾帮ArkhamNetwork服务器做过外包项目,六百。



还有一些零零碎碎的小项目,在此不表。而无论是Minecade还是ArkhamNetwork,甚至还挤不进大服务器的行列。可以想像得见一线服务器的员工们,一个月能赚多少了!

所以说如今Minecraft的多人游戏完全是一条成熟的产业线,服主带着充足的资金聘请员工,为服务器开发高质量内容吸引玩家;玩家则购买增值内容甚至主动捐款来令服务器盈利。这样的良性循环在整个游戏界来讲都是很难得的。


与此同时,单人游戏的体验也在稳速提高,1.5的红石更新,1.6的马匹更新,1.7的世界观更新,而勤快追上步伐的Bukkit计划又让服务器们得以争先抢后地在CraftBukkit新版本发布的第一时间更新服务器上的内容,为的就是吸引人流。

原本就蓬勃的Minecraft游戏界在2013年进入了黄金时代。有充足的利润打底,服务器们开发的新内容一次比一次高质量,Quakecraft、Hide and Seek、Prison、Factions、Arcade等游戏模式的名字已经深入民心,玩家们也乐意付钱,于是两边和乐融融,近年来游戏业流行的Freemium的模式竟然在他们身上得到最好的实现。Minecraft在这一年突破1000万销量,很大程度上要归功于辛勤的服主们。

黄金时代终究是要过去的。狂喜的人们似乎没有发现,一朵乌云已经慢慢逼近......

//辉煌背后

Bukkit时代看似辉煌,但实际上有不少隐患出现:

第一是Bukkit本身的衰落。2012年2月,Bukkit的开发团队(Dinnerbone,EvilSeph,Grum,Tahg)收到来自Mojang的offer,于是欣然应邀加盟Mojang;作为条件,他们不能再开发Bukkit,而是负责开发新版本的SMP和其他与Minecraft有关的工作,比如编写Plugin API。

Dinnerbone和Grum这两位可以说是对整个Bukkit计划贡献最大的人,反编译和反混淆由Grum全权负责,然后Dinnerbone则接过代码坐在电脑桌前除了上厕所外不停歇地码上二三十个小时(这就是爱啊<3),为的就是以最快的速度将新版本的Bukkit API和CraftBukkit呈现在大众面前。如今他们走了,虽然有人接班,但是他们都没有了Dinnerbone和Grum的那份旁人难以理解的激情,更新对他们来说更像是一份义务而不是责任。这也不能怪他们,但伴之而来的就是CraftBukkit的更新越来越慢,当初两天就能更新完,现在要花上两个月;而Bukkit在1.5后鲜有再加入新的API,意思就是上文提到的“翻译”活越来越少人肯去做,导致许多SMP的新功能都无法单纯地利用Bukkit API实现,必须还得配合之前提到的那种直接修改源代码的蛋疼方法...

作为过来人,我可以肯定地告诉你们:阅读Minecraft的源代码太蛋疼了...

第二是收费泛滥。服务器们收费的方式推陈出新,以Hypixel为例,VIP出完了出VIP+,VIP+出完了出MVP,MVP出完了再出MVP+...




几十美金几十美金地收...国内一线MMO都没这么贵啊

就算玩家们乐意,他们的家长也不乐意呀!不少熊孩子一个月花了几千美刀在Minecraft上,而家长们又怎会了解Bukkit服务器们的商业模式,于是出现了家长们愤怒地在推特上向Notch投诉并要求全额退款,否则要将Mojang告上法庭的啼笑皆非的情况。

Mojang躺着也中枪:关我屁事啊!?

第三是版权问题。CraftBukkit内置了Minecraft反编译过后的源代码,无形中已经侵犯了Mojang的版权;更搞笑的是,Bukkit计划采用的是GPL协议!一个开源计划里却包含了反编译过的商业代码,这一点本身能够不被大众口诛笔伐实属幸运。

Mojang当然知道Bukkit计划是怎么回事,不过他们对这些第三方服务器端也就是睁一只眼闭一只眼,只要你不把Minecraft重新打包一次就拿出去卖,你改成Q块世界我也不管你。


我知道你们还没完全消化这几段的内容(o´Д`)=з但敬请记住这几个关键词吧:Bukkit衰落、收费泛滥和版权纠纷。

因为故事要进入高潮阶段了。

淡定,淡定

//Bukkit与Spigot的冷战(2013~2014)

自从原开发团队离去后,先不提越来越慢的更新和越来越落后的API,CraftBukkit的性能也饱受诟病。许多人都自己私下制作了CraftBukkit的优化版本,当时最出名的是CraftBukkit++,着重优化了农作物生长的部分,能显著提高TPS(ticks per second,TPS越低,服务器就越卡,和我们玩游戏时说的FPS帧数很相似)。但是每个优化版本都只是优化了一小部分,这个优化通讯,这个优化实体,这个优化AI,这个优化地图加载...有没有一个版本的CraftBukkit,能集齐百家所长呢?

有的,那就是由md_5大神开发的Spigot。

md_5同学的头像,橙色的史莱姆

来来来,先上个功能列表:


•TPS increases


•Optimized growth,decay and chunk ticking


•Automatic conversion between online and offline mode.


•Auto stack merging for items and experience orbs


•Chunk garbage collector to prevent chunk leaks


•Convert your offline mode server to online mode without losing player data


•Configurable values for many vanilla/Bukkit messages(e.g.whitelist,no permission)


•Disabling of random light updates


•Optimized anti x-ray(Orebfuscator)


•Efficient Netty networking engine


•Smart crash and hang detection/prevention


•Reverse compatibility with CraftBukkit plugins


•Entity activation and tracking ranges to ensure client-side and server-side resources are only used when needed


最新评论
暂无评论
参与评论


【爆款新游】【潜力佳作】分析系列
推广