从零开始的贪吃蛇游戏



  • 打算从零开始开发贪吃蛇游戏,写个帖子,记录一下制作过程

    先准备一个空项目,当然是熟悉的qt on android框架

    0_1524188175385_QQ图片20180420093243.jpg

    刚开始什么都没有,先做一个虚拟摇杆

    0_1524191936095_QQ截图20180420103825.png

    审美不行,只能P成这样,经过测试,显示的角度如图,为啥会是这样结果,暂时不明,不过不影响使用

    关于摇杆控制贪吃蛇转向问题,我的设想是以蛇头为自身坐标系,当向上时候,实际以蛇头的方向向前

    0_1524202991870_QQ截图20180420134300.png

    自身坐标系已经把我绕晕了,改用全局坐标系,虚拟摇杆和qml界面,四象限对应关系

    0_1524205926415_QQ截图20180420143148.png

    if(angle<0)
            {
                rotationHead.angle = Math.abs(angle) + 90
            }
            else if(angle>=90 && angle <= 180)
            {
                rotationHead.angle = 180-angle+270
            }
            else
            {
                //0-90
                rotationHead.angle = 90-angle
            }
    

    通过timer控制蛇移动

    Timer{
            id: timerGame
            interval: 30
            repeat: true
            running: bStartGame
            onTriggered: {
                snake.move(step)
            }
        }
    

    move函数里面一点点移动贪吃蛇,先移动蛇头,根据旋转角度和step计算新的坐标,后面的身体和尾巴使用前一个点的坐标

    贪吃蛇跑出屏幕很不好,我又不想这样算死亡,需要计算屏幕宽度,重置坐标,希望蛇跑出屏幕,从另一头再出来

    修改

    从虚拟遥感到蛇头移动,其实不用坐标系转换,计算角度时候我使用的是Math.atan2,这里的参数 是(y, x),我原来用的(x, y),修正后,不用转换坐标系,蛇头就是正常的😭

    var radian = Math.atan2(snakerCY - centerY, snakerCX - centerX )
    

    传统贪吃蛇都是一格格移动,后一个移动到前一个位置,我想实现很流畅的移动,改用下面这个方案

    • Timer设定30ms更新,每次更新蛇头移动10px,身体部分靠角度计算实现移动

    现实是残酷的,结果是身体各种乱飞,然后我放弃了这种方案,改为传统一格格移动

    • Timer设定500ms更新,每次更新蛇头移动100px(一个身位),身体部分设置为前一个位置,实现移动
    • 为了去掉一格格闪现效果(卡顿效果),设定移动动画,实现假流畅效果

    游戏截图,

    0_1524574165718_Screenshot_20180424-203539.jpg

    新的设计影响到贪吃蛇从屏幕循环,所以去掉屏幕循环,为了防止蛇跑出屏幕,追加乒乓球效果,就是碰到屏幕反弹

    0_1524633827546_QQ截图20180425132334.png

    我很笨拙的把四象限挨个判断了一遍,效果还是不错的

    function checkScreen(x, y){
            var w = Screen.width
            var h = Screen.height
    
            var point = Qt.point(x,y)
    
    
            if(x<=0 && (rotationHead.angle>=90 && rotationHead.angle<=180)){
                rotationHead.angle = 180-rotationHead.angle
            }
            else if(x<=0 && (rotationHead.angle>=-180 && rotationHead.angle<=-90)){
                rotationHead.angle = -180-rotationHead.angle
            }
            else if(x>=w && (rotationHead.angle>=-90 && rotationHead.angle<=0)){
                rotationHead.angle = -180-rotationHead.angle
            }
            else if(x>=w && (rotationHead.angle>=0 && rotationHead.angle<=90)){
                rotationHead.angle = 180-rotationHead.angle
            }
    
            if(y<=0 && (rotationHead.angle>=-180 && rotationHead.angle<=-90) ){
                rotationHead.angle = -rotationHead.angle
            }
            else if(y<=0 && (rotationHead.angle>=-90 && rotationHead.angle<=0) ){
                rotationHead.angle = -rotationHead.angle
            }
            else if(y>=h && (rotationHead.angle>=0 && rotationHead.angle<=90) ){
                rotationHead.angle = -rotationHead.angle
            }
            else if(y>=h && (rotationHead.angle>=90 && rotationHead.angle<=180) ){
                rotationHead.angle = -rotationHead.angle
            }
    
    
            return point
        }
    

    随着分数的提升,加快蛇的爬行速度

    onScoreChanged: {
    
            //每10分提升一个level的速度(-10)
            //level最大20,对应分数200
            var level = Math.floor(score/10)
    
            if(level==0){
            }
            else{
                if(speed - 10*level <= 100){
                    speed = 100
                }
                else{
                    speed = 300-10*level
                }
            }
        }
    

    增加一个加速按钮,可以手动加速爬行

    0_1524640903680_Screenshot_20180425-152046.jpg

    有了两个按钮,出现了新的问题,因为用MouseArea实现的,这两个按钮不能同时按下去

    多点触控

    为了能同时旋转+加速,采用多点触控,MultiPointTouchArea组件是qml提供的现成的多点触控组件

    touchPoints: [
                TouchPoint{
                    id: point1
                },
                TouchPoint{
                    id: point2
                }
            ]
    

    添加了两个触控点,需要考虑两个点分别占用摇杆和加速键的情况

        property int touchYaoGan: 0     //0=没有,1=触控点1占用,2=触控点2占用
        property int touchSpeed: 0    //0=没有,1=触控点1占用,2=触控点2占用
    

    把两个操作的逻辑在MultiPointTouchArea上又实现了一遍,完成后的体验真酸爽

    消减

    如果一直不死,游戏缺乏难度,因此追加了,如果碰到边界,会消减一段身体,当然头是不能消得。
    这样就变成了,永远不死,但是分数不会无限增加,想加个分数排行榜

    菜单

    在游戏界面按下手机返回键,显示菜单,菜单界面我很努力的去P了,支持恢复,重新开始,排行榜,退出

    0_1524654206568_Screenshot_20180425-190156.jpg

    排行榜界面

    0_1524654313544_Screenshot_20180425-190213.jpg

    下次加个开始界面

    修复

    首先修复一个bug,快速轻点加速键,会暂停贪吃蛇,所以为加速键追加了一个长压判断

    //长压判断
        Timer{
            id: timerLongPress
            interval: 300
            onTriggered: {
                //加速键被长压
                fire.scale = 0.9
                isFired = true
                longPressed = false
            }
        }
    

    开始画面

    0_1524719315192_startBg.png

    提示

    追加操作提示按钮

    0_1524721293282_QQ图片20180426134044.jpg

    保存数据

    使用QSettings 将最大分数进行本地化保存

    void MyCommon::saveScore(double score)
    {
        QSettings settings(QDir::currentPath()+"/data.ini", QSettings::IniFormat);
        settings.setValue("config/score", score);
    }
    
    double MyCommon::getScore()
    {
        QSettings settings(QDir::currentPath()+"/data.ini", QSettings::IniFormat);
        double score = 0.0;
        score = settings.value("config/score").toDouble();
    
        return score;
    }
    

    音效

    这个吃东西的音效,特别脆

    SoundEffect {
            id: playSound
            source: "qrc:/music/music/eat1.wav"
        }
    

    完成

    程序下载:下载地址

    源代码下载:下载地址



  • 赞赞赞

    关注一波



  • 可以尝试一下使用Qt 5.10.1来开发软件哦。这是一个很不错的点子。



  • @青山白云 虚拟遥感?想要出好一点的效果,的确需要数学计算能力较好呢。
    话说我最近制作了一个这样的小例子:
    0_1524212567922_2018-04-20-162123_642x516_scrot.png



  • @jiangcaiyang qt3d?



  • @青山白云 是自己建立在Qt OpenGL基础上制作的小例子程序。


  • 网站研运

    二度加持~~,赶紧下载一波。



  • 试玩了一下,很不错哦


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群