Django学习笔记-实现联机对战(下)
笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。
CONTENTS
- 1. 编写移动同步函数move_to
- 2. 编写攻击同步函数shoot_fireball
1. 编写移动同步函数move_to
与上一章中的 create_player 同步函数相似,移动函数的同步也需要在前端实现 send_move_to 和 receive_move_to 函数。我们修改 MultiPlayerSocket 类(在目录 ~/djangoapp/game/static/js/src/playground/socket/multiplayer 中):
class MultiPlayerSocket {constructor(playground) {this.playground = playground;// 直接将网站链接复制过来,将https改成wss,如果没有配置https那就改成ws,然后最后加上wss的路由this.ws = new WebSocket('wss://app4007.acapp.acwing.com.cn/wss/multiplayer/');this.start();}start() {this.receive();}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);}};}send_create_player(username, avatar) {...}receive_create_player(uuid, username, avatar) {...}// 根据uuid找到对应的Playerget_player(uuid) {let players = this.playground.players;for (let i = 0; i < players.length; i++) {let player = players[i];if (player.uuid === uuid)return player;}return null;}send_move_to(tx, ty) {let outer = this;this.ws.send(JSON.stringify({'event': 'move_to','uuid': outer.uuid,'tx': tx,'ty': ty,}));}receive_move_to(uuid, tx, ty) {let player = this.get_player(uuid);if (player) { // 确保玩家存在再调用move_to函数player.move_to(tx, ty);}}
}
然后修改一下后端通信代码(~/djangoapp/game/consumers/multiplayer 目录中的 index.py 文件):
from channels.generic.websocket import AsyncWebsocketConsumer
import json
from django.conf import settings
from django.core.cache import cacheclass MultiPlayer(AsyncWebsocketConsumer):async def connect(self):...async def disconnect(self, close_code):...async def create_player(self, data): # async表示异步函数...async def group_send_event(self, data): # 组内的每个连接接收到消息后直接发给前端即可await self.send(text_data=json.dumps(data))async def move_to(self, data): # 与create_player函数相似await self.channel_layer.group_send(self.room_name,{'type': 'group_send_event','event': 'move_to','uuid': data['uuid'],'tx': data['tx'],'ty': data['ty'],})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)
最后我们还需要调用函数,首先我们需要在 AcGamePlayground 类中记录下游戏模式 mode:
class AcGamePlayground {...// 显示playground界面show(mode) {...this.mode = mode; // 需要将模式记录下来,之后玩家在不同的模式中需要调用不同的函数this.resize(); // 界面打开后需要resize一次,需要将game_map也resize...}...
}
然后在 Player 类中进行修改,当为多人模式时,需要广播发送 move_to 信号:
class Player extends AcGameObject {...add_listening_events() {let outer = this;this.playground.game_map.$canvas.on('contextmenu', function() {return false;}); // 取消右键的菜单功能this.playground.game_map.$canvas.mousedown(function(e) {const rect = outer.ctx.canvas.getBoundingClientRect();if (e.which === 3) { // 1表示左键,2表示滚轮,3表示右键let tx = (e.clientX - rect.left) / outer.playground.scale;let ty = (e.clientY - rect.top) / outer.playground.scale;outer.move_to(tx, ty); // e.clientX/Y为鼠标点击坐标if (outer.playground.mode === 'multi mode') {outer.playground.mps.send_move_to(tx, ty);}} else if (e.which === 1) {...}});...}...
}
现在即可实现多名玩家的同步移动。当 A 窗口中的玩家移动时,首先该窗口(Player 类)的监听函数会控制该玩家自身进行移动,接着判定为多人模式,因此再调用 MultiPlayerSocket 类中的 send_move_to 函数向服务器发送信息(通过 WebSocket 向服务器发送一个事件),接着服务器端(~/djangoapp/game/consumers/multiplayer/index.py 文件中)的 receive 函数会接收到信息,发现事件 event 为 move_to,就会调用 move_to 函数,该函数会向这个房间中的其他所有玩家群发消息,每个窗口都会在前端(MultiPlayerSocket 类中)的 receive 函数接收到信息,通过事件路由到 receive_move_to 函数,该函数就会通过 uuid 调用每名玩家的 move_to 函数。
2. 编写攻击同步函数shoot_fireball
由于发射的火球是会消失的,因此需要先将每名玩家发射的火球存下来,此外我们实现一个根据火球的 uuid 删除火球的函数,在 Player 类中进行修改:
class Player extends AcGameObject {constructor(playground, x, y, radius, color, speed, character, username, avatar) {...this.fire_balls = []; // 存下玩家发射的火球...}...// 向(tx, ty)位置发射火球shoot_fireball(tx, ty) {let x = this.x, y = this.y;let radius = 0.01;let theta = Math.atan2(ty - this.y, tx - this.x);let vx = Math.cos(theta), vy = Math.sin(theta);let color = 'orange';let speed = 0.5;let move_length = 0.8;let fire_ball = new FireBall(this.playground, this, x, y, radius, vx, vy, color, speed, move_length, 0.01);this.fire_balls.push(fire_ball);return fire_ball; // 返回fire_ball是为了获取自己创建这个火球的uuid}destroy_fireball(uuid) { // 删除火球for (let i = 0; i < this.fire_balls.length; i++) {let fire_ball = fire_balls[i];if (fire_ball.uuid === uuid) {fire_ball.destroy();break;}}}...
}
由于火球在 Player 中存了一份,因此我们在删除火球前需要将它从 Player 的 fire_balls 中删掉。且由于 FireBall 类中的 update 函数过于臃肿,可以先将其分成 update_move 以及 update_attack,我们修改 FireBall 类:
class FireBall extends AcGameObject {// 火球需要标记是哪个玩家发射的,且射出后的速度方向与大小是固定的,射程为move_lengthconstructor(playground, player, x, y, radius, vx, vy, color, speed, move_length, damage) {...}start() {}update_move() {let true_move = Math.min(this.move_length, this.speed * this.timedelta / 1000);this.x += this.vx * true_move;this.y += this.vy * true_move;this.move_length -= true_move;}update_attack() { // 攻击碰撞检测for (let i = 0; i < this.playground.players.length; i++) {let player = this.playground.players[i];if (player !== this.player && this.is_collision(player)) {this.attack(player); // this攻击player}}}update() {if (this.move_length < this.eps) {this.destroy();return false;}this.update_move();this.update_attack();this.render();}get_dist(x1, y1, x2, y2) {...}is_collision(player) {...}attack(player) {...}render() {...}on_destroy() {let fire_balls = this.player.fire_balls;for (let i = 0; i < fire_balls.length; i++) {if (fire_balls[i] === this) {fire_balls.splice(i, 1);break;}}}
}
然后我们在 MultiPlayerSocket 类中实现 send_shoot_fireball 和 receive_shoot_fireball 函数:
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);}};}...send_shoot_fireball(tx, ty, fireball_uuid) {let outer = this;this.ws.send(JSON.stringify({'event': 'shoot_fireball','uuid': outer.uuid,'tx': tx,'ty': ty,'fireball_uuid': fireball_uuid,}));}receive_shoot_fireball(uuid, tx, ty, fireball_uuid) {let player = this.get_player(uuid);if (player) {let fire_ball = player.shoot_fireball(tx, ty);fire_ball.uuid = fireball_uuid; // 所有窗口同一个火球的uuid需要统一}}
}
现在我们需要实现后端函数:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
from django.core.cache import cacheclass MultiPlayer(AsyncWebsocketConsumer):...async def shoot_fireball(self, data):await self.channel_layer.group_send(self.room_name,{'type': 'group_send_event','event': 'shoot_fireball','uuid': data['uuid'],'tx': data['tx'],'ty': data['ty'],'fireball_uuid': data['fireball_uuid'],})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)
最后是在 Player 类中调用函数:
class Player extends AcGameObject {constructor(playground, x, y, radius, color, speed, character, username, avatar) {...}start() {...}add_listening_events() {let outer = this;this.playground.game_map.$canvas.on('contextmenu', function() {return false;}); // 取消右键的菜单功能this.playground.game_map.$canvas.mousedown(function(e) {const rect = outer.ctx.canvas.getBoundingClientRect();if (e.which === 3) { // 1表示左键,2表示滚轮,3表示右键...} else if (e.which === 1) {let tx = (e.clientX - rect.left) / outer.playground.scale;let ty = (e.clientY - rect.top) / outer.playground.scale;if (outer.cur_skill === 'fireball') {let fire_ball = outer.shoot_fireball(tx, ty);if (outer.playground.mode === 'multi mode') {outer.playground.mps.send_shoot_fireball(tx, ty, fire_ball.uuid);}}outer.cur_skill = null; // 释放完一次技能后还原}});$(window).keydown(function(e) {if (e.which === 81) { // Q键outer.cur_skill = 'fireball';return false;}});}// 计算两点之间的欧几里得距离get_dist(x1, y1, x2, y2) {...}// 向(tx, ty)位置发射火球shoot_fireball(tx, ty) {...}destroy_fireball(uuid) { // 删除火球...}move_to(tx, ty) {...}is_attacked(theta, damage) { // 被攻击到...}// 更新移动update_move() {...}update() {...}render() {...}on_destroy() {for (let i = 0; i < this.playground.players.length; i++) {if (this.playground.players[i] === this) {this.playground.players.splice(i, 1);break;}}}
}
相关文章:
Django学习笔记-实现联机对战(下)
笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。 CONTENTS 1. 编写移动同步函数move_to2. 编写攻击同步函数shoot_fireball 1. 编写移动同步函数move_to 与上一章中的 create_player 同步函数相似,移动函数的同…...
一文了解什么SEO
搜索引擎优化 (SEO) 是一门让页面在 Google 等搜索引擎中排名更高的艺术和科学。 一、搜索引擎优化的好处 搜索引擎优化是在线营销的关键部分,因为搜索是用户浏览网络的主要方式之一。 搜索结果以有序列表的形式呈现,网站在该列表中的排名越高&#x…...
SpringBoot+Jpa+Thymeleaf实现增删改查
SpringBootJpaThymeleaf实现增删改查 这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例。 1、pom依赖 pom 包里面添加Jpa 和 Thymeleaf 的相关包引用 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.…...
最快的包管理器--pnpm创建vue项目完整步骤
1.用npm全局安装pnpm npm install -g pnpm 2.在要创建vue项目的包下进入cmd,输入: pnpm create vue 3.输入项目名字,选择Router,Pinia,ESLint,Prettier之后点确定 4.cd到创建好的项目 ,安装依赖 cd .\刚创建好的项目名称\ p…...
算法通过村第九关-二分(中序遍历)黄金笔记|二叉搜索树
文章目录 前言1. 有序数组转二叉搜索树2. 寻找连个正序数组的中位数总结 前言 提示:有时候,我感觉自己一辈子活在两个闹钟之间,早上的第一次闹钟,以及5分钟之后的第二次闹钟。 --奥利弗萨克斯《意识的河流》 每个专题都有简单题&a…...
Mock.js之Element-ui搭建首页导航与左侧菜单
🎬 艳艳耶✌️:个人主页 🔥 个人专栏 :《Spring与Mybatis集成整合》《springMvc使用》 ⛺️ 生活的理想,为了不断更新自己 ! 1、Mock.js的使用 1.1.什么是Mock.js Mock.js是一个模拟数据的生成器,用来帮助前…...
robotframework在Jenkins执行踩坑
1. Groovy Template file [robot_results.groovy] was not found in $JENKINS_HOME/email_template 1.需要在managed files 添加robot_results.groovy。这个名字需要和配置在构建项目里default content一致(Extended E-mail Notification默认设置里Default Content…...
关于ElementUI之首页导航与左侧菜单实现
目录 一.Mock 1.1.什么是Mock.js 1.2.特点 1.3.安装与配置 1.3.1. 安装mock.js 1.3.2.引入mock.js 1.4.mockjs使用 1.4.1.定义测试数据文件 1.4.2.mock拦截Ajax请求 1.4.3.界面代码优化 二.总线 2.1.是什么 2.2.前期准备 2.3.配置组件与路由关系 2.3.1. 配置组件 …...
基于springboot小区疫情防控系统
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
【k8s】YAML语言基础
文章目录 YAML介绍语法支持的数据类型注意事项json与yaml互转 YAML介绍 YAML是一个类似于XML、JSON的标记语言。强调以数据为中心,并不是以标记语言为中心 <heima><age>15</age><address>Beijing</address> </heima>heima:age:…...
AI时代的中国困境: ChatGPT为什么难以复制
如今,几乎所有中国互联网大厂都公布了自己的“类ChatGPT”解决方案,有些还公布了背后的关于AI技术模型的详情。 其中最高调的是百度,其“文心一言”解决方案号称即将接入数十家内容平台和数以百计的媒体、自媒体。腾讯公布的微信 AI 模型“W…...
如何使用Docker安装最新版本的Redis并设置远程访问(含免费可视化工具)
文章目录 安装Docker安装Redisredis.conf文件远程访问Redis免费可视化工具相关链接Docker是一种开源的应用容器引擎,使用Docker可以让我们快速部署应用环境,本文介绍如何使用Docker安装最新版本的Redis。 安装Docker 首先需要安装Docker,具体的安装方法可以参考Docker官方文…...
怒刷LeetCode的第8天(Java版)
目录 第一题 题目来源 题目内容 解决方法 方法一:双指针和排序 编辑第二题 题目来源 题目内容 解决方法 方法一:双指针 方法二:递归 方法三:快慢指针 方法四:栈 第三题 题目来源 题目内容 解决方法…...
Vue Hooks 让Vue开发更简单与高效
Vue Hooks 让Vue开发更简单与高效 介绍 Vue Hooks 是一个基于 Vue.js 的插件,它提供了一种新的方式来编写 Vue 组件,使得开发更加简单和高效。它借鉴了 React Hooks 的概念,通过使用 Hooks,我们可以在不编写类组件的情况下&…...
Go编程规范
文章目录 注释转义符定义变量方法一:指定变量类型,声明后若不赋值,使用默认值方法二:根据值自行判定变量类型(类型推导)方法三:省略var, 注意:左侧的变量不应该是已经声明过的,否则会导致编译错误[推荐]全局…...
premiere 新建 视频导入 视频拼接 视频截取 多余视频删除
1 新建项目 文件 -> 新建 -> 项目 2 导入 2.1 方法一 直接从本地 将 文件拖入对应的文件夹 2.2 方法二 鼠标右键在指定素材文件夹, 选择导入 选择对应本地文件夹对应素材 3 预设 -> 粗剪 -> 在指定模块处 创建序列预设 3.1 指定模块处 鼠标右键 -> 新建项目…...
笔记01:第一行Python
NameError 名字不含特殊符号(只能是英文、数字、下划线、中文等)名字区分大小写名字先定义后使用 SyntaxError 不符合Python语法书写规范除了语法成分中的保留拼写错误输出中文符号if、for、def等语句末尾忘记冒号 IdentationError 缩进错误&#x…...
资产连接支持会话分屏,新增Passkey用户认证方式,支持查看在线用户信息,JumpServer堡垒机v3.7.0发布
2023年9月25日,JumpServer开源堡垒机正式发布v3.7.0版本。在这一版本中,在用户管理层面,为了提高使用JumpServer操作资产的效率,JumpServer支持对会话进行分屏操作,用户可以在一个浏览器页面打开多个会话,方…...
uniapp项目实践总结(二十二)分包优化和游客模式
导语:这篇主要介绍应用分包和游客模式相关的内容。 目录 应用分包游客模式 应用分包 微信对于小程序的打包压缩后的代码体积是有限制的,网页和 APP 也可以适用分包功能,因此需要进行分包添加以及分包优化。 分包添加 在pages.json文件中…...
Unity中UI组件对Shader调色
文章目录 前言一、原理在Shader中直接暴露的Color属性,不会与UI的Image组件中的Color形成属性绑定。因为UI的Image组件中更改的颜色是顶点颜色,如果需要在修改组件中的颜色时,使Shader中的颜色也同时改变。那么就需要在应用程序阶段传入到顶点…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
