国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

從零到一,擼一個(gè)在線斗地主(上篇)

raoyi / 2527人閱讀

摘要:原文從零到一,擼一個(gè)在線斗地主上篇作者背景朋友來(lái)深圳玩,若說(shuō)到在深圳有什么好玩的,那當(dāng)然是宅在家里斗地主了可是天算不如人算,撲克牌丟了幾張不全大熱天的,誰(shuí)愿意出去買(mǎi)牌啊。

原文:從零到一,擼一個(gè)在線斗地主(上篇) | AlloyTeam
作者:TAT.vorshen

背景:朋友來(lái)深圳玩,若說(shuō)到在深圳有什么好玩的,那當(dāng)然是宅在家里斗地主了!可是天算不如人算,撲克牌丟了幾張不全……大熱天的,誰(shuí)愿意出去買(mǎi)牌啊。不過(guò)問(wèn)題不大,作為移動(dòng)互聯(lián)網(wǎng)時(shí)代的程序猿,當(dāng)然是擼一個(gè)手機(jī)在線斗地主來(lái)代替實(shí)體牌了。

github地址:https://github.com/vorshen/landlord

閱讀前注意:
本文分為上下兩篇,本篇講準(zhǔn)備工作以及前端一些布局相關(guān)的知識(shí);下一篇講webassembly實(shí)現(xiàn)核心邏輯和server端相關(guān)。

由于源碼在github上全部都有,所以文章更偏向于思路的講解。

業(yè)余時(shí)間有限,游戲樣式丑= =,有些細(xì)節(jié)也沒(méi)打磨,敬請(qǐng)諒解。不過(guò)還是達(dá)到了閉環(huán),線下開(kāi)黑娛樂(lè)應(yīng)該沒(méi)有問(wèn)題。

游戲大概樣式

準(zhǔn)備 技術(shù)選型與準(zhǔn)備

typescript + canvas + webassembly + c++(server)
首先肯定是Web的,人齊有個(gè)局域網(wǎng)server端啟動(dòng),然后QQ、微信、瀏覽器訪問(wèn),直接就開(kāi)干了啊。既然是Web的,那必須是typescript啊,我覺(jué)得寫(xiě)過(guò)ts的,這輩子應(yīng)該不會(huì)再想寫(xiě)js了吧……

斗地主作為一個(gè)元素不多、沒(méi)炫酷場(chǎng)景的游戲,其實(shí)dom完全可以吃得住。但是做個(gè)Web游戲,不用個(gè)canvas作為舞臺(tái),總感覺(jué)哪里不對(duì)勁。所以最終我們還是用canvas來(lái)渲染。這里我們就沒(méi)有用成熟的渲染引擎了,鍛煉鍛煉自己。

既然作為練手作品,總要折騰點(diǎn),webassembly作為目前很火的技術(shù),我們當(dāng)然要嘗試一下啦,所以游戲的一些核心邏輯采用了webassembly實(shí)現(xiàn),這里會(huì)在下一篇詳細(xì)講解。

編碼前

既然是自己從零到一,產(chǎn)品設(shè)計(jì)開(kāi)發(fā)都得是自己,我們先簡(jiǎn)單梳理一下游戲的流程。我們這個(gè)斗地主不同于QQ斗地主,QQ斗地主是隨機(jī)進(jìn)入房間,無(wú)法開(kāi)黑。而我們追求的是一起玩,所以游戲房間的概念是一大不同。

簡(jiǎn)單列了一下我們游戲的流程:

快速進(jìn)入,即開(kāi)即玩,無(wú)需注冊(cè)

創(chuàng)建房間或搜索加入房間

進(jìn)入房間之后,傳統(tǒng)的斗地主邏輯

傳統(tǒng)的斗地主邏輯如下:

雖然這里貼出來(lái)了,但自己真正開(kāi)始寫(xiě)的時(shí)候,壓根沒(méi)梳理,就是一把梭,上來(lái)就擼碼。結(jié)果發(fā)現(xiàn)了不少邏輯上的沖突點(diǎn)和細(xì)節(jié)點(diǎn),斗地主看起來(lái)是一個(gè)小游戲,不過(guò)邏輯還蠻復(fù)雜的,再加上在線非單機(jī),完全低估了游戲的復(fù)雜度,一把辛酸淚……

設(shè)計(jì)沒(méi)啥好說(shuō)的,從網(wǎng)上找了幾個(gè)圖就當(dāng)作基本的元素了(難看就難看了……沒(méi)辦法)

下面就正式開(kāi)始了

布局 橫屏

首先斗地主這個(gè)游戲是橫屏的,這個(gè)蛋疼了,因?yàn)閣eb對(duì)橫屏的控制太弱了一點(diǎn)。我們無(wú)法強(qiáng)制橫版,全部依賴(lài)系統(tǒng)的行為。

既然橫屏限制多不好用,那么我們能不能直接用豎屏來(lái)模擬橫屏呢?也就是手機(jī)保持豎屏狀態(tài),然后我們整個(gè)頁(yè)面旋轉(zhuǎn)一下,就模擬了豎屏了,寫(xiě)樣式布局啥的,完全可以按照橫屏的來(lái)寫(xiě),還是挺方便的。

原理如下:

大概代碼

// 獲取旋轉(zhuǎn)元素父元素的寬高
let width = this._app.root.offsetWidth;
let height = this._app.root.offsetHeight;

this._box = document.createElement("div");
this._box.className = "room-box";
// 寬高反轉(zhuǎn)
this._box.style.width = `${height}px`;
this._box.style.height = `${width}px`;
this._box.style.transform = `translateX(${width}px) rotate(90deg)`;

注意!這樣的橫屏,會(huì)導(dǎo)致無(wú)法直接使用點(diǎn)擊事件的clientX/Y,這里也需要進(jìn)行一下轉(zhuǎn)換,具體代碼在Stage.ts中,這里不再展開(kāi)。

不過(guò)這種方案在模擬器上看起來(lái)沒(méi)啥問(wèn)題,真機(jī)上還是有缺陷的,就是標(biāo)題欄的問(wèn)題,如圖

不過(guò)我覺(jué)得這個(gè)還行,無(wú)傷大雅,所以就采取了這種方式

適配

游戲分為三個(gè)場(chǎng)景頁(yè)面:首頁(yè),大廳頁(yè),房間頁(yè)。其中首頁(yè)和大廳頁(yè)其實(shí)也就是走個(gè)流程,我們很隨意,房間頁(yè)就是對(duì)戰(zhàn)相關(guān),最為復(fù)雜,這里就以房間頁(yè)來(lái)說(shuō)。下面是經(jīng)典的QQ斗地主的房間頁(yè):

我們大致劃分一下模塊,如圖所示:

不考慮細(xì)節(jié)的情況下還是比較簡(jiǎn)單的,可以看出,主要就是六大區(qū)域:

頂部信息展示區(qū)

底部信息展示區(qū)

左側(cè)玩家區(qū)域

右側(cè)玩家區(qū)域

主視角玩家區(qū)域

特效區(qū)域

我們這就不考慮出牌特效啥的了(找?guī)讉€(gè)基礎(chǔ)的素材就要了我命了),如果用dom實(shí)現(xiàn),那直接flex就安排的明明白白,如下(只是舉例子,沒(méi)有用前面橫屏的方式)




    
    
    
    
    


    

上面是flex的實(shí)現(xiàn),很輕松,但是,我們使用canvas渲染,該如何針對(duì)不同屏幕尺寸進(jìn)行適配呢?
這里有兩種大的考慮方向:

canvas模擬彈性布局

縮放解決

canvas模擬彈性布局

眾所周知我們用原生canvas接口,繪制元素,都是用絕對(duì)定位的形式,不支持flex。看了下業(yè)界一些游戲渲染引擎,alloyrender、erget、easelJS也都是用x,y坐標(biāo)控制顯示對(duì)象的位置。

我的理解是既然你采用canvas了,自然是會(huì)出現(xiàn)頻繁重繪,彈性布局更偏向于靜止的頁(yè)面場(chǎng)景,對(duì)于游戲上需求不大,沒(méi)必要花大功夫吃力不討好。不過(guò)我們這個(gè)斗地主是一個(gè)偏頁(yè)面靜止的游戲,感興趣的同學(xué)可以嘗試嘗試,針對(duì)上面那五個(gè)模塊用固定大小+百分比的方式來(lái)實(shí)現(xiàn)一下彈性布局。由于時(shí)間和篇幅關(guān)系,這里就不貼效果圖和代碼了。

這種方式的優(yōu)勢(shì)是可以把屏幕使用率拉滿,也不會(huì)有變形;

劣勢(shì)就是太麻煩了,光是這五個(gè)區(qū)域的布局還好,但是還涉及到區(qū)域里面細(xì)節(jié)的時(shí)候,實(shí)在是hold不住了,所以我最終也沒(méi)有采用這種方式。如果有那些簡(jiǎn)單的布局場(chǎng)景,還是可以試試。

縮放解決

看名字就知道是采用「縮放」來(lái)抹平不同屏幕尺寸的差異了。怎么縮放,也是有很多種方案,我羅列兩個(gè)我覺(jué)得比較好的,應(yīng)該也是用的比較多的

全部展示+黑邊

核心展示+無(wú)黑邊

兩者的原理如下所示:

二者的針對(duì)的場(chǎng)景也不太相同

「全部展示+黑邊」:所有內(nèi)容都必須展示出來(lái),黑邊可以用大背景掩蓋住

「核心展示+無(wú)黑邊」:整個(gè)舞臺(tái)可以很大,用戶(hù)只需要聚焦核心區(qū)域

綜上所述,我們肯定要采用的是第一種方式了

渲染

整個(gè)頁(yè)面不是很復(fù)雜,為了練手,我們也沒(méi)有用業(yè)界成熟的渲染引擎。但是總不能用canvas原生的寫(xiě)法,所以首先我們封裝了幾個(gè)基礎(chǔ)的組件

DisplayObject 顯示對(duì)象基類(lèi),只要對(duì)象要顯示,一定要繼承該類(lèi)

Container 容器類(lèi)

Bitmap 位圖類(lèi)

Text 文本類(lèi)

以上是這次游戲中需要用到的渲染相關(guān)的基類(lèi),我們具體的展示對(duì)象(撲克牌),或者容器(手牌)都是繼承它們,再進(jìn)行一些擴(kuò)充。具體的代碼github上都能看到。
下面用張圖表示一下整個(gè)項(xiàng)目中組件情況

這里假設(shè)我們要正式開(kāi)發(fā)一個(gè)游戲,借助渲染引擎,意味著不需要考慮base部分了。那么大概流程是如下的。

我們要先規(guī)劃出場(chǎng)景,確定有幾個(gè)場(chǎng)景。

針對(duì)1中的場(chǎng)景,確定每個(gè)場(chǎng)景有哪些基于base的上層組件

組件抽象復(fù)用性判斷(不同場(chǎng)景類(lèi)似的組件,是不是可以抽象成一個(gè))

工具庫(kù)、第三方庫(kù)確定

流程基本上就是如此。

這里我們用頁(yè)面上最重要的一個(gè)組件為例,講一下

BasePukesContainer是非常重要的一個(gè)組件,如其名,它是負(fù)責(zé)撲克牌展示的。玩家的手牌(HandPukes)、玩家出的牌(DesktopPukes)都是繼承于它,所以BasePukesContainer抽象就很重要了

首先,我們確定下BasePukesContainer作為一個(gè)撲克牌展示承載容器,需要哪些方法

能帶著撲克牌(子元素)展示

能批量的增刪撲克牌

撲克牌的支持多種對(duì)齊方式、多行展示等

列個(gè)圖,看了BasePukesContainer已有的,和需要補(bǔ)充的

紅色部分是目前繼承base下來(lái)缺失的,那么我們就要擴(kuò)充

最終代碼如此(完整源碼看github)

class BasePukesContainer extends Container {
    // 撲克牌寬度
    protected _pukeWidth: number;
    // 撲克牌高度
    protected _pukeHeight: number;
    // 撲克牌水平對(duì)齊方式
    protected _horizontalAlign: PUKE_HORIZONTAL_ALIGN;
    // 撲克牌垂直對(duì)齊方式
    protected _verticalAlign: PUKE_VERTICAL_ALIGN;
    // 撲克牌之間兩兩的覆蓋大小
    private _interval: number;

    /**
     * 移除某張撲克牌
     * @param {*} object 
     */
    protected _deletePuke(object: BasePuke) {}

    /**
     * 加入單張撲克牌
     * @param {*} puke 
     */
    protected _postPuke(puke: BasePuke, zIndex?: number) {}

    /**
     * 觸發(fā)更新維護(hù)的撲克牌的位置
     */
    protected _updatePukes() {}

    constructor(options: i_BasePukesContainerOptions) {}

    /**
     * 移除部分撲克牌
     * @param {string[]} pukes
     */
    deletePukes(pukes: string[]) {}

    /**
     * 添加部分撲克牌
     * @param {string[]} pukes
     */
    postPukes(pukes: string[]) {}

    /**
     * 刪除所有牌
     */
    deleteAll() {}
}

渲染引擎的組件和使用思想都講完了,當(dāng)然細(xì)節(jié)和基礎(chǔ)組件肯定遠(yuǎn)遠(yuǎn)不止這些,比如動(dòng)畫(huà)、粒子等等,感興趣的可以看下業(yè)界渲染引擎的源碼,帶著理解去讀,應(yīng)該還是挺易懂的。

交互

靜態(tài)渲染相關(guān)的都講完了,下面我們說(shuō)說(shuō)游戲開(kāi)發(fā)中的交互

問(wèn)題

撲克牌排列渲染好了,玩家得出牌啊,touchstart和touchmove都應(yīng)該觸發(fā)選牌。問(wèn)題是canvas不是dom,不管展示啥,理論上要不是fill出來(lái)的,要不然是stroke出來(lái)的,都沒(méi)法綁定交互事件啊。

其實(shí)這個(gè)問(wèn)題也不算是問(wèn)題了,基本上大家應(yīng)該都知道解決方案。

雖然fill出來(lái)的東西我們無(wú)法綁定事件,但是,我們可以給canvas標(biāo)簽綁上事件啊。然后根據(jù)event的clientX/Y相對(duì)于canvas的位置,找到對(duì)應(yīng)渲染的元素啊。

具體原理如下

(x3, y3)就是clientX/Y

它是全局坐標(biāo),我們先減去(x1, y1),得到相對(duì)于canvas舞臺(tái)的坐標(biāo)(x", y")

此時(shí)一切都是相對(duì)于canvas舞臺(tái)的坐標(biāo)系了,我們用(x", y")去和[x2, y2, w, h]這個(gè)矩形對(duì)比,判斷點(diǎn)在不在矩形中,如果在,就意味著點(diǎn)擊到了元素

如果頁(yè)面比較簡(jiǎn)單,確實(shí)解決了。然后有些事情并非那么簡(jiǎn)單……

元素重疊


有兩個(gè)元素(撲克)存在重疊,玩家點(diǎn)擊在了重疊的區(qū)域,該如何響應(yīng)?

組件嵌套


剛剛只有兩個(gè)坐標(biāo)系,屏幕坐標(biāo)系和canvas坐標(biāo)系,如果再引入一個(gè)container呢,是不是又多了一個(gè)相對(duì)坐標(biāo)?茫茫無(wú)盡的嵌套,該怎么辦呢?

不規(guī)則圖形


一個(gè)點(diǎn)是否在矩形中,很好判斷;是否在圓中,也好判斷,但如果是不規(guī)則圖形呢?

解決

針對(duì)元素重疊,首先我們肯定是不能觸發(fā)層級(jí)低元素的點(diǎn)擊事件的,那么就是我們判斷點(diǎn)是否在矩形中的時(shí)候,一定要按順序來(lái)。正好Container也保證了這個(gè)順序,代碼類(lèi)似如下。

/**
 * touchstart,touchmove的時(shí)候觸發(fā)
 */
private _touch = (data: { x: number, y: number }) => {
    let {
        x, y
    } = data;
    let len = this._children.length;
    let i;
    let temp: BasePuke;
    let puke: BasePuke | undefined;

    for (i = len - 1; i >= 0; i--) {
        temp = this._children[i];
        if (temp.contain(x, y)) {
            puke = temp;
            break;
        }
    }

    if (puke) {
        this._choosePuke(puke);
    }
}

組件嵌套就稍微麻煩了些,這里的核心沖突是鼠標(biāo)點(diǎn)擊的位置是絕對(duì)坐標(biāo),而canvas舞臺(tái)里面的元素,都是相對(duì)坐標(biāo)。要對(duì)比的話,要么將絕對(duì)坐標(biāo)轉(zhuǎn)為相對(duì)的,要么把相對(duì)的轉(zhuǎn)成絕對(duì)坐標(biāo)。

這里我們采用的是將絕對(duì)坐標(biāo)轉(zhuǎn)為相對(duì)的,比如當(dāng)點(diǎn)擊坐標(biāo)為(x1, y1)時(shí),需要判斷是否點(diǎn)擊中了[x2, y2, w, h]這個(gè)矩形(注意:這個(gè)x2, y2是經(jīng)過(guò)層層嵌套的)

我們就需要求出(x1, y2)這個(gè)全局坐標(biāo),轉(zhuǎn)換到(x2, y2)坐標(biāo)系的矩陣,然后變化一下即可
代碼如下:

// DisplayObject.ts
/**
 * 判斷是否在AABB中
 * 注意,這里x,y是global的坐標(biāo),沒(méi)有經(jīng)過(guò)transform
 * 所以要進(jìn)行逆矩陣計(jì)算
 * @param {*} x 
 * @param {*} y 
 */
contain(x: number, y: number) {
    let point = new Point(x, y);
    let matrix: Matrix2D;

    // 先求出完整的矩陣
    if (this._parent) {
        matrix = this._parent._getGlobalMatrix();
    } else {
        matrix = new Matrix2D();
    }

    // 再求逆矩陣
    matrix.invert();
    
    // 點(diǎn)進(jìn)行矩陣變換
    point.transformWithMatrix(matrix);

    let rect = this._getAABB();

    return rect.contains(point);
}

變化矩陣就是根據(jù)需要判斷的元素,先獲取其全局的變換矩陣,然后求逆矩陣即可。如果了解矩陣的同學(xué),應(yīng)該很好理解,不了解的同學(xué),可以查閱一下相關(guān)資料,這里篇幅原因,就不詳細(xì)說(shuō)明了。

絕對(duì)轉(zhuǎn)相對(duì)是如此的,相對(duì)轉(zhuǎn)絕對(duì)也是類(lèi)似的做法。

最后一個(gè)就是不規(guī)則圖形,規(guī)則圖形我們都可以用幾何法甚至代數(shù)法判斷其是否在元素內(nèi)部,其實(shí)判斷的核心在于「邊」。但是不規(guī)則圖形,單純的想用「邊」的方式來(lái)判斷,太難了,所以就有了像素級(jí)別的判斷法:反畫(huà)家算法。還是篇幅問(wèn)題,這里不進(jìn)行展開(kāi),感興趣的同學(xué)自行查閱(我們這個(gè)斗地主游戲也沒(méi)有使用)。

總結(jié)

到這里,上文就要結(jié)束了。我們從需求開(kāi)始分析,將游戲中展示相關(guān)的工作都準(zhǔn)備完畢,解決了橫屏問(wèn)題,自己封裝了個(gè)簡(jiǎn)易的渲染引擎,確定好了上層組件,也準(zhǔn)備好了交互手勢(shì),可以說(shuō)非邏輯部分都已經(jīng)搞定了,已經(jīng)可以單機(jī)展示出來(lái)了。

那么該如何接收他人消息?游戲的同步是什么樣的?用戶(hù)進(jìn)出房間有什么注意事項(xiàng)?出牌核心邏輯部分該如何編寫(xiě)?Webassembly用在了哪里,如何使用?

敬請(qǐng)期待下篇。

AlloyTeam 歡迎優(yōu)秀的小伙伴加入。
簡(jiǎn)歷投遞: alloyteam@qq.com
詳情可點(diǎn)擊 騰訊AlloyTeam招募Web前端工程師(社招)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/106196.html

相關(guān)文章

  • 前端之從零開(kāi)始系列

    摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門(mén)技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫(xiě)函數(shù)庫(kù)中高級(jí)前端面試手寫(xiě)代碼無(wú)敵秘籍如何用不到行代碼寫(xiě)一款屬于自己的類(lèi)庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門(mén)技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...

    Youngdze 評(píng)論0 收藏0
  • 從零到一一個(gè)在線地主(下篇)

    摘要:原文從零到一,擼一個(gè)在線斗地主下篇作者上篇回顧我們說(shuō)了斗地主游戲的渲染展示部分,最后也講了下中交互的情況,下篇的重點(diǎn)就是游戲邏輯。 原文:從零到一,擼一個(gè)在線斗地主(下篇) | AlloyTeam作者:TAT.vorshen 上篇回顧:我們說(shuō)了斗地主游戲的渲染展示部分,最后也講了下canvas中交互的情況,下篇的重點(diǎn)就是游戲邏輯。 邏輯主要分成兩塊:流程邏輯和撲克牌對(duì)比邏輯。 gith...

    CloudDeveloper 評(píng)論0 收藏0
  • 從零到一:用Phaser.js寫(xiě)意地開(kāi)發(fā)小游戲(Chapter 1 - 認(rèn)識(shí)Phaser.js)

    摘要:由于公司項(xiàng)目轉(zhuǎn)型,需要?jiǎng)?chuàng)造一個(gè)小游戲平臺(tái),需要使用一個(gè)比較成熟的前端游戲框架來(lái)快速開(kāi)發(fā)小游戲。僅支持開(kāi)發(fā)游戲,因?yàn)閷?zhuān)注,所以高效。早在年的光棍節(jié)前一天晚上,這個(gè)游戲就誕生了。原型是一個(gè)之前很火的非常魔性的小游戲,叫尋找程序員。 showImg(https://segmentfault.com/img/bVMGY5?w=900&h=500); 寫(xiě)在前面 實(shí)際上我從未想過(guò)我會(huì)接觸到H5小游...

    didikee 評(píng)論0 收藏0
  • 從零到一:用深度優(yōu)先算法檢測(cè)有向圖的環(huán)路(應(yīng)用場(chǎng)景:性格測(cè)試)

    摘要:小結(jié)使用深度優(yōu)先算法,我們能夠檢測(cè)性格測(cè)試游戲的邏輯正確性,相比以往課堂上的理論,在這里算是一個(gè)具體的應(yīng)用場(chǎng)景吧。其實(shí)深度優(yōu)先算法的應(yīng)用面也很廣,遲早還會(huì)再碰面的。 showImg(https://segmentfault.com/img/bVStEU?w=900&h=500); 寫(xiě)在前面 在開(kāi)始前想先說(shuō)一下關(guān)于這個(gè)課題的感想——能學(xué)以致用是一件很快樂(lè)的事情。 深度優(yōu)先算法(簡(jiǎn)稱(chēng)DFS...

    nevermind 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<