引言
在前面的文章《网络斗地主游戏的完整设计与实现(一)项目的基本结构》介绍了项目的整体结构。接下来说明一下系统中用到的核心技术路线
项目的源码可在CSDN资源中下载
游戏界面的呈现
斗地主游戏的玩家分成三个,而且在未叫牌与叫牌后玩家的角色会发生变化,因此要用不同的图片加以呈现。本项目没有使用cocos2d之类的游戏框架,而是使用基本的javascrpt+jquery实现游戏界面。
具体对这个项目来说,主要的游戏界面分为几大类:
1.背景类
这个项目中背景基本上保持不变,所以只要在网页上显示一副较大的背景图片就可以了,如下图所示
在背景图上的一些位置预留了空白,用于显示相应的文本。
- 人物图像类
这里主要显示两类角色,“地主”与“农民”,随着游戏状态的变化,人物的形象也有可能发生变化。但基本的人物形象如下图所示:
与背景图片不同之处在于,人物图像需要显示在某个合适的位置,所以要有办法移动图片显示的位置。这一点可以通在将图片用DIV标签包裹起来,然后动态地设置DIV的位置来实现。
由于原生的javascript不直接支持class的定义,所以用function的方式包装了图片的显示功能
相应的代码如下:
ImageDiv.js
//图象DIV
function ImageDiv(imgFileName,tagValue)
{
this.Name="ImageDiv";
this.value=tagValue;
this.imgFileName=imgFileName;
this.x=0;
this.y=0;
this.z=0;
this.visib=false
this.Div=document.createElement("div");
this.Div.style.position='absolute';
this.Div.name=this.Name;
this.Div.style.width='74px';
this.Div.style.height='98px';
//this.Div.style.backgroundColor='#ffffff';
this.Div.style.visibility='hidden';
if(imgFileName!="")
this.Div.innerHTML="<img src='"+imgFileName+"'>";
document.body.appendChild(this.Div);
this.GetDiv=function()
{
return this.Div;
}
this.GetValue=function()
{
return this.value;
}
this.GetX=function()
{
return this.x;
}
this.GetY=function()
{
return this.y;
}
this.GetZ=function()
{
return this.z;
}
this.SetClass=function(Classf)
{
this.Div.style.width=null;
this.Div.style.height=null;
this.Div.className=Classf;
}
this.SetId=function(Id)
{
this.Div.id=Id;
}
this.SetW=function(value)
{
this.Div.style.width=value+'px';
}
this.SetH=function(value)
{
this.Div.style.height=value+'px';
}
this.SetX=function(value)
{
this.Div.style.left=value+'px';
this.x=value;
}
this.SetY=function(value)
{
this.Div.style.top=value+'px';
this.y=value;
Y=value;
}
this.SetZ=function(zValue)
{
this.Div.style.zIndex=zValue;
this.z=zValue;
}
this.SetXY=function(nx,ny)
{
this.Div.style.left=nx+'px';
this.x=nx;
this.Div.style.top=ny+'px';
this.y=ny;
}
this.SetV=function(bVisible)
{
if(bVisible)
{
this.Div.style.visibility='visible';
}
else
{
this.Div.style.visibility='hidden';
}
}
this.SetImg=function(imgurl)
{
this.Div.innerHTML="<img src='"+imgurl+"'>";
this.imgFileName=imgurl;
}
this.SetHtml=function(Html)
{
this.Div.innerHTML=Html;
}
this.remove=function()
{
document.body.removeChild(this.Div);
}
}
其中的imgFileName指明要显示的图片文件,而SetX,SetY方法设置了图片显示的位置,SetZ方法设置叠放次序。
3.扑克牌图像类
由于扑克牌图像有时需要显示一张,有时需要显示一组,所以用CardDiv.js与CardGroupDiv.js进行包装。而且扑克牌又分为水平显示与重直显示两种情形,所以又分别用CardHorizonGrpDiv.js与CardVerticalGrpDiv.js进行包装。相应的源码可以将完整的项目下载后进行查看。
4.文字类
需要在指定的位置动态显示文本块,用TextDiv.js与TextGroupDiv.js进行包装。与显示图像类似的是要可以动态地设置文本块的位置。
游戏状态的刷新
为了能够及时地刷新游戏的状态,需求前端与后台服务器进行通信。由于浏览器与服务器间的通信过程是请求、应答式的。一旦服务上的状态发生了变化,该如何及时地通知前端呢?理论是有两种解决方案
1,由服务器推送消息给客户端
2,由客户端连续不断地向服务器进行查询,也就是轮询。
本项目采用轮询的方式,客户端每秒向服务器发起一次请求,检查是否有新的状态需要更新。与此同时,每秒发出一次请求,也做为心跳请求,如果服务器发现客户端长期没有心跳,就会判断客户端已经离线。
由存储过程实现后台业务逻辑
数据库表只能保存静态数据,如果要想实现后台的业务逻辑,必须安排在某处执行相关的代码。一般来说这种后台业务逻辑缓组成一个业务逻辑层。这层代码可以由jsp,serverlet,php,asp等等服务器脚本技术来实现。
本项目将业务逻辑交由数据库的存储过程来实现。为此设计了一个调用其它存储过程的入口存储过程。
CREATE PROCEDURE dbo.callProc
@procName varchar(100) ,
@inStr varchar(200) ,
@outStr varchar(4000) output
as
set nocount on
begin
declare @SQLString nvarchar(500)
declare @ParmDefinition nvarchar(500)
declare @outPara varchar(4000)
if rtrim(ltrim(@inStr)) <>''
begin
set @inStr = @inStr + ','
end
set @SQLString = N'exec '+ @procName + ' '+@inStr+ '@outPara out'
set @ParmDefinition = N'@outPara varchar(4000) OUTPUT'
EXECUTE sp_executesql @SQLString, @ParmDefinition, @outPara=@outStr output
return 0
end
这样一来,就可以极大地简化后台脚本代码。下面是调用这个入口存储过程的php代码
proc.php
<?php
// 测试用url http://localhost:8080/poker/proc.php?proc=addPlayer&inParas=t1,t1
require_once 'conf/db.config.php';
handleRequest();
function handleRequest(){
//接收网页参数
$procName=trim($_REQUEST["proc"]);
$inStr=trim($_REQUEST["inParas"]);
if ($procName==""){
echo "<script>alert('no procdure name or parameters');</script>";
return;
}
try{
$db = new PDO( "sqlsrv:Server=".DATABASE_SERVER.";Database=".DATABASE_NAME, DATABASE_USER, DATABASE_PWD);
}catch (Exception $e) {
$json = ['msg'=>$e->getMessage(),'error'=>-1];
echo json_encode($json);
return;
}
try{
$res = execStoreProc($db,$procName,$inStr);
}catch (Exception $e) {
$json = ['msg'=>$e->getMessage(),'error'=>-1];
echo json_encode($json);
return;
}
echo $res;
}
//执行存储过程,返回输出参数
function execStoreProc($db,$procName,$inStr)
{
$sql="exec callproc ?,?,?";
$stmt = $db->prepare($sql, array($db::ATTR_CURSOR=>$db::CURSOR_SCROLL));
// 绑定入参
$stmt->bindParam(1, $procName, $db::PARAM_STR);
$stmt->bindParam(2, $inStr, $db::PARAM_STR);
// 绑定出参
$result ='';
$stmt->bindParam(3, $result, $db::PARAM_STR | $db::PARAM_INPUT_OUTPUT, 4000);
$stmt->execute();
return $result;
}
为了在javascript代码中调用存储过程,再用js函数包装一下:
proc.js
//调用存储过程
function callProc(procName,inputStr,callBackFunc)
{
$.post
(
"proc.php","proc="+procName+"&inParas=" + inputStr ,callBackFunc
);
}
这样一来,在javascript中就通过调用函数callProc,实现了调用数据库的存储过程。