九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

简单图形编程的学习(2)--- 文章讲解如何用Qt的QPainter画点,并通过代码示例实现了随机点、雪花和星空等动画效果。 点 (Qt实现)

简单图形编程的学习( 2)-–点 (Qt实现)

一、画点

在Qt中画点的函数是QPainter的drawPoint函数,还是放在QPainter体现了Qt决心将所有的绘图指令放在一个对象中。(除了OpenGL)既然如此,使用方法上和drawText也就差不太多了。

开篇来个最简单的示例吧,画点世界的HelloWorld,随机的点。

这个工程的全部文件都贴出来,也作为Qt中实现动画的一种示例:

Main.cpp:

#include <QtGui/QApplication>
#include <QTest>
#include <QTime>
#include "pointwidget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    PointWidget w;
    w.show();
    QTime timer;
    // 这是能随机绘点的关键,没有设置此属性,默认相当于每次Qt都会完整的将上一次的屏幕擦除,
    // 新版的Qt中已经没有了repaint(bool)接口了。
    w.setAttribute(Qt::WA_OpaquePaintEvent);
    while(true)
    {
        timer.start();
        // 调用此函数即相当于Windows中的GetMessage,系列函数,包括了tranlate,分发函数等的所有操作
        a.processEvents();
        w.repaint();

        // 此处是控制帧数的关键点
        while(timer.elapsed() < 33)
        {
            QTest::qSleep(1);
        }
    }
}

Pointwidget.h

#ifndef POINTWIDGET_H
#define POINTWIDGET_H

#include <QtGui/QWidget>
#include <QPainter>
#include <QObject>
namespace Ui
{
    class PointWidget;
}

class PointWidget : public QWidget
{
    Q_OBJECT

public:
    PointWidget(QWidget *parent = 0);
    ~PointWidget();

protected:
    void paintEvent(QPaintEvent *event);
    //void keyPressEvent(QKeyEvent *event);
    //void closeEvent(QCloseEvent *event);
private:
    Ui::PointWidget *ui;
};

#endif // POINTWIDGET_H

Pointwidget.cpp

#include "pointwidget.h"
#include "ui_pointwidget.h"

PointWidget::PointWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::PointWidget)
{
    ui->setupUi(this);
    qsrand(time(NULL));
}

PointWidget::~PointWidget()
{
    delete ui;
}

void PointWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

    for(int i = 0; i < 1000; ++i)
    {
        int r = qrand() % 255;
        int g = qrand() % 255;
        int b = qrand() % 255;
        QPen pen(qRgb(r,g,b));
        painter.setPen(pen);

        int x = qrand() % 800;
        int y = qrand() % 800;

        // 其实核心内容就是调用这一个函数而已
        painter.drawPoint(x,y);
    }
}

同样还是比较简单,当然,得首先熟悉Qt的基本机制,其中控制循环的方式特别重要,虽然这里可以使用与Win32 Timer类似的定时器技术来实现这样简单的绘制,但是对于帧数的稳定控制还是这样的代码比较可靠,另外,w.setAttribute(Qt::WA_OpaquePaintEvent);一句够初学者找够久的了。。。(我就找了很久)

知道这些以后,剩下的也就是一个qrand函数+ drawPoint函数的理解量了。这里不放截图了,这么简单的东西放个截图我都觉得没有意思。

1.老电视机雪花点的效果:

Main.cpp中用

PointWidget w;
QPalette palette;
palette.setColor(QPalette::Window, QColor(0,0,0));
w.setPalette(palette);

几行代码改变Widget的背景,这里说明一下,其实这样改变背景个人感觉属于Qt中面向对象过头的一个问题,事实上远远复杂于一个简单的SetBkColor函数,但是Qt将所有与界面颜色相关的东西封在QPalette中,以后同时改变多个属性的时候会稍微简单一点(其实也没有简单到哪去,多次调用SetTextColor,SetBkColor等语句也不见的复杂到哪去)

老电视机雪花效果中每次都需要擦除重绘避免点的叠加所以一下语句注释掉

// w.setAttribute(Qt::WA_OpaquePaintEvent);

paintEvent实现:

void PointWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

    for(int i = 0; i < 1000; ++i)
    {
        // 老电视机都是白色雪花点
        QPen pen(qRgb(255,255,255));
        painter.setPen(pen);

        int x = qrand() % 800;
        int y = qrand() % 800;

        painter.drawPoint(x,y);
    }
}

完整代码就不贴了。

2.移动的星空:

主要实现代码:

PointWidget::PointWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::PointWidget)
{
    ui->setupUi(this);
    qsrand(time(NULL));

    // 初始化星空中的点
    for(int i = 0; i < POINT_NUMBER; ++i)
    {
        marPoint[i].rx() = qrand() % 800;
        marPoint[i].ry() = qrand() % 800;
    }
}

PointWidget::~PointWidget()
{
    delete ui;
}

void PointWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

    QPen pen(qRgb(255,255,255));
    painter.setPen(pen);
    for(int i = 0; i < POINT_NUMBER; ++i)
    {
        marPoint[i].rx()++;
        if(marPoint[i].x() > 800)
        {
            marPoint[i].rx() = 0;
        }

        painter.drawPoint(marPoint[i]);
    }
}

思路还是与以前的一样,无非是西安初始化一些点,然后改变其x坐标,但是要说明的是,Qt为我们简化了很多操作,首先,默认情况下,会擦出每一帧,这样就不用我们手动通过覆盖上一个点的方式去完成,另外,默认使用了双缓冲方式显示图片(与上个特性其实是统一的。。。。)完全不闪。。。。

二、小结

一个个简单的点就能够构成的效果比本文展示的要多的多,参考以前的文章,因为笔者对Qt的熟悉程度有限,重新实现程序费时费力,并且也不太适应目前所用的QtCreator工具(主要是vi功能弱的吐血,Eclipse的vi模拟就够差的了,QtCreator虽然原生就带,但是几乎还不如不用,还是VS中的ViEmu强大),程序的其他效果及截图也请参考此系列相关其他文章了:

简单图形编程的学习(2)-–点 (Windows GDI实现)

简单图形编程的学习(2)-–点 (small basic实现)

阅读全文....

简单图形编程的学习(1)--- 本文为Qt图形编程入门,通过文字绘制实例,讲解了基础显示、字体设置、旋转及动画实现,展示了Qt绘图接口的简洁高效。 文字 (Qt实现)

**
**

简单图形编程的学习(1)-–文字 (Qt实现)

一、   全部简单图形编程的学习说在前面的话

此系列文章均假设读者已经具备一定的对应的程序编写知识,无论是最简单的small basic,还是因为常用而被人熟知的Windows GDI,或者是Linux下用的更多的Qt(一般我用PyQt),甚至是现在国内知道的人并不多的Android,我都不准备讲太多基础的语法,或者与平台相关的太多背景知识,这些靠读者先行学习,我仅仅准备在自己学习的过程中找点乐子:)看看我用一些简单的接口都能想出干什么事情,然后展示给大家看看,图形程序实在是最好展示的一类程序了,不像其他程序一样,哪怕我讲了一堆的boost,真正见识到boost强大的又有几个呢?-_-!要知道,今天起,所有程序都是窗口程序,不再是命令行!!!!人类用了多久才走到这一步我不知道。。。。我用了25年…….(从我出生算起)或者1年(从工作开始)

另外,想要看怎么编写窗口应用程序的就不要走错地方了,这里不是想怎么描述怎么使用一个又一个的控件,这里都是讲绘图的-_-!

二、   谈谈Qt

由于今天是第一篇,所以谈谈Qt,Qt原来是奇趣(trolltech)公司的产品,目前奇趣已经被诺基亚收购。很久前学习Linux的时候就知道Qt了(可谓久仰大名,如雷贯耳),最重要的是,Qt以其良好的面向对象特性,可移植性著称,这也是我特别喜欢的特性,所以当年在学习Python并选择一个GUI的时候,综合考虑,最终没有选择PyGTK,wxPython等一样优秀的开源产品,(见以前的一篇文章《pyqt学习 的开始,顺便小谈目前gui的选择…》而是PyQt。Qt的原始版本是C++,(但是目前官方已经支持JAVA)而PyQt其实不是官方支持的产品,学到后来,因为PyQt没有好用的IDE(所以一直用Gvim代替着,Eric怎么看都不习惯,一堆按钮没有章法),而且没有合适的教程参考学习(大部分都是qt3时代的东西),最重要的是个人还是比较喜欢看纸质书籍,而国内很难看到PyQt书籍的出版了。。。。。。。。所以,其实PyQt在学习了一段时间其实慢慢的慢下来了,再加上那时候预知了OPhone将横空出世,将较大的经历转移到了JAVA,Android的学习中去了。。。。可怜的Python啊。。。。呵呵,连一款完整的GUI都还没有学会-_-!Qt的网址目前是http://qt.nokia.com/。目前个人计划是考虑先学好Qt的根本,C++的Qt吧,将来想做界面的时候用PyQt应该也快,毕竟接口还是一样的。因为在家,没有好用的Eclipse C++环境(在家用Windows,Windows下C++还是较为习惯VS,而装着Ubuntu + Eclipse + g++ + qt eclipse插件的笔记本在公司),暂时用qt creator来代替吧,虽然个人觉得qt creator还是太过于功能简单的IDE。。。。

三、   Qt的文字显示

Qt原始的设计是用于手工编码产生GUI的,所以简单的程序编码也比较简单,(不像Win32和MFC,再简单的程序也是一大堆废物代码),一个简单的文字显示示例如下:(用qt creator的一个很大问题是没有语法高亮,所以我不得不将代码先拷贝到VS中然后再拷贝过来以达到语法高亮的效果)

// main.cpp(程序主程序)

#include <QtGui/QApplication>

#include "fontwidget.h"

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   FontWidget w;
   w.show();
   return a.exec();
}

Fontwidget.h:

#ifndef FONTWIDGET_H
#define FONTWIDGET_H

#include <QtGui/QWidget>

namespace Ui
{
   class FontWidget;
}

class FontWidget : public QWidget
{
   Q_OBJECT

public:
   FontWidget(QWidget *parent = 0);
   ~FontWidget();

protected:
   void FontWidget::paintEvent(QPaintEvent *event);

private:
   Ui::FontWidget *ui;
};

#endif // FONTWIDGET_H

FontWidget.cpp:

#include "fontwidget.h"
#include "ui_fontwidget.h"
#include <QPainter>
#include <QObject>

FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
   ui->setupUi(this);
}

FontWidget::~FontWidget()
{
   delete ui;
}

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   QString text = tr("Hello World");
   painter.drawText(10,10, text);
}

说是简单,其实为了展示一个较为完成的自定义Widget的Qt程序,还是牵扯了较多的额外代码,其实真正有意义的仅仅只有

QPainter painter(this);
QString text = tr("Hello World");
painter.drawText(10,10, text);

3句而已,这里,说明一下paintEvent,在Qt中Event与signal特别重要,可以理解为两套不同的消息机制,Event主要用于控件的底层,不直接受你控制,而signal的使用范围不限,并且你可以无限的扩展。Event这种不直接受控制的特性也是非常有用的,因为给出了一套较为容易理解的程序执行流程和规范,基本上,这里Event和Win32(MFC)中的消息较为类似,paintEvent类似Win32中的WM_DRAW消息的响应(在MFC中就是OnDraw),使用方法上与MFC也较为类似,即通过继承来改变。

需要特别注意的是,QPainter的drawText和Windows的DrawText设计思想上不太一样,这里的Y坐标指的是字符的Baseline位置(基线)。

这点我刚开始不太习惯,(因为很简单的原因),而QPainter的用rect为参数的drawText版本仍然与一般的drawText很类似,比如下面这样:

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   QString text = tr("Hello World");
   painter.drawText(QRect(0,0, 100, 20), text);
}

那么,此处的rect的左上角就代表了文字的左上角,所以个人感觉这里有点比较奇怪。更奇怪的是,不仅仅是这一个借口,drawText的接口只要是用x,y直接来表示位置的时候就是指文字的baseline,用QRect的时候就是top。。。。。。。。。。

总而言之,基本的文字显示方法在Qt中就很明显了,QPainter对象的drawText,需要注意y坐标的意思。。。。。。。。。。

四、   字体

其实字体是个更加复杂的问题。。。。有多少人知道在字体的现实问题上MS,Apple,Adobe的研究人员投入了多少精力啊。。。。今天的TrueType可不是一开始就存在的。。。。去《简单图形编程的学习(1)-–文字 (Windows GDI实现)》看看window版本中的字体结构有多少参数吧。。。。。。。要是人工去记就像记圆周率一样,相对来说,因为Qt引入了QFont这样较为面向对象的方式,简单了很多.

Qt中对字体也给与了重要支持(其实对于很多人来说PC就是一个文字处理的机器,所以字体相当重要,想想MS最赚钱的软件吧。。Office),

仅仅关于字体就有很多类:(链接直接指向nokia的class reference)

QFontQFontComboBox, QFontDatabase, QFontDialog, QFontEngineInfo, QFontEnginePlugin, QFontInfo

这里,我仅仅想改变QPainter的drawText的字体,所以仅仅使用QFont。

另外,QPen可以用于改变drawText的颜色,而这些都是QPainter的属性,通过setXXX来设置。

示例如下:

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   QFont font("Times",50);
   QString text = tr("Hello World");
   painter.setPen(Qt::blue);
   painter.setFont(font);
   painter.drawText(QRect(0,0, 1000, 100), text);
}

效果如插图1.这里是第一次展示Qt的截图,顺便说明一下,Qt在Window下并不是使用Windows的原生控件,而是通过Qt控件模拟而成。不要怀疑Qt有原生的控件啊,这也是我学习Qt的原因之一,Qt可是有它的原生平台KDE的(也是Qt的杀手级应用),因为学习Qt我甚至将以前用了好些年的Gnome经验和体验都放弃了,开始适应并改用KDE,ubuntu的KDE版本还有特定的名字Kubuntu。。。。。。。。没有办法使用compiz还让我郁闷了好一阵子,因此还查看了一些KDE,Gnome的相关资料,KDE与Gnome之间的竞争估计也算是IT世界著名而漫长的战争之一了吧(类似的例子还有vim-emacs)。可怜的KDE盛极一时,因为Qt而慢慢被众多公司所抛弃,但是竟然因为Qt是某家公司的产品(虽然开源)而被冷落。。。可见开源世界的人们对自由的极高标准。。。。

五、   旋转的字体

按照来自Charles Petzold的创意,见《简单图形编程的学习(1)-–文字 (Windows GDI实现)》中的例子,自然我也想要在Qt中实现旋转的字体,并且,这一次不会再有抄袭Charles Petzold的嫌疑了lol,呵呵,虽然还是抄袭了创意,对比Windows中的例子,你会很惊讶的发现Qt的实现实在太简单了,简单的可怕。

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   QFont font("Times",50);
   QString text = tr("   Rotation");
   painter.setFont(font);
   int x = (width() / 2);
   int y = (height() / 2);
   painter.setViewport(x, y, width(), height());

   for(int i = 0; i < 12; ++i)
   {
      painter.rotate(30);
      painter.drawText(0, 0, text);
   }
}

用QPainter::setViewport调整后Viewport后,painter有现成的函数rotate使用,看看实现效果:)见插图2。

还是老样子,让它旋转起来,这里我还是用两种方式,一种是定时器,一种是手动接管事件循环的方式(Windows的实现是在消息循环外接管消息循环)。

1.      用定时器的版本:

Fontwidget.h:

#ifndef FONTWIDGET_H
#define FONTWIDGET_H

#include <QtGui/QWidget>
#include <QPainter>
#include <QObject>

namespace Ui
{
   class FontWidget;
}

class FontWidget : public QWidget
{
   Q_OBJECT

public:
   FontWidget(QWidget *parent = 0);
   ~FontWidget();

protected:
   void paintEvent(QPaintEvent *event);
   void showEvent(QShowEvent* event);
   void timerEvent(QTimerEvent *event);

private:
   Ui::FontWidget *ui;
   int myTimerID;
   unsigned int miOritation;
   QFont *pFont;
   QString *pText;
};

#endif // FONTWIDGET_H

Fontwidget.cpp:

#include "fontwidget.h"
#include "ui_fontwidget.h"

FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
   ui->setupUi(this);
   miOritation = 0;
   pFont = new QFont("Times",50);
   pText = new QString(tr("   Rotation"));
}

FontWidget::~FontWidget()
{
   delete ui;
}

void FontWidget::showEvent(QShowEvent* event)
{
   myTimerID = startTimer(33);
}

void FontWidget::timerEvent(QTimerEvent *event)
{
   if(event->timerId() == myTimerID)
   {
      miOritation += 1;
      repaint();
   }
}

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   int x = (width() / 2);
   int y = (height() / 2);
   painter.setViewport(x, y, width(), height());

   painter.setFont(*pFont);

   for(int i = 0; i < 12; ++i)
   {
      painter.rotate(30+miOritation);
      painter.drawText(0, 0, *pText);
   }
}

按照正常的思路就是这样做了,但是会发现严重的问题(虽然效果也很有意思),那就是一个循环内多次的rotate后的文字竟然速度不一样,实现了奇怪的后面文字追前面文字的效果。。。。。。。。如插图3所示。呵呵,还好有宝典在手。。。。。《C++ GUI Qt4编程》第二版第八章中有类似的描述:”for循环中的代码有一个小缺陷,如果执行了更多的迭代,这一问题会变得很明显。每次调用rotate(),就高效地用一个旋转矩阵去乘当前的世界变换,从而创建一个新的世界变换。浮点数的舍入误差不断的累积,得到了越来越不准确的世界变换。”就是这个原因,才有了我们这样的效果。我们这里比原书中的例子rotate了更多次,因为这里是动画(每秒30帧左右),原书是静态画面。原书的解决方案是使用QPainter的save(),restore()函数为每次迭代保存和加载原始的矩阵。

正确的做法是:

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   int x = (width() / 2);
   int y = (height() / 2);
   painter.setViewport(x, y, width(), height());

   painter.setFont(*pFont);

   for(int i = 0; i < 12; ++i)
   {
      int liOritation = 30 * i \+ miOritation;
      painter.save();
      painter.rotate(liOritation);
      painter.drawText(0, 0, *pText);
      painter.restore();
   }
}

这样的效果就正常了,而且正常的惊人,因为我们没有为图形的显示进行任何优化以防止闪烁,(《简单图形编程的学习(1)-–文字 (Windows GDI实现)》中类似的例子闪烁的就非常明显)在《C++ GUI Qt4编程》中说明了Qt4竟然会自动为我们对图形的现实进行双缓冲处理-_-!呵呵,甚至不用我们通过编程手动指定就能实现这样的效果,这也算是Qt框架设计之人性化的又一个体现吧,我很享受这样的人性化^^。

2.      手动接管事件循环的版本:

这个版本就类似于《简单图形编程的学习(1)-–文字 (Windows GDI实现)》中使用PeekMessage的版本了,以后要想用Qt做游戏并达到很好的动画效果估计也就靠这种方式来实现了。

Main:

#include <QtGui/QApplication>
#include "fontwidget.h"
#include <QtTest/QTest>
#include <QTime>

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   FontWidget w;
   w.show();
   QTime timer;
   while(true)
   {
      timer.start();
      a.processEvents();
      w.addOritation();
      w.repaint();

      while(timer.elapsed() < 33)
      {
        QTest::qSleep(1);
      }
   }
}

fontwidget.h

#ifndef FONTWIDGET_H
#define FONTWIDGET_H

#include <QtGui/QWidget>
#include <QPainter>
#include <QObject>

namespace Ui
{
   class FontWidget;
}

class FontWidget : public QWidget
{
   Q_OBJECT

public:
   FontWidget(QWidget *parent = 0);
   ~FontWidget();

   void addOritation() { miOritation++; }

protected:
   void paintEvent(QPaintEvent *event);
   void keyPressEvent(QKeyEvent *event);
   void closeEvent(QCloseEvent *event);
private:
   Ui::FontWidget *ui;
   int myTimerID;
   unsigned int miOritation;
   QFont *pFont;
   QString *pText;
};

#endif // FONTWIDGET_H

fontwidget.cpp:

#include "fontwidget.h"
#include "ui_fontwidget.h"
#include <QtGui>

FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
   ui->setupUi(this);
   miOritation = 0;
   pFont = new QFont("Times",50);
   pText = new QString(tr("   Rotation"));

}

FontWidget::~FontWidget()
{
   delete ui;
}

void FontWidget::keyPressEvent(QKeyEvent *event)
{
   // 响应Esc键以退出程序
   if(event->key() == Qt::Key_Escape)
   {
      exit(0);
   }
}

void FontWidget::closeEvent(QCloseEvent *event)
{
   exit(0);
}

void FontWidget::paintEvent(QPaintEvent *event)
{
   QPainter painter(this);
   int x = (width() / 2);
   int y = (height() / 2);
   painter.setViewport(x, y, width(), height());

   painter.setFont(*pFont);

   for(int i = 0; i < 12; ++i)
   {
      int liOritation = 30 * i \+ miOritation;
      painter.save();
      painter.rotate(liOritation);
      painter.drawText(0, 0, *pText);
      painter.restore();
   }
}

效果非常好:)相对来说会比定时的版本动画平稳很多,并且,Qt还是让这样的动画没有任何闪烁,学习过的GUI不算不可计数,但是真正让人感觉很愉快的是在仅仅发现Qt一种,控制能力还是很强大,灵活,并且很为程序员着想,不愧是以前专门卖框架的公司开发的。。。。

六、   参考:

1. 《C++ GUI Qt4编程》第二版(原版名《C++ GUI Programming with Qt4,Second Edition》,Jasmin Blanchette,Mark Summerfield著,电子工业出版社

插图

插图1:

插图2:

插图3:

阅读全文....

Qt Creator的库依赖问题

Qt Creator中途添加Qt库依赖很困难。创建时未选的模块,不仅包含路径不同,也无法在IDE中添加库依赖,导致链接失败,最终需手动修改Makefile才能解决。

阅读全文....

简单图形编程的学习(2)--- 本文讲解Windows GDI的SetPixel函数,展示了仅用画点就能创造出星空、雪花等多种生动有趣的图形效果。 点 (Windows GDI实现)

**
**

简单图形编程的学习(2)-–点 (Windows GDI实现)

一、 又一顿牢骚

虽然知道不应该老是说些与技术无关的话。。。。但是有的时候又总是想说。。。。难怪有同事说我最近已经有点像唐僧了-_-!总而言之,因为相对来说书看的太快,(现在租的房子离公司太远,老是坐地铁,导致有了非常固定的看书时间),而因为工作一直太忙,一直加班回家太晚的原因,所以实际的实践太慢(基本上现在就是以写博客的形式),所以Qt与Android的部分要是同步跟上我DirectX的书都要看好几本了,这样的方式好像不太好,所以目前暂时还是以Windows的为主了。。。。这也体现了一点理想与现实的差距-_-!虽然初期项目目标过大,项目中即时调整起码还能保证项目完成吧。。。。。(扯的远了)。

二、 画点

很简单的一个Windows GDI函数,SetPixel,原型如下:

COLORREF SetPixel(
  HDC _ hdc_,           // handle to DC
  int _ X_,             // x-coordinate of pixel
  int _ Y_,             // y-coordinate of pixel
  COLORREF _ crColor_   // pixel color
);

也许属于最最简单的Windows GDI 之一了,但是如同在Small Basic画点中所讲解的一样,画点这样简单的函数可以实现无限的效果。还是那句话,能够设置一个像素点,就能够描绘一个世界。。。。。。唯一限制程序实现的就是编写程序人的思维。

因为Windows代码本身的复杂性,不能如Small Basic那样直击要害,要是每次给出完整代码太占用空间,这里给出《Tricks Of the Windows Game Programming GURUS》一书中的框架代码,方便以后代码的添加。

// T3D Game Console, creates a game console application

// INCLUDES ///////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN  // just say no to MFC

#include <tchar.h>
#include <windows.h>   // include important windows stuff
#include <windowsx.h>
#include <mmsystem.h>
#include <iostream> // include important C/C++ stuff
#include <conio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <io.h>
#include <fcntl.h>

// DEFINES ////////////////////////////////////////////////

// defines for windows 
#define WINDOW_CLASS_NAME _T("WINCLASS1")

// MACROS /////////////////////////////////////////////////

#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEYUP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// GLOBALS ////////////////////////////////////////////////
HWND      main_window_handle = NULL; // globally track main window
HINSTANCE hinstance_app      = NULL; // globally track hinstance

char buffer[80];                     // general printing buffer

#define FRAME_PER_SECOND (20)
#define TIME_IN_FRAME (1000/FRAME_PER_SECOND)
#define WIDTH 800
#define HEIGHT 600


// FUNCTIONS //////////////////////////////////////////////
LRESULT CALLBACK WindowProc(HWND hwnd, 
            UINT msg, 
            WPARAM wparam, 
            LPARAM lparam)
{
   // this is the main message handler of the system
   PAINTSTRUCT   ps;     // used in WM_PAINT
   HDC           hdc;  // handle to a device context
   char buffer[80] = {0};        // used to print strings

   // what is the message 
   switch(msg)
   { 
   case WM_CREATE: 
     {
       // do initialization stuff here
       // return success
       return(0);
     } break;

   case WM_PAINT: 
     {
       // simply validate the window 
       hdc = BeginPaint(hwnd,&ps);

       // end painting
       EndPaint(hwnd,&ps);

       // return success
       return(0);
     } break;

   case WM_DESTROY: 
     {

       // kill the application, this sends a WM_QUIT message 
       PostQuitMessage(0);

       // return success
       return(0);
     } break;

   default:break;

   } // end switch

   // process any messages that we didn't take care of 
   return (DefWindowProc(hwnd, msg, wparam, lparam));

} // end WinProc

///////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   {
      Sleep(1);
   }

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
   // this is called once after the initial window is created and
   // before the main event loop is entered, do all your initialization
   // here

   // return success or failure or your own return code here
   return(1);

} // end Game_Init

/////////////////////////////////////////////////////////////

int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
   // this is called after the game is exited and the main event
   // loop while is exited, do all you cleanup and shutdown here

   // return success or failure or your own return code here
   return(1);

} // end Game_Shutdown

// WINMAIN ////////////////////////////////////////////////
int WINAPI WinMain( HINSTANCE hinstance,
            HINSTANCE hprevinstance,
            LPSTR lpcmdline,
            int ncmdshow)
{

   WNDCLASSEX winclass; // this will hold the class we create
   HWND     hwnd; // generic window handle
   MSG        msg;    // generic message
   HDC        hdc;      // graphics device context

   // first fill in the window class stucture
   winclass.cbSize         = sizeof(WNDCLASSEX);
   winclass.style        = CS_DBLCLKS | CS_OWNDC | 
      CS_HREDRAW | CS_VREDRAW;
   winclass.lpfnWndProc  = WindowProc;
   winclass.cbClsExtra   = 0;
   winclass.cbWndExtra   = 0;
   winclass.hInstance    = hinstance;
   winclass.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
   winclass.hCursor    = LoadCursor(NULL, IDC_ARROW); 
   winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
   winclass.lpszMenuName = NULL;
   winclass.lpszClassName = WINDOW_CLASS_NAME;
   winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

   // save hinstance in global
   hinstance_app = hinstance;

   // register the window class
   if (!RegisterClassEx(&winclass))
      return(0);

   // create the window
   if (!(hwnd = CreateWindowEx(NULL,                  // extended style
      WINDOW_CLASS_NAME,     // class
      _T("Show Point 0.1"), // title
      WS_OVERLAPPEDWINDOW | WS_VISIBLE,
      0,0,    // initial x,y
      WIDTH,HEIGHT,  // initial width, height
      NULL,   // handle to parent 
      NULL,   // handle to menu
      hinstance,// instance of this application
      NULL))) // extra creation parms
      return(0);

   // save main window handle
   main_window_handle = hwnd;

   // initialize game here
   Game_Init();

   // enter main event loop
   while(TRUE)
   {
      // test if there is a message in queue, if so get it
      if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
      { 
        // test if this is a quit
        if (msg.message == WM_QUIT)
           break;

        // translate any accelerator keys
        TranslateMessage(&msg);

        // send the message to the window proc
        DispatchMessage(&msg);
      } // end if

      // main game processing goes here
      Game_Main();

   } // end while

   // closedown game here
   Game_Shutdown();

   // return to Windows like this
   return(msg.wParam);

} // end WinMain

///////////////////////////////////////////////////////////

1. 随机在屏幕上画随机颜色点

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;
   HDC hdc;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   hdc = GetDC(main_window_handle);

   // draw 1000 pixels
   for (int index=0; index < 1000; index++)
   {
      // get random position
      int x = rand()%WIDTH;
      int y = rand()%HEIGHT;

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
      SetPixel(hdc, x,y, color);

   } // end for index

   // release the dc
   ReleaseDC(main_window_handle, hdc);

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   {
      Sleep(1);
   }

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

同样还是简单,当然,前提是框架存在并且熟悉了一大堆Windows的特性以后,说实话,Windows的死消息机制也不是一天两天就熟悉的了的,我当年学MFC的时候那是一愣一愣的,用了较旧以后才能大概知道怎么回事儿。知道这些以后,剩下的也就是一个Rand函数+SetPixel函数的理解量了。这里不放截图了,这么简单的东西放个截图我都觉得没有意思。

2. 老电视机雪花点的效果:

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;
   HDC hdc;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   hdc = GetDC(main_window_handle);

   // draw 1000 pixels
   for (int index=0; index < 500; index++)
   {
      // get random position
      int x = rand()%WIDTH;
      int y = rand()%HEIGHT;

      COLORREF color = RGB(255,255,255);
      SetPixel(hdc, x,y, color);

   } // end for index

   // release the dc
   ReleaseDC(main_window_handle, hdc);

   Sleep(10);
   InvalidateRect(main_window_handle, NULL, TRUE);
   //while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   //{
   // Sleep(1);
   //}

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

这样基本能够达到效果,但是实际上还有点问题,为了统一程序的架构方便讲解才这样写代码,这里因为利用Windows原有消息去擦除原有点,放在WM_DRAW中去做画点效果会更好一点。

3. 闪烁的星空:

#define POSITION_COUNT (500)
int gaWidth[POSITION_COUNT];
int gaHeight[POSITION_COUNT];

///////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;
   HDC hdc;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   hdc = GetDC(main_window_handle);
   for (int index=0; index < POSITION_COUNT; index++)
   {
      SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));
      gaWidth[index] += 1;
      gaHeight[index] += 1;
   } // end for index

   Sleep(1000);

   Sleep(10);
   // release the dc
   ReleaseDC(main_window_handle, hdc);
   //while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   //{
   // Sleep(1);
   //}

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
   // this is called once after the initial window is created and
   // before the main event loop is entered, do all your initialization
   // here

   // 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置
   for (int index=0; index < POSITION_COUNT; index++)
   {
      // get random position
      gaWidth[index] = rand()%WIDTH;
      gaHeight[index] = rand()%HEIGHT;

   } // end for index

   // return success or failure or your own return code here
   return(1);

} // end Game_Init

按Small Basic实现的方式我发现闪烁效果没有Small Basic好,原因可能是C的Win32程序速度比Small Basic快太多了,以至于几乎看不到闪烁,于是,按上面这样将闪烁实际的用Sleep停留一下才能看到较好的效果。也算是不同语言实现同一个算法(大概这么称呼实现这个效果的方法吧)需要注意的不同之处。

4. 屏幕刮花效果

///////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;
   HDC hdc;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   hdc = GetDC(main_window_handle);
   for (int index=0; index < POSITION_COUNT; index++)
   {
      SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));

      // 这算是实现的一种,横向刮花
      gaWidth[index] += 1;

      // 这算是实现的另一种,斜向刮花
      //gaWidth[index] += 1;
      //gaHeight[index] += 1;

      // 这算是实现的又一种,纵向刮花,纵向刮花建议配合血红色颜色观看效果。。。。。。。
      //gaHeight[index] += 1;

   } // end for index

   // release the dc
   ReleaseDC(main_window_handle, hdc);
   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   {
      Sleep(1);
   }

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
   // this is called once after the initial window is created and
   // before the main event loop is entered, do all your initialization
   // here

   // 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置
   for (int index=0; index < POSITION_COUNT; index++)
   {
      // get random position
      gaWidth[index] = rand()%WIDTH;
      gaHeight[index] = rand()%HEIGHT;

   } // end for index

   // return success or failure or your own return code here
   return(1);

} // end Game_Init

一个屏幕刮花效果,不同的方向给人感受完全不同,最有意思的是纵向向下的红色效果,如插图1.让我想起某游戏死亡时的结束画面,满眼都是向下流动的血液。。。。。。。。

5. 移动的星空:

本来用我在Small Basic中的方法实现也可以出现移动的星空的,但是我发现《Tricks of the Windows Game Programming GURUS》一书中的例子更加有趣,这里引用一下了。

///////////////////////////////////////////////////////////

void Init_Stars(void)
{
// this function initializes all the stars

for (int index=0; index < NUM_STARS; index++)
    {
    // select random position
    stars[index].x = rand()%WINDOW_WIDTH;
    stars[index].y = rand()%WINDOW_HEIGHT;

    // set random velocity  
    stars[index].vel = 1 + rand()%16;

    // set intensity which is inversely prop to velocity for 3D effect
    // note, I am mixing equal amounts of RGB to make black -> bright white   
    int intensity = 15*(17 - stars[index].vel);
    stars[index].col = RGB(intensity, intensity, intensity); 

    } // end for index

} // end Init_Stars

////////////////////////////////////////////////////////////

void Erase_Stars(void)
{
// this function erases all the stars
for (int index=0; index < NUM_STARS; index++)
    SetPixel(global_dc, stars[index].x, stars[index].y, RGB(0,0,0));

} // end Erase_Stars

////////////////////////////////////////////////////////////

void Draw_Stars()
{
// this function draws all the stars
for (int index=0; index < NUM_STARS; index++)
    SetPixel(global_dc, stars[index].x, stars[index].y, stars[index].col);

} // end Draw_Stars

////////////////////////////////////////////////////////////

void Move_Stars(void)
{
// this function moves all the stars and wraps them around the 
// screen boundaries
for (int index=0; index < NUM_STARS; index++)
    {
    // move the star and test for edge
    stars[index].x+=stars[index].vel;

    if (stars[index].x >= WINDOW_WIDTH)
        stars[index].x -= WINDOW_WIDTH;
   
    } // end for index

} // end Move_Stars

////////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
// this is the main loop of the game, do all your processing
// here

// get the time
DWORD start_time = GetTickCount();

// erase the stars
Erase_Stars();

// move the stars
Move_Stars();

// draw the stars
Draw_Stars();

// lock to 30 fps
while((start_time \- GetTickCount() < 33));

// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
   SendMessage(main_window_handle,WM_CLOSE,0,0);

// return success or failure or your own return code here
return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here

// first get the dc to the window
global_dc = GetDC(main_window_handle);

// initialize the star field here
Init_Stars();

// return success or failure or your own return code here
return(1);

} // end Game_Init

/////////////////////////////////////////////////////////////

int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here

// release the global dc
ReleaseDC(main_window_handle, global_dc);

// return success or failure or your own return code here
return(1);

} // end Game_Shutdown

书中例子利用不同的亮度,及不同的移动速度来模拟一种类3D的效果。。。。。。。。。。。。估计不是一般的人能想到的,呵呵,要知道,仅仅利用了一个SetPixel函数。。。。。。。

Have Fun,aha?在有了一个大概的思路以后,其实Window下面的GDI编程也没有那么难吧,的确是没有那么难吧?毕竟我们专注的也就一个SetPixel函数而已 ,越专注反而能让我们有更多新的想法。

这里用Windows GDI来实现我从Small Basic学到的一招显示文字的华丽技巧,你以前要是从来没有看过类似例子(也不是个在图形编程领域混过很多年的骨灰级程序员)你别说看了没有感觉惊艳,要知道其实仅仅是利用了画点和文字输出两个如此平常而简单的特性。

6. 星空中的文字

///////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   for (int index=0; index < 1000; index++)
   {
      // get random position
      int x = rand()%WIDTH;
      int y = rand()%HEIGHT;

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
      SetPixel(ghDC, x,y, color);

   } // end for index

   TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));

   // release the dc
   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   {
      Sleep(1);
   }

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
   // this is called once after the initial window is created and
   // before the main event loop is entered, do all your initialization
   // here
   HFONT hfont;

   ghDC = GetDC(main_window_handle);

   hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));

   SelectObject(ghDC, hfont);

   SetTextColor(ghDC, RGB(0,0,0));
   SetBkColor(ghDC, RGB(0,0,0));

   DeleteObject(hfont);

   // return success or failure or your own return code here
   return(1);

} // end Game_Init

/////////////////////////////////////////////////////////////

int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
   // this is called after the game is exited and the main event
   // loop while is exited, do all you cleanup and shutdown here

   ReleaseDC(main_window_handle, ghDC);

   // return success or failure or your own return code here
   return(1);

} // end Game_Shutdown

按照以前的实现思想,我很快写出了上述程序,结果效果和我想象的不太一样,反而有点像个面具。。。。。留出了眼睛的观察窗口。。。-_-!见插图2。实际的原因想了一下才知道,背景前景都是黑的,当时的第一想法是不能让星空给盖了,但是这样不都黑了啊?。。。。呵呵,改成下面这个样子,效果就出来了,多的行用红色标明,看了就明白了,让背景透明,能够被星空给盖了,这样才能显示出文字。

///////////////////////////////////////////////////////////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
   DWORD dwStartTime;

   dwStartTime = GetTickCount();
   // this is the main loop of the game, do all your processing
   // here

   // for now test if user is hitting ESC and send WM_CLOSE
   if (KEYDOWN(VK_ESCAPE))
      SendMessage(main_window_handle,WM_CLOSE,0,0);

   for (int index=0; index < 300; index++)
   {
      // get random position
      int x = rand()%WIDTH;
      int y = rand()%HEIGHT;

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
      SetPixel(ghDC, x,y, color);

   } // end for index

   TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));

   // release the dc
   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
   {
      Sleep(1);
   }

   // return success or failure or your own return code here
   return(1);

} // end Game_Main

////////////////////////////////////////////////////////////

int Game_Init(void *parms = NULL, int num_parms = 0)
{
   // this is called once after the initial window is created and
   // before the main event loop is entered, do all your initialization
   // here
   HFONT hfont;

   ghDC = GetDC(main_window_handle);

   hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));

   SelectObject(ghDC, hfont);

   SetTextColor(ghDC, RGB(0,0,0));
   SetBkMode (ghDC, TRANSPARENT);

   DeleteObject(hfont);

   // return success or failure or your own return code here
   return(1);

} // end Game_Init

/////////////////////////////////////////////////////////////

int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
   // this is called after the game is exited and the main event
   // loop while is exited, do all you cleanup and shutdown here

   ReleaseDC(main_window_handle, ghDC);

   // return success or failure or your own return code here
   return(1);

} // end Game_Shutdown

我第一次看到这个例子的时候真的感叹作者是个天才-_-!也许是自己太笨了所以想不到用这样的方式去显示文字吧。这次来个系列效果,可以参考《简单图形编程的学习(2)-–点 (small basic实现)》文中的插图效果,或者简单的自己运行一下就好了。

三、 小结

一个个简单的点就能够构成如此繁多的效果,简直有点不可思议,但是其实,能够绘制一个点,就能够绘制整个世界,要知道,整个屏幕不过也就是一个一个像素构成的,呵呵。其实,从另外的角度来说,一连串连续的点就能构成一条直线,一排排直线就能构成一个面,有了点,线,面,还有什么不够构成的?你可以表达整个世界。另外,就我看的老的游戏编程书籍介绍,DirectDraw的原始接口也仅仅是能画点/位图而已,游戏的开发的先驱们还不是用这样简单的接口实现了那么多画面丰富,效果华丽的2D游戏啊?

插图

插图一:

插图2:

阅读全文....

简单图形编程的学习(2)--- 本文用Small Basic的SetPixel函数,展示了如何用简单的点创造出星空、雪花等惊艳的视觉效果。 点 (small basic实现)

简单图形编程的学习( 2)—点 (small basic实现)

一、 又一顿牢骚

虽然知道不应该老是说些与技术无关的话。。。。但是有的时候又总是想说。。。。难怪有同事说我最近已经有点像唐僧了-_-!总而言之,因为相对来说书看的太快,(现在租的房子离公司太远,老是坐地铁,导致有了非常固定的看书时间),而因为工作一直太忙,一直加班回家太晚的原因,所以实际的实践太慢(基本上现在就是以写博客的形式),所以Qt与Android的部分要是同步跟上我DirectX的书都要看好几本了,这样的方式好像不太好,所以目前暂时还是以Windows的为主了。。。。这也体现了一点理想与现实的差距-_-!虽然初期项目目标过大,项目中即时调整起码还能保证项目完成吧。。。。。(扯的远了)。

二、 画点

画点,在Small Basic中属于太基础的东西,当然,其实在Small Basic中什么都基础。。。呵呵

GraphicsWindow.SetPixel

函数用于画点,这里的点仅仅只有一个像素,所以叫SetPixel(设置像素),和Windows GDI的命名一致(其实Small Basic中的画图函数很多都与Windows GDI一致),参数的解释如下:

SetPixel Draws the pixel specified by the x and y co-ordinates using the specified color.

GraphicsWindow.SetPixel(x, y, color)

x The x co-ordinate of the pixel. y The y co-ordinate of the pixel. color The color of the pixel to set. Returns Nothing

1. 随机在屏幕上画随机颜色点(HHT897)

GraphicsWindow.BackgroundColor = "White"

GraphicsWindow.PenColor = "LightBlue"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height


While ("True")

  GraphicsWindow.SetPixel(Math.GetRandomNumber(gw), Math.GetRandomNumber(gh), GraphicsWindow.GetRandomColor())

EndWhile

So简单。。。不是吗?效果如插图1

但是,不要小看点的作用,点可以用于模拟星空。。。。。。。。。这里展示几个效果,真的觉得small basic用于演示什么叫简单的技术惊艳的效果非常合适。。。。。。在图形领域,感觉技术固然重要,但是思维强大也能利用简单的技术实现惊艳的效果。

2. 老电视机雪花点的效果:(PXB396)

GraphicsWindow.BackgroundColor = "DarkNight"

GraphicsWindow.PenColor = "LightBlue"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height


While ("True")
  For i = 1 To 1000
    GraphicsWindow.SetPixel(Math.GetRandomNumber(gw), Math.GetRandomNumber(gh), "White")
  EndFor

  Program.Delay(10)

  GraphicsWindow.Clear()

EndWhile

3. 闪烁的星空:(TPK996)

GraphicsWindow.BackgroundColor = "DarkNight"

GraphicsWindow.PenColor = "LightBlue"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height

' 以数组记录下随机出来的点,这样才能保证星空是在闪烁而不是移动
For i = 1 To 500
  width[i] = Math.GetRandomNumber(gw)
  height[i] = Math.GetRandomNumber(gh)
EndFor

While ("True")
  For i = 1 To 500
    GraphicsWindow.SetPixel(width[i], height[i], "White")
  EndFor
  Program.Delay(1000)
  GraphicsWindow.Clear()
EndWhile

4. 屏幕刮花效果(RMP025)

GraphicsWindow.BackgroundColor = "DarkNight"

GraphicsWindow.PenColor = "LightBlue"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height


For i = 1 To 500
  width[i] = Math.GetRandomNumber(gw)
  height[i] = Math.GetRandomNumber(gh)
EndFor

While ("True")
  For i = 1 To 500
    GraphicsWindow.SetPixel(width[i], height[i], "White")
    width[i] = width[i] + 1
  EndFor

  Program.Delay(10)
EndWhile

5. 移动的星空:(ZGB224)

GraphicsWindow.BackgroundColor = "DarkNight"

GraphicsWindow.PenColor = "LightBlue"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height


For i = 1 To 50
  width[i] = Math.GetRandomNumber(gw)
  height[i] = Math.GetRandomNumber(gh)
EndFor

While ("True")
  Program.Delay(1)
  For i = 1 To 50
    GraphicsWindow.SetPixel(width[i], height[i], "White")
    width[i] = width[i] + 1
    
    ' 保证星空不是直接消失了-_-!
    If(width[i] > gw) Then
      width[i] = 0
    EndIf
  EndFor

  GraphicsWindow.Clear()
EndWhile

Have Fun,aha?呵呵,的确是,很久没有这样爽的写程序了,有了思维,很简单的就能体现在Small Basic上,让人愉快。后面的字母都是可以直接在Small Basic中import的,现在Small Basic 0.6出来了,我用的都是Small Basic 0.6。另外,发现没有,不像在讲其他语言/程序的时候一样,对各个参数一通饱讲,10分钟还没有看到一个函数的参数,对于Small Basic的程序我感觉仅仅需要展示效果和源代码就好了,展示的直接就是编程的思想,而不是语言,因为语言本身如此的简单。完成上面所有的示例都没有花掉我一个小时。。。。。很难想象用GDI或者DX我要用多久。。。。。。。呵呵,虽然我用C/C++出身的(现在也靠这个吃饭),也稍微学习过一下汇编,但是我怎么感觉我对越简单的语言越有好感啊?LUA, Python, Bash ,JAVA都稍微学过一点,但是实在是没有如Small Basic这样让人愉快的语言了^^,也许最最重要的一点在于,现有的大部分语言(上面提及的都是),逻辑表达能力虽然很强,库很丰富,但是为了适应足够广阔的领域并达到工业强度,GUI编程方面都是复杂的让人吐血,MFC就不说了,TK号称简单,其实我感觉也好不到哪去,我没有尝试过用Bash没写GUI,Qt已经算是非常好的GUI库了,但是上百个类足够让你头晕目眩。Small Basic这样的语言虽然是玩具,也就因为其是玩具才敢这么简单。。。。。。。。。呵呵,欣赏它,起码作为一种简单的演示也不错。

上面的例子都是自己随便想的,下面看一个偷师来的例子,以前在讲small Basic的时候其实已经展示过了,但是因为这个例子给了我太多惊喜,我决定反复提起,告诉你们什么叫编程思维利用简单的技术,你别说看了没有感觉惊艳,要知道其实仅仅是利用了画点和文字输出两个如此平常而简单的特性。

6. 星空中的文字(HQG707)

GraphicsWindow.BackgroundColor = "midnight"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
GraphicsWindow.FontSize = 100
Turtle.Move (100)
Turtle.Turn (1*1)
While ("True")

  For i = 1 To 50
   GraphicsWindow.SetPixel(Math.GetRandomNumber(gw),Math.GetRandomNumber(gh),GraphicsWindow.GetRandomColor())
  EndFor

  Turtle.Move(1)
  GraphicsWindow.BrushColor = "Black"
  GraphicsWindow.DrawBoundText(30,110,gw-20,"Small Basic")

EndWhile

第一次看到这个例子的时候我真的感叹作者是个天才-_-!也许是自己太笨了所以想不到用这样的方式去显示文字吧。这次来个系列效果,如星空中的文字-插图1-4。怎么样?效果惊艳吧?呵呵,直接运行一下程序吧,将文字改成你想要的,你会有更好的感觉。

三、 小结

一个个简单的点就能够构成如此繁多的效果,简直有点不可思议,但是其实,能够绘制一个点,就能够绘制整个世界,要知道,整个屏幕不过也就是一个一个像素构成的,呵呵。其实,从另外的角度来说,一连串连续的点就能构成一条直线,一排排直线就能构成一个面,有了点,线,面,还有什么不够构成的?你可以表达整个世界。

插图1:

插图2:

星空中的文字-插图1:

星空中的文字-插图2:

星空中的文字-插图3:

星空中的文字-插图4:

阅读全文....