循环

循环语句

对于人自身而言,长时间重复性的做同样一件事情,很容易疲劳并出错。但对于计算机而言,这却是它们的特长。我们已经学习过使用printf()函数向屏幕输出文本,假定现在要完成“重要的事情说三遍”这一壮举,我们可以这么做。

printf( "重要的事情说三遍!" );
printf( "重要的事情说三遍!" );
printf( "重要的事情说三遍!" );

然而,如果重要的事情要说三百遍呢?懒,催动了我们的进步,因此有了循环语句。

循环语句具有一个条件测试部分与循环体部分
循环体部分由一条或多条语句构成,当测试通过时执行循环体,否则结束循环。
循环体语句还拥有一个控制部分,用于促使测试条件到达不成立状态,以达到退出循环的目的。

while语句

while语句的语法如下:

while ( 表达式 )
{
    循环体语句;
}

表达式测试在循环执行之前进行,所以如果测试一开始为假,循环体就根本不会执行。

int i = 1;
while ( i <= 10 )
{
    printf( "%d\n", i );
    i += 1;
}

for语句

for语句是while语句的一种简写,因此更为常用,语法如下所示:

for( 表达式1; 表达式2; 表达式3; )
{
    循环语句;
}

// 对应的while语句
表达式1;
while ( 表达式2 )
{
    循环体语句;
    表达式3;
}

表达式1为初始化部分,它只在循环开始时执行一次。表达式2为条件部分,它在循环体每次执行前都要执行一次。表达式3称为控制部分,它在循环体每次执行完毕,在条件部分即将执行之前执行。

int i;
for ( i = 1; i <= 10; i += 1 )
   printf( "%d\n", i );

break/continue

在循环语句中使用break语句,可以永久终止循环。在执行完break语句之后,程序将会跳转到循环正常结束后应该执行的那条语句处。continue语句仅用于终止本次循环。下面的示例中,for循环在输出数值1/2/3/4后便终止了循环体的运行,而while循环则是除了5之外的数值都被输出。你可以将这个while语句改为for语句,就会发现使用for语句更简明。

int i;
for ( i = 1; i <= 10; i += 1 )
{
    if ( i == 5 )
        break;
    printf( "%d ", i );
}

i = 1;
while ( i <= 10 )
{
    if ( i == 5 )
    {
        i += 1; // 不能写在continue之后,否则会形成死循环
        continue;
    }

    printf( "%d ", i );
    i += 1;
}

无限循环

for语句中的3个三表达式都是可选的,这表示它们可以省略掉。如果省略掉条件部分(即表达式2),表示测试的值始终为真,这时程序将进入无限循环状态(也称为死循环)。这相当于while语句的表达式部分给定真值一样。

for ( ;; ) printf( "F");
while ( 1 ) printf( "W");

如果运行了上面的程序,它将会不停的输出语句,这时可以通过任务管理器强行结束。显然无限循环并不是我们设计的目的,然而这样的设计结构确是在实际开发中存在的。比如说游戏引擎,运行于一个无限循环中:捉捕用户输入、渲染场景、输出画面等。当然,无限循环之所以存在,主要得益于break的存在:永久退出循环。

for ( ;; )
{
    printf( "F" );
    count += 1;
    if ( count == 20 )
        break;
}

使用循环铺砖

在2D游戏地形实现中,通常是将地图切分许多称之为Tile的小块,然后根据地表信息使用对应的贴图对其进行装饰,比如哪里放置地面,哪里放置水域,又或者建筑等。对于现阶段的练习,出于简易性,我们将仅使用一张“地面碎石泥土”图片(Ground.png)来对地形进行填充。使用图片浏览器查看Group.png,可以知道它的大小是64x64,这代表地形中一个Tile的大小。

glimix.com

为了便于理解,我们先看一下最终效果图。需要注意的是,网格线与数字标号是后期加上去的。

glimix.com

地图水平方向我们使用10张贴图进行重复,宽度为:64x10=640(像素)
垂直方向没有完整标出,它是使用8张贴图完成,高度为:64x8=512(像素)
因此,我们创建的窗口大小为640x512。窗口的大小也就是地图的大小,这两个值现在被保存在width/height变量中。

int width = 640;    // 窗口宽度
int height = 512;   // 窗口高度

首先,我们计算地图可容纳Tile的行列数

int cols = width / 64;      // 地图宽度可容纳的列数
int rows = height / 64;     // 地图高度可容纳的行数

在当前地图范围下,cols=10,rows=8,这表明地图被分割为80个Tile,也暗示我们需要将ground.png贴花80次,才能铺满整个地面。按照本节的理念,重复的事情用循环,这里我们选择for语句。

for ( int i = 0; i < cols*rows; i++ )

接下来看绘图部分,glmxDrawImage函数需要一个确切的x/y坐标,因此我们需要根据当前被绘制Tile索引计算出正确的坐标来。由于已经知道了地图宽度可容器的列数cols,计算当前Tile索引对应的行列值就很简单了。

int row = i / cols;    // 当前tile所在的行
int col = i % cols;    // 当前tile所在的列

每一行可以放置cols个Tile,因此i/cols就得到了当前Tile所在的行索引。
i%cols由计算出了正确的列索引,有了这两个数据后,根据图片大小,就可以计算出当前Tile的坐标了。

int x = col * 64;
int y = row * 64;

至此,整个完整的流程如下

void draw()
{
    //=========================================================================
    // 第一部分:绘制地面
    //=========================================================================
    //
    // 使用循环绘制地图地面(Ground.png的大小是64x64)
    int cols = width / 64;      // 宽口宽度可容纳的列数
    int rows = height / 64;     // 宽口高度可容纳的行数

    // 总共放置cols*rows张Ground.png
    for ( int i = 0; i < cols * rows; i++ )
    {
        // 根据当前计数索引计算图像所处的行与列
        int row = i / cols;
        int col = i % cols;

        // 根据行列计算图像绘制坐标
        int x = col * 64;
        int y = row * 64;

        // 画出图像
        glmxDrawImage( pic1, x, y );
    }
}

更新飞机

单独一个地面也显得有些过于无趣,在上一个例子中,我们已经加入了可飞行的飞机,只是它的方向有那么点不正确,这次我们一并修正一下。首先我们加入一个bool变量,表明飞机是否需要“调头”。

bool flip = false;  // 飞机是否需要转向

glimix.com

我们的飞机默认行为是从屏幕右边飞向左边,这与图示中的方向一致,因此这个变量初始化为false,表明不需要转向。接下来,我们更新边界判断语句,当飞机到达左边时,让其转向。

if ( xpos <= 0 )
{
    // 如果已经飞出屏幕左边,则改变飞行方向让其向右边飞行。
    xspeed = +2;
    flip = true; // 让飞机转向
}
else if ( xpos >= width )
{
    // 如果已经飞出屏幕右边,则改变飞行方向让其向左边飞行。
    xspeed = -2;
    flip = false; // 不转向(与原图像方向一致)
}

不要以为这样就完事了,这里只是设置了转向变量。同图可知,真正的转向需要变换图像的方向,把飞机水平反转一下就有“调头”的效果了,这是使用glmxDrawImageEx()函数实现的。

glmxDrawImageEx( pic2, xpos, 80, 38, 34, GLMX_FLIP_HORIZONTAL );

前三个参数与glmxDrawImage()函数一致,38,34是指飞机图像的宽度与宽度,最后一个是绘制标志值,它是一个枚举类型,暂时可以认同这个类型与int一致,只是预定义了一组值供你使用罢了。这里的传递的是GLMX_FLIP_HORIZONTAL,如其名:水平翻转。

下面是这个示例的完整代码。为了让我们有更好的理解,在飞机正常飞行时,我们仍旧使glmxDrawImage函数,如果你理解了程序,可以统一使用glmxDrawImageEx函数改写,以消除这种使用不一致性,让程序代码更漂亮。

#include "glimix.h"

int width = 640;    // 窗口宽度
int height = 512;   // 窗口高度

int pic1 = -1;      // 保存第一张图像的id(初始设置为-1,表示没有加载成功)
int pic2 = -1;      // 保存第二张图像的id

int xpos = 640;     // 飞机的起始位置(在屏幕右端)
int xspeed = -2;    // 飞机的速度(-2表示向左飞行,+2表示向右飞行)
bool flip = false;  // 飞机是否需要转向

void draw()
{
    //=========================================================================
    // 第一部分:绘制地面
    //=========================================================================
    //
    // 使用循环绘制地图地面(Ground.png的大小是64x64)
    int cols = width / 64;      // 宽口宽度可容纳的列数
    int rows = height / 64;     // 宽口高度可容纳的行数

    // 总共放置cols*rows张Ground.png
    for ( int i = 0; i < cols * rows; i++ )
    {
        // 根据当前计数索引计算图像所处的行与列
        int row = i / cols;
        int col = i % cols;

        // 根据行列计算图像绘制坐标
        int x = col * 64;
        int y = row * 64;

        // 画出图像
        glmxDrawImage( pic1, x, y );
    }

    //=========================================================================
    // 第二部分:绘制飞机
    //=========================================================================
    //
    // 更新飞机位置
    xpos += xspeed;

    if ( xpos <= 0 )
    {
        // 如果已经飞出屏幕左边,则改变飞行方向让其向右边飞行。
        xspeed = +2;
        flip = true; // 让飞机转向
    }
    else if ( xpos >= width )
    {
        // 如果已经飞出屏幕右边,则改变飞行方向让其向左边飞行。
        xspeed = -2;
        flip = false; // 不转向(与原图像方向一致)
    }

    if ( flip )
    {
        // 使用扩展的Draw方法让飞机转向
        // 飞机图像的的大小是38x34
        glmxDrawImageEx( pic2, xpos, 80, 38, 34, GLMX_FLIP_HORIZONTAL );
    }
    else
    {
        // 保持原图像方向
        glmxDrawImage( pic2, xpos, 80 );
    }
}

int main()
{
    // 创建一个大小为widthxheight,标题为"GLimix C tutorials"的窗口
    glmxInit( "GLimix C tutorials", width, height );

    // 让GLimix_C库能够使用你的draw函数
    glmxDrawFunc( draw );

    // 加载图像文件
    pic1 = glmxAddImage( "ground.png" );
    pic2 = glmxAddImage( "enemy1.png" );

    // 等待直到点击窗口的关闭按钮
    glmxMainLoop();

    return 0;
}

glimix.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值