单机游戏挖坑游戏?


陝西免費單機三人挖坑是能夠線上免費領取很多遊戲金幣的,讓玩家自由開啟挑戰的,是十分棒的正規棋牌遊戲應用,玩法上癮, 能夠在平臺上認識很多的棋牌小夥伴,是很多玩家都覺得體驗過程很棒的正規遊戲,壹起來下載使用吧!
陝西免費單機三人挖坑遊戲功能:
1、集成了多種經典有趣的手機棋牌遊戲,手機應用方便攜帶;
2、無需注冊,告別繁瑣的注冊步驟,支持多種登錄,隨時隨地都可以在線。
3、一站式棋牌服務,享受非常優越的客服待遇,24小時無限在線競爭。
4、智能的遊戲匹配機制,匹配真實玩家的實力。
5、靈活的玩家不需要等待整個遊戲,快節奏的遊戲很有趣
6、配備頂級的防作弊系統,玩家可以放心在遊戲中玩牌;
7、實時在線維護更新,絕對是最新的象棋模式和玩法,免費解鎖免費玩法。
8、全新人性化的系統功能,讓玩家可以輕鬆上手,在線輕鬆PK戰鬥。
陝西免費單機三人挖坑遊戲用法:
1、每天都有活動哦,絕對測試棋牌技能,有信心挑戰吧!
2、這是十分專業團隊打造的,是線上福利不間斷的棋牌手機遊戲
3、豐富多彩的系統任務給你帶來不同的獎勵哦,享受樂趣吧
4、做好客戶服務,為用戶提供一對一解決方案,盡享樂趣;
5、遊戲玩法非常多樣,玩家只需點擊一下按鈕,就可以隨心所欲地玩遊戲。
6、100%實名對戰,實名玩家認證,無機器人,在線與各種撲剋朋友鬥智鬥勇。
7、首次充值可獲得188枚金幣,每日充值可獲得88枚金幣。
8、這不僅僅是一個單一的遊戲玩法,更是對玩家日常生活的一次考驗!
陝西免費單機三人挖坑遊戲特色:
1、該平台支持多種登錄遊戲的方式,你可以以任何你想要的方式登錄。
2、全國真正的撲剋朋友們,告別了無聊的煩惱,告別了單調的人機大戰。
3、免費樂趣初學者領域,可以隨時鍛煉你的牌技,增加你的知識。
4、優美優美的中國風,真實的國際象棋防偽,最舒適的遊戲環境
5、全新優化簡單安裝包,超級節省流量,內存也特別小!
6、當完全優化時,遊戲的整體節奏會很流暢,你也不會在玩了很長時間後感到無聊。
7、高清遊戲畫面和穩定的操作系統,為您提供最流暢的遊戲體驗;
8、穩定的網絡環境和小流量的消費控制,讓你可以離開wifi自由玩~
陕西免费单机三人挖坑-陕西免费单机三人挖坑下载客户端v1.0
陝西免費單機三人挖坑遊戲亮點:
1、有許多經典的在線棋牌遊戲,滿足用戶的需求,一鍵解鎖;
2、最傳統的撲剋遊戲,讓玩家體驗最正宗的撲剋遊戲玩法。
3、歡迎每天帶新的好禮物,只要你邀請新玩家來玩就可以得到非常豐厚的對抗獎勵;
4、現在你想要提高你的紙牌技能,成為一流的紙牌玩家,遊戲中挑戰更多的玩家。
5、100%公平的遊戲環境,承諾絕對沒有人可以幫助,每個人都可以在這裏公平公正地玩;
6、超級多的棋盤遊戲玩法,你想玩的一切,玩的開心!
7、邀請朋友組成團隊一起戰鬥,體驗與朋友一起戰鬥的樂趣;
8、全新排行榜模式,在線與各種撲剋朋友鬥智鬥勇。一周的禮物。
陝西免費單機三人挖坑遊戲評測:
1、有很多有趣的遊戲,你可以在任何時候順利地玩,有很多驚喜;
2、美麗流暢的遊戲畫面,完美的操作,不會產生一個障礙!
3、整個棋牌的遊戲時間是非常短的,讓玩家只需要花費五分鍾就能夠完成一局;
4、特色積分系統,許多積分和獎勵,可以在商場裏交換超級遊戲道具
5、支持局內語音對話,並可使用有趣的表情道具功能,
6、為玩家提供最好的遊戲服務,一對一的解決問題
7、多種遊戲競爭模式,充分滿足所有用戶的多樣化需求
8、要始終保持冷靜穩定的心態,與鬥智鬥勇的對手,局勢不亂;
}

本文作者3TUSK(@3TUSK),使用CC-BY 4.0协议授权。
平时都说 Minecraft 是单线程游戏,因此如何如何性能差,如何如何不能有效利用处理器资源,如何如何“一核有难,八核围观”。
可能有人会问了:干嘛不多线程呢?
真的就这么简单吗?
本文将详细解释这个问题的成因,以及为什么 Mod 开发时不能轻易使用多线程,以及对应的解决方案。不过要注意,虽然本文力求术语的解释精确,但难免会有疏忽之处,若有问题,烦请不吝赐教。
概念
说起多线程,有那么几个老生常谈的概念不得不再次在此重申一遍。这里,用群众喜闻乐见的“挖坑”这件事进行类比:
并发:有一群人在挖坑。可能是挖同一个坑,也可能是各自挖各自的坑。
单线程 vs. 多线程:一个人在挖坑 vs. 一群人在挖坑。坑的数量不明,人员的组成不明,挖坑的具体安排不明。
同步:可以是下面中的某一个:
线程同步:一群人在挖坑,同时有一群人在拉土,坑挖好了的时候拉土的人才开始工作。
数据同步:一群人在挖坑,以某种方式保证所有人都知道挖坑进度,防止挖到别人的坑里并产生事故。
异步:一个或一群人在挖坑,忽然有人指示开挖新坑,但并没有人为之所动;几小时后连新坑都挖完了,但具体中间是怎么安排的并没有人知道。
Minecraft 到底是不是单线程游戏?
技术上来说不是。它至少有这样几个线程:
服务器线程。游戏的主要逻辑都在这个线程上发生。即便是客户端,也会有这样一个线程(即所谓的“集成服务器”(IntegratedServer)线程,这里的“集成”是相对于只有游戏逻辑,没有显示,专门在纯命令行等环境下使用的“专用服务器”(DedicatedServer)来说的)。
渲染线程(Minecraft 1.8 起)。这是跟显卡打交道的线程。
网络线程(Minecraft 1.8 起?)。这个线程负责服务器线程和客户端线程的通信。可能不止一个。
世界生成线程(Minecraft 1.13 起)。世界生成器会在这个线程上跑。
这几个线程是 99% 的 Mod 或插件开发者肯定会与之打交道的线程。当然还有别的线程,但开发者通常不会跟那些线程打交道,就暂且不提。
那“单线程游戏”的说法从何而来?
注意这句:
服务器线程。游戏的主要逻辑都在这个线程上发生。
游戏的所有大大小小的逻辑,无一例外,都在服务器线程上执行。通常所说的“主线程”大多数时候都是指服务器线程。这个线程大约是在做这个事情(伪(C)代(+)码(+)):
void gameLoop() {
while (keepGameRunning) {
int tickTime = 0;
for (int i = 0; i < 20; i++) {
long lastTick = System.currentTimeMills();
this->doGameLogic();
int tickCost = System.currentTimeMills() - lastTick;
if (tickCost < 50) {
tickTime += 50;
sleep(50 - tickCost);
} else {
tickTime += tickCost;
}
}
if (tickTime > 1000) {
this->warnUserTPSLag();
}
}
}
一圈循环下来,20 个 tick 就这样过去了。一个 tick 里游戏要做的事情有很多——所有的 Entity 要刷新一遍,所有的 TileEntity 也要刷新一遍,还要随机选几十个方块刷新一遍,因此光照可能会重新计算,这些操作搞不好会触发更多的方块刷新。请不要忘记玩家可能在挖矿、跑路、种地、打怪升级、开新区块触发世界生成等等的操作。同时 Minecraft
本身还会发几个数据包,也会处理几个数据包。可谓事无巨细,统一在一个线程上处理。单线程说法因此而来。
那我让一个 tick 分散到多个线程上去不就可以了?
真的可以吗?
让我们回到挖坑这件事上来。一个人挖坑,的确不会快到哪里去。但两个人挖坑就一定比一个人快吗?也许你会发现只有一把铲子可以用。好一点的情况可以是有三把铲子可以用,但请注意这是资源浪费,因为一个人不太可能同时挥舞两把铲子。即便是有不多不少两把铲子,你还可能会遇到因不合理的挖坑分工导致的工伤事故,于是挖坑计划就会被推迟,你也许会因此付出更多的资源。
让我们再把问题扩大化:现在我们要找一万人挖一个天坑。有鉴于要挖的是一个天坑,你大概不会直接让一万人上铲子蛮干,而是让这一万人中的一部分会操纵挖掘机的人来操作挖掘机挖坑,剩下的人则是负责挖掘机干不了的细活以及后勤工作等等。但请注意,你挖的不是隧道,是坑,你不能把这帮人分成两队,一队从上面开始挖,一队从下面开始挖,这样只会让上面的人和挖掘机忽然掉下来,并把下面的人和挖掘机砸成板。分成三队自然更不靠谱,理由同分成两队。退一步讲,你该怎么让一帮人从下面往上开始挖坑?如果你要先钻到下面去,你不是已经先钻出一个坑来了?
所以最靠谱方案的还是从上面一起往下挖。那么能不能分成两队,一队挖左半边一队挖右半边呢?听上去可以,但请考虑一下,两队工作效率还有可能不对等,结果挖着挖着,忽然坑塌了,于是又是新的事故出现了。
所以结论是,挖坑不能像挖隧道那样分成两队分别行动,约定在某地点打通并胜利会师。
回到 Minecraft 上来。刚才的坑就是 Minecraft 的一个游戏刻(Gametick,部分 CBer 使用缩写 gt),挖坑的过程大抵就是方块、实体、TileEntity 刷新的过程。所有的刷新操作都会对周围的方块造成影响,反映到区块上,就是对区块数据的写操作,比如方块破坏、亮度更新、方块状态变更等等。两种实体(Entity 和
TileEntity)的刷新还通常会改变自身及周围方块及实体的状态。试想,一个 TileEntity 的刷新操作不在服务器线程上完成,然后它对周围的方块进行了修改,但一部分被修改的方块也不幸被选中进行刷新,此时就会出现数据竞争的情况——两个线程同时修改一个对象里的数据。此时有三个选择:
加锁。基本是套一个 synchronized 临界区域这样的东西,但加锁释锁也有开销,更何况你可能面对的是成百上千个 TileEntity 同时加锁,然后若干线程在等这些锁被释放。
免锁逻辑。对于 TileEntity 和普通实体来说这个可能好办一些,但对于采用了享元设计的方块来说... 免锁逻辑真的能实现吗?TileEntity 修改区块数据时打算怎么办?
放弃多线程,致力于优化游戏本身的性能瓶颈。
显然 1. 和 2. 的开销过于巨大,Mojang 是商业公司,他要养活那些为 Minecraft 付出辛劳与汗水的程序员,这样的重构会带来更多麻烦,使不得。
事实上,很多游戏都有类似的情况,其流程不能轻易被拆解成可以并发处理的小流程。
线程不安全
如前文所述,因为数据竞争,所以 Minecraft 是一个线程不安全的游戏。你在别的线程上修改数据,也许会造成毁灭性的后果。
// 这个方法会在网络通信线程上执行,而非游戏主线程。
public void processPacketData(CustomPacketFromClient packet) {
removeSomeTileEntities();
}
// 同时,有一个 TileEntity 还在更新...
pubilc void update() {
if (!world.isRemote) {
addSomeNewTileEntities();
}
}
这样也就只能等待 GG 了——ConcurrentModificationException。你当然也可以说换成线程安全的容器,但如果你不是要把一个游戏刻分散到多个线程上去的话,线程安全的容器只是多此一举,除了徒增烦恼,并不能带来实质性的改变。
异步
正因为 Minecraft 的主要逻辑都集中在服务器线程上,所以下面的代码是错误的:
// 错误示范,请勿模仿!
@SubscribeEvent
public void onTick(WorldTickEvent.Pre event) {
Thread.sleep(10000L); // 延时 10 秒后执行逻辑
doLogic();
}
因为只有一个线程在执行游戏逻辑,Thread.sleep(10000L) 会使得整个游戏暂停十秒钟(一万毫秒),很容易令客户端与服务器失去连接,进而造成诸如“单机游戏时提示连接超时然后退回服务器选择界面”这样的诡异情况。很不幸,的确有新手会犯这样的错误。
正确的解决思路有两种:
1. 依赖游戏刻,而非现实世界中的时间。
// 本段代码以 Forge 为框架
private int counter = 0;
@SubscribeEvent
public void onTick(TickEvent.WorldTickEvent event) {
if (counter++ > 200) { // 理想状态下,TPS 为 20,
// 此时 10 秒内会执行不多不少 200 个游戏刻。
doLogic();
counter = 0; // 重置计数器。
}
}
2. 如果是 Bukkit 或者 Sponge 这样的框架,其提供的异步操作 API 是允许指定延迟时间的。
// 本段代码以 Sponge 为框架
Sponge.getScheduler().createAsyncExecutor(myPluginObject)
.schedule((Runnable) () -> doLogic(), 10, TimeUnit.SECONDS);
事情就这样结束了吗?并没有。
还有这样的情况:某种强力的斧子一砍就是一整棵树,但这棵树特别特别高,于是一整个游戏刻全都在全力追上砍树的进程了。怎么办?
多线程在这个时候似乎符合直觉了:因为 Minecraft 特殊的物理系统,我完全可以让树分好几段“掉”下来。但请注意,这里还是有数据同步的问题——不仅是木头会掉下来,树叶也会在失去与木头的连接后开始凋谢检查,所以正常的并发根本行不通。
Minecraft 自身有一个简单的异步执行操作的机制,addScheduledTask(Runnable task)(MCP func_152344_a),这个方法来自 IThreadListener 接口,所以你可以在客户端独有的 Minecraft
或者客户端和服务器都有的 MinecraftServer 两个类中找到这个方法。所有通过此方法规划的任务都会在未来的某一个游戏刻时执行。大约是下面这个逻辑(伪(C)代(+)码(+)):
void gameLoop() {
while (keepGameRunning) {
// Do other logic
for (int i = 0; i < 20; i++) {
// Do other logic
auto task = this->scheduledTaskQueue.front();
this->scheduledTaskQueue.pop();
task.run();
// Do other logic
}
// Do other logic
}
}
如果你需要让你的逻辑分散在多个游戏刻上,用这个 addScheduledTask 就可以。这样做大约相当于,让一个人每天挖一点坑,过个几百天就能挖出大坑。
addScheduledTask 还有一个用途是让一部分代码在主线程上工作,比如刚刚接收到的数据包,解析完数据,要执行逻辑时,需要这么做。
多线程的正确用法
但实际上,使用多线程有时候的确是合理的。比如 Mod 检查更新的时候。
public void versionCheck() {
try {
URL url = new URL("...");
InputStream remoteData = url.openStream();
List<String> lines = readAllLines(remoteData);
} catch (Exception e) {
logError(e);
}
}
请注意,这个 IO 操作会阻塞线程。换言之,如果网络不好,这个东西就会卡住无法继续执行,直到超时。此时多线程就是一个合理的解决方案。
new Thread(VersionCheck::versionCheck, "VersionCheck").start();}

我要回帖

更多关于 挖坑游戏 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信