《HTML5游戏编程核心技术与实战》——2.7 案例:《你画我猜》

简介:

本节书摘来自异步社区《HTML5游戏编程核心技术与实战》一书中的第2章,第2.7节,作者: 向峰 更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.7 案例:《你画我猜》

在这一小节中,我们将利用前面介绍的知识,来创作一个《你画我猜》游戏中的主要功能。《你画我猜》是一款老少皆宜的多人在线的网络游戏,2012年风靡一时,玩法其实也来源于生活当中,经常在娱乐节目中出现。通常在节目中是这样玩的,主持人写出一个词语,然后由一个参与者根据这个词语画出相应的图案,由另一个参与者来根据这个图案猜出这个词语,而《你画我猜》就把现实生活中的这个玩法转到了电脑上,玩法就是这么简单。当然,本章还无法开始做出一个网络游戏,哪怕是一个简单的单机游戏,本书第10章有《你画我猜》这个游戏完整的实现,本章将会完成《你画我猜》中的画板部分。没玩过《你画我猜》游戏的,也应该用过Windows的画板吧?好了,下面我们来实现这个画板。

2.7.1 UI界面设计
首先,我们来设计一个画板的主页面,关于使用div布局游戏的UI不是本书的重点,我们只关注游戏逻辑的实现,以下代码只是一个参考实现:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>你画我猜</title>
<link href="images/CSS.css" rel="stylesheet" type="text/css">
</head>
<body>
<!--主容器层-->
<div id="main" class="main">
<div>
 <div class="tx"></div>
 <div class="wz"> 
  <div id="dround"></div>
  <!--显示问题区域-->
  <span id="question"></span>
 </div>
 <!--显示时间-->
 <div id="qTime" class="time">60</div>
 <div class="tc">离开房间<img src="images/edit_undo.png" align="absmiddle"></div>
</div>
<div class="qc"></div>
<div>
<!--绘图主层-->
 <div class="hbbox">
 <!--绘图区域-->
  <div id="hb" class="hb">
   <canvas id="paintArea" width="525" height="370" ></canvas>
  </div>
 </div>
<!--工具选项区-->
<div class="hbr">
<div id="operDiv">
  <!--颜色选取区-->
  <div id="ys" class="ys"></div>
  <!--画笔大小选择区-->
  <div id="bc" class="bc"></div>
  <!--定义橡皮擦工具-->
  <div id="ssx" class="tb">
   <div id="btnRub" class="tb2"><img src="images/01945.png"></div>
   <div id="btnClear" class="tb3"><img src="images/053753321.gif"></div>
  </div>
</div>
<!--消息发布主层-->
<div id="msgArea" class="msgArea"></div>
 <div class="bd">
 <input type="button" id="btnSendMsg" value="发送"><input type="text" size="13"  
    id="txtMsg">  
 </div>
</div>
</div>
</div>
</body>

设计好以上的主页面后,得到如图2-23所示的图,当然,样式部分不是重点,所以忽略,我们需要了解这个画板中和程序相关的部分。

好了,定义好图2-23所示的UI界面后,开始实现功能。


<a href=https://yqfile.alicdn.com/64544f968884da39f1f6c0a2473b636843de9b86.png" >

2.7.2 定义画板对象
首先,我们把整个画板看成一个画板对象,这个画板对象中有一些基本的属性,如画板的大小,当前画笔使用的颜色、宽度等,于是就有了以下的Paint类:

var Painter = {
  //绑定的环境上下文
  ctx:null,
  //宽度
  w:0,
  //高度
  h:0,   
  //当前画笔颜色
  bColor:null,
  //当前画笔大小
  bWidth:null
}

画板定义好了以后,我们需要逐渐往里面增加相应的功能代码。

2.7.3 初始化画笔选项区
接下来,我们需要动态产生颜色区域以及画笔区域,因为在前面设计UI的时候,只给这两个区域留下了空白容器,需要我们通过代码来产生。于是,可以给Painter定义一个初始化画笔的方法,具体的代码如下:

//初始化画笔
initBrush:function()
  {
  //定义画板颜色
  var bColor = ["#000000", "#999999", "#FFFFFF", "#FF0000", "#FF9900", "#FFFF00", 
  "#008000", "#00CCFF", "#0099FF", "#FF33CC", "#CC66FF", "#FFCCCC", "#6633FF", "#CCFFCC"];
  var bDiv = $("#ys"), 
    self = this;
 //产生颜色层
 for(var i=0;i<bColor.length;i++)
 {
   var b = $("<div class='bys'></div>").css("background-color", bColor[i]);
    //修改颜色
    b.on("click", function(){
    //触发更新画板状态事件
    self.fire("onPaintUpdate", {"color":$(this).css("background-color")});
   });
   bDiv.append(b);
 }
 //绑定画笔大小
 var bWidth = [2, 8, 16, 24];
 var bcDiv = $("#bc");
 for(i = 0;i<bWidth.length;i++)
 {
   var bw = $("<div class='bwid' data-bidx='"+(i)+"'></div>");
   bw.css("background-image", "url(images/bc"+(i+1)+".png)");   
   //修改画笔大小
   bw.on("click", function(){
    //触发更新画板状态事件
    self.fire("onPaintUpdate", {"width":bWidth[this.getAttribute("data-bidx")]});
   });
   bcDiv.append(bw);
 }
}

以上函数中,我们创建了颜色区域和画笔区域中的选择层部分,当用户鼠标点击颜色选择区域的时候,就会使用fire方法触发一个onPaintUpdate事件,修改当前用户画笔的颜色。同样,当用户鼠标点击画笔选择区域时,也会触发onPaintUpdate事件,修改当前用户画笔的大小。fire方法是Paint类中自定义的一个方法,用于处理画板中的各种自定义事件的触发,具体实现如下:

//触发画板事件
fire:function(eventName, param)
{
 if(this[eventName])
 {
   this[eventName](param);
 }    
}

本质上,fire方法就是调用相应的事件处理程序,相应的,我们需要添加一个onPatintUpdate方法,相关代码如下:

//画板更新事件,当画板的参数比如画笔颜色,大小改变时触发
onPaintUpdate:function(data)
{
 var w = data.width||this.bWidth,
    c = data.color||this.bColor;
 var param = {"width":w, "color":c};
 //设置画笔大小
 this.setBrushWidth(w);
 //设置画笔颜色
 this.setBrushColor(c);      
}

在这个更新画笔的方法中,需要根据传入的参数调用setBrushWidth()和setBrushColor()方法更新当前画笔的色彩和大小,这两个方法的具体代码如下:

//设置画笔颜色
setBrushColor:function(color)
{
 this.bColor = color||"black";
 this.ctx.strokeStyle = this.bColor;    
},
//设置画笔宽度
setBrushWidth:function(width)
{
 this.bWidth = width||1;
 this.ctx.lineWidth = this.bWidth;
}

到此为止,关于画笔颜色和大小的选择已经完成,接下来,我们看看如何实现用户在画板上用画笔绘画。
**
2.7.4 实现画板绘制**
使用画笔在画板上绘制,从原理上来说,canvas上是可以画图形的,所以我们只要在鼠标的移动事件上面做文章就可以了。鼠标在canvas上面移动的时候,可以跟踪当前鼠标的坐标,然后在这些坐标点上绘制点就可以了,这是我们最初的想法,但实际上这种效果并不理想。因为,首先,canvas并没有提供画点的方法,当然,可以通过绘制一个极小的矩形实现,但这不是主要问题。主要问题在于,虽然我们移动鼠标是连续移动,但电脑没有足够高的灵敏度捕捉鼠标的移动轨迹。事实上,如果采用这种方法,将会在canvas上留下断断续续的点的轨迹,这不是我们要的结果,我们需要的是把这些点用线连起来。所以最终的算法是,捕捉鼠标移动的坐标,然后把当前点和上一个点用线连接起来,最终,就可以达到一个比较好的效果。

根据以上的原理部分,我们在Paint类中增加一个initCanvas()方法,用于初始化画板,绑定鼠标在canvas上面的相关事件,其实现代码如下:

//初始化画板
initCanvas:function()
{
 //绑定绘图canvas
 var can = $("#paintArea"), self = this;
 //绑定鼠标按下时间
 can.on("mousedown", function(e){    
   e.preventDefault();
   this.x = e.offsetX,
   this.y = e.offsetY;
   self.fire("onStartDraw", {"x":this.x, "y":this.y});
   //绑定鼠标移动事件
   can.on("mousemove", function(e){
    var nx = event.offsetX, ny = event.offsetY;
    self.fire("onDrawing", {"x":nx, "y":ny});
    this.x = nx;
    this.y = ny;
   });
   //绑定鼠标抬起事件
   can.on("mouseup", function(){
    //取消鼠标移动事件
    can.off("mousemove");
   });
 })
}

这里,我们在canvas中绑定了鼠标按下、移动和抬起事件,在按下和移动的时候,触发了onStartDraw和onDrawing方法,这两个方法主要用于绘制线条,代码如下:

//开始画画事件
onStartDraw:function(data){
  //开始路径
  this.ctx.beginPath();
  this.ctx.moveTo(data.x, data.y);    
},
//画画事件
onDrawing:function(data)
{
  this.ctx.lineTo(data.x, data.y);
  this.ctx.stroke();   
}

主要绘制部分的工作,基本都完成了。噢,别忘了,还有一个擦除区域。当我们绘制错误的时候,需要擦除图像,于是,我们可以完成初始化擦除区域的方法如下:

//初始化橡皮擦
initEraser:function()
{
 var self = this;
 //绑定清除屏幕事件
 $("#btnClear").click(function(){
  self.clear();
 });
 //擦除
 $("#btnRub").click(function(){
  self.setBrushColor("white");
  self.setBrushWidth(32);
 });
}

好了,大功告成,为了让Paint类开始工作,我们最好写一个init()方法,完成以上全部的初始化工作,以便让用户方便使用,init()方法可参考完整代码部分。

2.7.5 整合代码
好了,整个Painter对象就完成了,最终,我们得到的Paint对象的完整代码如下:

(function(){
  //定义画板对象
  var Painter = {
   //绑定的环境上下文
   ctx:null, 
   //宽度
   w:0,
   //高度
   h:0,   
   //当前画笔颜色
   bColor:null,
   //当前画笔大小
   bWidth:null,
   //初始化
  init:function()
  {
   var can = $("#paintArea")[0];
   this.ctx = can.getContext("2d");
   this.w = can.width;
   this.h = can.height;
   this.setBGColor();
   this.setBrushColor();
   this.setBrushWidth();
   this.ctx.lineCap = "round";
   this.ctx.lineJoin = "round";
   //初始化画板事件
   this.initCanvas();
   //初始化画笔颜色
   this.initBrush();
   //初始化橡皮擦
   this.initEraser();
  },
  //初始化画笔
  initBrush:function()
  {
   //定义画板颜色
   var bColor = ["#000000", "#999999", "#FFFFFF", "#FF0000", "#FF9900", "#FFFF00",  
             "#008000", "#00CCFF", "#0099FF", "#FF33CC", "#CC66FF", "#FFCCCC",  
             "#6633FF", "#CCFFCC"];
   var bDiv = $("#ys"), 
      self = this;
   //产生颜色层
   for(var i=0;i<bColor.length;i++)
   {
     var b = $("<div class='bys'></div>").css("background-color", bColor[i]);
     //修改颜色
     b.on("click", function(){
      //触发更新画板状态事件
      self.fire("onPaintUpdate", {"color":$(this).css("background-color")});
     });
    bDiv.append(b);
   }
   //绑定画笔大小
   var bWidth = [2, 8, 16, 24];
   var bcDiv = $("#bc");
   for(i = 0;i<bWidth.length;i++)
   {
     var bw = $("<div class='bwid' data-bidx='"+(i)+"'></div>");
     bw.css("background-image", "url(images/bc"+(i+1)+".png)");   
     //修改画笔大小
     bw.on("click", function(){
      //触发更新画板状态事件
      self.fire("onPaintUpdate", {"width":bWidth[this.getAttribute("data-bidx")]});
     });
     bcDiv.append(bw);
   }
  }, 
  //初始化橡皮擦
  initEraser:function()
  {
   var self = this;
   //绑定清除屏幕事件
   $("#btnClear").click(function(){
    self.clear();
   });
   //擦除
   $("#btnRub").click(function(){
    self.setBrushColor("white");
    self.setBrushWidth(32);
   });
  },
  //设置背景颜色
  setBGColor:function(color){
   this.ctx.fillStyle = color||"white";
   this.ctx.fillRect(0, 0, this.w, this.h);
  },
  //设置画笔颜色
  setBrushColor:function(color)
  {
   this.bColor = color||"black";
   this.ctx.strokeStyle = this.bColor;    
  },
  //设置画笔宽度
  setBrushWidth:function(width)
  {
   this.bWidth = width||1;
   this.ctx.lineWidth = this.bWidth;
  },
  //初始化画板
  initCanvas:function()
  {
   //绑定绘图canvas
   var can = $("#paintArea"), self = this;
   //绑定鼠标按下时间
   can.on("mousedown", function(e){    
    e.preventDefault();
    this.x = e.offsetX,
    this.y = e.offsetY;
    self.fire("onStartDraw", {"x":this.x, "y":this.y});
    //绑定鼠标移动事件
    can.on("mousemove", function(e){
     var nx = event.offsetX, ny = event.offsetY;
     self.fire("onDrawing", {"x":nx, "y":ny});
     this.x = nx;
     this.y = ny;
    });
    //绑定鼠标抬起事件
    can.on("mouseup", function(){
    //取消鼠标移动事件
    can.off("mousemove");
    });
   })
  },
  //清除canvas
  clear:function()
  {
   this.ctx.clearRect(0, 0, this.w, this.h);
  },
  //触发画板事件
  fire:function(eventName, param)
  {
   if(this[eventName])
   {
    this[eventName](param);
   }    
  },
  //开始画画事件
  onStartDraw:function(data){
   //开始路径
   this.ctx.beginPath();
   this.ctx.moveTo(data.x, data.y);    
  },
  //画画事件
  onDrawing:function(data)
  {
   this.ctx.lineTo(data.x, data.y);
   this.ctx.stroke();   
  },
  //画板更新事件,当画板的参数比如画笔颜色,大小改变时触发
  onPaintUpdate:function(data)
  {
   var w = data.width||this.bWidth, c = data.color||this.bColor;
   var param = {"width":w,"color":c};
   //设置画笔大小
   this.setBrushWidth(w);
   //设置画笔颜色
   this.setBrushColor(c);      
  }
 }
  //画板初始化
 Painter.init();
 window.Painter = Painter;
}())

以上的Paint类在painter.js文件中,最后,完整的主页面代码index.htm如下:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>你画我猜</title>
<link href="images/CSS.css" rel="stylesheet" type="text/css">
</head>
<body>
<!--主容器层-->
<div id="main" class="main">
<div>
 <div class="tx"></div>
 <div class="wz"> 
  <div id="dround"></div>
  <!--显示问题区域-->
  <span id="question"></span>
 </div>
 <!--显示时间-->
 <div id="qTime" class="time">60</div>
 <div class="tc">离开房间<img src="images/edit_undo.png" align="absmiddle"></div>
</div>
<div class="qc"></div>
<div>
<!--绘图主层-->
 <div class="hbbox">
 <!--绘图区域-->
  <div id="hb" class="hb">
   <canvas id="paintArea" width="525" height="370" ></canvas>
  </div>
 </div>
<!--工具选项区-->
<div class="hbr">
<div id="operDiv">
  <!--颜色选取区-->
   <div id="ys" class="ys"></div>
   <!--画笔大小选择区-->
   <div id="bc" class="bc"></div>
   <!--定义橡皮擦工具-->
   <div id="ssx" class="tb">
    <div id="btnRub" class="tb2"><img src="images/01945.png"></div>
    <div id="btnClear" class="tb3"><img src="images/053753321.gif"></div>
   </div>
</div>
<!--消息发布主层-->
<div id="msgArea" class="msgArea"></div>
 <div class="bd">
 <input type="button" id="btnSendMsg" value="发送"><input type="text" size="13"  
    id="txtMsg">  
 </div>
</div>
</div>
</div>
</body>
<script src="../js/jquery.js" charset="utf-8"></script>
<script src="../js/painter.js" charset="utf-8"></script>
<script>
//检测是否支持HTML5
function isSupportHTML5()
{
 return (typeof(Worker) !== "undefined");
}
//初始化
$(document).ready(function(){ 
 if(!isSupportHTML5())
 {
  alert("您的浏览器不支持HTML5,请更换浏览器!");
 }
 else
 {
  $("#main").show(); 
 }  
});
</script>
</html>

至此,《你画我猜》的绘制部分已经完成,至于多人游戏的部分,在学习了后面的网络编程部分后,我们会得到一个完整的案例。

相关文章
|
4月前
|
前端开发 Python
HTML笔记+案例(上)
HTML笔记+案例
46 0
|
5月前
|
JSON 前端开发 Java
利用Spring Boot处理JSON数据实战(包括jQuery,html,ajax)附源码 超详细
利用Spring Boot处理JSON数据实战(包括jQuery,html,ajax)附源码 超详细
62 0
|
11天前
|
前端开发 UED
【专栏:HTML与CSS实战项目篇】创建一个具有复杂布局的电商详情页
【4月更文挑战第30天】构建复杂布局的电商详情页涉及页面结构规划、样式设计和交互效果实现。首先规划顶部导航栏、商品图片展示区、商品信息区、用户评价区和相关商品推荐区。在样式设计上,注重色彩搭配、字体选择、布局与间距及图片处理。交互效果包括图片放大、添加到购物车按钮、滚动监听和评论互动,以提升用户体验。实际开发中需考虑跨设备兼容性和用户体验优化。
|
11天前
|
移动开发 前端开发 JavaScript
【专栏:HTML与CSS实战项目篇】使用HTML5与CSS3制作一个动态表单验证页面
【4月更文挑战第30天】本文介绍了使用HTML5和CSS3创建动态表单验证页面的方法。首先,简述HTML5用于构建网页内容,CSS3用于描述样式。接着,分四步展示实现过程:1) 设计包含输入框和提示信息的表单结构;2) 使用CSS3创建样式,增强视觉效果;3) 使用JavaScript监听输入事件,动态验证表单并显示错误信息;4) 测试和调试确保跨平台兼容性。通过学习,开发者能掌握创建带验证功能的表单,提升用户体验。
|
11天前
|
前端开发 测试技术 UED
【专栏:HTML 与 CSS 实战项目篇】实现一个在线产品展示页面
【4月更文挑战第30天】本文介绍了使用HTML和CSS创建吸引人的在线产品展示页面的实战步骤,包括页面设计规划、HTML结构搭建、CSS样式设计、具体页面实现、交互效果添加、优化与提升。通过简洁布局、产品列表和详情页设计,实现易用且具吸引力的展示效果。优化图片和代码,提升性能,以助企业在数字时代脱颖而出。
|
11天前
|
编解码 前端开发 JavaScript
【专栏:HTML与CSS实战项目篇】打造一个动态新闻网站
【4月更文挑战第30天】构建动态新闻网站,运用HTML和CSS提升编程技能和网页设计理解。项目包括首页、新闻列表页和详情页,设计简洁易用,包含顶部导航、轮播图和新闻列表。页面布局注重吸引力和易用性,色彩搭配选用冷色调为主,辅以亮色点缀。字体选择清晰易读,布局保持整洁。交互效果如轮播图、导航栏高亮和响应式设计增强用户体验。本文提供基础新闻网站构建指南,为进一步功能扩展和优化打下基础。
|
11天前
|
前端开发 测试技术 定位技术
【专栏:HTML 与 CSS 实战项目篇】构建一个企业级网站:HTML 与 CSS 实战
【4月更文挑战第30天】本文介绍了使用HTML和CSS构建企业级网站的实战步骤,包括项目概述、页面结构设计、HTML结构搭建、CSS样式设计、具体页面实现、优化与提升。通过合理布局、美观样式和响应式设计,创建现代、简洁的网站,包含主页、关于我们、产品展示、新闻动态和联系我们等页面。优化图片和代码,确保性能,助力企业在数字时代树立良好形象并提升沟通效率。
|
11天前
|
编解码 前端开发 UED
【专栏:HTML与CSS实践篇】响应式网站开发实战
【4月更文挑战第30天】本文探讨了响应式网站开发,它能根据用户设备自动调整布局,提供最佳浏览体验。通过HTML和CSS,利用媒体查询、Flexbox和百分比宽度等技术实现响应式设计。媒体查询按屏幕尺寸定义CSS规则,Flexbox处理元素排列。文章通过新闻网站首页设计实例,展示了如何应用这些理论,包括使用Flexbox设计导航栏,使用媒体查询调整轮播图和内容区域,以及创建自适应页脚。遵循移动优先原则,关注性能优化和用户体验,响应式设计是前端开发的关键,为多设备用户提供优质浏览体验。
|
2月前
|
JavaScript
jQuery选择器案例之——index.html
jQuery选择器案例之——index.html
9 1
|
2月前
|
移动开发 HTML5
HTML5表格简单应用案例之[招聘需求表]
HTML5表格简单应用案例之[招聘需求表]
12 0