乒乓球小游戏2-绘制球拍

上一期我们成功运行了一个Piston示例,打开了一个窗口并在窗口上画了个红色的方框,为什么画的是一个红色的方框呢?我们能不能画个绿色的长方形呢?

stremlit教程_学编程


接下来我们就研究下代码:

打开main.rs文件

stremlit教程_学编程_02


在文件的第一行使用的extern关键子,声明需要使用外部crate piston_window;我们先前介绍过crate是rust组织代码共享代码的一种形式,类似Java中的jar包。extern关键字我们先前也有提到,说它在2018版本中几乎可以不用了。我们可以把这一行代码注释掉,再运行看下效果。

没有报错吧,这主要得益于最新的rust 2018版本中对use关键字的功能增强。我们看第二行代码就是使用use关键字把piston_window引入到当前作用域中。rust会先在本项目也就是我们的game_pingpang中查找piston_window如果找不到就会在Cargo.toml中配置的依赖中查找。这就不需要extern再提前声明需要第三方依赖了,这也是为什么我们需要在Cargo.toml文件中添加piston_window依赖的原因。

接下来是声明了一个main函数,创建了个PistonWindow类型的可变变量window.怎么创建的呢?是使用类型WindowSettings创建的,从类型名称就可以看出这个类型是用来设置窗口配置信息的。关联方法new的第一个参数Hello Piston是设置窗口的title。也就是显示在窗口头部的信息。我们把它改成Game Pingpang。第二个参数是一个数组用来设置窗口的大小,640表示窗口的宽度为640像素,480表示窗口的高度为480像素。接下来调用exit_on_esc方法设置按esc键时是否退出。我们待会可以试下按esc键,看程序是否退出。然后调用build方法创建一个窗口。窗口有可能创建失败,所以build方法的返回值为option类型,我们还需要调用unwrap方法获取窗口对象。

上一期我们介绍过如果要实现一个动画,需要不断的重新绘制图像。在piston中当绘制图像或者重新绘制图像时会激发render和update事件也就是渲染和更新事件。我们就可以使用while循环不段的获取window的事件,并在事件发生后调用window的draw 2d方法在窗口中绘制二维图像。这里是先调用clear函数将窗口整个清空。接下来调用rectangle函数绘制方框。rectange有四个参数,我们这里先介绍前俩个。第一个参数是一个有四个值的数组,分别表示RGBA颜色值中的红色、绿色、蓝色和透明度值,这里红色值为1.0也就是100%红色,据说多看绿色对眼睛好一些,我们可以把绿色值设为1.0把红色值和蓝色值设为0.0,这样就把颜色改为了绿色。

第二个参数也是一个有四个值的数组,其中前俩个值代表绘制的方框在窗口的位置,这里的0.0和0.0也就代表方框的左上角在窗口坐标系的0.0,0.0的位置也就是窗口的左上角。后面的俩个值分别代表方框的宽度和高度,这里的宽度和高度都是100,所以这是一个边长为100像素的正方形,我们需要一个长方形做球拍,可以把高度改为20像素。这样我们就绘制了一个宽为100像素高为20像素的绿色长方形球拍。我们运行看下效果,没有报错,球拍已经绘制出来了,窗口的的title已经改成了Game pingpang,按ESC建,可以退出当前程序。虽然现在代码可以正常执行,但是代码都写在main方法中,违反了单一职责原则也不方便维护吧,接下来我们给main方法瘦下身,我们新建个绘制方法,把绘制方框的功能放到这个函数中,绘制时需要用到context和graphics,我们的绘制方法需要包含这俩个参数,在闭包中声明参数是不需要声明参数类型的,但在函数中是需要声明参数类型的,怎么知道context,graphics的类型呢?当然我们可以通过查看api文档得知,这里跟大家介绍个小技巧,可以通过声明俩个变量ctx和gph并声明它们的类型为空也就是一个空的圆括号,并给它们分别赋值为context和graphics。编写好后,运行下代码,这样编译器的提示信息就告诉了我们context和graphics的类型。

我们可以看到context的类型是piston_window模块下的Context类型,而graphics的类型是back_end模块下的GfxGraphics类型,从模块名称看,GfxGraphics是个具体的实现类,应该还有上层抽象类型,我们可以先尝试使用Graphics试下,修改gph的类型为Graphics引用类型。ctx为Context类型。再运行看下效果。从错误信息里可以看出的确是存在Graphics的,但是它是个特征,我们先前使用特征声明类型时是不是添加过impl关键字,我们在Graphics前添加impl关键字再试下。错误信息提示我们impl后跟特征这种写法只能用在函数声明或者方法的返回值类型声明中,我们也刚好要把graphics做为函数draw的参数,给draw函数添加俩个参数,ctx和gph。然后把绘制相关的代码都复制到draw函数中,在main方法中只需要调用draw函数进行绘制,这下main方法就清爽多了吧。

我们运行看下有没有报错,这次没有报错了。

接下来我们还可以把绘制球拍相关的代码抽取出来,球拍有位置大小等信息,所以我们可以定义一个球拍类型,封装球拍具备的相关信息,这样如果我们需要修改球拍的样式只需要修改球拍类型就好。接下来我们打开src/lib.rs文件,在lib.rs文件中声明tianlangstudio模块,在tianlangstuido模块中定义struct类型Racket也就是球拍,球拍有位置坐标信息x,y,有大小信息宽度和高度。为了方便实例化Racket对象我们再给Racket添加一个new关联方法,当调用new关联方法时创建一个坐标在窗口底部水平居中的球拍,我们知道窗口的宽度是640,球拍的宽度是100,所以球拍x坐标的值应该为270。窗口的高度是480,球拍的高度是20,所以球拍的y坐标值应该为460,这样球拍刚好贴紧窗口底部。设置球拍的宽度为100像素,高度为20像素。接下来再给Racket类型添加一个draw方法,将绘制球拍的功能放到球拍的绘制方法中。我们需要在外部使用Racket类型和相关方法,所以要给他们添加pub关键字。在main.rs文件中,我们就可以引入tianlangstuido模块,使用Racket的关联函数new创建一个球拍,并调用球拍对象的draw方法绘制球拍。这样函数draw是不是就清爽多了。可读性也好多了。接下来我们运行看下效果。运行效果跟我们预期一直,球拍紧贴窗口底部并水平居中。现在球拍还是静态的,接下来我们实现通过按左右方向键移动球拍的功能。