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.mzph.cn/news/95486.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vars函数

vars() 是一个内置函数&#xff0c;用于返回一个对象的属性和属性值的字典。具体地说&#xff0c;它返回对象的 __dict__ 属性&#xff0c;如果对象没有 __dict__ 属性&#xff0c;则会引发 TypeError 异常。通常&#xff0c;vars() 函数用于查看对象的属性&#xff0c;特别是在…

Feign调用异常触发降级捕获异常

通过配置fallbackFactory来捕获异常信息&#xff0c;代码如下 FeignClient(name "user", fallbackFactory UserFallBackFactory.class) public interface UserFeign {PostMapping("/get/list")Map getList();}Component public class UserFallBackFacto…

【机器学习】训练集/验证集/测试集释疑

文章目录 序言1. 训练集、验证集、测试集是什么2. 为什么需要验证集3. 验证集是必须的吗4. 验证集和测试集上的表现会不同吗5. 如何从Train/Test Set划分Validation Set6. 训练集、验证集和测试集的比例怎么设置7. 模型表现不好时测试集可以反复使用来调整模型吗8. 训练集、验证…

【C++】AVL树 红黑树

AVL树 AVL树也是二叉搜索树的一种。因为对于普通的二叉搜索树&#xff0c;当插入的数据在有序或接近有序的情况下&#xff0c;二叉搜索树很可能退化成单支树&#xff0c;导致查找效率低下。而AVL树就很好的解决了这个问题。 首先&#xff0c;AVL树是一棵二叉搜索树。同时对于A…

在Ubuntu上通过Portainer部署微服务项目

这篇文章主要记录自己在ubuntu上部署自己的微服务应用的过程&#xff0c;文章中使用了docker、docker-compose和portainer&#xff0c;在部署过程中遇到了不少问题&#xff0c;因为博主也是初学docker-compose&#xff0c;通过这次部署实战确实有所收获&#xff0c;在这篇文章一…

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

全志ARM926 Melis2.0系统的开发指引⑦ 编写目的11. 调屏11.1. 调屏步骤简介11.1.1. 判断屏接口。11.1.2. 确定硬件连接。11.1.3. 配置显示部分 sys_config.fex11.1.3.1. 配置屏相关 IO 11.1.4. Lcd_panel_cfg.c 初始化文件中配置屏参数11.1.4.1. LCD_cfg_panel_info11.1.4.2. L…

ili9431液晶 tft_espi图形库演示 时钟、天气、滚动、气象图标

米思齐tft_spi模块库演示程序。心知天气、阿里云时钟、WiFi信号强度检测、1分钟滚屏、更新天气时间为15分钟、加入天气图标。更新天气次数。断网检测 。此程序为tft_eSPI图形库演示、如感觉好可以自行优化。 ili9431tft_espi库是用于ESP32和ESP8266芯片的TFT LCD驱动程序库&am…

2023版 STM32实战6 输出比较(PWM)包含F407/F103方式

输出比较简介和特性 -1-只有通用/高级定时器才能输出PWM -2-占空比就是高电平所占的比例 -3-输出比较就是输出不同占空比的信号 工作方式说明 -1-1- PWM工作模式 -1-2- 有效/无效电平 有效电平可以设置为高或低电平&#xff0c;是自己配置的 周期选择与计算 周期重…

安卓教材学习

文章目录 教材学习第一行代码 Android 第3版环境配置gradle配置下载包出现问题 教材学习 摘要&#xff1a;选了几本教材《第一行代码 Android 第3版》&#xff0c;记录一下跑案例遇到的问题&#xff0c;和总结一些内容。 第一行代码 Android 第3版 环境配置 gradle配置 gradl…

UGUI交互组件Toggle

一.Toggle对象的构造 Toggle和Button类似&#xff0c;是交互组件的一种 如果所示&#xff0c;通过菜单创建了两个Toggle&#xff0c;Toggle2中更换了背景和标记资源 对象说明Toggle含有Toggle组件的对象Background开关背景Checkmark开关选中标记Label名称文本 二.Toggle组件属…

力扣 -- 377. 组合总和 Ⅳ

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int combinationSum4(vector<int>& nums, int target) {int nnums.size();vector<double> dp(target1);//初始化dp[0]1;//填表for(int i1;i<target;i){for(int j0;j<n;j){//填表if(…

解密京东面试:如何应对Redis缓存穿透?

亲爱的小伙伴们&#xff0c;大家好&#xff01;欢迎来到小米的微信公众号&#xff0c;今天我们要探讨一个在面试中可能会遇到的热门话题——Redis缓存穿透以及如何解决它。这个话题对于那些渴望进入技术领域的小伙伴们来说&#xff0c;可是必备的哦&#xff01; 认识Redis缓存…

C++设计模式-原型(Prototype)

目录 C设计模式-原型&#xff08;Prototype&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-原型&#xff08;Prototype&#xff09; 一、意图 用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象。 二、适用性 当…

springmvc中DispatcherServlet关键对象

以下代码为 spring boot 2.7.15 中自带的 spring 5.3.29 RequestMappingInfo 请求方法相关信息封装&#xff0c;对应的信息解析在 RequestMappingHandlerMapping 的 createRequestMappingInfo() 中实现。 对于 RequestMapping 赋值的相关信息进行解析 protected RequestMappi…

第0次 序言

突然想起有好多书没有看&#xff0c;或者看了也没留下任何记录&#xff0c;以后有空必须得好好整理才行&#xff0c;这次就从《Linux命令行和shell脚本编程大全开始》 本文完全是闲聊&#xff0c;自娱自乐&#xff0c;我觉得做开发是一件很快乐的事情&#xff0c;但是工作是开发…

标准化数据模型

标准化数据模型 标准化被定义为减少或消除数据集中冗余的过程。 它已成为关系数据库中数据建模的事实上的方法&#xff0c;很大程度上是由于这些系统最初设计时所围绕的底层资源限制&#xff1a;缓慢的磁盘和昂贵的 RAM。更少的数据冗余/重复意味着更有效地从磁盘读取数据并占…

网课搜题 小猿题库多接口微信小程序源码 自带流量主

多接口小猿题库等综合网课搜题微信小程序源码带流量主&#xff0c;网课搜题小程序, 可以开通流量主赚钱 搭建教程1, 微信公众平台注册自己的小程序2, 下载微信开发者工具和小程序的源码3, 上传代码到自己的小程序 源码下载&#xff1a;https://download.csdn.net/download/m0_…

set和map的封装

目录 介绍 红黑树代码 set insert的迭代器转换问题 为什么会有这样的问题? 如何解决 代码 map 注意点 代码 介绍 set和map的底层都是红黑树,所以我们可以在自己实现的红黑树(简易版)的基础上,进行封装,成为简易的set和map 红黑树代码 #pragma once#include <…

实现悲观锁和乐观锁

悲观锁和乐观锁是并发控制机制,用于多线程环境中管理共享资源的访问。 悲观锁示例 在悲观锁中,假设多个线程会相互干扰,因此在访问共享资源之前,每个线程都会尝试获取锁,以确保独占访问。Java中可以使用synchronized关键字实现悲观锁。 public class PessimisticLockEx…

力扣 -- 873. 最长的斐波那契子序列的长度

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int lenLongestFibSubseq(vector<int>& nums) {int nnums.size();unordered_map<int,int> hash;for(int i0;i<n;i){hash[nums[i]]i;}int ret2;vector<vector<int>> dp(n,v…