JavaScript >> Javascript 文檔 >  >> Node.js

讓我們用 Node.js 做一個繪圖遊戲

到目前為止,您可能已經聽說過 node.js。它是一個建立在 Google 的 V8 JavaScript 引擎之上的異步 Web 服務器(與使 Chrome 快速運行的引擎相同)。使用 node,您可以用 JavaScript 編寫可擴展的 Web 服務,可以處理大量的同時連接,這使其非常適合作為遊戲、網絡聊天和其他實時任務的後端。

理念

今天我們將製作一個簡單的在線繪圖遊戲。該應用程序將允許用戶通過拖動和移動鼠標在頁面上繪圖,並將結果顯示在大畫布元素上。然而,與所有其他類似實驗的不同之處在於,人們會在他們這樣做時實時看到對方。為了實現這一點,我們將利用 node.js 的 socket.io 庫,它使用從 websockets 到 AJAX 長輪詢的一系列技術來為我們提供實時數據通道。因此,該示例適用於所有現代瀏覽器。

安裝 node.js

要運行遊戲,您需要安裝 node.js。它不應該超過幾分鐘,而且相當簡單。您可以繼續從官方網站下載安裝程序。或者,如果您希望在 Linux 或 OSX 中從終端安裝它,也可以運行這組命令(您只需要運行第一個腳本:node-and-npm-in-30-seconds.sh )。

完成安裝後,您還可以訪問節點包管理器 npm。使用此實用程序,您可以安裝有用的庫和可以導入到 node.js 腳本中的代碼位。對於這個例子,我們需要上面提到的 socket.io 庫和 node-static,它將為繪圖應用程序的 HTML、CSS 和 JS 文件提供服務。再次,打開你的終端(如果你在 Windows 上,則打開一個新的命令提示符窗口)並編寫以下命令:

npm install [email protected] node-static

這應該不會超過幾分鐘即可完成。

運行應用程序

如果您只想獲取文件並在計算機上測試應用程序,則需要從上面的按鈕下載存檔,並將其解壓縮到硬盤驅動器的某個位置。之後,打開命令提示符/終端並導航到該文件夾(當然您還記得 cd 命令是如何工作的,不是嗎?)。在此之後,輸入此命令並按回車鍵:

node app.js

您應該會收到一個 socket.io 調試消息(否則您的路徑可能是錯誤的;繼續使用該 cd 命令練習!)。這意味著一切都已啟動並正在運行!現在打開 http://localhost:8080 你應該看到你自己的演示副本。不錯!

如果您遵循本文的步驟並從頭開始構建應用程序,這些說明也適用。這讓我們回到了教程:

HTML

第一步是創建一個新的 HTML 文檔。在其中,我們將放置用戶將在其上繪製的畫布元素,以及用於保存鼠標指針的 div。每個鼠標指針都是一個帶有 .pointer 的 div 絕對定位在頁面上的 css 類(我們不會在本文中討論樣式,打開 assets/css/styles.css 看看)。

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Node.js Multiplayer Drawing Game | Tutorialzine Demo</title>

        <!-- The stylesheets -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>
        <div id="cursors">
            <!-- The mouse pointers will be created here -->
        </div>

        <canvas id="paper" width="1900" height="1000">
            Your browser needs to support canvas for this to work!
        </canvas>

        <hgroup id="instructions">
            <h1>Draw anywhere!</h1>
            <h2>You will see everyone else who's doing the same.</h2>
            <h3>Tip: if the stage gets dirty, simply reload the page</h3>
        </hgroup>

        <!-- JavaScript includes. Notice that socket.io.js is served by node.js -->
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

可以看到畫佈設置為固定寬度 1900px 和高度 1000px,但是顯示器較小的用戶只能看到其中的一部分。一個可能的改進是根據屏幕尺寸放大或縮小畫布,但我將把它留給你。

為了讓用戶瀏覽器和 node.js 之間的實時通信通道正常工作,我們需要包含 socket.io 兩個地方都有庫,但是你不會找到 socket.io.js 下載存檔中 index.html 底部包含的文件。這是因為 socket.io 攔截了對 /socket.io/socket.io.js 的請求 並自行提供,因此您不必在您的應用程序中明確上傳此文件。

客戶端

在其他教程中,我們通常會將此部分命名為 JavaScript,但這次我們在客戶端(用戶的瀏覽器)和服務器(node.js)上都有 JavaScript,因此必須進行適當的區分。

您在下面看到的代碼在此人的瀏覽器中運行。它使用 socket.io 連接到服務器並在事件發生時通知我們。該事件是由其他客戶端發出並由 node.js 轉發給我們的消息。這些消息包含鼠標坐標、用戶的唯一 id 以及當前是否正在繪製。

assets/js/script.js

$(function(){

    // This demo depends on the canvas element
    if(!('getContext' in document.createElement('canvas'))){
        alert('Sorry, it looks like your browser does not support canvas!');
        return false;
    }

    // The URL of your web server (the port is set in app.js)
    var url = 'http://localhost:8080';

    var doc = $(document),
        win = $(window),
        canvas = $('#paper'),
        ctx = canvas[0].getContext('2d'),
        instructions = $('#instructions');

    // Generate an unique ID
    var id = Math.round($.now()*Math.random());

    // A flag for drawing activity
    var drawing = false;

    var clients = {};
    var cursors = {};

    var socket = io.connect(url);

    socket.on('moving', function (data) {

        if(! (data.id in clients)){
            // a new user has come online. create a cursor for them
            cursors[data.id] = $('<div class="cursor">').appendTo('#cursors');
        }

        // Move the mouse pointer
        cursors[data.id].css({
            'left' : data.x,
            'top' : data.y
        });

        // Is the user drawing?
        if(data.drawing && clients[data.id]){

            // Draw a line on the canvas. clients[data.id] holds
            // the previous position of this user's mouse pointer

            drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
        }

        // Saving the current client state
        clients[data.id] = data;
        clients[data.id].updated = $.now();
    });

    var prev = {};

    canvas.on('mousedown',function(e){
        e.preventDefault();
        drawing = true;
        prev.x = e.pageX;
        prev.y = e.pageY;

        // Hide the instructions
        instructions.fadeOut();
    });

    doc.bind('mouseup mouseleave',function(){
        drawing = false;
    });

    var lastEmit = $.now();

    doc.on('mousemove',function(e){
        if($.now() - lastEmit > 30){
            socket.emit('mousemove',{
                'x': e.pageX,
                'y': e.pageY,
                'drawing': drawing,
                'id': id
            });
            lastEmit = $.now();
        }

        // Draw a line for the current user's movement, as it is
        // not received in the socket.on('moving') event above

        if(drawing){

            drawLine(prev.x, prev.y, e.pageX, e.pageY);

            prev.x = e.pageX;
            prev.y = e.pageY;
        }
    });

    // Remove inactive clients after 10 seconds of inactivity
    setInterval(function(){

        for(ident in clients){
            if($.now() - clients[ident].updated > 10000){

                // Last update was more than 10 seconds ago.
                // This user has probably closed the page

                cursors[ident].remove();
                delete clients[ident];
                delete cursors[ident];
            }
        }

    },10000);

    function drawLine(fromx, fromy, tox, toy){
        ctx.moveTo(fromx, fromy);
        ctx.lineTo(tox, toy);
        ctx.stroke();
    }

});

基本思想是我們使用 socket.emit() 在每次鼠標移動時向 node.js 服務器發送一條消息。這會產生大量數據包,因此我們將其速率限制為每 30 毫秒一個數據包($.now() 函數由 jQuery 定義,並返回自紀元以來的毫秒數)。

mousemove 事件不會在移動的每個像素上調用,但是我們使用了一個技巧來繪製實線而不是單獨的點 - 在畫布上繪製時,我們使用 lineTo 方法,以便鼠標坐標之間的距離是用直線連接。

現在讓我們來看看服務器吧!

服務器端

閱讀完客戶端代碼後,您可能會擔心服務器上的代碼會更長。但是你會弄錯的。服務器端的代碼更短更簡單。它的作用是在人們在瀏覽器中訪問應用程序的 url 時提供文件,併中繼 socket.io 消息。這兩項任務都由庫輔助,因此盡可能簡單。

app.js

// Including libraries

var app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    static = require('node-static'); // for serving files

// This will make all the files in the current folder
// accessible from the web
var fileServer = new static.Server('./');

// This is the port for our web server.
// you will need to go to http://localhost:8080 to see it
app.listen(8080);

// If the URL of the socket server is opened in a browser
function handler (request, response) {

    request.addListener('end', function () {
        fileServer.serve(request, response); // this will return the correct file
    });
}

// Delete this row if you want to see debug messages
io.set('log level', 1);

// Listen for incoming connections from clients
io.sockets.on('connection', function (socket) {

    // Start listening for mouse move events
    socket.on('mousemove', function (data) {

        // This line sends the event (broadcasts it)
        // to everyone except the originating client.
        socket.broadcast.emit('moving', data);
    });
});

有了這個,我們的繪圖應用就完成了!

完成!

當你看到其他人同時在畫畫時,畫畫會更有趣。隨意使用示例並改進它!一些想法:在光標旁邊顯示不同的畫筆、橡皮擦、顏色和形狀,甚至是國旗會很棒。去狂野吧!


下一篇
No
Tutorial JavaScript 教程
  1. 學習 JavaScript 中的值、類型和運算符

  2. 使用 Jquery 操作 Web

  3. 使用 SVG 的更好方法

  4. 如何成為後端開發人員:學習的重要技能

  5. 了解現代 Web 堆棧:Babel

  6. 使用 Mocha 和 Chai 測試 TypeScript

  7. 容器陷阱

  1. 修復 CSS 流體網格中的子像素舍入問題

  2. 🔥 快速提示:如何將 PureComponent 創建為函數式方式

  3. 學習 JavaScript 集(簡單而強大的內置對象)

  4. 代碼註釋(大部分)違反了 DRY

  5. 一種簡單有效的學習和練習 JavaScript 的方法。

  6. Redux 還是上下文 API?

  7. 給自己的一封信

  1. 異步 Javascript - 04 - 承諾

  2. 使用 NodeJS 構建測驗 REST API

  3. 為您的下一次面試提供五個* JavaScript 概念

  4. 使用 HTML CSS &JS 構建 Pexels 克隆網站 |在 2021 年創建完整的網站