支持在线联机对战的简易版吃鸡游戏,前后端分离,前端支持小程序端和Web端。技术栈:Django,WebSocket,微服务,Vue,Redis,nginx。代码编写在云服务终端完成
功能包括:
- 游戏菜单、游戏设置:熟悉http协议
- 存储对局信息、账户信息:熟悉数据库操作
- 在Redis中存储对局状态:熟悉内存数据库操作
- 在线聊天室、实时移动、实时放技能:熟悉websocket协议
- 在线匹配系统:熟悉thrift和微服务
- 配置nginx:熟悉nginx部署云服务
项目逻辑:通过acapp/acapp/urls.py的path('', include('game.urls.index')),找到acapp/game/urls/index.py,通过该文件中的
from game.views.index import index,path("", index, name="index")找到acapp/game/views/index.py的index函数:
def index(request):
return render(request, "multiends/web.html")
把请求、web.html结合,生成完整的 HTML 页面并返回给浏览器:
找到acapp/game/templates/multiends/web.html,该文件引入了acapp/game/static/js/dist/game.js(/acapp/scripts/compress_game_js.sh脚本将js/src/下的js文件打包得到一个js/dist/game.js文件,减少http请求js文件的数量)
通过game.js渲染web.html,看到前端页面:
game.js的主类先实例化了settings对象:构造器里执行start()函数,调用getinfo()函数,通过ajax向后端发送请求,通过url找到对应函数并执行,并向前端返回Json字典resp
Docker环境配置
购买云服务后,新建用户wky,并配置别名wkyserver和免密登录,将django_lesson_1_0.tar传到云服务器(具体见租云服务器及配置Docker环境)
docker load -i django_lesson_1_0.tar
docker run -p 20000:22 -p 8000:8000 --name django_server -itd django_lesson:1.0 # 记得开放这两个端口,20000端口用于ssh登录,8000端口用于调试
docker attach django_server
adduser wky
usermod -aG sudo wky
然后ctrl+p, ctrl+q挂起容器,回到云服务器层
ssh wky@localhost -p 20000 # 在云服务器层ssh连接到docker层的wky,测试20000端口是否开放
然后logout(ctrl+d)回到云服务器层,再logout回到本地终端
配置别名djangoserver:
cd .ssh
vim config
Host djangoserver
HostName 39.99.43.230
User wky
port 20000
配置免密登录
ssh-copy-id djangoserver
回到本地终端:将配置文件.bashrc .vimrc .tmux.config传到djangoserver
git并创建项目
生成ssh-key,将秘钥传到github
cd
ssh-keygen
cd .ssh
cat id_rsa.pub
创建django项目:
django-admin startproject acapp
cd acapp
git init
git config --global user.name "kaiyanWang"
git config --global user.email "321202864@qq.com"
git remote add origin git@github.com:kaiyanWang/acapp.git
运行
python3 manage.py runserver 0.0.0.0:8000
如果发现报错,将云服务器ip写到allowed_hosts里(字符串形式写):
vim acpp/setting.py
浏览器打开云服务器ip:8000,出现django页面,表示运行成功。
创建game应用
python3 manage.py startapp game
项目系统设计(game目录下)
menu:菜单页面playground:游戏界面settings:设置界面
项目文件结构(game目录下,将views.py,urls.py,models.py删除,新建目录)
urls目录:管理路由,即链接与函数的对应关系views目录:管理 http 函数models目录:管理数据库数据(urls,views,models目录下都是python文件,因此要加__init__.py)templates目录:管理html文件static目录:管理静态文件,比如:css:对象的格式,比如位置、长宽、颜色、背景、字体大小等js:对象的逻辑,比如对象的创建与销毁、事件函数、移动、变色等image:图片audio:声音- ……
consumers目录:管理 websocket 函数
更改时区:在acapp/acapp/settings.py中,将TIME_ZONE改为’Asia/Shanghai’
让Django 框架识别并启用 game 应用:将acapp/game/apps.py中的GameConfig加到acapp/acapp/settings.py中的INSTALLED_APPS:’game.apps.GameConfig’
设置静态文件地址(static里存开发者文件,media存用户文件):在acapp/acapp/settings.py添加 import os STATIC_ROOT = os.path.join(BASE_DIR, ‘static’)
MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’) MEDIA_URL = ‘/media/’
图片下载方式:wget --output-document=自定义图片名称 图片地址
jquery库:
nginx
配置nginx之前:通过8000端口访问Django项目
配置nginx之后,通过443或80端口访问nginx,再通过uwsgi访问Django项目
数据库表注册
在项目的根目录下使用以下命令可以用tab查看补全
python3 manage.py shell
每张数据库表都对应Django里的一个class acapp/game/admin.py可以将自定义的数据表注册到管理员页面,更新数据库:
python3 manage.py makemigrations
python3 manage.py migrate
写函数(每个函数对应一个url)
每次写函数都要修改3个文件:views,urls,js
第三方授权登录
OAuth 2.0 —— 它是目前最主流的「第三方授权登录 / API 授权」协议,核心作用是让用户无需暴露账号密码,就能授权第三方应用访问其在另一平台的资源。
OAuth 2.0 核心流程
- 点击 “第三方平台一键登录”,client向server发送请求,server端给用户返回apply_code_url(
https://github.com/login/oauth/authorize/?client_id=Ov23li2n3MttGt46yXlq&redirect_uri=http%3A//39.99.43.230%3A8000/settings/github/receive_code/)跳转到第三方授权页面; - 用户点击第三方平台授权按钮,执行
redirect_uri对应的函数:- 第三方平台给server返回一个「授权码(code)」;
- server通过第三方平台的access_token_url(参数需要code,clent_id,client_secret)获取access_token,再通过userinfo_url(需要access_token)获取用户信息
- 注册用户并直接登录
在安装 clash 的本地电脑上运行: ssh -NfR 7890:localhost:7890 -p 57457 root艾特服务器地址
在服务器上配置代理: export https_proxy=http://127.0.0.1:7890 export http_proxy=http://127.0.0.1:7890
redis
sql数据库:关系型数据库,基于磁盘存储,存储的是数据库表
redis:内存型键值数据库,数据主要存储在内存中,存储的是key, value对
redis:存储的是key, value对;单线程,不会出现读写冲突;
在Django中集成Redis
step 1. 安装 django_redis
pip install django_redis
step 2. 配置 settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
USER_AGENTS_CACHE = 'default'
step 3. 启动 redis-server
sudo redis-server /etc/redis/redis.conf
可以使用:python3 manage.py shell操作redis:
from django.core.cache import cache
cache.keys('*') # 找所有的key
cache.set('wky', 1, 5) # 设置关键字,5秒之后过期
cache.has_key('wky') # 查看key为wky是否存在
cache.get('wky') # 查看wky的值
cache.delete('wky') # 删除key为wky的键值对
联机对战
1、统一长度单位 2、配置django_channels(实现websockets,websockets是websocket的安全协议)
step1. 安装 channels_redis:
pip install channels_redis
step2. 配置 acapp/asgi.py
内容如下:
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from game.routing import websocket_urlpatterns
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'acapp.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})
step3. 配置 acapp/settings.py
在 INSTALLED_APPS 中添加 channels,添加后如下所示:
INSTALLED_APPS = [
'channels',
'game.apps.GameConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
在文件末尾添加的内容如下:
ASGI_APPLICATION = "acapp.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
step4. 配置 game/routing.py
这一部分的作用相当于 http 的 urls。
from django.urls import path
websocket_urlpatterns = [
]
step5. 编写 game/consumers
这一部分的作用相当于 http 的 views。
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class MultiPlayer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
print('accept')
self.room_name = "room"
await self.channel_layer.group_add(self.room_name, self.channel_name)
async def disconnect(self, close_code):
print('disconnect')
await self.channel_layer.group_discard(self.room_name, self.channel_name);
async def receive(self, text_data):
data = json.loads(text_data)
print(data)
step6. 启动 django_channels
在 ~/acapp 目录下执行:
daphne -b 0.0.0.0 -p 5015 acapp.asgi:application
3、编写同步函数 create_player、move_to、shoot_fineball、attack、blink
点击“多人模式”,相当于在地址栏输入url:ws://39.99.43.230:5015/ws/multiplayer/,对应后端函数MultiPlayer.as_asgi(),建立websocket连接,双向传送数据。
同步逻辑:点击后多人模式后,前端执行create_player()创建自己之后,还会send_create_player(),发送数据给后端,后端通过receive(self, text_data)函数接受前端发来的消息,并判断类型,向前端每个窗口广播await self.create_player(data),每个窗口接收消息后(前端js的start()函数执行了receive()函数,receive()函数中的WebSocket.onmessage 绑定回调函数(监听服务器推送的消息),这个绑定操作只需要执行 1 次即可生效,接收服务器的请求),判断是否是自己,如果不是就在本窗口执行create_player()函数创建用户。
为了防止状态不同步,攻击只能由一个窗口判断,然后广播给其他窗口,并且被击中的球的位置由攻击者决定(被击中会有击退效果)
例:同步move_to函数 前端js
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) {
player.move_to(tx, ty);
}
}
在原来的监听键盘q+鼠标左键后调用发送函数,传给服务器。
后端接受前端发送的消息,判断类型
async def receive(self, text_data):
data = json.loads(text_data)
event = data['event']
if event == "create_player":
await self.create_player(data)
elif event == "move_to":
await self.move_to(data)
前端移动后,发送自己的移动信息给后端
if (outer.playground.mode === "multi mode") {
outer.playground.mps.send_move_to(tx, ty); // 广播:移动
}
启动Django后端:python3 manage.py runserver 0.0.0.0:8000 启动联机服务:daphne -b 0.0.0.0 -p 5015 acapp.asgi:application 将所有的js文件打包:./scripts/compress_game_js.sh