功能实现

  1. 自定义按钮类
  2. 交互部分的优化
  3. 游戏场景的跳转
  4. 背景音乐管理

定义按钮类

既然我们做的是游戏,那么交互性一定是十分重要的,就拿界面上的按钮作比方,你不管按或者不按,它如果都是一个状态的话,那游戏体验的确会大打折扣,所以我们最起码要给按钮做三种不同的状态。

而SFML中又没有button类,所以我们只能自己写了。

我们先定义头文件,并分别在Button.cpp和Game.h中引用,来写方法和实现实例化。

#pragma once
#include <SFML/Graphics.hpp>
using namespace sf;
class Button :public Sprite {     //继承SFML的Sprite类
public:
    Texture tNormal;            //三种不同状态的纹理
    Texture tHover;
    Texture tClick;
    void checkMouse(Vector2i, Event);                    //检查鼠标状态
    void setTextures(Texture, Texture);                    //加载纹理,两种状态
    void setTextures(Texture, Texture, Texture);        //加载纹理,三种状态
};

然后写方法

#include "Button.h"
void Button::setTextures(Texture _tNormal, Texture _tClick) {
    tNormal = _tNormal;
    tClick = _tClick;
    setTexture(tNormal);        //默认加载普通纹理
}
void Button::setTextures(Texture _tNormal, Texture _tHover, Texture _tClick) {
    tNormal = _tNormal;
    tHover = _tHover;
    tClick = _tClick;
    setTexture(tNormal);        //默认加载普通纹理    
}
void Button::checkMouse(Vector2i mouse, Event event) {
    //判断鼠标是不是在按钮内,前提是放正的矩形,一般情况下都是这样,如果是奇形怪状的需要再写别的方法
    if ((mouse.x > getPosition().x && mouse.x < getPosition().x + getTexture()->getSize().x) &&
        (mouse.y > getPosition().y && mouse.y < getPosition().y + getTexture()->getSize().y)) {
        if (event.type == Event::EventType::MouseButtonPressed && event.mouseButton.button == Mouse::Left) {
            setTexture(tClick);        //加载点击状态的纹理
        } else {
            setTexture(tHover);        //加载悬浮状态的纹理
        }
    } else {
        setTexture(tNormal);        //加载正常状态的纹理
    }
}

然后Game.h中定义我们三种纹理和按钮

Texture tStartBtnNormal, tStartBtnHover, tStartBtnClick;        //加载纹理
Button startBtn;                                                //自定义button类

最后在Game.cpp中添加

loadMediaData()

if (!tStartBtnNormal.loadFromFile("data/button/start.png")) {
    cout << "找不到data/button/start.png" << endl;
}
if (!tStartBtnHover.loadFromFile("data/button/startHover.png")) {
    cout << "找不到data/button/startHover.png" << endl;
}
if (!tStartBtnClick.loadFromFile("data/button/startClick.png")) {
    cout << "找不到data/button/startClick.png" << endl;
}
//精灵绑定纹理
startBtn.setTextures(tStartBtnNormal, tStartBtnHover, tStartBtnClick);

drawStart()

void Game::drawStart() {        //初始场景
    window.clear();                            //清屏
    startBtn.setPosition(1200, 200);
    window.draw(startBtn);
    window.display();                        //展示屏幕
}

Input()

void Game::Input() {
    Event event;
    Vector2i mousePosition = Mouse::getPosition(window);
    while (window.pollEvent(event)) {                //接受事件
        if (event.type == Event::Closed) {
            window.close();                            //关闭键关闭窗口
            gameQuit = true;
        }
        if (event.type == sf::Event::EventType::KeyReleased && event.key.code == sf::Keyboard::Escape) {
            window.close();                            //按esc键关闭窗口
            gameQuit = true;
        }
        startBtn.checkMouse(mousePosition, event);
    }
}

下面是实际效果

完善按钮类

上面的按钮只是一个简单的雏形,和我预想的稍微有些出入

所以做一下优化

我们给button增加几个属性

enum BtnState {
    NORMAL, HOVER, CLICK, RELEASE
};
int btnState;    

写一个表示设定按钮状态的函数setState()

void Button::setState(int state) {
    btnState = state;
    switch (btnState) {
        case 0:
            setTexture(tNormal); break;
        case 1:
            setTexture(tHover); break;
        case 2:
            setTexture(tClick); break;
        case 3:
            setTexture(tNormal); break;
        default:
            break;
    }
}

修改一下checkMouse()函数

int Button::checkMouse(Vector2i mouse, Event event) {
    //判断鼠标是不是在按钮内,前提是放正的矩形,一般情况下都是这样,如果是奇形怪状的需要再写别的方法
    if ((mouse.x > getPosition().x && mouse.x < getPosition().x + getTexture()->getSize().x) &&
        (mouse.y > getPosition().y && mouse.y < getPosition().y + getTexture()->getSize().y)) {
        if (event.type == Event::EventType::MouseButtonPressed && event.mouseButton.button == Mouse::Left) {            //如果在范围里按下左键,一定是CLICK状态                                                                                //如果按下左建时
            setState(CLICK);
        } else if (event.type == Event::EventType::MouseButtonReleased && event.mouseButton.button == Mouse::Left) {    //如果范围内释放左键,
            if (btnState == CLICK) {                                                                                    //如果之前是CLICK状态,那么就是RELEASE状态,表示按下
                setState(RELEASE);                                                                                        //否则的话什么也不干
            }
        } else {                                                                                                        //如果鼠标移动的话,检测是不是按着的,不是就表示HOVER状态咯
            if (btnState != CLICK) {
                setState(HOVER);
            }
        }
    } else {                                                                                                            //鼠标在按钮范围外
        if (event.type == Event::EventType::MouseButtonReleased && event.mouseButton.button == Mouse::Left) {            //在范围外释放鼠标左键
            setState(NORMAL);                                                                                            //回归NORMAL状态
        } else if (btnState == HOVER) {                                                                                    //如果是HOVER,也就是也没有按下过,回归NORMAL状态
            setState(NORMAL);                                                                                            //其他就保持原样 比如按住不放的时候
        }
    }
    return btnState;                                                                                                    //最后返回按钮状态
}

这样差不多就能达到预期的效果了

然后我们其中一个按钮悬浮与按下的状态是相比原来高宽变大,所以为了保持按钮的位置看起来不那么奇怪,我们为其设置偏移量,然后再绘制

void Button::offset(double _x, double _y) {
    setPosition(getPosition().x + _x, getPosition().y + _y);
}

然后在Input()中调用

backToMenuBtn.offset(-5, -5);    //设定偏移量

看下效果

拆分Input()函数

之前我们只有一个场景,所以事件都写在一个Input里,现在我们多了一个场景,我们就需要startInput()以及fightInput()等等。

所以对Input部分作出优化,当场景不同时使用不同的Input

void Game::Input() {
    Event event;
    Vector2i mousePosition = Mouse::getPosition(window);
    while (window.pollEvent(event)) {                //接受事件
        if (event.type == Event::Closed) {
            window.close();                            //关闭键关闭窗口
            gameQuit = true;
        }
        if (event.type == sf::Event::EventType::KeyReleased && event.key.code == sf::Keyboard::Escape) {
            window.close();                            //按esc键关闭窗口
            gameQuit = true;
        }
        switch (gameSceneState) {
            case SCENE_START:
                startInput(mousePosition, event); break;
            case SCENE_FIGHT:
                fightInput(mousePosition, event); break;
            default:
                break;
        }
    }
}
void Game::startInput(Vector2i mousePosition, Event event) {
    
}
void Game::fightInput(Vector2i mousePosition, Event event) {
    
}

限制窗口大小

另外,在游玩过程中发现直接拉边框修改游戏窗口大小会导致按钮响应不了,把按钮的位置坐标改为百分比窗口大小也没用,推测是按钮绘制完后,窗口的大小改变会导致逻辑上的按钮的位置和画面上的按钮的位置不一样??

可以直接给定窗口大小,在绘制窗口时检测窗口大小是否符合规定的大小

void Game::Draw() {
    Vector2u size;
    size.x = windowWidth;
    size.y = windowHeight;
    window.setSize(size);
}

我们后面也可能涉及到游戏分辨率的修改,这样正好就可以派上用场了

场景的跳转

分别在两个Input中写对应事件即可

void Game::startInput(Vector2i mousePosition, Event event) {
    backToMenuBtn.setState(0);
    if (startBtn.checkMouse(mousePosition, event) == 3) {
        gameSceneState = SCENE_FIGHT;
        loadMusic();
    }
}
void Game::fightInput(Vector2i mousePosition, Event event) {
    startBtn.setState(0);
    switch (backToMenuBtn.btnState) {
        case 1:
        case 2:
            backToMenuBtn.offset(-5, -5);    //设定偏移量
        default:
            break;
    }
    if (backToMenuBtn.checkMouse(mousePosition, event) == 3) {
        gameSceneState = SCENE_START;
        loadMusic();
    }
}

背景音乐管理

我们目前有两个背景音乐,当切换场景时就播放对应场景的音乐

音乐由两种变量来控制:一是音乐开关,我们之后会制作音乐开关的按钮,二是场景的状态

所以我们这么写音频加载的函数

void Game::loadMusic() {
    gameStartMusic.setLoop(true);            //背景音乐循环
    fightMusic.setLoop(true);
    switch (gameSceneState) {
        case SCENE_START:
            fightMusic.stop();
            if (startMusicState) {            //音乐开关
                gameStartMusic.play();
            } else {
                gameStartMusic.stop();
            }break;
        case SCENE_FIGHT:
            gameStartMusic.stop();
            if (fightMusicState) {            //音乐开关
                fightMusic.play();
            } else {
                fightMusic.stop();
            }break;
        default:
            break;
    }
}

成果

gif只有画面没有声音,自己想像一下吧

Last modification:February 12th, 2021 at 06:13 am