Mastering Qt 5 学习笔记-snake

在这里插入图片描述

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
#include <QtGui/QOpenGLContext>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
	//format用于渲染的 OpenGL 配置文件和版本、是否启用立体缓冲区以及交换行为。
    QSurfaceFormat format;
    if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL)
    {
        format.setVersion(3, 2);
        //设置所需的 OpenGL 上下文配置文件
        format.setProfile(QSurfaceFormat::CoreProfile);
    }
    //将最小深度缓冲区大小设置为 size。
    format.setDepthBufferSize(24);
    //将首选模板缓冲区大小设置为大小位
    format.setStencilBufferSize(8);

    QQuickView view;
    view.setFormat(format);
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    QObject::connect(view.engine(), &QQmlEngine::quit, &app, &QGuiApplication::quit);
    view.setSource(QUrl("qrc:/main.qml"));
    view.show();

    return app.exec();
}

main.qml

在这里插入图片描述

import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Scene3D 2.0
Item
{
    id: mainView
    property int score: 0
    readonly property alias window: mainView
    width: 1280; height: 768
    visible: true
    Keys.onEscapePressed:
    {
        Qt.quit()
    }
	//
    Rectangle
    {
        id: hud

        color: "#31363b"
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top : parent.top
        height: 60
		//上面的分数
        Label
        {
            id: snakeSizeText
            anchors.centerIn: parent
            font.pointSize: 25
            color: "white"
            text: "Score: " + score
        }
    }
	//游戏场景
    Scene3D
    {
        id:scene
        anchors.top: hud.bottom
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        focus: true
        aspects: "input"
        GameArea
        {
            id:gameArea
            initialSnakeSize: 5
        }
    }
	//状态
    OverlayItem
    {
        id: overlayItem
        anchors.fill: mainView
        visible: false

        //qml signal/slot connection
        Connections
        {
            target: gameArea
            onStateChanged:
            { //This item contains the state variable, so we can be notified when the state variable is updated using onStateChanged
                overlayItem.state = gameArea.state;
            }
        }
    }
}

OverlayItem.qml

import QtQuick 2.0
Item
{
    id: root
    states:
    [
        //两种状态
        State
        {
            name: "PLAY"
            PropertyChanges { target: root; visible: false }
        },
        State
        {
            name: "GAMEOVER"
            //PropertyChanges 用于定义状态中的属性值或绑定。 这使项目的属性值可以在状态之间更改时更改。
            PropertyChanges { target: root; visible: true }
            PropertyChanges { target: gameOver; visible: true }
        }
    ]
    GameOverItem
    {
        id: gameOver
    }
}

GameArea.qml

import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Extras 2.0
import Qt3D.Input 2.0
import QtQuick 2.9 as QQ2 //防止名字冲突
import "engine.js" as Engine

Entity
{
    id: root
    property alias gameRoot: root
    property alias timerInterval: timer.interval
    property int initialTimeInterval: 500
    property int initialSnakeSize: 5
    property string state: ""

	//创建摄像头
    Camera
    {
        id: camera
        property real x: 24.5
        property real y: 14.0
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        aspectRatio: 16/9
        nearPlane : 0.1
        farPlane : 1000.0
       	通过将相机放置在 Qt.vector3d(x, y, 33)处,位于屏幕外
        position: Qt.vector3d( x, y, 33.0 )
        upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
        viewCenter: Qt.vector3d( x, y, 0.0 )
    }

    Entity
    {
        id: sun
        components:
            [
            //定向光是一种行为类似于太阳光的光源。 来自定向光的光从同一方向以相同的强度照射所有对象,无论它们在场景中的哪个位置。
            DirectionalLight
            {
                color: Qt.rgba(0.8, 0.8, 0.8, 0.5)
                worldDirection: Qt.vector3d(-0.6, -0.5, -1)
            }
        ]
    }


    //渲染设置
    //RenderSettings 类型保存与渲染过程相关的设置并托管FrameGraph
    RenderSettings
    {
        id: frameFraph
        activeFrameGraph: ForwardRenderer
        {
            //黑色背景
            clearColor: Qt.rgba(0, 0, 0, 1)
            camera: camera
        }
    }
	//捕获键盘事件; 它负责将按键事件分派给活动的 KeyboardHandler
    KeyboardDevice
    {
        id:keyboardController
    }

    InputSettings { id: inputSettings }


    QQ2.Component.onCompleted:
    {
        console.log("Start game...");
        Engine.start();
        timer.start()
    }

    QQ2.Timer
    {
        id:timer
        interval: initialTimeInterval
        repeat: true
        onTriggered: Engine.update()
    }



    KeyboardHandler
    {
        //KeyboardHandler 组件附加到控制器,每次按下一个键时都会调用 onPressed() 函数
           id: input
           sourceDevice: keyboardController
           focus: true
           onPressed:  Engine.handleKeyEvent(event);
     }

    Background
    {
        position: Qt.vector3d(camera.x,camera.y,0)
        scale3D: Qt.vector3d(camera.x * 2,camera.y * 2,0)
    }
     components: [frameFraph, input]
}

Wall:这代表了蛇不能去的区域

SnakePart:这代表蛇身体的一部分

Apple:这代表在随机位置生成的苹果

Background:这个代表了蛇和苹果后面的背景

import Qt3D.Core 2.0
Entity
{
    property int type: 0
    property vector2d gridPosition: Qt.vector2d(0, 0)
}

wall

import Qt3D.Core 2.0
import "factory.js" as Factory
//墙壁没有任何视觉表现,它是不可见的
GameEntity
{
    id: root
    type: Factory.WALL_TYPE
    property alias position: transform.translation
    Transform
    {
        id: transform
    }
    components: [transform]
}

SnakePart.qml

如果添加到 GameArea 场景中,SnakePart 块将显示一个绿色立方体。 SnakePart 块不是完整的蛇,而是它身体的一部分。 请记住,蛇每吃一个苹果就会长大。 意味着将 SnakePart 的新实例添加到 SnakePart 中。

import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Extras 2.0
import "factory.js" as Factory

GameEntity
{
    id: root
    type: Factory.SNAKE_TYPE
    property alias position: transform.translation

    phong 照明效果基于 3 个照明组件环境、漫反射和镜面反射的组合。 这些组件的相对强度通过它们的反射系数来控制,这些系数被建模为 RGB 三元组:
环境光是物体在没有任何其他光源的情况下发出的颜色。
漫反射是为粗糙表面反射而发射的颜色。
镜面反射是光亮表面反射所发出的颜色。
表面的光泽度由浮动属性控制。
此材质使用具有单一渲染通道方法的效果并执行每个片段照明。
        
    PhongMaterial
    {
        id: material
        diffuse: "green"
    }
	//一个长方体网格
    CuboidMesh
    {
        id: mesh
    }

    Transform
    {
        id: transform
    }

    components: [material, mesh, transform]
}

Background.qml

import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Extras 2.0

Entity
{
    id: root

    property alias position: transform.translation
    property alias scale3D: transform.scale3D

    MaterialBackground
    {
        id: material
    }

    CuboidMesh
    {
        id: mesh
    }

    Transform
    {
        id: transform
    }

    components: [material, mesh, transform]
}


背景块将显示在蛇和苹果的后面。 乍一看,这个实体看起来很像 SnakePart。 但是,Material 不是 Qt3D 类。 它是一种依赖于着色器的自定义材质。 让我们看看 MaterialBackground.qml:

MaterialBackground.qml

import Qt3D.Core 2.0
import Qt3D.Render 2.0

Material
{
    id: material
    //保存材质使用的参数列表
    parameters:
    [
        Parameter
        {
            name: "score"
            value: score
        },

        Parameter
        {
            name: "resolution"
            value: Qt.vector2d(window.width, window.height)
        },

        Parameter
        {
            name: "diffuseTexture"
            value: Texture2D
            {
                id: diffuseTexture
                minificationFilter: Texture.LinearMipMapLinear
                magnificationFilter: Texture.Linear

                wrapMode
                {
                    x: WrapMode.Repeat
                    y: WrapMode.Repeat
                }
                generateMipMaps: true
                maximumAnisotropy: 16.0
                TextureImage
                {
                    source: "qrc:/images/grass.jpg"
                }
            }
        }
    ]
	//Qt3D 以一种非常方便的方式支持着色器。 只需将您的着色器文件添加到 .qrc 资源文件并将其加载到给定材质的效果属性中。
    effect: Effect
    {
        FilterKey
        {
            id:forward
            name: "renderingStyle"
            value: "forward"
        }
        techniques:
        [
            Technique
            {
                filterKeys: [forward]
                graphicsApiFilter
                {
                    api: GraphicsApiFilter.OpenGL
                    profile: GraphicsApiFilter.CoreProfile //fix
                    majorVersion: 3
                    minorVersion: 2
                }

                renderPasses: RenderPass
                {
                    shaderProgram: ShaderProgram
                    {
                        vertexShaderCode: loadSource("qrc:/shaders/gl3/grass.vert")
                        fragmentShaderCode: loadSource("qrc:/shaders/gl3/grass.frag")
                    }
                }
            },
            Technique
            {
                filterKeys: [forward]
                graphicsApiFilter
                {
                    api: GraphicsApiFilter.OpenGLES
                    profile: GraphicsApiFilter.CoreProfile //fix
                    majorVersion: 2
                    minorVersion: 0
                }

                renderPasses: RenderPass
                {
                    shaderProgram: ShaderProgram
                    {
                        vertexShaderCode: loadSource("qrc:/shaders/es2/grass.vert")
                        fragmentShaderCode: loadSource("qrc:/shaders/es2/grass.frag")
                    }
                }
            }
        ]
    }
}

像国际象棋一样,这个棋盘将由行和列组成。 但是在我们的蛇游戏中,每个方格可以是:

苹果
一条蛇
一堵墙
空的

以下board的示例

在这里插入图片描述

这是一个 10x8 的board; 在游戏区周围有墙(W)。 一个苹果 (A) 以 7x2 生成。 最后,我们有一条蛇(S)从 3x4 开始到 5x5 结束

board.js

function Board(columnCount, rowCount, blockSize)
{
    //行列数
    this.columnCount = columnCount;
    this.rowCount = rowCount;
    //方块大小
    this.blockSize = blockSize;
   
    this.maxIndex = columnCount * rowCount;
    this.data = new Array(this.maxIndex);
}

Board.prototype.init = function()
{
    for (var i = 0; i < this.data.length; i++)
    {
        this.data[i] = null;
    }
}

Board.prototype.index = function(column,row)
{
    return column + (row * this.columnCount);
}


Board.prototype.setData = function(data, column, row)
{
    this.data[this.index(column, row)] = data;
}

Board.prototype.at = function(column,row)
{
    return this.data[this.index(column,row)];
}

在这里插入图片描述

factory.js

var SNAKE_TYPE = 1;
var WALL_TYPE = 2;
var APPLE_TYPE = 3;
//首先,我们定义所有游戏实体类型。 在我们的例子中,我们有苹果、蛇和墙类型。 然后,我们从 QML 文件创建游戏项目组件。 工厂将使用这些组件来动态创建新的游戏实体。
var snakeComponent = Qt.createComponent("SnakePart.qml");
var wallComponent = Qt.createComponent("Wall.qml");
var appleComponent = Qt.createComponent("Apple.qml");
//我们现在可以添加构造函数和 removeAllEntities() 实用程序函数来删除所有实例化的实体:
function GameFactory()
{
    //这个工厂有三个成员变量:
    //描述的游戏board的引用
    this.board = null;
    //parentEntity 变量的引用即游戏区域
    this.parentEntity = null;
    //一个实体数组,保留对创建的项目的引用
    this.entities = [];
}

//调用者提供了实体类型和board坐标(列和行)
GameFactory.prototype.createGameEntity = function(type,column,row)
{
    var component;
    //一旦我们有了组件,我们就可以调用 component.createObject() 来创建这个组件的实例
    switch(type)
    {
    case SNAKE_TYPE:
        component = snakeComponent;
        break;

    case WALL_TYPE:
        component = wallComponent;
        break;

    case APPLE_TYPE:
        component = appleComponent;
        break;
    }
	//这个新组件的父级将是 this.parentEntity,在我们的例子中是 GameArea
    var gameEntity = component.createObject();
    gameEntity.setParent(this.parentEntity);
//    console.log("");
    //然后,我们可以更新板,更新实体位置,并将这个新实体添加到实体数组中
    this.board.setData(gameEntity, column, row);
    gameEntity.gridPosition = Qt.vector2d(column, row);
    gameEntity.position.x = column * this.board.blockSize;
    gameEntity.position.y = row * this.board.blockSize;

    this.entities.push(gameEntity);
    return gameEntity;
}

//我们现在可以添加构造函数和 removeAllEntities() 实用程序函数来删除所有实例化的实体:
GameFactory.prototype.removeAllEntities = function()
{
    //removeAllEntities() 函数将从它们的父项(即游戏区域)中删除项目并创建一个新的空实体数组。 这确保旧实体被垃圾收集器删除
    for(var i = 0; i < this.entities.length; i++)
    {
        this.entities[i].setParent(null);
    }
 }

engine.js

//第一行是从另一个 JavaScript 文件导入 JavaScript 文件的 Qt 方法
.import "factory.js" as Factory
.import "board.js" as Board
//然后,我们可以轻松实例化工厂变量和 50x29 board的变量
var COLUMN_COUNT = 50;
var ROW_COUNT = 29;
var BLOCK_SIZE = 1;

var factory = new Factory.GameFactory();
var board = new Board.Board(COLUMN_COUNT,ROW_COUNT,BLOCK_SIZE);

var snake = [];
var direction;

function start()
{
    //提供了启动引擎时所做的工作
    //初始化引擎
    initEngine();
	//创建蛇
    createSnake();
    //在游戏区域周围创建墙壁
    createWalls();
	//生成第一个苹果
    spawnApple();
    //将 GameArea 状态切换为 PLAY
    gameRoot.state = "PLAY";
    
}
//初始化引擎
function initEngine()
{
    //此函数初始化并重置所有变量
    //第一个任务是将 GameArea 计时器间隔设置为其初始值
    //蛇每吃一个苹果,这个间隔就会缩短
    //从而提高蛇的移动速度
    timer.interval = initialTimeInterval;
    score = 0;

    //然后我们初始化工厂,提供对 board 和 gameRoot 引用
    factory.board = board;
    //gameRoot 指的是 GameArea
    //该实体将是工厂实例化的所有项目的父级
    factory.parentEntity = gameRoot;
    factory.removeAllEntities();
	//我们从工厂中移除所有现有实体并调用板的 init() 函数来清除板
    board.init();
    //最后,我们为蛇设置了默认方向
    //向量 -1,0 表示蛇将开始向左移动
    direction = Qt.vector2d(-1, 0);
}

//创建初始蛇
function createSnake()
{
    snake = [];
    var initialPosition = Qt.vector2d(25,12);
    for(var i = 0; i < initialSnakeSize;i++)
    {
        snake.push(factory.createGameEntity(Factory.SNAKE_TYPE,
                                            initialPosition.x + i,
                                            initialPosition.y));
    }
}
//在游戏区域周围创建墙壁
function createWalls()
{
    for(var x = 0;x < board.columnCount;x++)
    {
        factory.createGameEntity(Factory.WALL_TYPE, x, 0);
        factory.createGameEntity(Factory.WALL_TYPE, x, board.rowCount - 1);
    }

    for(var y = 1; y<board.rowCount - 1;y++)
    {
        factory.createGameEntity(Factory.WALL_TYPE,0,y);
        factory.createGameEntity(Factory.WALL_TYPE, board.columnCount - 1, y);
    }
}

//生成第一个苹果
function spawnApple()
{
    var isFound = false;
    var position;
    while(!isFound)
    {     
        position = Qt.vector2d(Math.floor(Math.random() * board.columnCount),
                                             Math.floor(Math.random() * board.rowCount));
        if((board.at(position.x,position.y) === null))
        {
            isFound = true;
        }
    }
    factory.createGameEntity(Factory.APPLE_TYPE, position.x, position.y);

    if(timerInterval > 10)
    {
        timerInterval -= 2;
    }
}



function moveSnake(column,row)
{
    var last = snake.pop();
    board.setData(null,last.gridPosition.x, last.gridPosition.y);
    setPosition(last,column,row);
    snake.unshift(last);
}

function update()
{
    if(gameRoot.state  === "GAMEOVER")
    {
        return;
    }

    var headPosition = snake[0].gridPosition;
    var newPosition = headPosition.plus(direction);
    var itemOnNewPosition = board.at(newPosition.x,newPosition.y);

    if(itemOnNewPosition === null)
    {
        moveSnake(newPosition.x,newPosition.y);
        return;
    }

    switch(itemOnNewPosition.type)
    {
        case Factory.SNAKE_TYPE:
            gameRoot.state = "GAMEOVER";
            break;

         case Factory.WALL_TYPE:
             gameRoot.state = "GAMEOVER";
             break;

         case Factory.APPLE_TYPE:
             itemOnNewPosition.setParent(null);
             board.setData(null,newPosition.x,newPosition.y);
             snake.unshift(factory.createGameEntity(
                               Factory.SNAKE_TYPE,
                               newPosition.x,
                               newPosition.y));
            spawnApple();
             score++;
             break;

    }
}


function setPosition(item, column,row)
{
    board.setData(item,column,row);
    item.gridPosition = Qt.vector2d(column,row);
    item.position.x = column * board.blockSize;
    item.position.y = row * board.blockSize;
}


function handleKeyEvent(event)
{
    switch(event.key)
    {
    case Qt.Key_R:
        start();
        break;

    case Qt.Key_W:
        if(direction !== Qt.vector2d(0,-1))
        {
            direction = Qt.vector2d(0,1);
        }
        break;

      case Qt.Key_A:
          if(direction !== Qt.vector2d(-1,0))
          {
            direction = Qt.vector2d(1,0);
          }
          break;
       case Qt.Key_S:
           if(direction !== Qt.vector2d(0,1))
           {
                direction = Qt.vector2d(0,-1);
           }
           break;
          case Qt.Key_D:
              if(direction !== Qt.vector2d(1,0))
              {
                direction = Qt.vector2d(-1,0);
              }
              break;
    }
}


function debugLogBoard()
{
    console.log("");
    for (var row = board.rowCount -1 ; row >= 0; row--)
    {
        var line = "";
        for (var column = 0; column < board.columnCount; column++)
        {
            var item = board.at(column, row);
            if (item === null)
            {
                line += "."
            } else
            {
                line += item.type;
            }
        }
        console.log(line);
    }
}


我们现在将创建一个“游戏结束”HUD,在您输掉游戏时显示。 创建一个新文件 GameOverItem.qml:

GameOverItem.qml

import QtQuick 2.0
import QtQuick.Controls 2.4

Item
{
    
    id: root
    anchors.fill: parent
    onVisibleChanged:
    {
        scoreLabel.text = "Your score: " + score
    }
	//一个黑色矩形填充整个屏幕
    Rectangle
    {
        anchors.fill: parent
        color: "black"
        //不透明度值为 75%
        //因此,游戏区域仍将在游戏结束屏幕后面 25% 处可见
        opacity: 0.75
    }
	//显示文本“游戏结束”的 gameOverLabel 标签
    Label
    {
        id: gameOverLabel
        anchors.centerIn: parent
        color: "white"
        font.pointSize: 50
        text: "Game Over"
    }

    Label
    {
        id: scoreLabel
        width: parent.width
        anchors.top: gameOverLabel.bottom
        horizontalAlignment: "AlignHCenter"
        color: "white"
        font.pointSize: 20
    }

    Label
    {
        width: parent.width
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 50
        horizontalAlignment: "AlignHCenter"
        color: "white"
        font.pointSize: 30
        text:"Press R to restart the game"
    }
}

OverlayItem.qml

import QtQuick 2.0
//您可以看到 states 元素是一个 Item 属性
Item
{
    id: root
    //默认情况下, states 元素包含一个空字符串 state。 这里我们定义了两个名为 PLAY 和 GAMEOVER 的状态项。
    states:
    [
        //之后我们可以将属性值绑定到一个状态
        State
        {
            name: "PLAY"
            PropertyChanges { target: root; visible: false }
        },
        State
        {
            name: "GAMEOVER"
            //在我们的例子中,当状态为 GAMEOVER 时,我们将此 OverlayItem 及其 GameOverItem 的可见性设置为 true
            //否则,对于状态 PLAY,我们将其隐藏
            PropertyChanges { target: root; visible: true }
            PropertyChanges { target: gameOver; visible: true }
        }
    ]

    GameOverItem
    {
        id: gameOver
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值