R语言游戏框架设计.docx
《R语言游戏框架设计.docx》由会员分享,可在线阅读,更多相关《R语言游戏框架设计.docx(14页珍藏版)》请在冰点文库上搜索。
R语言游戏框架设计
R语言游戏框架设计
目录
1.贪食蛇的面向对象改造
2.游戏框架定义
3.在框架中重新实现贪食蛇游戏
1.贪食蛇的面向对象改造
我们可以利用面向对象的方法论,对贪食蛇游戏进行抽象整理,并实现代码的面向对象的改造。
R语言支持3种面向对象的编程实现方式,我选择基于RC的面向对象编程,关于RC详细使用请参考文章:
R语言基于RC的面向对象编程。
由于我们之前的代码都是通过函数来封装的,所以代码重构还是比较简单的,只要把Snake对象的属性和方法定义清楚就行了。
1.1定义Snake类
定义Snake类,画出类图,包括属性和方法。
属性解释:
∙name:
游戏的名字
∙stage:
当前的游戏场景
∙e:
游戏中的变量,environment类型
∙m:
游戏地图矩阵
∙width:
矩阵的宽
∙height:
矩阵的高
方法解释:
∙initialize():
构建函数,用于RC类的初始化
∙init():
给stage1场景初始化游戏变量
∙fail():
失败查询
∙furit():
判断并生成水果坐标
∙head():
生成蛇头移动坐标。
∙body():
生成蛇尾移动坐标。
∙drawTable():
绘制游戏背景。
∙drawMatrix():
绘制游戏矩阵。
∙stage0():
创建开机场景,可视化输出。
∙stage1():
创建游戏场景,stage1()函数内部,封装了游戏场景运行时的函数,并进行调用。
∙stage2():
创建结束场景,可视化输出
∙keydown():
监听键盘事件。
∙run():
启动函数
1.2全局函数调用顺序图
接下来,根据UML规范画出顺序图,主要包括全局函数调用和stage1场景游戏环境调用。
全局函数调用关系。
1.通过run()函数启动游戏,进入stage0场景,注册键盘事件。
2.在stage0场景按任意键切换到stage1场景。
3.init()出始化stage1场景的游戏变量。
4.stage1()运行游戏
5.当游戏失败fail()或按q键
6.游戏进行stage2场景,显示游戏结束画面,
7.按空格键回到stage0重新开始,按q键退出程序。
1.3stage1场景游戏环境函数调用顺序图
stage1场景游戏环境函数调用关系。
1.游戏进入stage1场景,按上下左右(up,down,left,right)方向键操作蛇头的前进路线。
2.furit()函数检查,如果地图上水果被吃掉,生成一个新水果,记录到矩阵中。
3.head()函数,通过上下左边键的操作,进行蛇头的移动,记录到矩阵中。
4.fail()函数失败检查,no未失败继续,yes失败进行stage2场景。
5.body()函数,蛇身体移动,记录到矩阵中。
6.drawTable()函数,画出游戏背景画布。
7.drawMatrix()函数,画出游戏矩阵。
利用UML的方法,通过类图和顺序图的描述,我们就把贪食蛇的游戏程序进行了面向对象的设计改造。
不用着急去写代码,我们再想想如何进行游戏框架的提取。
2.游戏框架定义
要设计一个完整、易用、有良好扩展的游戏框架是比难的,但我们可以基于贪食蛇的游戏,一步一步来做抽象。
抽象过程就是把程序对象化,上面的我们已经做了;第二步再把公用的属性和方法提取封装,可以统一把公用的部分提取到一个Game的父类里面,让Snake类继承Game类,从而实现游戏框架定义。
我们画出Game类和Snake类的类图。
∙Game类公共属性,包括了所有的Snake类的属性,这是因为这些属性都是全局的,其他的游戏也会用到,而且每个游戏中的属性,可以在e中进行定义。
∙Game类公共方法,包括了游戏全局调用的方法,但不包括Snake游戏stage1场景中运行的方法。
在Game类的方法中,我们主要实现的都是开发的辅助功能。
∙Snake类方法,同样还有Game类的方法,这是用到方法的重写技术。
子类的方法,先调用父类的同名方法,然后再执行子类方法里的程序。
这样我们就简单地分离了游戏框架Game类和游戏实现Snake类,下面我们要做的就是把代码按照设计进行实现,看看我们的设计是否是合理的。
3.在框架中重新实现贪食蛇游戏
Game类的代码实现,用R语言的RC面向对象编程进行代码编写。
Game<-setRefClass('Game',
fields=list(
#系统变量
name="character",#名字
debug='logical', #调试状态
width='numeric', #矩阵宽
height='numeric',#矩阵高
#应用变量
stage='numeric', #场景
e='environment', #环境空间变量
m='matrix', #数据矩阵
isFail='logical' #游戏失败
),
methods=list(
#构造函数
initialize=function(name,width,height,debug){
name<<-"RGameFramework"
debug<<-FALSE
width<<-height<<-20 #矩阵宽高
},
#初始化变量
init=function(){
e<<-new.env() #环境空间
m<<-matrix(rep(0,width*height),nrow=width) #数据矩阵
isFail<<-FALSE
},
#开机画图
stage0=function(){
stage<<-0
init()
},
#结束画图
stage2=function(){
stage<<-2
},
#游戏中
stage1=function(default=FALSE){
stage<<-1
if(FALSE){ #默认游戏中界面
plot(0,0,xlim=c(0,1),ylim=c(0,1),type='n',xaxs="i",yaxs="i")
text(0.5,0.7,label="Playing",cex=5)
}
},
#矩阵工具
index=function(col){
return(which(m==col))
},
#失败操作
fail=function(msg){
print(paste("GameOver",msg))
isFail<<-TRUE
keydown('q')
return(NULL)
},
#键盘事件,控制场景切换
keydown=function(K){
if(stage==0){#开机画面
stage1()
return(NULL)
}
if(stage==2){#结束画面
if(K=="q")q()
elseif(K=='')stage0()
return(NULL)
}
},
#启动程序
run=function(){
par(mai=rep(0,4),oma=rep(0,4))
stage0()
getGraphicsEvent(prompt="SnakeGame",onKeybd=function(K){
if(debug)print(paste("keydown",K))
return(keydown(K))
})
}
)
)
Snake类的代码实现,继承Game类,并实现贪食蛇游戏的私有方法。
#引用game.r文件
source(file="game.r")
#Snake类,继承Game类
Snake<-setRefClass("Snake",contains="Game",
methods=list(
#构造函数
initialize=function(name,width,height,debug){
callSuper(name,width,height,debug)#调父类
name<<-"SnakeGame"
},
#初始化变量
init=function(){
callSuper() #调父类
e$step<<-1/width#步长
e$dir<<-e$lastd<<-'up'#移动方向
e$head<<-c(2,2)#初始蛇头坐标
e$lastx<<-e$lasty<<-2#蛇头上一个点坐标
e$tail<<-data.frame(x=c(),y=c())#初始蛇尾坐标
e$col_furit<<-2#水果颜色
e$col_head<<-4#蛇头颜色
e$col_tail<<-8#蛇尾颜色
e$col_path<<-0#路颜色
e$col_barrier<<-1#障碍颜色
},
#失败检查
lose=function(){
#head出边界
if(length(which(e$head<1))>0|length(which(e$head>width))>0){
fail("Outofledge.")
return(NULL)
}
#head碰到tail
if(m[e$head[1],e$head[2]]==e$col_tail){
fail("headhittail.")
return(NULL)
}
},
#随机的水果点
furit=function(){
if(length(index(e$col_furit))<=0){#不存在水果
idx<-sample(index(e$col_path),1)
fx<-ifelse(idx%%width==0,10,idx%%width)
fy<-ceiling(idx/height)
m[fx,fy]<<-e$col_furit
if(debug){
print(paste("furitidx",idx))
print(paste("furitaxis:
",fx,fy))
}
}
},
#snakehead
head=function(){
e$lastx<<-e$head[1]
e$lasty<<-e$head[2]
#方向操作
if(e$dir=='up')e$head[2]<<-e$head[2]+1
if(e$dir=='down')e$head[2]<<-e$head[2]-1
if(e$dir=='left')e$head[1]<<-e$head[1]-1
if(e$dir=='right')e$head[1]<<-e$head[1]+1
},
#snakebody
body=function(){
if(isFail)return(NULL)
m[e$lastx,e$lasty]<<-e$col_path
m[e$head[1],e$head[2]]<<-e$col_head#snake
if(length(index(e$col_furit))<=0){#不存在水果
e$tail<<-rbind(e$tail,data.frame(x=e$lastx,y=e$lasty))
}
if(nrow(e$tail)>0){#如果有尾巴
e$tail<<-rbind(e$tail,data.frame(x=e$lastx,y=e$lasty))
m[e$tail[1,]$x,e$tail[1,]$y]<<-e$col_path
e$tail<<-e$tail[-1,]
m[e$lastx,e$lasty]<<-e$col_tail
}
if(debug){
print(paste("snakeidx",index(e$col_head)))
print(paste("snakeaxis:
",e$head[1],e$head[2]))
}
},
#画布背景
drawTable=function(){
if(isFail)return(NULL)
plot(0,0,xlim=c(0,1),ylim=c(0,1),type='n',xaxs="i",yaxs="i")
if(debug){
#显示背景表格
abline(h=seq(0,1,e$step),col="gray60")#水平线
abline(v=seq(0,1,e$step),col="gray60")#垂直线
#显示矩阵
df<-data.frame(x=rep(seq(0,0.95,e$step),width),y=rep(seq(0,0.95,e$step),each=height),lab=seq(1,width*height))
text(df$x+e$step/2,df$y+e$step/2,label=df$lab)
}
},
#根据矩阵画数据
drawMatrix=function(){
if(isFail)return(NULL)
idx<-which(m>0)
px<-(ifelse(idx%%width==0,width,idx%%width)-1)/width+e$step/2
py<-(ceiling(idx/height)-1)/height+e$step/2
pxy<-data.frame(x=px,y=py,col=m[idx])
points(pxy$x,pxy$y,col=pxy$col,pch=15,cex=4.4)
},
#游戏场景
stage1=function(){
callSuper()
furit()
head()
lose()
body()
drawTable()
drawMatrix()
},
#开机画图
stage0=function(){
callSuper()
plot(0,0,xlim=c(0,1),ylim=c(0,1),type='n',xaxs="i",yaxs="i")
text(0.5,0.7,label=name,cex=5)
text(0.5,0.4,label="Anykeyboardtostart",cex=2,col=4)
text(0.5,0.3,label="Up,Down,Left,Rigthtocontroldirection",cex=2,col=2)
text(0.2,0.05,label="Author:
DanZhang",cex=1)
text(0.5,0.05,label="http:
//blog.fens.me",cex=1)
},
#结束画图
stage2=function(){
callSuper()
info<-paste("Congratulations!
Youhaveeat",nrow(e$tail),"fruits!
")
print(info)
plot(0,0,xlim=c(0,1),ylim=c(0,1),type='n',xaxs="i",yaxs="i")
text(0.5,0.7,label="GameOver",cex=5)
text(0.5,0.4,label="Spacetorestart,qtoquit.",cex=2,col=4)
text(0.5,0.3,label=info,cex=2,col=2)
text(0.2,0.05,label="Author:
DanZhang",cex=1)
text(0.5,0.05,label="http:
//blog.fens.me",cex=1)
},
#键盘事件,控制场景切换
keydown=function(K){
callSuper(K)
if(stage==1){#游戏中
if(K=="q")stage2()
else{
if(tolower(K)%in%c("up","down","left","right")){
e$lastd<<-e$dir
e$dir<<-tolower(K)
stage1()
}
}
return(NULL)
}
return(NULL)
}
)
)
snake<-function(){
game<-Snake$new()
game$initFields(debug=TRUE)
game$run()
}
snake()
最后,我们运行snake.r的程序,就完成了整个贪食蛇游戏。
游戏运行效果,可以查看上一篇文章,R语言游戏之旅贪食蛇入门 的游戏截图和动画。
对于贪食蛇游戏的开发,我们已经完成了,虽然界面还是比较土,而且没有时间维度的操作。
那么我们换一个角度思考,如果不太要求画面效果,而且不需要时间维度的游戏,是不是R语言会表现的更好呢?
当然,这类游戏也有很多,比如最近流行的2048。
那么接下来,就用我们的游戏框架,再来做一个2048的游戏吧,试试在100行之内实现。