@a1nsworth

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

В моей игре 2 игрока, управление на ОДНОЙ клавиатуре. Каким образом мне сделать так, чтобы я мог управлять каждым из них ?
Параллельность ? Если да , то как это сделать ?
  • Вопрос задан
  • 209 просмотров
Пригласить эксперта
Ответы на вопрос 3
@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.
Ответ написан
GavriKos
@GavriKos Куратор тега Разработка игр
Так же как одним. Считываете коды клавиш, и исходя из нажатых клавишь делаете действия для того игрока, которому принадлежит нажатая клавиша.
Ответ написан
saboteur_kiev
@saboteur_kiev Куратор тега C++
software engineer
Советую почитать о том, что такое NKRO
Не факт что даже если будет верным код, найдешь клавиатуру где все будет работать
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы