第一次在掘金髮文章,瑟瑟發抖。
這個主要是為了學習使用一下canvas和websocket,專案地址。求star~
你畫我猜大家應該都玩過,一個人畫,其他人猜。目前實現了最基本的功能,慢慢修改。
專案截圖
完成進度
- 登入,登入後username儲存到了sessionStorage中。
- 座位,登入後可以選擇座位,並通過ws告訴所有人你的座位。
- 傳送內容,登入後可以通過ws將輸入內容釋出給所有人。
- 聊天記錄,可以接收所有人的聊天打字內容,可以清屏。
- 開始遊戲,一樓可以點選開始遊戲按鈕,開始遊戲後不可再調整座位。
- 按座位順序輪流進行畫圖與看圖猜詞,畫圖玩家不可以輸入內容與傳送內容。
- 隨機在詞表中選擇詞語。用於練習只寫了12個詞。
- 傳送內容為正確詞時特殊顯示(遊戲開始後有效)。
- 記分
- 多房間遊戲
- 移動端
客戶端
前端從前面的截圖可以看到,基本分為四部分,畫圖部分,選擇座位部分,聊天顯示部分,傳送聊天部分。四部分當然要使用一個websocket連線,所以使用了vuex,同時在vuex中儲存使用者名稱等資料。
建立ws連線,存入vuex中。
this.ws = new WebSocket(`ws://localhost:3000/`);
this.$store.commit(`wsStore/connect`,ws);
複製程式碼
登入部分
這裡登入不太重要,就直接把使用者名稱放到sessionStorage裡了,同時再存入vuex中供其他部分使用。
saveName(){
if(!window.sessionStorage){
alert("瀏覽器不支援sessionStorage!");
}else{
let storage = window.sessionStorage;
storage.setItem("username",this.inputName);
}
this.username=this.inputName;
this.showLogin=0;
this.$store.commit(`username/setUsername`,this.inputName)
}
複製程式碼
完整程式碼可以檢視github裡的src/components/HomeHeader.vue檔案。
畫圖部分
這部分是最主要的部分,先定義一個canvas
<canvas id="drawBoard" width="700" height="400"></canvas>
複製程式碼
然後是一個class draw,這裡只是使用畫筆的函式,從上面gif可以看出來還可以畫直線,矩形,圓,調整顏色等,最後也都是寫到這個類裡面的。
class canvasDraw{
constructor(id,ws){
this.ws= ws;
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext(`2d`);
this.stage_info = this.canvas.getBoundingClientRect()
this.path = {
beginX: 0,
beginY: 0,
endX: 0,
endY: 0
}
this.isDraw=false
}
draw(){
let that = this
this.canvas.onmousedown = () => {
that.ctx.beginPath()
that.path.beginX = event.pageX - that.stage_info.left
that.path.beginY = event.pageY - that.stage_info.top
that.ctx.moveTo(
that.path.beginX,
that.path.beginY
)
that.isDraw=true
}
this.canvas.onmousemove = () => {
if(that.isDraw){
that.drawing(event)
}
}
this.canvas.onmouseup = () => {
that.isDraw=false
that.ws.send(`stop,`)
}
}
drawing(e) {
this.path.endX = e.pageX - this.stage_info.left
this.path.endY = e.pageY - this.stage_info.top
this.ctx.lineTo(
this.path.endX,
this.path.endY
)
this.ctx.stroke()
this.ws.send(`draw,${this.path.beginX},${this.path.beginY},${this.path.endX},${this.path.endY}`)
}
clearCanvas(){
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
this.ws.send(`clear,`)
}
}
複製程式碼
可以看到,建構函式主要是儲存起始和終點的座標,雖然畫筆可以隨便畫,但是也是由一小段一小段線段組成的。
然後draw函式,當監聽到mousedown事件後,新建一條路徑,將起始座標儲存下來,隨著mousemove事件,滑鼠每移動一點,就畫一條線段,直到監聽到mouseup事件,完成一次畫線。
畫直線、矩形、圓的原理差不多,都是儲存滑鼠的起始和終點座標,然後算一下矩形的長和寬,圓的圓心和半徑即可。以畫圓的程式碼為例。
drawRound(){
this.canvas.onmousedown=()=>{
this.ctx.beginPath()
this.path.beginX = event.pageX - this.stage_info.left
this.path.beginY = event.pageY - this.stage_info.top
}
this.canvas.onmouseup=()=>{
this.path.endX = event.pageX - this.stage_info.left
this.path.endY = event.pageY - this.stage_info.top
let width = this.path.endX-this.path.beginX;
let height = this.path.endY-this.path.beginY;
this.ctx.arc(this.path.beginX+width/2, this.path.beginY+height/2,Math.sqrt(width*width+height*height)/2,0, Math.PI *2);
this.ctx.fill();
this.ctx.stroke();
}
}
複製程式碼
改變線條寬度、顏色等就更簡單了,只要將選擇的數值傳到類中的如下函式裡即可。
changeColor(color){
this.ctx.strokeStyle = color;
}
複製程式碼
完整程式碼可以檢視github裡的src/components/DrawBoard.vue檔案。
座位部分
這一部分就沒什麼了,就點選button,做一下判斷就好了。例如
seatDown(index){
if(this.$store.state.wsStore.ws.readyState==3){
this.$message(`伺服器未連線`);
return ;
}
if(this.username==null||this.username==``){
this.$message(`請登陸`);
}else if(this.seats[index]==`空位`){
this.$store.state.wsStore.ws.send(`seats,${index},${this.username}`);
}else{
this.$message(`此位置有人`);
}
},
複製程式碼
如果是一樓的話,可以開始遊戲,開始遊戲就給後端發一個訊號,然後就是後端的一些邏輯了。
begin(){
this.$store.state.wsStore.ws.send(`begin,`);
},
複製程式碼
還有,如果遊戲開始之後,不可以再調整座位或者新使用者坐下。
watch:{
beginGame(newval,oldval){
let buttonEle = document.querySelectorAll(`button[component="seat"]`);
if(this.beginGame){
for(let bt of buttonEle){
bt.setAttribute(`disabled`,`disabled`);
}
}else{
for(let bt of buttonEle){
bt.removeAttribute(`disabled`);
}
}
}
},
複製程式碼
完整程式碼可以檢視github裡的src/components/TheSeats.vue檔案。
聊天顯示部分
聊天顯示部分只負責將接受到的伺服器的訊息顯示出來,可以清屏。
<div v-for="(record,index) in chatRecords" :key="index" class="record">
<span class="blue">{{record.username}}</span>:{{record.content}}
</div>
watch:{
msg(newval,oldval){
let record = {
username: this.msg[1],
content: this.msg[2]
};
this.chatRecords.push(record);
let div = document.getElementById(`totalBoard`)
div.scrollTop = div.scrollHeight;
console.log(div.scrollTop,div.scrollHeight+100)
}
},
複製程式碼
完整程式碼可以檢視github裡的src/components/ChatBoard.vue檔案。
聊天輸入部分
聊天輸入部分只負責將資料傳送給伺服器,然後伺服器再傳送給所有使用者,上面的聊天顯示部分接受到之後顯示出來。
submitWord(){
if(this.$store.state.wsStore.ws.readyState==3){
this.$message(`伺服器未連線`);
return ;
}
if(this.username==null||this.username==``){
this.$message(`請登陸`);
return ;
}
if(this.guassWord!=``){
this.$store.state.wsStore.ws.send(`chat,${this.username},${this.guassWord}`);
this.guassWord=``;
}
}
複製程式碼
還有如果輪到自己畫的話,不可以傳送資訊。
watch:{
drawuser(newval,oldval){
let inputEle = document.querySelector(`input[component="inputBoard"]`);
let buttonEle = document.querySelector(`button[component="inputBoard"]`);
if(this.drawuser==this.username){
inputEle.setAttribute(`disabled`,`disabled`)
buttonEle.setAttribute(`disabled`,`disabled`);
}else{
inputEle.removeAttribute(`disabled`);
buttonEle.removeAttribute(`disabled`);
}
}
},
複製程式碼
完整程式碼可以檢視github裡的src/components/InputBoard.vue檔案。
服務端
服務端使用了node的ws模組,還在學習中,寫的比較菜,只是滿足了遊戲需要。
主要是將使用者畫圖時的起始和終點座標做一個轉發。
有使用者坐下或調整座位後傳送一下最終的座位資訊。
遊戲開始後,按座位順序,輪流給每個人傳送畫圖人和要畫的詞。
使用者輸入後判斷是否為正確詞,如果不對,就傳送原詞,如果猜對了,就傳送“猜對了”
// 匯入WebSocket模組:
const WebSocket = require(`ws`);
// 引用Server類:
const WebSocketServer = WebSocket.Server;
// 例項化:
const wss = new WebSocketServer({
port: 3000
});
let seats=[`空位`,`空位`,`空位`,`空位`,`空位`,`空位`]
let dict=[`老鼠`,`牛`,`老虎`,`兔子`,`龍`,`蛇`,`馬`,`羊`,`猴子`,`雞`,`狗`,`豬`]
let gameBegin = false;
let guassWord = ``;
wss.on(`connection`, function (ws) {
console.log(`[SERVER] connection()`);
ws.on(`message`, function (message) {
console.log(`[SERVER] Received: ${message}`);
let msg = message.split(`,`);
if(msg[0]=="seats"&&msg.length==3){
let i = seats.indexOf(msg[2])
if(i>-1){
seats[i]=`空位`;
}
seats[msg[1]]=msg[2];
wss.clients.forEach((client) => {
client.send(`seats,${seats}`)
})
}else if(msg[0]==`begin`){
function settime(username,ms){
let i = Math.floor(Math.random()*dict.length);
setTimeout(()=>{
guassWord=dict[i];
wss.clients.forEach((client) => {
client.send(`begin,${username},${dict[i]}`)
})
},ms)
}
gameBegin=true;
let ms = 0;
for(let username of seats){
if(username!=`空位`){
settime(username,ms)
ms+=10000;
}
}
setTimeout(()=>{
wss.clients.forEach((client) => {
client.send(`end,`)
})
gameBegin=false;
},ms)
}else if(gameBegin&&msg[0]==`chat`&&msg[2]==guassWord){
wss.clients.forEach((client) => {
client.send(`chat,${msg[1]},猜對啦!`)
})
}else{
wss.clients.forEach((client) => {
client.send(message)
})
}
})
});
複製程式碼
參考
參考了下面這篇文章,在其基礎上完善了小遊戲。