Canvas+Socket предлагает мультиплеер «Я рисую, как вы думаете».

Canvas
Canvas+Socket предлагает мультиплеер «Я рисую, как вы думаете».

Дамы и господа, давно не виделись, эта статья должна была встретиться с вами в 2018 году, но она была отложена до этого момента по разным причинам (мой горшок, мой горшок), Весенний фестиваль будет через неделю, Вот что сказать Всех с новым годом! ! !

Сегодня хороший день. Давайте воспользуемся socket.io и canvas для создания простой игры «Я рисую, угадай».

Китайский Новый год почти наступил, давайте расслабим нервы напряженного года и поиграем в маленькие игры, которые вы создали!

PS: Если вы не понимаете упомянутую далее реализацию обмена мгновенными сообщениями, вы можете просмотреть ее еще раз.здесь

Что ж, мир единоборств быстр и не ломается, поторопитесь первымСтруктура каталогов

Реализовать мгновенную передачу сообщений

Поскольку я написал статью, чтобы представить содержимое socket.io для реализации мгновенной связи, я постараюсь написать ее как можно быстрее.

Сначала запустите службу через экспресс и создайте соединение socket.io.

Запустите службу и установите соединение

Сервер

// app.js文件
const express = require('express');
const app = express();
// 设置静态文件夹
// 这样设置会自动识别当前文件夹下的index.html文件
app.use(express.static(__dirname));

const server = require('http').createServer(app);
const io = require('socket.io')(server);

server.listen(8888);

Запустите службу, а затем посетите localhost:8888, вы можете получить доступ к содержимому файла index.html.

Советы:

Иногда он будет сообщать, когда клиент получает доступ к ioio is not definedОшибка заключается в том, что вам нужно сначала запустить службу, прежде чем она автоматически сгенерируетsocket.io/socket.io.jsФайл, как показано на рисунке выше, можно сослаться на

клиент

// index.js文件

// 用来处理游戏对象数据
let gameObj = {};
// socket实例
let socket = io();
// 监听connect事件
socket.on('connect', () => {
    console.log('客户端连接成功'); 
});

В этот момент обновите страницу, и вы увидите ее в консоли.客户端连接成功эти 7 слов

отправлять/получать/отображать сообщения

Это всего лишь небольшой шаг, далее продолжим расписывать логику обработки сообщений.

Сервер

// app.js

...省略

+++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 区分是聊天还是在绘图
const LINE = 0;
const MESSAGE = 1;
const userList = ['皮卡丘', '巴大蝴', '比比鸟',  '妙蛙种子', '小火龙', '杰尼龟'];
+++++++++++++++++++++++++++++++++++++++++++++++++++++++

io.on('connection', socket => {
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 随机分配用户名并发送给所有人
    const user = userList[Math.floor(Math.random() * userList.length)];
    const message = `欢迎${user}加入游戏!!!`;

    // 将数据封装成json对象
    let data = {};
    // 通过type来区分
    data.type = MESSAGE;
    data.sender = '系统';
    data.message = message;
    // 将消息分发出去
    // 消息数据必须是字符串类型,so需要转换一下
    io.emit('message', JSON.stringify(data));

    socket.on('message', msg => {
        // 传过来的消息也是json字符串格式的,需要JSON.parse转成json
        let data = JSON.parse(msg);
        // 如果是聊天类型,就给sender赋值为当前用户名
        if (data.type === MESSAGE) {
            data.sender = user;
        }
        io.emit('message', JSON.stringify(data));
    });
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++
});

server.listen(8888);

В приведенном выше коде данные сообщения упакованы в формат json для удобства, ведь данные сообщения могут принимать только строковый формат.

а затем передать его при отправкеJSON.stringifyпревратить вjson-строка, чтобы не было ошибки

Конечно, при разборе соответствующих данных сообщения передатьJSON.parseпреобразовать в реальныйjsonВот и все

После того, как сервер закончил отправлять и получать сообщения, пришло время появиться клиенту.Помимо двух вышеперечисленных функций, клиент также будет отображать сообщения (звуки хер)

Итак, без лишних слов, приступим! ! !

клиент

// index.js文件

++++++++++++++++++++++++++++++++++++++++++++++++++++++
const LINE = 0;
const MESSAGE = 1;
++++++++++++++++++++++++++++++++++++++++++++++++++++++

let gameObj = {};
let socket = io();

++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 监听服务端发来的消息
socket.on('message', msg => {
    // 需要先用JSON.parse转一下
    let data = JSON.parse(msg);
    console.log(data);  // {type: 1, sender: "系统", message: "欢迎皮卡丘进入游戏"}
    
    // 如果类型为聊天
    if (data.type === MESSAGE) {
        let li = `<li><span>${data.sender}: </span>${data.message}</li>`;
        $('#history').append(li);
        // 聊天区域滚动到最新聊天内容位置
        $('#history-wrapper').scrollTop($('#history-wrapper')[0].scrollHeight);
    }
});

// 点击发送按钮发消息
$('#btn').click(sendMsg);
// 按回车键发送消息
$('#input').keyup(e => {
    let keyCode = e.keyCode;
    if (keyCode === 13) {
        sendMsg();
    }
});

// 发送消息函数
function sendMsg() {
    let value = $.trim($('#input').val());
    if (value !== '') {
        let data = {};
        data.type = MESSAGE;
        data.message = value;
        gameObj.socket.send(JSON.stringify(data));
        $('#input').val('');
    }
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++

Что делает приведенный выше код на стороне клиента?

  1. Константа отличает чат от рисования
    const LINE = 0;
    const MESSAGE = 1;
    
  2. Слушайте сообщения с сервера
    • слушать сообщения
    socket.on('message', msg => {});
    
    • Преобразование сообщения в формат json
    let data = JSON.parse(msg);
    
    • Тип сообщения - чат
    if (data.type === MESSAGE) {
        // 添加内容
        ...省略
        // 滚动到最新消息位置
        ...省略
    }
    
  3. отправлять сообщения
    • способ отправки сообщения
    function sendMsg() { ...省略 }
    
    • Нажмите или введите, чтобы отправить

Выше приведена базовая реализация передачи сообщений.Теперь мы собираемся ввести следующую ссылку, холст на сцене, продолжайте читать

Холст для рисования артбордов

Элемент canvas долго ждал, и наконец настала его очередь показать свои таланты.Любой, кто использовал canvas, знает, что мы обычно выполняем операции рисования в 2D, поэтому давайте займемся этим раньше.

// index.js文件

const LINE = 0;
const MESSAGE = 1;

++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 用原生来获取,jq对象中并没有我们需要的2d
let cvs = document.getElementById('canvas');
let ctx = cvs.getContext('2d');

let gameObj = {
    // 当前用户是否在绘图
    isDrawing: false,
    // 下一条线的起始点
    startX: 0,
    startY: 0
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++

...省略

socket.on('message', msg => {
    let data = JSON.parse(msg);

    if (data.type === MESSAGE) {
        ...省略
    }
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++
    else if (data.type === LINE) {
        // 这是画线函数,专门绘制所用
        drawLine(ctx, data.startX, data.startY, data.endX, data.endY, 1);
    }
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++
});


// 发送消息函数
...省略

++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 开始在画板上画画了
// 鼠标按下时的操作
$('#canvas').on('mousedown', function(e) {
    let cvsPos = $(this).offset(),
        mouseX = e.pageX - cvsPos.left || 0,
        mouseY = e.pageY - cvsPos.top || 0;

    // 更新一下startX和startY
    gameObj.startX = mouseX;
    gameObj.startY = mouseY;
    // 更新为绘图状态
    gameObj.isDrawing = true;
});
// 鼠标移动时的操作
$('#canvas').on('mousemove', function(e) {
    // 当绘图状态为true的时候才可以绘制
    if (gameObj.isDrawing) {
        let cvsPos = $(this).offset(),
            mouseX = e.pageX - cvsPos.left || 0,
            mouseY = e.pageY - cvsPos.top || 0;

        if (gameObj.startX !== mouseX && gameObj.startY !== mouseY) {
            // 开始绘制线段,drawLine为画线函数
            drawLine(ctx, gameObj.startX, gameObj.startY, mouseX, mouseY, 1, $('#color').val());

            // 既然画线了,那就把画的线段数据也打包成json传给服务端
            let data = {};
            data.startX = gameObj.startX;
            data.startY = gameObj.startY;
            data.endX = mouseX;
            data.endY = mouseY;
            data.type = LINE;
            // 别犹豫,直接通过socket发给服务端
            socket.send(JSON.stringify(data));

            // 这里还要更新一下startX和startY
            gameObj.startX = mouseX;
            gameObj.startY = mouseY;
        }
    }
});

// 鼠标抬起时的操作
$('#canvas').on('mouseup', function() {
    gameObj.isDrawing = false;
});

// 画线函数
function drawLine(ctx, x1, y1, x2, y2, thick) {
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineWidth = thick;
    ctx.strokeStyle = '#00a1f4';
    ctx.stroke();
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++

Не смотрите на приведенный выше код вдруг намного больше, а по сути это не что иное, как следующие пункты, не паникуйте, давайте разберемся

  1. Различать сообщения как данные, нарисованные линиями
    • В функции, которая прослушивает сообщения, передайтеdata.type === LINEЧтобы отличить данные, тип сообщения которых представляет собой рисование линий
  2. мышь на холсте событие
    • пресс-событие mousedown
      • записать место щелчка
      • Назначьте нажатую позицию для gameObj.startX и gameObj.startY
      • Измените состояние gameObj.isDrawing на true
    • событие перемещения мыши
      • зафиксировать место перемещения
      • рисовать отрезки линии
      • Отправка данных рисования линий на сервер
      • Обновить последнюю позицию хода в gameObj
    • событие подъема мыши
      • Изменить GameObj.isdrawing Состояние на начальное значение false
  3. Реализовать метод рисования линий
    • ctx.beginPath() - нарисовать начальный путь
    • ctx.moveTo(x1, y1) - точка штриха сегмента линии
    • ctx.lineTo(x2, y2) - траектория движения сегмента линии
    • ctx.lineWidth - ширина линии
    • ctx.storkeStyle - цвет линии
    • ctx.stroke() - нарисовать отрезок линии

Трудно сказать, легко сказать. Однако, в зависимости от уровня владения, будут разные ощущения.Если вы будете больше писать и больше практиковаться, то знания естественным образом попадут в ваши руки.

В принципе можно сказать, что основы socket.io и canvas были освоены на этом этапе.В конце концов, мы не должны забывать название статьи.Это многопользовательская игра, не только для простого развлечения, но и для все счастливы

Так что давайте работать усерднее и реализовывать многопользовательскую логику

Создайте многопользовательскую игру

игровая логика

Играя в игры, естественно есть логика и правила игры

Реализация игровой логики на стороне клиента относительно проста, и многие операции по-прежнему зависят от стороны сервера.

Итак, сначала пойдем от простого к сложному.

клиент

// index.js文件

const LINE = 0;
const MESSAGE = 1;

// 添加个游戏常量
const GAME = 2;

let gameObj = {
    ...省略

    // 游戏状态
    WAITTING: 0,
    START: 1,
    OVER: 2,
    RESTART: 3,
    // 当前轮到谁来绘图
    isPlayer: false
};

...省略

socket.on('message', msg => {
    let data = JSON.parse(msg);

    if (data.type === MESSAGE) {
        ...省略
    } else if (data.type === LINE) {
        ...省略
    } else if (data.type === GAME) { // 如果进行游戏,传过来的type值必须是GAME
        // 通过data.state来判断游戏当前的进度

        // 游戏开始的逻辑
        if (data.state === gameObj.START) {
            // 游戏要是开始了就需要清空画布
            ctx.clearRect(0, 0, cvs.width, cvs.height);

            // 清空聊天记录和隐藏重新开始
            $('#restart').hide();
            $('#history').html('');

            // 区分一下是当前画图的玩家还是猜图的玩家
            if (data.isPlayer) {
                gameObj.isPlayer = true;
                $('#history').append(`<li>轮到你了,请你画出<span class="answer">${data.answer}</span></li>`);
            } else {
                $('#history').append(`<li>游戏即将开始,请准备,你们有一分钟的时间去猜答案哦</li>`);
            }
        }

        // 游戏结束的逻辑
        if (data.state === gameObj.OVER) {
            gameObj.isPlayer = false;
            $('#restart').show();
            $('#history').append(`<li>本轮游戏的获胜者是<span class="winner">${data.winner}</span>,正确答案是: ${data.answer}</li>`);
        }

        if (data.state === gameObj.RESTART) {
            $('#restart').hide();
            ctx.clearRect(0, 0, cvs.width, cvs.height);
        }
    }
});

...省略

// 画线函数
...省略


// 重玩
$('#restart').on('click', function() {
    let data = {};
    data.type = GAME;
    data.state = gameObj.RESTART;
    socket.send(JSON.stringify(data));
});

Продолжаете разбираться, что делает приведенный выше код?

  1. Игровые константы различают типы сообщений и игровые состояния.
    const GAME = 2;
    
    let gameObj = {
        ...省略
        
        // 游戏状态
        WAITTING: 0,
        START: 1,
        OVER: 2,
        RESTART: 3,
        // 当前轮到谁来绘图
        isPlayer: false
    };
    
  2. data.type is GAME означает играть в игру
  3. data.state для оценки текущего прогресса игры
    • Начало игр
      • пустой холст
      • Очистить историю чата и скрыть кнопку перезапуска
      • Используйте data.isPlayer, чтобы различать, является ли это ящиком или угадывателем для отображения разных текстов.
    • игра окончена
      • Показать кнопку перезагрузки
      • Показать победителя и ответить
    • начать сначала
      • Очистить холст, скрыть кнопки

Вышеуказанные три пункта - это код для клиента для реализации многопользовательских игр, не останавливайтесь, это скоро закончится, и продолжайте писать логику сервера.

Сервер

// app.js文件

...省略

const LINE = 0;
const MESSAGE = 1;

// 添加游戏常量
const GAME = 2;

// 游戏状态和游戏逻辑
const WAITTING = 0;
const START = 1;
const OVER = 2;
const RESTART = 3;

let player = 0;
let wordsList = ['苹果', '运动鞋', '火箭', '足球', '小黄人', '汽车', '小鸟'];
let currentAnswer;
let currentState = WAITTING;
let timer;
// 连接的客户端数量
let len = 0;

io.on('connection', socket => {
    ...省略
    
    // 将数据封装成json对象
    ...省略
    
    
    // 把游戏的消息通知所有人
    let game = {};
    game.type = GAME;
    game.state = WAITTING;
    io.emit('message', JSON.stringify(game));

    // 遍历客户端的连接
    io.clients((err, client) => {
        if (err) throw err;
        len = client.length;
    });

    // 当前状态为等待并且连接数超过两个的时候才开始游戏
    if (currentState === WAITTING && len > 2) {
        startGame(socket);
    }
    
    
    socket.on('message', msg => {
        ...省略
        
        // 判断是不是有玩家答对了
        if (currentState === START && data.message === currentAnswer) {
            let game = {};
            game.type = GAME;
            game.answer = currentAnswer;
            game.winner = user;
            game.state = OVER;
            io.emit('message', JSON.stringify(game));

            currentState = WAITTING;
            
            clearTimeout(timer);
        }

        // 重新开始
        if (data.state === RESTART && data.type === GAME) {
            startGame(socket);
        }
    });
});

// 开始游戏方法
function startGame(socket) {
    // 分配一个玩家来画画
    player = (player + 1) % len;
    // 随机分配个图案
    let random = Math.floor(Math.random() * wordsList.length);
    currentAnswer = wordsList[random];

    // 通知所有玩家游戏开始
    let data = {};
    data.type = GAME;
    data.isPlayer = false;
    data.state = START;
    io.emit('message', JSON.stringify(data));

    // 遍历客户端,然后找到画画的那个用户告诉他相关data
    let count = 0;
    io.clients((err, client) => {
        client.forEach(item => {
            if (count === player) {
                let game = {};
                game.type = GAME;
                game.state = START;
                game.isPlayer = true;
                game.answer = currentAnswer;
                // 这条消息只有绘图的玩家才能看到
                socket.send(JSON.stringify(game));
            }
            count++;
        });
    });

    // 1分钟后游戏结束
    timer = setTimeout(() => {
        let obj = {};
        obj.type = GAME;
        obj.state = OVER;
        obj.winner = '没有人啊!';
        obj.answer = currentAnswer;
        io.emit('message', JSON.stringify(obj));
    }, 60 * 1000);
    
    // 当前状态修改为START
    currentState = START;
}

server.listen(8888);

Сервер, друг мой, что ты только что сделал?

  1. Добавьте игровые константы, состояние и логику игры.
    // 游戏常量
    const GAME = 2;
    // 游戏状态
    const WAITTING = 0;
    ...省略
    const RESTART = 3;
    // 游戏逻辑
    let player = 0;
    ...省略
    let len = 0;
    
  2. Инициализировать игру и количество игроков
    // 把游戏的消息通知所有人
    let game = {};
    ...省略
    io.emit('message', JSON.stringify(game));
    
    // 遍历客户端的连接
    io.clients((err, client) => {
        if (err) throw err;
        len = client.length;
    });
    
    // 当前状态为等待并且连接数超过两个的时候才开始游戏
    if (currentState === WAITTING && len > 2) {
        startGame(socket);
    }
    
  3. Запустить игру - StartGame
    • Назначьте игроков нарисовать
    player = (player + 1) % len;
    
    • Случайно назначить шаблон
    let random = Math.floor(Math.random() * wordsList.length);
    currentAnswer = wordsList[random];
    
    • Уведомить всех игроков о начале игры
    let data = {};
    ...省略
    io.emit('message', JSON.stringify(data));
    
    • Пройдитесь по клиенту, а затем найдите пользователя, который рисовал, и сообщите ему соответствующие данные
    let count = 0;
    io.clients((err, client) => {
        client.forEach(item => {
            // 匹配为分配的玩家才可以绘制答案
            if (count === player) {
                let game = {};
                ...省略
                // 这条消息只有绘图的玩家才能看到
                socket.send(JSON.stringify(game));
            }
            count++;
        });
    });
    
    • 1 минута игры окончена и изменено текущее состояние
    timer = setTimeout(() => {
        ...省略
    }, 60 * 1000);
    
    currentState = START;
    
  4. Прослушайте сообщение, чтобы определить, правильно ли ответил кто-то
    // 判断是不是有玩家答对了
    if (currentState === START && data.message === currentAnswer) {
        let game = {};
        game.type = GAME;
        game.answer = currentAnswer;
        game.winner = user;
        game.state = OVER;  // 状态修改为OVER
        // 把该消息数据传递给所有玩家
        io.emit('message', JSON.stringify(game));
        // 恢复当前状态初始值
        currentState = WAITTING;
        // 清空1分钟计时器
        clearTimeout(timer);
    }
    

Ну а тут кончено, маленькие друзья могут подбежать и попробовать, конечно, это король стукнуть

Просто напишите это здесь, спасибо за вашу постоянную заботу, я желаю всем гладкой свиньи в 2019 году, ха-ха, 886

последний последний: Адрес должен быть отправлен всем дляСсылаться накакие