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中的颜色也同时改变。那么就需要在应用程序阶段传入到顶点…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
