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中的颜色也同时改变。那么就需要在应用程序阶段传入到顶点…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
