Django学习笔记-实现聊天系统

笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。

CONTENTS

    • 1. 实现聊天系统前端界面
    • 2. 实现后端同步函数

1. 实现聊天系统前端界面

聊天系统整体可以分为两部分:输入框与历史记录。

我们需要先修改一下之前代码中的一个小 BUG,当在一个窗口中按 Q 时,另一个窗口中点击鼠标左键也能攻击,因为按下按键的事件被所有窗口都捕捉到了,这是不合理的。

我们之前监听的对象是 window,每个地图是一个 canvas 元素,因此我们可以绑定到 canvas 对象上。由于不是所有对象都能添加绑定事件的,因此我们还需要对 canvas 做一个修改,首先在 GameMap 类中修改一下 canvas 对象:

class GameMap extends AcGameObject {constructor(playground) {  // 需要将AcGamePlayground传进来super();  // 调用基类构造函数,相当于将自己添加到了AC_GAME_OBJECTS中this.playground = playground;this.$canvas = $(`<canvas tabindex=0></canvas>`);  // 画布,用来渲染画面,tabindex=0表示能够监听事件...}start() {this.$canvas.focus();  // 聚焦后才能监听事件}...
}

Player 类中修改:

class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) {  // Q键outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) {  // F键outer.cur_skill = 'blink';return false;}});}...
}

聊天的前端界面需要创建一个新的文件,我们在 ~/djangoapp/game/static/js/src/playground 目录下创建一个 chat_field 目录,并进入该目录创建 zbase.js 文件:

class ChatField {constructor(playground) {this.playground = playground;this.func_id = null;  // 在每次打开输入框时需要将之前历史记录框的计时函数删掉this.$history = $(`<div class='ac_game_chat_field_history'></div>`);this.$input = $(`<input type='text' class='ac_game_chat_field_input'>`);this.$history.hide();this.$input.hide();this.playground.$playground.append(this.$history);this.playground.$playground.append(this.$input);this.start();}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) {  // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;}});}show_history() {let outer = this;this.$history.fadeIn();  // 慢慢显示出来if (this.func_id) clearTimeout(this.func_id);this.func_id = setTimeout(function() {outer.$history.fadeOut();outer.func_id = null;}, 3000);  // 显示3秒后消失}show_input() {this.$input.show();this.show_history();  // 打开输入框顺带打开历史记录this.$input.focus();  // 聚焦一下才能输入}hide_input() {this.$input.hide();this.playground.game_map.$canvas.focus();  // 关闭输入框后要重新聚焦回Canvas上}
}

然后在 AcGamePlayground 类中创建出来:

class AcGamePlayground {...// 显示playground界面show(mode) {...// 单人模式下创建AI敌人if (mode === 'single mode'){for (let i = 0; i < 8; i++) {this.players.push(new Player(this, this.width / 2 / this.scale, 0.5, 0.07, this.get_random_color(), 0.15, 'robot'));}} else if (mode === 'multi mode') {this.mps = new MultiPlayerSocket(this);this.mps.uuid = this.players[0].uuid;  // 用每名玩家的唯一编号区分不同的窗口this.chat_field = new ChatField(this);  // 聊天区this.mps.ws.onopen = function() {outer.mps.send_create_player(outer.root.settings.username, outer.root.settings.avatar);};}}...
}

现在在 Player 类中即可监听事件:

class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (e.which === 13 && outer.playground.mode === 'multi mode') {  // 还没满人允许使用聊天功能outer.playground.chat_field.show_input();return false;} else if (e.which === 27 && outer.playground.mode === 'multi mode') {outer.playground.chat_field.hide_input();return false;}if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) {  // Q键outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) {  // F键outer.cur_skill = 'blink';return false;}});}...
}

然后我们还需要实现一下聊天区的 CSS 样式(在 ~/djangoapp/game/static/css 目录的 game.css 文件中):

....ac_game_chat_field_history {position: absolute;top: 40%;left: 15%;transform: translate(-50%, 50%);width: 20%;height:30%;color: white;background-color: rgba(77, 77, 77, 0.2);font-size: 1.5vh;padding: 5px;overflow: auto;
}.ac_game_chat_field_history::-webkit-scrollbar {  /* 滚动条 */width: 1;
}.ac_game_chat_field_input {position: absolute;top: 86%;left: 15%;transform: translate(-50%, 50%);width: 20%;height: 2vh;color: white;background-color: rgba(222, 225, 230, 0.2);font-size: 1.5vh;
}

现在我们实现在历史记录区域里添加新消息的功能:

class ChatField {constructor(playground) {...}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) {  // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) {  // 按Enter键时发送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input();  // 发送完消息后关闭输入框if (text) {  // 信息不为空才渲染出来outer.$input.val('');  // 将输入框清空outer.add_message(username, text);}return false;}});}render_message(message) {  // 渲染消息return $(`<div>${message}</div>`);}add_message(username, text) {  // 向历史记录区里添加消息let message = `[${username}] ${text}`;this.$history.append(this.render_message(message));this.show_history();  // 每次发新消息时都显示一下历史记录this.$history.scrollTop(this.$history[0].scrollHeight);  // 将滚动条移动到最底部}...
}

2. 实现后端同步函数

我们先在 WebSocket 前端实现发送和接收消息的函数:

class MultiPlayerSocket {...receive() {let outer = this;this.ws.onmessage = function(e) {let data = JSON.parse(e.data);  // 将字符串变回JSONlet uuid = data.uuid;if (uuid === outer.uuid) return false;  // 如果是给自己发送消息就直接过滤掉let event = data.event;if (event === 'create_player') {  // create_player路由outer.receive_create_player(uuid, data.username, data.avatar);} else if (event === 'move_to') {  // move_to路由outer.receive_move_to(uuid, data.tx, data.ty);} else if (event === 'shoot_fireball') {  // shoot_fireball路由outer.receive_shoot_fireball(uuid, data.tx, data.ty, data.fireball_uuid);} else if (event === 'attack') {  // attack路由outer.receive_attack(uuid, data.attackee_uuid, data.x, data.y, data.theta, data.damage, data.fireball_uuid);} else if (event === 'blink') {  // blink路由outer.receive_blink(uuid, data.tx, data.ty);} else if (event === 'message') {  // message路由outer.receive_message(data.username, data.text);}};}...send_message(username, text) {let outer = this;this.ws.send(JSON.stringify({'event': 'message','uuid': outer.uuid,'username': username,'text': text,}));}receive_message(username, text) {this.playground.chat_field.add_message(username, text);}
}

然后实现后端代码:

import json
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
from django.core.cache import cacheclass MultiPlayer(AsyncWebsocketConsumer):...async def message(self, data):await self.channel_layer.group_send(self.room_name,{'type': 'group_send_event','event': 'message','uuid': data['uuid'],'username': data['username'],'text': data['text'],})async def receive(self, text_data):data = json.loads(text_data)print(data)event = data['event']if event == 'create_player':  # 做一个路由await self.create_player(data)elif event == 'move_to':  # move_to的路由await self.move_to(data)elif event == 'shoot_fireball':  # shoot_fireball的路由await self.shoot_fireball(data)elif event == 'attack':  # attack的路由await self.attack(data)elif event == 'blink':  # blink的路由await self.blink(data)elif event == 'message':  # message的路由await self.message(data)

最后在前端的 ChatField 类中调用一下发送消息的函数即可:

class ChatField {...add_listening_events() {let outer = this;this.$input.keydown(function(e) {  // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) {  // 按Enter键时发送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input();  // 发送完消息后关闭输入框if (text) {  // 信息不为空才渲染出来outer.$input.val('');  // 将输入框清空outer.add_message(username, text);outer.playground.mps.send_message(username, text);  // 给其他玩家的窗口发送消息}return false;}});}...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/148997.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

pycharm配置python3.8版本专门用于undecteded_chromedriver测试

pycharm配置python3.8版本专门用于undecteded_chromedriver测试 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://pay.xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、Pycharm及python环境的配置 1.安装python-3.8.7rc1-amd64.e…

MySQL进阶_3.性能分析工具的使用

文章目录 第一节、数据库服务器的优化步骤第二节、查看系统性能参数第三节、 慢查询日志第四节、 查看 SQL 执行成本第五节、 分析查询语句&#xff1a;EXPLAIN5.1 基本语法5.2 EXPLAIN各列作用 第一节、数据库服务器的优化步骤 当我们遇到数据库调优问题的时候&#xff0c;可…

十三、Django之添加用户(原始方法实现)

修改urls.py path("user/add/", views.user_add),添加user_add.html {% extends layout.html %} {% block content %}<div class"container"><div class"panel panel-default"><div class"panel-heading"><h3 c…

全志ARM926 Melis2.0系统的开发指引①

全志ARM926 Melis2.0系统的开发指引① 1. 编写目的2. Melis2.0 系统概述3. Melis2.0 快速开发3.1. Melis2.0 SDK 目录结构3.2. Melis2.0 编译环境3.3. Melis2.0 固件打包3.4. Melis2.0 固件烧录3.5.串口打印信息3.6. Melis2.0 添加和调用一个模块3.6.1. 为什么划分模块&#xf…

[GWCTF 2019]枯燥的抽奖

参考 https://www.cnblogs.com/AikN/p/15764428.html [GWCTF 2019]枯燥的抽奖-CSDN博客 打开环境 笑死我了&#xff0c;怎么那么像我高中校长 查看源代码 看到check.php&#xff0c;去访问一下 ok看到源代码了 因为上次做过&#xff0c;看到这个我就想到用php_mt_seed逆推…

【Hello Linux】多路转接之 epoll

本篇博客介绍&#xff1a; 多路转接之epoll 多路转接之epoll 初识epollepoll相关系统调用epoll的工作原理epoll服务器编写成员变量构造函数 循环函数HandlerEvent函数epoll的优缺点 我们学习epoll分为四部分 快速理解部分概念 快速的看一下部分接口讲解epoll的工作原理手写epo…

解决ASP.NET Core的中间件无法读取Response.Body的问题

概要 本文主要介绍如何在ASP.NET Core的中间件中&#xff0c;读取Response.Body的方法&#xff0c;以便于我们实现更多的定制化开发。本文介绍的方法适用于.Net 3.1 和 .Net 6。 代码和实现 现象解释 首先我们尝试在自定义中间件中直接读取Response.Body&#xff0c;代码如…

Arcgis提取玉米种植地分布,并以此为掩膜提取遥感影像

Arcgis提取玉米种植地分布上&#xff0c;并以此为掩膜提取遥感影像 一、问题描述 因为之前反演是整个研究区&#xff0c;然而土地利用类型有很多类&#xff0c;只在农田或者植被上进行反演&#xff0c;需要去除水体、建筑等其他类型&#xff0c;如何处理得到下图中只有耕地类…

Javascript 事件的动态绑定

动态绑定事件&#xff0c;是指在代码执行过程中&#xff0c;通过Javascript代码来绑定事件。这种技术可以大大增强网页的交互性和用户体验。上一期介绍的是通过事件监听器 EventListener 去实现元素颜色的变化。这一期将通过动态绑定方法去实现&#xff0c;对象.事件 匿名函数…

常见服务器运维管理面板整理汇总

随着云计算技术的发展和普及&#xff0c;越来越多的用户开始使用云服务器来部署和运行应用程序和服务。为了方便用户管理和操作云服务器&#xff0c;各种类型的服务器管理面板应运而生。本文将整理和汇总一些常见的服务器管理面板&#xff0c;供用户参考选择。 1、宝塔面板【官…

AWS Lambda Golang HelloWorld 快速入门

操作步骤 以下测试基于 WSL2 Ubuntu 22.04 环境 # 下载最新 golang wget https://golang.google.cn/dl/go1.21.1.linux-amd64.tar.gz# 解压 tar -C ~/.local/ -xzf go1.21.1.linux-amd64.tar.gz# 配置环境变量 PATH echo export PATH$PATH:~/.local/go/bin >> ~/.bashrc …

springboot 集成 PageHelper 分页失效

前言 项目启动初期&#xff0c;在集成mybatis的分页插件&#xff0c;自定义封装了一个分页的工具类&#xff0c;方便后期项目的扩展。部分的代码如下&#xff1a; /*** 分页查询* 进行count计算** param pageNum 页数* param pageSize 每页数量* param supplier 查询操作* re…

Zabbix4自定义脚本监控MySQL数据库

一、MySQL数据库配置 1.1 创建Mysql数据库用户 [rootmysql ~]# mysql -uroot -p create user zabbix127.0.0.1 identified by 123456; flush privileges; 1.2 添加用户密码到mysql client的配置文件中 [rootmysql ~]# vim /etc/my.cnf.d/client.cnf [client] host127.0.0.1 u…

MacBook 录制电脑内部声音

MacBook 录制电脑内部声音 老妈喜欢跳广场舞&#xff0c;现在广场舞音频下载都收费了&#xff01;没办法&#xff0c;只能自己录歌了&#xff0c;外录有杂音大家也都知道&#xff0c;所以就只能采用内录的方式然后再用 Audition 调整一下音量大小。 一、&#xff08;前置条件&a…

jvm内存分配与回收策略

自动内存管理 解决两个问题 自动给对象分配内存 对象一般堆上分配&#xff08;而实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配&#xff09; 新生对象通常会分配在新生代&#xff0c;少数情况下&#xff08;例如对象大小超过一定阈值&#xff09;也可能…

Boost程序库完全开发指南:1.2-C++基础知识点梳理

主要整理了N多年前&#xff08;2010年&#xff09;学习C的时候开始总结的知识点&#xff0c;好长时间不写C代码了&#xff0c;现在LLM量化和推理需要重新学习C编程&#xff0c;看来出来混迟早要还的。 1.const_cast <new_type> (expression)[1] 解析&#xff1a;const_c…

Vmware 静态网络配置

概述 仅主机模式&#xff08;VMware1&#xff09;&#xff1a;使用host-only的方式是不能和外界通信的&#xff0c;只能够和本机的物理网卡通信 桥接&#xff08;VMnet0&#xff09;&#xff1a;使用桥接的方式使得自己的虚拟机和自己的真实机网卡在同一个网段 NAT&#xff0…

[Unity][VR]Oculus透视开发图文教程1-Passthrough应用XR项目设置

Oculus现在已向开发者公布了如何使用自己的设备Camera,本系列课程就来手把手地告诉你如何在Unity中使用这个特性。 第一步,既然用的是Quest的特性,那就需要先引入Quest的Unity开发SDK。并且完成基本的VR开发项目设置。 新建Unity项目后,在编辑器界面先点击Window,打开资…

2023蓝帽杯半决赛电子取证+CTF部分题解

文章目录 电子取证123456789101112131415 CTFWeb | MyLinuxBotWeb | AirticleShareCrypto | ezrsaPwn | AdminPwn | uafmisc|排排坐吃吃果果 电子取证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CTF Web | MyLinuxBot Web | AirticleShare import requests import times reques…

iMazing 2.17.10官方中文版含2023最新激活许可证码

iMazing 2.17.10官方中文版是一款iOS设备管理软件&#xff0c;该软件支持对基于iOS系统的设备进行数据传输与备份&#xff0c;用户可以将包括&#xff1a;照片、音乐、铃声、视频、电子书及通讯录等在内的众多信息在Windows/Mac电脑中传输/备份/管理。 iMazing 2.17.10官方中文…