【问题标题】:Vector is crashing my programVector 正在使我的程序崩溃
【发布时间】:2017-09-06 21:35:21
【问题描述】:

我的 SFML 游戏出现了严重问题。 我整天都在努力寻找解决方案,尝试了不同的事情,但到目前为止对我没有任何帮助。 这些是我的 .h 文件: Bullet.h

#pragma once
#include <SFML\Graphics.hpp>
#include <iostream>
#include <vector>

class Bullet
{
    friend class Player;
    friend class Game;
    float width;
    float height;
    float x;
    float y;
    std::vector<Bullet*> projectiles;
    sf::RectangleShape bullet;
    void draw_projectiles(sf::RenderWindow &window);
    void make_projectiles();

public:
    void check();
    Bullet();
    ~Bullet();
};   

Game.h

#pragma once
#include <SFML\Graphics.hpp>
#include "Player.h"
#include "Bullet.h"
#include <vector>
//#include "Enemy.h"

class Game
{
    friend class Player;
    sf::RenderWindow* window;
    sf::Event* evnt;
    Player* player;
    Bullet* bullet;

public:
    void Loop();
    void game_func();
    Game();
    ~Game();
};

Player.h

#pragma once
#include <SFML\Graphics.hpp>
#include <iostream>
#include "Game.h"
#include "Bullet.h"

class Player
{
    sf::RectangleShape player;
    Bullet* bullet;
    int ammo;
    float width;
    float height;
    int x;
    int y;
    float vel;

public:
    void draw(sf::RenderWindow &window);
    void move(sf::Event &evnt, sf::RenderWindow &window);
    Player();
    ~Player();
};   

cpp 文件来了 Bullet.cpp

#include "Bullet.h"

void Bullet::check()
{
    x = bullet.getPosition().x;
    y = bullet.getPosition().y;
}

void Bullet::draw_projectiles(sf::RenderWindow &window)
{

    for (int i = 0; i < 10; i++)
    {
        window.draw(projectiles[i]->bullet);
    }
}

void Bullet::make_projectiles()
{
    projectiles.push_back(new Bullet());
}

Bullet::Bullet()
{
    std::cout << "zostal utworzony nowy obiekt" << std::endl;
    width = 50;
    height = 50;
    bullet = sf::RectangleShape(sf::Vector2f(width, height));
    bullet.setFillColor(sf::Color::Yellow);
    bullet.setPosition(0, 0);
    x = bullet.getPosition().x;
    y = bullet.getPosition().y;
}

Bullet::~Bullet(){}

Game.cpp

#include "Game.h"

Game::Game()
{
    window= new sf::RenderWindow(sf::VideoMode(1280, 720), "SFML Game",             
    sf::Style::Close);
    player = new Player();
}

Game::~Game(){}

void Game::Loop()
{
    while (window->isOpen())
    {
        sf::Event evnt;
        while (window->pollEvent(evnt))
        {   
            //events
            if (evnt.type==sf::Event::Closed)
                window->close();

            player->move(evnt, *window);
            window->clear();
            player->draw(*window);
            window->display();
            bullet->draw_projectiles(*window);
        }
    }
}

void Game::game_func()
{
    Game::Loop();
}

Player.cpp

#include "Player.h"

void Player::draw(sf::RenderWindow &window)
{
    window.draw(player);
}

void Player::move(sf::Event &evnt, sf::RenderWindow &window)
{    
    x = player.getPosition().x;
    y = player.getPosition().y;
    float width = window.getSize().x;
    float height = window.getSize().y;
    Bullet obj;

    if (evnt.type == sf::Event::KeyPressed)
    {
        //movement
        if (evnt.key.code == sf::Keyboard::Key::W)
        {
            if (y <= 0)
            {
                return;
            }
            player.move(0, -1 * vel);
        }

        if (evnt.key.code == sf::Keyboard::Key::S)
        {
            if (y >= height - Player::height)
            {
                return;
            }
            player.move(0, 1 * vel);
        }

        if (evnt.key.code == sf::Keyboard::Key::A)
        {
            if (x <= 0)
            {
                return;
            }
            player.move(-1 * vel, 0);
        }
        if (evnt.key.code == sf::Keyboard::D)
        {
            if(x>width-Player::width)
            {
                return;
            }
            player.move(1 * vel, 0);
        }
        if (evnt.key.code == sf::Keyboard::Space)
        {
            obj.make_projectiles();
        }
    }
}

Player::Player()
{
    width = 100;
    height = 100;
    vel = 10;
    player =  sf::RectangleShape(sf::Vector2f(width, height));
    player.setFillColor(sf::Color::Red);
    player.setPosition(sf::Vector2f(15, 20));
}

Player::~Player(){}

还有 main.cpp

#include <SFML/Graphics.hpp>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include "Game.h"

int main()
{   
    Game gme;
    gme.game_func();
    return 0;
}

我尝试了许多不同的方法,但无法弄清楚为什么它不起作用。我在 Visual Studio 15 上运行。 所以这是我得到的错误:

Exception thrown: read access violation.

std::_Vector_alloc<std::_Vec_base_types<Bullet *,std::allocator<Bullet *> > 
>::_Mylast(...) returned 0x18.

我知道代码并不完美而且有点混乱,但我只是一个初学者并试图学习新东西。 我将不胜感激!

【问题讨论】:

  • 你违反了 3/5/0 的规则。您正在泄漏内存。使用 std::unique_ptr 而不是原始指针来拥有指针。
  • 我假设您在 IDE 中使用缩进。从 IDE 粘贴代码后,选择您的代码并使用编辑器中的 {} 选项。它应该保持缩进。
  • 离题:你有一个循环依赖 player.h 包括 qame.h 和 game.h 包括 player.h。这迟早会咬你。更多阅读:stackoverflow.com/questions/625799/…
  • 写一些小而简单的完美运行的东西,然后每次增加一点复杂性,在每一步都进行测试,并且永远不要添加不起作用的代码。这样您就离工作版本只有一步之遥,而且很容易隔离新的错误。

标签: c++ oop sfml


【解决方案1】:

我在最后几段中回答了你的问题,你可以跳到那段,但我建议你看看所有这些。首先你应该明白一个基本游戏在代码中应该是什么样子的。

游戏逻辑

您可以将游戏逻辑分为 2 个主要功能。初始化和循环。

初始化

在初始化函数中,您基本上加载了游戏运行所需的所有内容(这仅适用于小型游戏,因为在内存中加载数十个 gig 的 sprite 可能不是大型游戏的最佳解决方案。随着时间的推移,您会找出加载和释放资源的正确时间)。

循环

这称为主循环游戏循环。这个循环应该执行 3 个主要功能。 处理用户输入、更新世界和渲染世界。此循环应在游戏运行时(即窗口打开时)执行

所以你在伪 C++ 中的 main 应该是这样的:

Init();
while (window.isOpen())
{
    HandleEvents(window);  //user input
    Update(elapsedTime);
    Render(window);
}

我将解释这些函数的作用、参数的含义以及这些函数如何映射到您的代码。请记住,每个功能都有一个特定的任务,而且只有那个。当我在屏幕上绘制精灵时,我不会检查用户是否按下了按钮。

用户输入

从按钮按下和鼠标单击到按下退出按钮和调整窗口大小的所有操作都称为用户输入。用户的操作会生成所谓的事件,我们在每个循环开始时处理这些事件。现在此事件是特定于窗口的(如果窗口最小化或未聚焦,您将无法控制播放器)。这意味着窗口会生成事件(如果我在技术上对此有误,请纠正我)。这就是在处理事件时需要传递窗口的原因。

活动

在处理事件之前,您需要了解 sf::Event 是如何产生的(更多信息请参见sfml 页面)。长话短说 sf::Event 是一个联合(一次只有一个字段有效)。也就是说,如果您在window.pollEvent() 返回sf::Event::JoystickEvent 时尝试访问event.key,您将得到一个未定义的行为(我在不知道工会是什么的情况下过着幸福快乐的生活,从未使用它们并且可能永远不会使用它们,但它们是一个非常有趣的概念,至少值得一读)。好的,通过调用window.pollEvent() 并传递给它一个 sf::Event 实例来创建一个事件对象。此函数将为您提供队列中的事件,直到没有更多事件要给出,这就是它返回 false 的时候。考虑到这一点,您的事件处理代码将如下所示:

sf::Event ev;
while (window.pollEvent(ev))
{
    switch (ev.type)
    {
       //code for each type needed by your application
    }
}

请记住,关键事件不会处理实时输入(sf::Keyboard::isKeyPressed 会这样做)。这意味着,如果您希望您的角色在按住按钮时移动,则通过事件处理它会导致延迟,这可以通过打字的方式得到最好的解释(例如,当您按住 'a' 时,第一个字符是立即写入,其余输入在注册前延迟一秒)。这是一种解释方式,但可能不是最技术性的方式(我在这里寻求一点帮助:))。无论如何,这个问题可以通过使用 sf::Keyboard 的静态方法或通过在您的 Player 类中保留一个 bool 来解决事件 KeyPressedKeyReleased 来解决(更新将基于该处理布尔)。

世界更新

这是您的逻辑代码(尽管玩家移动也可以在事件部分处理,因为它是基于它们的)。在这里你更新你的实体(根据他的 AI 再移动敌人一个方块),在地图上移动太阳等等。请记住,这与绘图部分无关,在本节中你只改变你的状态对象。在您的游戏中,这意味着,在您通过用户触发的事件启动投射物后,您移动投射物的每一帧。此代码通常需要某种帧计数方法。

框架

一帧是循环的迭代,你可以说游戏在每一帧更新和绘制自己。框架是一个非常重要的概念,因为它们会产生一些问题。如果游戏每帧都更新自己,这意味着弹丸的每一帧都在移动,这意味着他的移动取决于你的电脑可以运行的 FPS。这是一个问题,因为虽然您的游戏可以在您的 PC 上以稳定的 60 FPS 速率运行,但在我的计算机上它可能以 53 或其他随机值运行。这意味着我电脑上的弹丸移动速度会变慢,我们不希望这样。

框架独立运动

这可以通过计算帧数来实现。一种方法是计算自上一帧以来经过的秒数,记住这一点,您可以获得实体在该特定帧中移动所需的空间量。例如,您想以 100 像素/秒的速度移动弹丸。如果你有 2FPS 这意味着在 2 帧中它需要移动 100px,所以每帧移动100 / 2px。所以公式是finalDistance / framerate。有更多的方法可以做到这一点,但在我看来,这是最容易理解的开始。那么这在 SFML 中是如何实现的呢?您基本上会保留一个时钟,在每次更新结束时重新启动。 getElapsedTime 和 restart 会这样做,但 restart 返回 elapsedTime 所以最好调用一次,因为一一调用它们可能会导致不同的时间和不同步。

sf::Clock clock;
while (window.isOpen())
{
    HandleEvents(window);
    Update(clock.restart());
    Render(window);
}

您只需使用move(vector * clock.getElapsedTime().asSeconds()) 移动实体,因为sf::Vector 具有operator* 重载浮点数(asSeconds() 的返回类型)。

渲染

渲染部分可能很复杂,但sfml 让它“简单快速”。基本上它是这样工作的:清除屏幕,绘制实体,显示屏幕。更技术性的答案如下:窗口由 2 个缓冲区组成,一个可见,一个隐藏。可见的是您在屏幕上看到的。当您调用clear() 时,您基本上清除了隐藏的窗口,draw() 也在隐藏窗口上绘制,最后display() 交换缓冲区。 这意味着除非您调用window.display(),否则您将看不到任何结果,并且如果您在绘图之前不调用clear(),您将获得window xp 体验。所以渲染函数可能看起来像这样:

 window.clear();
 window.draw(player); //or player.draw(window) based on your implementation
 //other draws
 window.display();

您的问题

您的代码中发生的情况是您尝试访问不存在的东西。您一次添加一个弹丸,但每帧绘制 10 个。

解决方案

为你的物品保留一个计数器。由于您使用的是已经提供的向量,因此您有 std::vector::size 可以返回您所期望的内容,因此您的代码将变成如下内容:

for (int i = 0; i < yourProjectiles.size(); i++)
{
    window.draw(yourProjectiles[i]->bullet);
}

您也可以使用迭代器(查找它们):

for (auto it = yourProjectiles.begin(); it != yourProjectiles.end(); ++it)
{
    window.draw(it->bullet);
}

内存管理

您不会释放内存。您必须研究动态内存分配。基本原则是每一个新的都应该有一个删除。大部分时间应该在类的析构函数中处理释放部分。我想有人可能会建议使用智能指针(std::shared_ptr) 来管理你的内存,但我不建议你这样做,因为你刚开始。智能指针是一个你应该牢记的概念,但是当你刚开始时,最好面对手动内存管理的困难(直到你习惯它)。

代码组织

一个类应该只用于一个目的。当您创建一个名为 Bullet 的类时,预计此 Bullet 将代表您游戏中的一个射弹,但当您的 Bullet 制造“射弹”并存储射弹时,它就变成了一个超自然实体。您的子弹 atm 包含指向其他子弹实例的指针,这些实例包含指向其他子弹实例的指针。这是一团糟。除非您想创建某种图形或树,否则您没有任何理由存储同一类实例的指针。

朋友太多

如果每个班级都是每个班级的朋友,那么您创建私有字段的原因是什么?朋友是一个非常强大的概念,应该小心使用,只有在你没有其他选择的情况下。我会避免使用这个关键字的唯一原因是它造成的混乱。它产生与公共属性相同的效果。当一切都可以从任何地方访问时,一切都可以从任何地方摧毁。当您创建一小组操作属性的方法时,您就知道问题出在哪里。

结论

我可能会建议更多地研究 C++,然后调试您的游戏,或者从头开始重新创建它。虽然我知道尝试新事物的感觉,但您应该始终小心不要误伤自己,并且在遇到此类错误时不要害怕回到基础。您在管理内存时遇到问题?阅读有关动态内存分配的更多信息,并使用它做一些示例应用程序。除此之外,我注意到您仍处于使用类的起点。我想说熟能生巧。看看其他人的代码,即使是像 sfml 这样的第 3 方库也可能会给你一些关于良好课堂实践的提示。好处是不需要查看这些库的源代码,您只需使用它们的接口即可。如果你喜欢它,这意味着它写得很好,你可以借用这种风格的一部分并在你的类中实现。最后我会说,如果您有任何其他问题,我很高兴并渴望通过电子邮件为您提供帮助。

【讨论】:

  • 奇怪的误报。您的完整答案中有很多链接吗?
  • 嗯,sfml 页面上有一个,但我删除了它(没有成功)。然而,答案很长(可能大约 50 行)
  • pastebin.com/rW8PMu8m 希望我做对了。非常感谢。
  • 你去 :)
  • 哇!非常感谢您的宝贵信息!这是很多新信息,我会尽力关注它们。我希望您能提供更多帮助,尤其是通过电子邮件或其他方式。 Stack有什么方法可以发送私信吗?
【解决方案2】:

我相信你正试图访问十个射弹:

for (int i = 0; i < 10; i++)
{
    window.draw(projectiles[i]->bullet);
}

但你一次只能添加一个:

projectiles.push_back(new Bullet());

【讨论】:

  • 实际上这 10 件事是我的一个实验留下的,因为我不断收到 vector.size() 错误,我想在每次按空格时创建一个新的子弹对象。将其添加到矢量并在每一帧上绘制。
  • 好吧,它肯定会导致崩溃,因为它试图访问不存在的元素。
  • 那你建议我怎么做?我应该阅读哪些内容以了解更多信息?
  • @Sniadek: for (auto&amp; projectile : projectiles) 遍历向量中的所有射弹。不多也不少。
猜你喜欢
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多