一个伟大的史诗级巨著
术士之战
功能
实现在线联机对战,可以根据玩家分数进行匹配,实现了实时聊天室。我们采用的是前后端分离式开发,所有的html渲染都要求在前端完成,降低服务器压力。
操作:
- 移动:鼠标右键
- 发射火球:q + 鼠标左键
- 闪现:f + 鼠标左键
需要打开的端口
- 80——http
- 8000——django测试
- 20000——ssh登录(映射到22)
- 443——https
设置.gitignore
缓存文件,可执行文件,编译文件 不要传到自己的 git 项目里
格式:**/文件名
项目系统设计
- menu : 菜单页面
- playground : 游戏界面
- settings : 设置界面
各种文件夹的作用
- templates 目录:管理 html 文件
- urls 目录:管理路由,即链接与函数的对应关系
- views 目录:管理 http 函数
- models 目录:管理数据库数据
- static目录:管理静态文件
- css : 对象的格式,比如位置、长宽、颜色、背景、字体大小等
一般一个工程,只有一个 css 文件就足够了
- js : 对象的逻辑,比如对象的创建与销毁、事件函数、移动、变色等一般一个工程会有很多个 .js 源文件,为了加快网络的传输,也为了每次写新的 .js 文件不用每个 html 都额外引入一次
考虑用一个 src 源文件夹来存储所有的 .js 源文件
然后用 dist 文件夹来存放由 src 下所有源文件整合生成的一个目标 .js 文件
这样既实现了快速传输的好处,也方便了后续编写 html 文件时,引入 .js 的便利
- image : 图片
路由规则
大致路由图
/-- "" -- index
/ -- "menu/" -- menu.index
/ "" --> "game.url" -->
/ \ -- "playground/" -- playround.index
start -> \-- "settings/" -- settings.index
\
\ "/admin" -- 到达管理员页面
根据路由图首先配置/acapp/acapp/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"))
]
手写游戏引擎
游戏中物体移动的原理就是若干张图片的切换,当图片切换的速度够快时,就形成了动画。我们将每秒闪过的图片张数定义为帧率,在这个项目中,帧率定为60,因此需要先实现一个基类AcGameObject
。
该基类需要具备一下函数:
1. start() 在游戏开始的第一帧时需要执行的任务(一般是创建对象)
2. update() 在游戏开始后的每一帧均会执行的任务(一般是渲染当前对象的各种状态)
3. on_destroy() 删掉该物体前需要执行的任务(一般是删掉动画,或者对局结算)
4. destroy() 删掉该物体
根据上述逻辑,我们就可以基本搭建出来一个游戏引擎的基类了,具体如下:
/static/js/playground/ac_game_object/zbase.js
let AC_GAME_OBJECTS = []; //用于记录当前画布中,需要渲染的对象有哪些
class AcGameObject {
constructor() {
AC_GAME_OBJECTS.push(this); //将当前新建的对象,加入到全局的画布中去,参与渲染
this.has_called_start = false; //是否执行过 start 函数
this.timedelta = 0; //当前帧距离上一帧的时间间隔
// 该数据记录是为了后续计算速度等参数的
}
start() { //只会在第一帧执行一次
}
update() { //每一帧均会执行一次
}
on_destroy() { //在被销毁前执行一次
}
destroy() { //删掉该物体
this.on_destroy(); //删掉该物体前,执行删前的操作
// 在全局渲染物体中,找到该物体,并将其删掉
for (let i = 0; i < AC_GAME_OBJECTS.length; i ++ ) {
if (AC_GAME_OBJECTS[i] === this) { //三等号,在js里额外加了一层类型相等约束
AC_GAME_OBJECTS.splice(i, 1);
break;
}
}
}
}
let last_timestamp;
let AC_GAME_ANIMATION = function(timestamp) { // 回调函数,实现:每一帧重绘时,都会执行一遍
for (let i = 0; i < AC_GAME_OBJECTS.length; i ++ ) {
let obj = AC_GAME_OBJECTS[i];
if (!obj.has_called_start) { //如果还未执行初始帧动作,就先执行
obj.start();
obj.has_called_start = true;
}
else { //执行过初始帧,就执行每一帧的任务
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
last_timestamp = timestamp; //更新最后一次时间戳
requestAnimationFrame(AC_GAME_ANIMATION);
}
requestAnimationFrame(AC_GAME_ANIMATION); // js提供的api,其功能如下官方文档所示
window.requestAnimationFrame(callback) 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。 参数 callback : 下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入 DOMHighResTimeStamp 参数,该参数与 performance.now() 的返回值相同,它表示 requestAnimationFrame() 开始去执行回调函数的时刻。
接下来所有对象,都是基于这个引擎完成的
AcWing一键登录实现逻辑
- 首先用户请求使用acwing账号登陆;
- web端向acwing求情OAuth2授权登录;
- 网页跳转至acwing页面,要求用户确认授权许可;
- 用户点击同意后,acwing会向web端提供授权码(code);
- web端在拥有code之后,会集合appid、appsecret发送到acwing;
- acwing在接收到后,会返回令牌(access_token,有效期2h)、refresh_token(刷新令牌,有效期30天)和Client的openid(用户的唯一标识)
- web在拥有令牌和openid后可以向acwing申请获取用户的头像和用户名
- 最后登录成功