• Как реализовать управление для двух игроков на одной клавиатуре SFML?

    @code_panik
    В самом простом случае можно реализовать так же, как реализовано управление одним игроком. Достаточно регистрировать события нажатия/отпускания клавиш. В sfml функция sf::Window::setKeyRepeatEnabled с аргументом false включает режим однократной регистрации нажатия клавиши. То есть событие keyPressed будет зарегистрировано однажды при нажатии клавиши, даже если она остается нажатой.

    Можем считать, что между событиями keyPressed и keyReleased клавиша остается нажатой. Сохраним нажатые клавиши с списке и на каждом шаге игрового цикла будем менять соответственно нажатым клавишам состояние игры, в том числе игрока.

    Пример реализации (C++17):
    #include <SFML/Graphics.hpp>
    #include <unordered_set>
    #include <unordered_map>
    
    using namespace std;
    
    void normalize(sf::Vector2i& v) {
    	int length = sqrt(1ll * v.x * v.x + 1ll * v.y * v.y);
    	if (length != 0) {
    		v.x /= length;
    		v.y /= length;
    	}
    }
    
    class Input {
    public:
    	using Keys = std::unordered_set<sf::Keyboard::Key>;
    
    	void registerKeyEvent(sf::Event e) {
    		if (e.type == sf::Event::KeyPressed)
    			pressedKeys_.insert(e.key.code);
    		else if (e.type == sf::Event::KeyReleased)
    			pressedKeys_.erase(e.key.code);
    	}
    
    	const Keys& pressedKeys() const {
    		return pressedKeys_;
    	}
    
    private:
    	Keys pressedKeys_;
    };
    
    class Player {
    public:
    	enum class Control {
    		moveUp, moveLeft, moveRight, moveDown
    	};
    
    	Player(sf::Vector2i initialPosition, sf::Color color)
    		: position_{ initialPosition } {
    		sprite_.setPosition(sf::Vector2f(initialPosition.x, initialPosition.y));
    		sprite_.setFillColor(color);
    		sprite_.setRadius(50);
    	}
    
    	Player& map(Control control, sf::Keyboard::Key key) {
    		keyControls_[key] = control;
    		return *this;
    	}
    
    	void process(const Input& input) {
    		velocity_ = {};
    		for (sf::Keyboard::Key k : input.pressedKeys()) {
    			if (auto it = keyControls_.find(k); it != keyControls_.end()) {
    				switch (it->second) {
    				case Control::moveDown:
    					velocity_.y = 1;
    					break;
    				case Control::moveUp:
    					velocity_.y = -1;
    					break;
    				case Control::moveLeft:
    					velocity_.x = -1;
    					break;
    				case Control::moveRight:
    					velocity_.x = 1;
    					break;
    				default:
    					break;
    				}
    			}
    		}
    		normalize(velocity_);
    		velocity_ *= speed_;
    	}
    
    	void move(sf::Vector2i position) {
    		position_ = position;
    		sprite_.setPosition(sf::Vector2f(position.x, position.y));
    	}
    
    	const sf::CircleShape& sprite() const {
    		return sprite_;
    	}
    
    	sf::Vector2i position() const {
    		return position_;
    	}
    
    	sf::Vector2i velocity() const {
    		return velocity_;
    	}
    
    private:
    	int speed_ = 300; // pixels / s
    	sf::Vector2i velocity_;
    	sf::Vector2i position_;
    	
    	sf::CircleShape sprite_;
    
    	std::unordered_map<sf::Keyboard::Key, Control> keyControls_;
    };
    
    class Game {
    public:
    	Game(sf::RenderWindow* gameWindow)
    		: window_(gameWindow) {
    		window_->setKeyRepeatEnabled(false);
    		auto width = window_->getSize().x;
    		auto height = window_->getSize().y;
    		Player mrRed(sf::Vector2i(width / 3, height / 3 * 2), sf::Color::Red);
    		mrRed.map(Player::Control::moveUp, sf::Keyboard::W)
    			.map(Player::Control::moveDown, sf::Keyboard::S)
    			.map(Player::Control::moveLeft, sf::Keyboard::A)
    			.map(Player::Control::moveRight, sf::Keyboard::D);
    		players_.emplace_back(std::move(mrRed));
    
    		Player mrBlue(sf::Vector2i(width / 3 * 2, height / 3 * 2), sf::Color::Blue);
    		mrBlue.map(Player::Control::moveUp, sf::Keyboard::Up)
    			.map(Player::Control::moveDown, sf::Keyboard::Down)
    			.map(Player::Control::moveLeft, sf::Keyboard::Left)
    			.map(Player::Control::moveRight, sf::Keyboard::Right);
    		players_.emplace_back(std::move(mrBlue));
    
    		Player mrGreen(sf::Vector2i(width / 2, height / 3), sf::Color::Green);
    		mrGreen.map(Player::Control::moveUp, sf::Keyboard::I)
    			.map(Player::Control::moveDown, sf::Keyboard::K)
    			.map(Player::Control::moveLeft, sf::Keyboard::J)
    			.map(Player::Control::moveRight, sf::Keyboard::L);
    		players_.emplace_back(std::move(mrGreen));
    	}
    
    	void update() {
    		processInputEvents();
    		sf::Time dt = clock_.restart();
    		processMovement(dt.asMilliseconds());
    		drawScene();
    	}
    
    private:
    	void processInputEvents() {
    		for (sf::Event e; window_->pollEvent(e); ) {
    			if (e.type == sf::Event::Closed) {
    				window_->close();
    				return;
    			}
    			input_.registerKeyEvent(e);
    		}
    		for (auto& player : players_)
    			player.process(input_);
    	}
    
    	void processMovement(sf::Int32 dt) {
    		for (int id = 0; id < players_.size(); ++id) {
    			auto& player = players_[id];
    			sf::Vector2i position = player.position() + sf::Vector2i(
    				player.velocity().x * 1e-3f * dt,
    				player.velocity().y * 1e-3f * dt
    			);
    			player.move(position);
    		}
    	}
    
    	void drawScene() {
    		window_->clear(sf::Color::White);
    		for (const auto& player : players_)
    			window_->draw(player.sprite());
    		window_->display();
    	}
    
    	std::vector<Player> players_;
    	Input input_;
    	sf::RenderWindow* window_;
    	sf::Clock clock_;
    };
    
    
    int main() {
    	sf::RenderWindow mainWindow(sf::VideoMode(600, 600), "Circles", sf::Style::Close);
    	mainWindow.setFramerateLimit(120);
    	Game game(&mainWindow);
    	while (mainWindow.isOpen())
    		game.update();
    	return 0;
    }


    Мы создаем три круга, которые могут двигаться независимо. Клавиши движений назначаются в конструкторе Game.

    FrameLimit ограничен, потому что скорость и другие результаты расчетов целочисленные и привязаны к пискелям. При слишком высокой частоте кадров изменения расстояний на каждом шаге симуляции будут незначительными, округлятся к 0. Лучше заменить целочисленную арифметику на float-арифметику, чтобы не было грубых ошибок округления как в normalize, и промежуточных преобразований sf::Vector.

    За поддержание актуальности списка нажатых клавиш отвечает class Input.
    Ответ написан
    6 комментариев
  • Как можно передать коллекцию в C#?

    Для передачи в foreach необходимо реализовать интерфейс System.Collections.IEnumerable или System.Collections.Generic.IEnumerable<T>
    Ответ написан
    1 комментарий