Loading

Django框架下的游戏项目

Django项目的介绍

项目模式

MVC模式:

views处理视图一切逻辑,model处理一切数据逻辑(存,取,算),controller最简单,仅仅作为v和m的胶水其他任何事情都不做。

假设这里有一个View会提交数据给Model进行处理以实现具体的行为,View会先把数据提交给Controller,然后Controller再将数据转发给Model。假如此时程序业务逻辑的处理方式有变化,那么只需要在Controller中将原来的Model换成新实现的Model就可以了,控制器的作用就是这么简单, 用来将不同的View和不同的Model组织在一起,顺便替双方传递消息,仅此而已。

DjangoMTV 模式本质上和 MVC 是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,DjangoMTV 分别是指:

  • M 表示模型(Model):编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
  • T 表示模板 (Template):负责如何把页面(html)展示给用户。
  • V 表示视图(View):负责业务逻辑,并在适当时候调用 ModelTemplate
  • 如果views函数中不存在数据调用,那么views视图会直接返回一个模板也就是html文件给前端网页。
  • 如果存在数据调用,那么views会调用model去数据库中查找数据。

访问页面路径

Django中有层级路由的概念,也就是我们可以通过在项目根目录下的url文件中将下层目录的url路径include进来,避免了我们必须将所有url路径都保存在根目录下的url文件内。

urls文件夹下的根目录下的index.py文件内,Django 寻找名为urlpatterns的变量并且按序匹配正则表达式。在找到匹配项 ,如'polls/',他就会将url中对应部分切掉,并进入到下一层url文件中继续匹配,使得Django最终可以匹配到我们在views内保存的函数。

前端页面页面

由于我的游戏项目是前后端分离的,所有的HTML渲染工作都是在前端页面完成的。因此前端的开发流程就是,先在 HTML 里创建好一个有 iddiv,然后在执行js文件时捕获div标签,并进行渲染。

前后端交互

前端实现动画效果

这里借用了unityMonoBehaviour类的概念,即:将所有需要在前端实现移动的对象全部存到jsobject类里面,并且每秒调用六十次requestAnimationFrame函数。接下来所有的一切游戏功能,都是基于这个引擎的基类完成的,当我们要把一个js类对象实现动画化,可以让其继承自MonoBehaviour类,并借鉴unityupdate()start()的概念,调用基类的构造函数,将自己加到object类中的数组中,实现动画化。

unity中的update

  • window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画。

    方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

创建用户系统

一个数据库驱动的 Web 应用的第一步就是定义模型。在我的游戏应用中,需要给玩家提供登录、注册等功能,要想实现这些功能,就需要给每名玩家在后台存储他的用户名,密码,头像等信息。

其中django已经为我们提供了一个User类。其中包含了usernamepasswordname等属性,能够满足我们创建用户()和验证用户的需求。我们仅仅需要再添加一个类来储存用户的头像和一键登录授权信息即可

#创建用户: 使用 create_user() 函数
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes, if you want to change other fields.

>>> user.last_name = 'Lennon'
>>> user.save()
#验证用户: 使用 authenticate() 来验证用户。它使用 username 和 password 作为参数来验证
from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # 数据库中已经储存了用户信息
else:
    # 数据库中没有该玩家信息

1.static

我们采用的 前后端分离式 开发,所有的 html 渲染都要求在前端完成。

开发流程就是,现在 html 里创建好一个有 iddiv,然后利用 js 文件,捕获到该 div,并进行渲染。

  • static/文件夹下有css, js, image, audio四个子文件夹。

直接向用户浏览器窗口发送js文件,文件内容直接是网页浏览到的内容(需要先将URL/static与本地/static路径join在一起)

  • css文件管理:一般一个工程只有一个css文件夹就足够了。

  • js文件管理:一个工程会有多个.js文件,为了避免每次写新的.js文件时import每个文件,考虑将所有.js文件存放在src文件夹下,并将其所有.js源文件通过创建一个脚本整合到dist文件夹中。

  • html文件管理

templates文件夹下创建的multiends文件夹,用来存放不同终端下的html文件,并返回给view

2.views

一类具有相同功能和模板的网页的集合,每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404

Django中,网页和其他内容都是从视图派生而来。Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据URL中域名之后的部分)

  • 存放各种函数,等待用户访问。
  • views文件夹下创建了三个模块的文件夹:menu, playground, settings
__init__.py:
  • __init__.py 文件的作用是将文件夹变为一个Python模块, Python 中的每个模块的包中,都有__init__.py 文件
  • 我们在导入一个包时,实际上是导入了它的__init__.py文件。这样我们可以在__init__.py文件中批量导入我们所需要的模块,而不再需要一个一个的导入。
index.py:

为了在返回html文件到用户浏览器,我们需要创建一个index.py文件,返回templatesmultiends文件夹下的html文件。

3.urls

服务器端跟据url格式,来选择调用什么函数。

  • 路由规则
                                     /-- "" -- index
                                    / -- "menu/" -- menu.index
             / "" --> "game.urls" --> 
            /                       \ -- "playground/" -- playround.index
id:scoket ->                         \-- "settings/" -- settings.index
            \
             \ "/admin" -- 到达管理员页面
  • 修改路由起点文件:~/ACAPP/app/urls.py
from django.contrib import admin                                                 
from django.urls import path, include

urlpatterns =[
     path('', include('game.urls.index')),
     path('admin/', admin.site.urls),
]
  • 再找到我们的第二分支~/ACAPP/game/urls/index.py
from django.urls import path, include
from game.views.index import index
   
   urlpatterns = [
       path("", index, name = "index"),
       path("menu/", include("game.urls.menu.index")),
       path("playground/", include("game.urls.playground.index")),
       path("settings/", include("game.urls.settings.index")),
   ]  

至此,我们修改完了我们期望的路由规则

4.models

存放项目数据结构,如数据库里的table,可以理解为各种class

每个模型被表示为 django.db.models.Model 类的子类。每个模型有许多类变量,它们都表示模型里的一个数据库字段。我们将会在 Python 代码里使用它们,而数据库同时会将它们作为列名。

5. templates

项目的 TEMPLATES 配置项描述了 Django 如何载入和渲染模板。

  • 调用templates内的HTML文件:

    views函数内载入 templates/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。

  • 「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。


请求与响应

Django 使用请求和响应对象在系统中传递状态。
  • 当一个页面被请求时,Django 会创建一个 HttpRequest 对象,这个对象包含了请求的元数据。然后,Django 加载相应的视图,将 HttpRequest 作为视图函数的第一个参数。每个视图负责返回一个 HttpResponse 对象。
HTTP定义了与服务器进行交互的不同方法,常见的有四种:GETPOSTPUTDELETE。其中,GETPOST最常用。
  • GET用来获取资源,它只是获取、查询数据,不会修改服务器的数据,从这点来讲,它是安全的。由于它是读取的,因此可以对GET请求的数据进行缓存。

  • POST则是可以向服务器发送修改请求,进行数据的修改的。举个例子:比如说我们要在知乎、或者论坛下面评论,这个时候就需要用到POST请求。但是它不能缓存,为什么呢?设想如果我们将“评论成功”的页面缓存在本地,那么当我发送一个请求的时候,直接返回本地的“评论成功”页面,而服务器端则什么也没有做,根本没有进行评论的更新,岂不是难以想象。

区别


运行服务器相关

  • uwsgi前:django3 manage.py runserver 0.0.0.0:8000

  • 运行django服务器的时候,突然远程连接断掉了,django服务器关闭不了,下面将展示如何关闭

  • 重新启动服务器显示端口已被占用

python3 manage.py runserver 0.0.0.0:8000

  • 现在我们要查找是哪个进程占用了8000端口,然后kill

netstat -atunp//查看进程端口号及运行的程序

  • 根据pid杀死指定进程:

kill 123456//pid号


将阿里云服务器重启之后发生了什么:

出现的问题:
  1. docker镜像进入exit 态,即dicker内所有进程全部关闭,包括但不限于uwsginginxredis

  2. 重启docker后,重新运行 uwsginginxredis项目网页仍然无法正常打开。

  3. 将之前的项目文件删除,并重新从GitHub上拉取之前的备份。

  4. 启动uwsgi服务器,项目网页有部分模块失效。(原先支持登录,登出,注册和第三方一键登录,现在只能登录登出)

    • 原因是之前没有启动redis服务器,很明显的道理,网页只有一部分功能失效,说明传给浏览器的.js文件肯定是没有出错的,已经上传的js代码没有问题,最坏的情况也就是缺少一部分代码。
    • 第一次没想到重启redis服务,一键登录系统一直出问题,直到我从头开始复盘整个一键登录模块实现流程,才发现可能是没有启动redis服务,我打开python3 manage.py shell尝试redis命令,果然报错,启动redis服务后一键登录模块重新上线!
  5. 根据Chrome网页的源代码报错和js代码debug,我发现发现从GitHub拉取的备份.js代码真的少了一部分,可能是之前上传的时候出现了问题。将缺少的js文件和url.py文件补充好后,项目重新上线并正常运行。

我的收获:
  1. 善于从网页报错和项目层级结构来寻找问题。比如网页提示.js文件出现问题,那么我会从构造函数的变量定义开始,再到start()函数内实现的监听函数中分别查看各个子模块函数的定义是否有问题。
  2. 从顶向下查看url文件。根据Django的分层路由特性,我们可以从settings根目录下的index.py文件开始,向下一层一层的查找相应子目录的index.py文件。
  3. 每次Git上传当天工作,我们一定要善于在GitHub上浏览我们的代码,速度会比在本地vim上快很多。(可能跟电脑也有关系xD)

项目环境初始化

文件结构

$ python3 manage.py startapp game
$ tree .
> .
> |-- acapp				  #Django-admin 新建一个 Django 项目
> |   |-- __init__.py
> |   |-- __pycache__
> |   |   |-- __init__.cpython-38.pyc
> |   |   |-- settings.cpython-38.pyc
> |   |   |-- urls.cpython-38.pyc
> |   |   `-- wsgi.cpython-38.pyc
> |   |-- asgi.py
> |   |-- settings.py
> |   |-- urls.py
> |   `-- wsgi.py
> |-- db.sqlite3
> |-- game
> |   |-- __init__.py
> |   |-- admin.py          # django网页后台管理员页面
> |   |-- apps.py           # 用的不多
> |   |-- migrations
> |   |   `-- __init__.py
> |   |-- models.py         # 定义网站里的数据库表
> |   |-- tests.py
> |   `-- views.py          # 视图,即函数
> `-- manage.py

配置nginxuwsgi服务器

nginx的作用

1. 静态HTTP服务器
  • 首先,Nginx是一个HTTP服务器,可以将服务器上的静态文件(如HTML、图片)通过HTTP协议展现给客户端。
2. 反向代理服务器
  • 客户端本来可以直接通过HTTP协议访问某网站应用服务器,网站管理员可以在中间加上一个Nginx,客户端请求NginxNginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器。

那么我们为什么要多此一举呢?下面列举一下Nginx的作用:

3. 负载均衡
  • 当网站访问量非常大,网站会越来越慢,一台服务器已经不够用了。于是将同一个应用部署在多台服务器上,将大量用户的请求分配给多台机器处理。
  • 带来的好处是,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。

Nginx可以通过反向代理来实现负载均衡。

4. 虚拟主机
  • 对于访问流量巨大的网站来说,他们需要负载均衡。但对于一些小网站来说,由于访问量较小,需要节约成本,将多个网站部署在一台服务器上。

  • 例如将http://www.aaa.comhttp://www.bbb.com两个网站部署在同一台服务器上,两个域名解析到同一个IP地址,但是用户通过两个域名却可以打开两个完全不同的网站,互相不影响,就像访问两个服务器一样,所以叫两个虚拟主机。

  • 例如,我们在服务器端口8080和8081分别开了一个应用,客户端通过不同的域名访问,根据server_name可以反向代理到对应的应用服务器。

    虚拟主机的原理是通过HTTP请求头中的Host是否匹配server_name来实现的。

    uWSGI的作用

    WSGI
    • WSGI的全称是Web Server Gateway Interface(Web服务器网关接口),它不是服务器、python模块、框架、API或者任何软件,只是一种描述web服务器(如nginxuWSGI等服务器)如何与web应用程序(如用Django、Flask框架写的程序)通信的规范、协议。
      当前运行在WSGI协议之上的web框架有,Flask, Django
    uwsgi

    WSGI一样,是uWSGI服务器的独占通信协议,用于定义传输信息的类型。每一个uwsgi packet4byte为传输信息类型的描述,与WSGI协议是两种东西,据说该协议是fcgi协议的10倍快。

    uWSGI
    • uWSGI是一个全功能的HTTP服务器,实现了WSGI协议、uwsgi协议、http协议等。它要做的就是把HTTP协议转化成语言支持的网络协议。比如把HTTP协议转化成WSGI协议,让Python可以直接使用。

    • 中间的反向代理服务器就是Nginx服务器,右边三台web服务器是uwsgi服务器。

    1

通信协议--WEB-Scoket

  • 我们已经有了 HTTP 协议,为什么还需要另一个协议?

    答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。而WebSocket能够实现全双工通信,可以由服务端主动发起消息,对于浏览器需要及时接收数据变化的场景非常适合

    1. 例如在Django中遇到一些耗时较长的任务我们通常会使用async来异步执行,那么浏览器如果想要获取这个任务的执行状态,在HTTP协议中只能通过轮训的方式由浏览器不断的发送请求给服务器来获取最新状态,这样发送很多无用的请求不仅浪费资源,还不够优雅,如果使用WebSokcet来实现就很完美了

    2. WebSocket的另外一个应用场景就是我们实现的另一个模块--聊天室。

      一个用户(浏览器)发送的消息需要实时的让其他用户(浏览器)接收,这在HTTP协议下是很难实现的,但WebSocket基于长连接加上可以主动给浏览器发消息的特性处理起来就游刃有余了

  • Django如何实现 web_socket

    Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket

创建菜单menu界面

0.实现游戏引擎

  • 游戏中,物体在移动,其实现原理是:每一个动作都会渲染多张图片出来,然后图片快速的切换,从而实现移动的过程。 因此,需要先实现一个游戏引擎的基类 AcGameObject ,使得每帧能渲染一张图片出来.
  • ~/ACAPP/game/templates/multiends/web.html里创建一个带有iddiv。给名字(id)的目的是我们以后可用js来控制它, 比如说移动它或改变它的一些性质等等。

Django路由

  • 我们在前端访问网页的路径是由/urls提供的,我们要知道,一个文件有许多不同的模块,比如settingsmenuplayground 等。
  • 因此django提供了层级路由,上层文件下的index.py可以调用下层目录的index.py 中的pathinclude。十分方便

Django后端提供的服务

联机模式下的玩家信息同步

玩家进入房间

  • 双向连接:http是单向连接,这里我们需要双向连接协议websocket

https:加密协议 wss加密

django 自带数据库splite3,面向对象的方式更改数据库里的表,存储在.py文件中

websocket,即ws协议连接serverclient:用来联机对战和聊天

1.文件结构
  • game/rooting.py 文件夹功能等于 urls 路由函数,我们在前端网页通过url访问。

  • game/consumers文件夹的功能等于 views 函数,存放后端服务器的所有异步函数。

  • 前端向后端发送消息:调用函数ws.send(),后端通过index.py中的reveive()函数接收。

2.标识元素
  • 一场游戏里,所有的元素(玩家,火球等)都需要唯一的标识uuid,来方便同步。为此,我们可以直接修改一下游戏引擎ac_game_object.js,对于每个元素都创建我们需要的唯一uuid

  • 通过广播实现玩家之间的信息同步,在哪个前端创建的对象,就以谁为准。使得每个窗口内,逻辑上相同的元素,其 uid 也相同即可:前端->服务器->群发消息。

  • 正常的游戏引擎是同步每一个玩家窗口的所有信息,通过传递所有物体的坐标来实现。我的游戏引擎是通过同步玩家的操作,效率更高,服务器压力更小。

  • 通过redis存每局信息,实现玩家同步。

3.django异步函数编程
  • 同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行。
  • 异步是和同步相对的,异步是指在处理调用这个事务之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果。
  • async def 用来定义异步函数,await (x) 表示当前协程任务等待睡眠时间x,此时会将该线程阻塞,允许其他任务运行。并获得一个事件循环,主线程调用asyncio.get_event_loop()时会创建事件循环,你需要把异步的任务丢给这个循环的run_until_complete()方法,事件循环会安排协同程序的执行。

玩家移动(通过ws协议调用后端函数实现流程)

  • js中实现sendreceive函数,在前端实现发送信息和接收信息。

  • 为了让游戏界面中对于要移动的元素做出移动动作,需要对 move_to 函数做出一些修改。

    首先要标识出当前为多人模式,然后在用户窗口检测到鼠标点击事件,并且模式为多人模式时,此时每次移动都会向uwsgi协议下的js函数触发一次通信。

  • 以实现move_to函数为例,

    1. js检测到窗口的点击鼠标事件,会调用js文件内的玩家移动函数,实现在本窗口内的玩家移动。
    2. js判断当前游戏模式是多人模式时,会路由到websocket协议下的send_move_to(tx, ty).js函数,向服务器发送一个JSON事件。
    3. 在服务器后端python async异步函数接收到websocket发送的消息,根据JSON字典中的值,路由到不同的函数,如move_to(uuid, tx, ty).python函数,并通过实现向所有玩家layer群发消息。
    4. 在前端的receive_move_to().js函数收到消息,通过唯一标识符uuid来判断是哪个玩家发生移动,从而实现这名玩家在所有layer中移动。

玩家发射火球

前端监听window对象,也就是整个playground界面。

为了只让一个客户端进行攻击命中的判断,因此只有发出方的火球才做碰撞检测。

其他客户端对于该火球只有动画效果

又由于碰撞检测是在一台客户端上进行的,因此多端之间可能会存在同步上的延迟

为此的解决方法是:碰撞检测成功时,强制把被击中玩家移动到发起方客户端中的位置,以避免击中延迟上发生的事情


判断是否被火球击中


状态机:只有在fighting状态下才允许监听函数运行

通知牌:

父类:

渲染:


聊天系统

前端部分:

window和canvas的监听事件不同:

在canvas中加入聚焦属性。

聚焦:

打开、关闭聊天框

历史消息记录

后端部分:


thrift系统

一切不能阻塞,或需要第三方服务的功能都需要thrift服务。

posted @ 2022-03-25 23:18  王子春  阅读(394)  评论(0编辑  收藏  举报