I try to program a top-down-shooter in SFML at the moment, but ran into a problem. I'm quite new to C++ and programming in general, so please excuse messy code and/or overly complicated solutions.
I have two std::lists, one containing the randomly spawning enemies, the other containing the bullets I fired. When a bullet hits a enemy, both of them should get erased, but it doesn't work.
Here's the problem-part of my code:
for(MonsterIt = MonsterList.begin(); MonsterIt != MonsterList.end(); MonsterIt++)
{
//Here would be Monster-Movement
//Collision Monster-Player (MonsterIt = iterator of MonsterList)
if ((MonsterIt -> getPosition().x + 25) >= PlayerX - 25 &&
(MonsterIt -> getPosition().x - 25) <= PlayerX + 25 &&
(MonsterIt -> getPosition().y + 25) >= PlayerY - 25 &&
(MonsterIt -> getPosition().y - 25) <= PlayerY + 25 )
{
MonsterList.erase(MonsterIt);
break;
}
window.draw(*MonsterIt);
}
That's the way I've done collision between Monster and Player. That worked fine, so I tried the same with Monsters and Lasers:
for(LaserIt = LaserList.begin(); LaserIt != LaserList.end(); LaserIt++)
{
//Here would be "Laser-Movement"
//Collision-Laser // Doesn't work
if ((MonsterIt -> getPosition().x + 25) >= //
(LaserIt -> getPosition().x - 7) && //
(MonsterIt -> getPosition().x - 25) <= //
(LaserIt -> getPosition().x + 7) && //
(MonsterIt -> getPosition().y + 25) >= //
(LaserIt -> getPosition().y - 7) && //
(MonsterIt -> getPosition().y - 25) <= //
(LaserIt -> getPosition().x + 7)) //
{ //
MonsterList.erase(MonsterIt); //
//
LaserList.erase(LaserIt); //
//
break; //
} //
window.draw(*LaserIt);
}
When I put in the part of code I marked (with // on right side) I get a "list iterator not dereferencable"-error while debugging as soon as I shoot. When I cut out said code it runs fine (I can shoot, walk into monsters and they disappear, etc.). Because of that I guess the rest of my code is working.
So, is collision between iterators of different lists even possible?
And if so, how do I do it?
If you need more information or code, please ask. I'd be glad for your help...
The iterator you use is not valid.
There are several solutions to your problems. First one:
for(MonsterIt = MonsterList.begin(); MonsterIt != MonsterList.end(); MonsterIt++)
{
//Here would be Monster-Movement
//Collision Monster-Player (MonsterIt = iterator of MonsterList)
// Collision with laser, inside the loop for monsters
for(LaserIt = LaserList.begin(); LaserIt != LaserList.end(); LaserIt++)
{
}
window.draw(*MonsterIt);
}
This solution is not really C++ish. With OOP, you can have a much clearer code:
class Player
{
public:
int X, Y; // for code simplicity on SO.
};
// ...
Player player;
for(MonsterIt = MonsterList.begin(); MonsterIt != MonsterList.end(); MonsterIt++)
{
MonsterIt->update(player,LaserList);
if(!MonsterIt->isAlive())
{
// Remove monster from list *without* break
}
}
Then, Monster is a class like the following:
class Monster
{
bool alive;
public:
bool isAlive() const { return alive; }
bool collideWithPlayer(Player p) const
{
// Returns whether it collide with player
// ...
}
bool collideWithLaser(Laser l) const
{
// Returns whether it collide with one laser
// ...
}
bool collideWithLasers(LaserList l) const
{
for(LaserIt = LaserList.begin(); LaserIt != LaserList.end(); LaserIt++)
if(collideWithLaser(*LaserIt))
return true;
return false;
}
void update(Player p, LaserList l)
{
if(collideWithPlayer(p) || collideWithLasers(l))
alive = false;
else
window.draw(this);
}
};
Related
I am creating a game with a 3D grid for flying entities, So I have a lot of points and connections in the air where there aren't any obstructions. I didn't want to decrease the resolution of my grid so I thought I could just skip over chunks (or empties as I call them) of the Astar map while they're not containing any obstructions, and I modified Godot's Astar algorithm to do this.
Unfortunately this ended up being slower than looping through points one at a time due to the way I implemented this modification, which needs to loop through all the edge points of an empty.
2D representation of how one edge point of an empty connects to all other edge points:
This ends up looping through a larger number of points than letting the A* algorithm work it's way through the empty.
So I'm sorta stumped on how to make this more efficient while still preserving the most optimal path.
I could potentially narrow down what faces of the empty should be scanned over by first comparing the center points of all 8 faces of the empty (as my grid consists of hexagonal prisms). Or maybe I should somehow use the face center points of the empty's faces exclusively instead of all edge points.
I mainly want to know if anyone has worked on an issue like this before, and if so what would be the recommended solution?
Here is the astar loop for reference:
bool AStar::_solve(Point *begin_point, Point *end_point, int relevant_layers) {
pass++;
//make sure parallel layers are supported
// or if *relevant_layers is 0 then use all points
bool supported = relevant_layers == 0 || (relevant_layers & end_point->parallel_support_layers) > 0;
if (!end_point->enabled || !supported) {
return false;
}
bool found_route = false;
Vector<Point *> open_list;
SortArray<Point *, SortPoints> sorter;
begin_point->g_score = 0;
begin_point->f_score = _estimate_cost(begin_point->id, end_point->id);
open_list.push_back(begin_point);
while (!open_list.empty()) {
Point *p = open_list[0]; // The currently processed point
if (p == end_point) {
found_route = true;
break;
}
sorter.pop_heap(0, open_list.size(), open_list.ptrw()); // Remove the current point from the open list
open_list.remove(open_list.size() - 1);
p->closed_pass = pass; // Mark the point as closed
//if the point is part of an empty, look through all of the edge points of said empty (as to skip over any points within the empty).
OAHashMap<int, Point*> connections;
PoolVector<Empty*> enabled_empties;
int size = p->empties.size();
PoolVector<Empty*>::Read r = p->empties.read();
for (int i = 0; i < size; i++) {
Empty* e = r[i];
supported = relevant_layers == 0 || (relevant_layers & e->parallel_support_layers) > 0;
//if the empty is enabled and the end point is not within the empty
if (e->enabled && supported && !end_point->empties.has(e)) {
enabled_empties.append(e);
//can travel to any edge point
for (OAHashMap<int, Point*>::Iterator it = e->edge_points.iter(); it.valid; it = e->edge_points.next_iter(it)) {
int id = *it.key;
Point* ep = *(it.value);
ep->is_neighbour = false;
//don't connect to the same point
if (id != p->id && (i == 0 || !connections.has(id))) {
connections.set(id, ep);
}
}
}
}
//add neighbours to connections
for (OAHashMap<int, Point*>::Iterator it = p->neighbours.iter(); it.valid; it = p->neighbours.next_iter(it)) {
int id = *it.key;
Point* np = *(it.value);// The neighbour point
np->is_neighbour = true;
//don't need to check for duplicate point connections if no empties
if (size == 0 || !connections.has(id)) {
//don't add points within enabled empties since they're meant to be skipped over
if (np->empties.size() > 0 && !np->on_empty_edge) {
bool in_enabled_empty = false;
PoolVector<Empty*>::Read r1 = np->empties.read();
for (int i = 0; i < np->empties.size(); i++) {
if (enabled_empties.has(r1[i])) {
in_enabled_empty = true;
break;
}
}
if (!in_enabled_empty) {
connections.set(id, np);
}
}
else {
connections.set(id, np);
}
}
}
for (OAHashMap<int, Point *>::Iterator it = connections.iter(); it.valid; it = connections.next_iter(it)) {
Point *e = *(it.value); // The neighbour point
//make sure parallel layers are supported
// or if *relevant_layers is 0 then use all points
supported = relevant_layers == 0 || (relevant_layers & e->parallel_support_layers) > 0;
if (!e->enabled || e->closed_pass == pass || !supported) {
continue;
}
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
bool new_point = false;
if (e->open_pass != pass) { // The point wasn't inside the open list.
e->open_pass = pass;
open_list.push_back(e);
new_point = true;
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
continue;
}
e->prev_point = p;
e->prev_point_connected = e->is_neighbour;
e->g_score = tentative_g_score;
e->f_score = e->g_score + _estimate_cost(e->id, end_point->id);
if (new_point) { // The position of the new points is already known.
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptrw());
} else {
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptrw());
}
}
}
return found_route;
}
Note: I'm still not exactly sure what the sorter does.
the entire code can be seen here in a_star.cpp and a_star.h
Edit:
if anyone wants to reference or use this, I've modified the Astar code to add user-defined octants and to use a user-defined straight line function (they are user-defined so they can work with any type of grid) to be used between octants when possible to further decrease runtime, and it works very well in terms of speed. Though the pathing is not optimal, especially when adding a lot of obstacles/restricting the available positions.
I created an Enemy vector and used push_back twice to add two identical enemies. Inside a for loop, I created a collision system to prevent my player from going inside the enemies. However,for some reason it only works with the last enemy added.
I would like to know what should I do in order to make it work with every single enemy in that vector.
EnemyCounter = 0;
for (iter = EnemyArray.begin(); iter != EnemyArray.end(); iter++)
{
EnemyArray[EnemyCounter].update(Seconds);
window.draw(EnemyArray[EnemyCounter].CharacterSprite);
if (player.RectShape.getGlobalBounds().intersects(EnemyArray[EnemyCounter].RectShape.getGlobalBounds()))
{
if (player.LastDirection == 0) player.canMoveDown = false;
else if (player.LastDirection == 1) player.canMoveLeft = false;
else if (player.LastDirection == 2) player.canMoveRight = false;
else if (player.LastDirection == 3) player.canMoveUp = false;
}
if (!player.RectShape.getGlobalBounds().intersects(EnemyArray[EnemyCounter].RectShape.getGlobalBounds())) player.ResetCanMove();
EnemyCounter++;
}
I just created a countdown method in Java, but i have a problem when the countdowner broadcasts the messages: 60(and down) seconds until the game starts!
The broadcast gets sent *4. Does anyone know any solution to this?
Here is my code:
Main plugin;
public StartCountdown(Main pl) {
plugin = pl;
}
public static int timeUntilStart;
#Override
public void run() {
for(Player p1 : Bukkit.getOnlinePlayers()){
if(timeUntilStart == 0) {
if(!Game.canStart()) {
plugin.restartCountdown();
ChatUtilities.broadcast(ChatColor.RED + "Not enough players to start. Countdown will");
ChatUtilities.broadcast(ChatColor.RED + "restart.");
p1.playSound(p1.getLocation(), Sound.ENDERDRAGON_WINGS, 5, 1);
return;
}
Game.start();
}
for(Player p : Bukkit.getOnlinePlayers()){
p.setLevel(timeUntilStart);
if(timeUntilStart < 11 || timeUntilStart == 60 || timeUntilStart == 30) {
p.playSound(p.getLocation(), Sound.ORB_PICKUP, 5, 0);
if(timeUntilStart == 1) {
p.playSound(p.getLocation(), Sound.ORB_PICKUP, 5, 1);
}
ChatUtilities.broadcast(String.valueOf(timeUntilStart)
+ " §6Seconds until the game starts!");
}
}
}
timeUntilStart -= 1;
}
}
You are broadcasting for every player that is online. You need to move any code that you don't want to run for every player outside of the for loop.
#Override
public void run() {
if (timeUntilStart == 0) {
if (!Game.canStart()) {
plugin.restartCountdown();
ChatUtilities.broadcast(ChatColor.RED + "Not enough players to start. Countdown will");
ChatUtilities.broadcast(ChatColor.RED + "restart.");
for (Player p : Bukkit.getOnlinePlayers()) p.playSound(p.getLocation(), Sound.ENDERDRAGON_WINGS, 5, 1);
return;
}
Game.start();
}
boolean broadcast;
for (Player p : Bukkit.getOnlinePlayers()) {
p.setLevel(timeUntilStart);
if (timeUntilStart < 11 || timeUntilStart == 60 || timeUntilStart == 30) {
p.playSound(p.getLocation(), Sound.ORB_PICKUP, 5, 0);
if (timeUntilStart == 1) p.playSound(p.getLocation(), Sound.ORB_PICKUP, 5, 1);
broadcast = true;
}
}
if (broadcast) ChatUtilities.broadcast(String.valueOf(timeUntilStart) + " §6Seconds until the game starts!");
timeUntilStart -= 1;
}
as Tanner Little said above
You are broadcasting for every player that is online. You need to move any code that you don't want to run for every player outside of the for loop.
You need to make sure as well that you are cancelling the task. I would recommend using the inbuilt scheduler. You can access the scheduler in this way
private int countDownTimer
private int countDownTime
public void runCountDown() {
countDownTimer = Bukkit.getScheduler.scheduleSyncDelayedTask(plugin, new runnable() {
public void run {
if (countDownTime <= 0) {
//do your bradcasting here
for (Player ingame : Bukkit.getOnlinePlayers()) {
//Do your player specific stuff here
}
Bukkit.getScheduler.cancelTask(countDownTimer);
}
if (countDownTime % 10 == 0) { //You can pick whaterver times u want this is just for an example
//Do periodic broadcasting
}
countDownTime -= 1;
}
}, 0L, 20L); //This means that it would wait 0 ticks to start the countdown and do the task every 20 ticks ie) 1 second.
}
Hopefully this helps you.
How can I call an if statement (or any other function) without calling it again, until it's finished executing? I have an update function that calls another function (in another class) but because it executes every update, the user doesn't get the time to actually complete the IF statements (the if statements rely on user input) so therefor it returns nothing or only the first part.
Code (where the update is):
public void Update(){
if(Mouse.isButtonDown(0)){
changeTile();
}
while(Keyboard.next()){
if(Keyboard.getEventKey() == Keyboard.KEY_RIGHT && Keyboard.getEventKeyState()){
moveIndex();
}
if(Keyboard.getEventKey() == Keyboard.KEY_LEFT && Keyboard.getEventKeyState()){
moveIndexBack();
}
}
}
changeTile function:
public void changeTile(){
boolean start = true;
if(start){
while(Mouse.next()){
if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState()){
//system uses tileTypes because player textures are tiletypes itself, so in the end we can let them swim by changing tiletypes
int xCoord = (int) Math.floor(Mouse.getX() / 64);
int yCoord = (int) Math.floor((HEIGHT - Mouse.getY() - 1) / 64);
tileType tile1 = map[xCoord][yCoord].getType();
System.out.println("first tile is set to:" + tile1);
start = false;
}
}
}
if(!start){
while(Mouse.next()){
if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState()){
int xCoord2 = (int) Math.floor(Mouse.getX() / 64);
int yCoord2 = (int) Math.floor((HEIGHT - Mouse.getY() - 1) / 64);
tileType tile2 = map[xCoord2][yCoord2].getType();
System.out.println("second tile is set to:" + tile2);
}
}
}
bool GameUtil::isValidPath(std::vector<int>& path, Player* player, Game* game) {
///**Get borad*/
std::vector<Square*> board = game->getBoard();
int maxDistanceTravel = 0;
int playerCanTravel = 0;
//first square and last square must be present
if (path[0] == 0 && path[path.size() - 1] == (board.size() - 1)) {
for (int i = 0; i < path.size() - 1; i++) {
/**Max distance of each board from player*/
maxDistanceTravel = compute(board.at(path[i]), player);
playerCanTravel = path[i + 1] - path[i];
if ((playerCanTravel > maxDistanceTravel) && (playerCanTravel <= 0)) {
return false;
}
}
return true;
}
return false;
}
I am a new c++ learner and I am getting the same error over and over again but could not figure out what is wrong with it, it is not obviously an out of range, please help, thanks.
I would first try doing some early checking to make sure you're inputs are valid. Perhaps something like:
bool GameUtil::isValidPath(std::vector<int>& path, Player* player, Game* game) {
assert(!path.empty());
if (path.size() < 2) return false; // or whatever your minimum size is.
As long as you're at it, you should probably scrub the other inputs.
bool GameUtil::isValidPath(std::vector<int>& path, Player* player, Game* game) {
assert(!path.empty());
assert(game);
assert(player);
if (path.size() < 2) return false; // or whatever your minimum size is.
I have a rule of thumb to use references for arguments that should never be null. It looks like in your code that game isn't allowed to be null. So perhaps you could change the function:
bool GameUtil::isValidPath(std::vector<int>& path, Player& player, Game& game) {
assert(!path.empty());
if (path.size() < 2) return false; // or whatever your minimum size is.
And then change where you call. So probably in your code you have something like:
// somewhere buried in your code:
Game * myGame = .../// however you created it.
Game * myPlayer = .../// however you created it.
GameUtil * myGameUtil = .../// however you created it.
...
auto isValid = myGameUtil->isValidPath(path, myPlayer, myGame);
You change it to:
// somewhere buried in your code:
Game * myGame = .../// however you created it.
Game * myPlayer = .../// however you created it.
GameUtil * myGameUtil = .../// however you created it.
...
auto isValid = myGameUtil->isValidPath(path, *myPlayer, *myGame);
Hope that helps.