如何做網(wǎng)站對(duì)比網(wǎng)站優(yōu)化排名網(wǎng)站
筆記內(nèi)容轉(zhuǎn)載自 AcWing 的 Django 框架課講義,課程鏈接:AcWing Django 框架課。
CONTENTS
- 1. 實(shí)現(xiàn)聊天系統(tǒng)前端界面
- 2. 實(shí)現(xiàn)后端同步函數(shù)
1. 實(shí)現(xiàn)聊天系統(tǒng)前端界面
聊天系統(tǒng)整體可以分為兩部分:輸入框與歷史記錄。
我們需要先修改一下之前代碼中的一個(gè)小 BUG,當(dāng)在一個(gè)窗口中按 Q 時(shí),另一個(gè)窗口中點(diǎn)擊鼠標(biāo)左鍵也能攻擊,因?yàn)榘聪掳存I的事件被所有窗口都捕捉到了,這是不合理的。
我們之前監(jiān)聽(tīng)的對(duì)象是 window
,每個(gè)地圖是一個(gè) canvas
元素,因此我們可以綁定到 canvas
對(duì)象上。由于不是所有對(duì)象都能添加綁定事件的,因此我們還需要對(duì) canvas
做一個(gè)修改,首先在 GameMap
類中修改一下 canvas
對(duì)象:
class GameMap extends AcGameObject {constructor(playground) { // 需要將AcGamePlayground傳進(jìn)來(lái)super(); // 調(diào)用基類構(gòu)造函數(shù),相當(dāng)于將自己添加到了AC_GAME_OBJECTS中this.playground = playground;this.$canvas = $(`<canvas tabindex=0></canvas>`); // 畫(huà)布,用來(lái)渲染畫(huà)面,tabindex=0表示能夠監(jiān)聽(tīng)事件...}start() {this.$canvas.focus(); // 聚焦后才能監(jiān)聽(tīng)事件}...
}
在 Player
類中修改:
class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) { // Q鍵outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) { // F鍵outer.cur_skill = 'blink';return false;}});}...
}
聊天的前端界面需要?jiǎng)?chuàng)建一個(gè)新的文件,我們?cè)?~/djangoapp/game/static/js/src/playground
目錄下創(chuàng)建一個(gè) chat_field
目錄,并進(jìn)入該目錄創(chuàng)建 zbase.js
文件:
class ChatField {constructor(playground) {this.playground = playground;this.func_id = null; // 在每次打開(kāi)輸入框時(shí)需要將之前歷史記錄框的計(jì)時(shí)函數(shù)刪掉this.$history = $(`<div class='ac_game_chat_field_history'></div>`);this.$input = $(`<input type='text' class='ac_game_chat_field_input'>`);this.$history.hide();this.$input.hide();this.playground.$playground.append(this.$history);this.playground.$playground.append(this.$input);this.start();}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 輸入框也需要監(jiān)聽(tīng)ESC事件if (e.which === 27) {outer.hide_input();return false;}});}show_history() {let outer = this;this.$history.fadeIn(); // 慢慢顯示出來(lái)if (this.func_id) clearTimeout(this.func_id);this.func_id = setTimeout(function() {outer.$history.fadeOut();outer.func_id = null;}, 3000); // 顯示3秒后消失}show_input() {this.$input.show();this.show_history(); // 打開(kāi)輸入框順帶打開(kāi)歷史記錄this.$input.focus(); // 聚焦一下才能輸入}hide_input() {this.$input.hide();this.playground.game_map.$canvas.focus(); // 關(guān)閉輸入框后要重新聚焦回Canvas上}
}
然后在 AcGamePlayground
類中創(chuàng)建出來(lái):
class AcGamePlayground {...// 顯示playground界面show(mode) {...// 單人模式下創(chuàng)建AI敵人if (mode === 'single mode'){for (let i = 0; i < 8; i++) {this.players.push(new Player(this, this.width / 2 / this.scale, 0.5, 0.07, this.get_random_color(), 0.15, 'robot'));}} else if (mode === 'multi mode') {this.mps = new MultiPlayerSocket(this);this.mps.uuid = this.players[0].uuid; // 用每名玩家的唯一編號(hào)區(qū)分不同的窗口this.chat_field = new ChatField(this); // 聊天區(qū)this.mps.ws.onopen = function() {outer.mps.send_create_player(outer.root.settings.username, outer.root.settings.avatar);};}}...
}
現(xiàn)在在 Player
類中即可監(jiān)聽(tīng)事件:
class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (e.which === 13 && outer.playground.mode === 'multi mode') { // 還沒(méi)滿人允許使用聊天功能outer.playground.chat_field.show_input();return false;} else if (e.which === 27 && outer.playground.mode === 'multi mode') {outer.playground.chat_field.hide_input();return false;}if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) { // Q鍵outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) { // F鍵outer.cur_skill = 'blink';return false;}});}...
}
然后我們還需要實(shí)現(xiàn)一下聊天區(qū)的 CSS 樣式(在 ~/djangoapp/game/static/css
目錄的 game.css
文件中):
....ac_game_chat_field_history {position: absolute;top: 40%;left: 15%;transform: translate(-50%, 50%);width: 20%;height:30%;color: white;background-color: rgba(77, 77, 77, 0.2);font-size: 1.5vh;padding: 5px;overflow: auto;
}.ac_game_chat_field_history::-webkit-scrollbar { /* 滾動(dòng)條 */width: 1;
}.ac_game_chat_field_input {position: absolute;top: 86%;left: 15%;transform: translate(-50%, 50%);width: 20%;height: 2vh;color: white;background-color: rgba(222, 225, 230, 0.2);font-size: 1.5vh;
}
現(xiàn)在我們實(shí)現(xiàn)在歷史記錄區(qū)域里添加新消息的功能:
class ChatField {constructor(playground) {...}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 輸入框也需要監(jiān)聽(tīng)ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) { // 按Enter鍵時(shí)發(fā)送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input(); // 發(fā)送完消息后關(guān)閉輸入框if (text) { // 信息不為空才渲染出來(lái)outer.$input.val(''); // 將輸入框清空outer.add_message(username, text);}return false;}});}render_message(message) { // 渲染消息return $(`<div>${message}</div>`);}add_message(username, text) { // 向歷史記錄區(qū)里添加消息let message = `[${username}] ${text}`;this.$history.append(this.render_message(message));this.show_history(); // 每次發(fā)新消息時(shí)都顯示一下歷史記錄this.$history.scrollTop(this.$history[0].scrollHeight); // 將滾動(dòng)條移動(dòng)到最底部}...
}
2. 實(shí)現(xiàn)后端同步函數(shù)
我們先在 WebSocket 前端實(shí)現(xiàn)發(fā)送和接收消息的函數(shù):
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; // 如果是給自己發(fā)送消息就直接過(guò)濾掉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);} else if (event === 'attack') { // attack路由outer.receive_attack(uuid, data.attackee_uuid, data.x, data.y, data.theta, data.damage, data.fireball_uuid);} else if (event === 'blink') { // blink路由outer.receive_blink(uuid, data.tx, data.ty);} else if (event === 'message') { // message路由outer.receive_message(data.username, data.text);}};}...send_message(username, text) {let outer = this;this.ws.send(JSON.stringify({'event': 'message','uuid': outer.uuid,'username': username,'text': text,}));}receive_message(username, text) {this.playground.chat_field.add_message(username, text);}
}
然后實(shí)現(xiàn)后端代碼:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
from django.core.cache import cacheclass MultiPlayer(AsyncWebsocketConsumer):...async def message(self, data):await self.channel_layer.group_send(self.room_name,{'type': 'group_send_event','event': 'message','uuid': data['uuid'],'username': data['username'],'text': data['text'],})async def receive(self, text_data):data = json.loads(text_data)print(data)event = data['event']if event == 'create_player': # 做一個(gè)路由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)elif event == 'attack': # attack的路由await self.attack(data)elif event == 'blink': # blink的路由await self.blink(data)elif event == 'message': # message的路由await self.message(data)
最后在前端的 ChatField
類中調(diào)用一下發(fā)送消息的函數(shù)即可:
class ChatField {...add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 輸入框也需要監(jiān)聽(tīng)ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) { // 按Enter鍵時(shí)發(fā)送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input(); // 發(fā)送完消息后關(guān)閉輸入框if (text) { // 信息不為空才渲染出來(lái)outer.$input.val(''); // 將輸入框清空outer.add_message(username, text);outer.playground.mps.send_message(username, text); // 給其他玩家的窗口發(fā)送消息}return false;}});}...
}