Вектор сбивает мою программу

У меня серьезная проблема с моей игрой 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();
};   

Сюда приходят файлы cppBullet.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.

Я знаю, что код не идеален и немного грязен, но я только начинающий и пытаюсь изучать новые вещи. Я буду признателен за любую помощь!

2 ответа

Решение

Я ответил на ваш вопрос в моих последних абзацах, вы можете перейти к этому абзацу, но я призываю вас взглянуть на все это. Прежде всего вы должны понять, как должна выглядеть базовая игра в коде.

Логика игры

Вы можете разделить игровую логику на 2 основные функции. Инициализация и цикл.

инициализация

В функции инициализации вы в основном загружаете все необходимое для запуска вашей игры (это доступно только для небольших игр, поскольку загрузка десятков гигабайт спрайтов в память может быть не лучшим решением для больших. Со временем вы поймете, подходящее время для загрузки и освобождения ресурсов).

Петля

Это называется основной цикл или игровой цикл. Этот цикл должен выполнять 3 основные функции. Обработка пользовательского ввода, обновление мира и рендеринг мира. Этот цикл должен выполняться во время игры (т.е. когда окно открыто)

Так что ваш основной в псевдо-C++ должен выглядеть примерно так:

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

Я объясню, что делают функции, что означают аргументы и как эти функции отображаются в вашем коде. Имейте в виду, что каждая функция имеет одну конкретную задачу и только это. Я не буду проверять, нажимает ли пользователь кнопку, пока я рисую спрайты на экране.

Пользовательский ввод

Все от нажатия кнопки и щелчка мыши до нажатия кнопки выхода и изменения размера окна называется пользовательским вводом. Действия пользователя генерируют так называемые события, которые мы обрабатываем в начале каждого цикла. Теперь эти события относятся к конкретному окну (вы не можете контролировать игрока, если окно свернуто или не сфокусировано). Это означает, что окно генерирует события (если я технически ошибаюсь, пожалуйста, исправьте меня). Это причина того, что когда вы обрабатываете события, вам нужно пропустить окно.

События

Прежде чем обрабатывать события, вам необходимо понять, как создается sf::Event (подробнее о sfml страница). Короче говоря, sf::Event является объединением (только одно поле является действительным одновременно). То есть, если вы попытаетесь получить доступ event.key когда window.pollEvent() вернул sf::Event::JoystickEvent вы получите неопределенное поведение (я прожил долгую счастливую жизнь, не зная, что такое союзы, никогда не использовал их и, вероятно, никогда не буду, но это довольно интересная концепция, о которой стоит хотя бы прочитать). Итак, объект события создается путем вызова 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, либо сохраняя bool в вашем классе Player, который отвечает на события KeyPressed а также KeyReleased(обновление будет обрабатываться на основе этого bool).

Мировое Обновление

Вот ваш логический код (хотя движение игрока также может быть обработано в разделе событий, так как оно основано на них). Здесь вы обновляете свои сущности (перемещаете противника еще на один блок на основе его ИИ), перемещаете солнце вокруг карты и т. Д. Имейте в виду, что это не имеет ничего общего с частью рисунка, в этом разделе вы только изменяете состояние своего объекты. В вашей игре это означает, что после того, как вы запустили проектив через событие, инициированное пользователем, каждый кадр, в который вы перемещаете снаряд. Этот код обычно требует какой-то метод подсчета кадров.

Рамки

Кадр - это итерация цикла, можно сказать, что игра обновляется и отрисовывает себя каждый кадр. Рамки - очень важная концепция, потому что у них возникают некоторые проблемы. Если игра обновляет себя каждый кадр, это означает, что каждый кадр движется снарядом, так что это означает, что его движение зависит от FPS, который может запустить ваш компьютер. Это проблема, потому что, в то время как ваша игра может работать на вашем компьютере так, как вы хотите, со стабильной скоростью 60 FPS, у меня она может работать со скоростью 53 или другим случайным значением. Это означает, что снаряды на моем компьютере будут двигаться медленнее, а мы этого не хотим.

Рамочное независимое движение

Это может быть достигнуто путем подсчета кадров. Один из способов сделать это - подсчитать количество секунд, прошедших с последнего кадра, имея в виду, что вы можете получить объем пространства, необходимого вашей сущности для перемещения в этом конкретном кадре. Например, вы хотите переместить ваш снаряд со скоростью 100px/sec. Если у вас есть 2FPS, это означает, что в 2 кадрах необходимо переместить 100px, поэтому каждый кадр перемещается 100 / 2 гарнизонная лавка Таким образом, формула finalDistance / framerate, Есть больше способов сделать это, но, на мой взгляд, это самый простой для понимания в начале. Так как это реализовано в SFML? В основном вы сохраняете часы, которые вы перезапускаете в конце каждого обновления. getElapsedTime и рестарт делает это, но рестарт возвращает elapsedTime поэтому лучше вызывать его один раз, так как вызов их один за другим может привести к разным временам и рассинхронизации.

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

И вы просто перемещаете свои объекты с move(vector * clock.getElapsedTime().asSeconds()) поскольку sf::Vector имеет operator* перегружен для поплавков (тип возврата asSeconds()).

Rendering

Часть рендеринга может быть очень сложной, но sfml делает это "просто и быстро". В основном это работает так: вы очищаете экран, вы рисуете свои сущности, вы отображаете экран. Более технический ответ заключается в следующем: окно состоит из 2 буферов, один видимый и один скрытый. Видимый - тот, который вы видите на экране. Когда вы звоните clear() вы в основном очищаете скрытый, draw() рисует также на скрытое окно, и, наконец, display() меняет местами буферы Это означает, что вы не увидите никаких результатов, если не позвоните window.display(), и вы получите опыт Windows XP, если вы не позвоните clear() перед рисованием. Так что функция Render может выглядеть так:

 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 создает "снаряды" и хранит снаряды, он становится паранормальной сущностью. Ваш атлет пули содержит указатели на экземпляры других пуль, которые содержат указатели на экземпляры других пуль. Это полный беспорядок. Если вы не хотите создавать граф или дерево какого-либо рода, у вас нет причин хранить указатели экземпляров одного и того же класса.

Слишком много друзей

Если каждый класс дружит с каждым классом, какова причина создания личных полей? Friend - это очень мощная концепция, и ее следует использовать с осторожностью, только если у вас нет других вариантов. Единственная причина, по которой я бы избегал этого ключевого слова, это беспорядок, который он создает. Создает тот же эффект, что и публичные атрибуты. Когда все доступно отовсюду, все может быть уничтожено отовсюду. Когда вы создаете небольшой набор методов, которые управляют вашими атрибутами, вы знаете, в чем проблема.

Заключение

Я мог бы предложить немного больше взглянуть на C++ и после этого отладить вашу игру, или воссоздать ее с нуля. Хотя я знаю, каково это попробовать что-то новое, вы всегда должны быть осторожны, чтобы не выстрелить себе в ногу, и не бояться возвращаться к основам, когда сталкиваетесь с такими ошибками. У вас проблемы с управлением памятью? Узнайте больше о динамическом распределении памяти, сделайте несколько примеров приложений, использующих его. Кроме того, я заметил, что вы все еще в начале использования классов. Я бы сказал, что практика делает идеальным. Посмотрите на код других людей, даже эти сторонние библиотеки, такие как sfml, могут дать вам несколько советов по хорошей практике работы с классами. Хорошо, что не нужно смотреть на исходный код этих библиотек, вы просто используете их интерфейс. Если вам это нравится, это означает, что он хорошо написан, и вы можете позаимствовать часть этого стиля и реализовать его в своих классах. В заключение я скажу, что я очень рад и хочу помочь вам по электронной почте, если у вас есть какие-либо вопросы относительно чего-либо.

Я полагаю, что вы пытаетесь получить доступ к десяти снарядам:

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

Но вы добавляете только по одному за раз:

projectiles.push_back(new Bullet());
Другие вопросы по тегам