Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Kolizje w grze RPG czyli jak opanować Elipsy

Ostatnio zmodyfikowano 2024-05-07 23:07
Autor Wiadomość
tBane
Temat założony przez niniejszego użytkownika
Kolizje w grze RPG czyli jak opanować Elipsy
» 2024-05-01 21:06:52
Witam.
Napisałem system kolizji(w main.cpp), który nie działa i potrzebuję waszej pomocy.
Kolizje generowane są jako przecięcia elips dla obiektów.

Elipsy zawsze mają stosunek promieni 1 - 1/2

 paczka z projektem
https://drive.google.com/file/d/1LHXaJ7SS30S1l3_kiPrKL-nP4blfTajD/view?usp=sharing

https://megawrzuta.pl/download/5f7dc8b4c798ec4a9528ddc32785e21d.html

Textures.hpp
C/C++
#ifndef Textures_hpp
#define Textures_hpp

using namespace std;

class Texture {
public:
   
string name;
   
sf::Texture * texture;
   
float cx, cy; // coordinates of center on the texture
   
   
Texture( string pathfile, float cx, float cy ) {
       
       
name = "";
       
       
int i = pathfile.size() - 1;
       
while( pathfile[ i ] != '/' )
           
 name = pathfile[ i-- ] + name;
       
       
this->cx = cx;
       
this->cy = cy;
       
       
texture = new sf::Texture;
       
texture->loadFromFile( pathfile );
       
       
cout << "load texture: " << pathfile << " as: " << name << endl;
   
}
   
}
;

std::vector < Texture * > textures;

void loadTexture( string pathfile, float cx, float cy ) {
   
textures.push_back( new Texture( pathfile, cx, cy ) );
   
}

Texture * getTexture( string name ) {
   
   
for( auto & t: textures ) {
       
if( t->name == name ) {
           
return t;
       
}
    }
   
   
return nullptr;
}
#endif

GameObjects.hpp
C/C++
#ifndef GameObjects_hpp
#define GameObjects_hpp

class GameObject {
public:
   
   
float x, y, radius;
   
   
GameObject( float x, float y, float radius ) {
       
this->x = x;
       
this->y = y;
       
this->radius = radius;
   
}
   
   
~GameObject() {
    }
   
virtual void update() { }
   
virtual void render( sf::RenderWindow * window ) { }
}
;


#endif

NatureObjects.hpp
C/C++
#ifndef NatureObjects_hpp
#define NatureObjects_hpp

class NatureObject
    : public GameObject
{
public:
   
Texture * texture;
   
sf::Sprite * sprite;
   
sf::CircleShape * collider;
   
   
NatureObject( float x, float y, float radius, Texture * texture = nullptr )
        :
GameObject( x, y, radius )
   
{
       
this->texture = texture;
       
sprite = new sf::Sprite();
       
if( texture != nullptr )
       
{
           
sprite->setTexture( * texture->texture );
           
sprite->setOrigin( texture->cx, texture->cy );
           
sprite->setPosition( x, y );
       
}
       
       
collider = new sf::CircleShape( radius );
       
collider->setOrigin( radius, radius );
       
collider->setScale( 1.0f, 0.5f );
       
collider->setFillColor( sf::Color( 128, 64, 64, 128 ) );
       
collider->setPosition( x, y );
       
       
   
}
   
   
~NatureObject() { }
   
   
void update() { }
   
   
void render( sf::RenderWindow * window ) {
       
window->draw( * sprite );
       
window->draw( * collider );
   
}
}
;

#endif

Player.hpp
C/C++
#ifndef Player_hpp
#define Player_hpp

enum class states { idle, run };

class Player
    : public GameObject
{
public:
   
sf::Texture * idleTextures[ 16 ]; // idle for top, right, bottom, left
   
sf::Texture * runTextures[ 16 ]; // run for top, right, bottom, left
   
sf::Sprite * bodySprite;
   
sf::CircleShape * collider;
   
int direction;
   
int step;
   
float stepSize = 4.0f;
   
states state;
   
   
Player()
        :
GameObject( 0, 0, 32 )
   
{
       
direction = 2;
       
step = 0;
       
state = states::idle;
       
       
// idle textures
       
for( int i = 0; i < 16; i++ )
           
 idleTextures[ i ] = new sf::Texture();
       
       
idleTextures[ 0 ]->loadFromFile( "hero/idleTop0.png" );
       
idleTextures[ 1 ]->loadFromFile( "hero/idleTop1.png" );
       
idleTextures[ 2 ]->loadFromFile( "hero/idleTop2.png" );
       
idleTextures[ 3 ]->loadFromFile( "hero/idleTop3.png" );
       
       
idleTextures[ 4 ]->loadFromFile( "hero/idleRight0.png" );
       
idleTextures[ 5 ]->loadFromFile( "hero/idleRight1.png" );
       
idleTextures[ 6 ]->loadFromFile( "hero/idleRight2.png" );
       
idleTextures[ 7 ]->loadFromFile( "hero/idleRight3.png" );
       
       
idleTextures[ 8 ]->loadFromFile( "hero/idleBottom0.png" );
       
idleTextures[ 9 ]->loadFromFile( "hero/idleBottom1.png" );
       
idleTextures[ 10 ]->loadFromFile( "hero/idleBottom2.png" );
       
idleTextures[ 11 ]->loadFromFile( "hero/idleBottom3.png" );
       
       
idleTextures[ 12 ]->loadFromFile( "hero/idleLeft0.png" );
       
idleTextures[ 13 ]->loadFromFile( "hero/idleLeft1.png" );
       
idleTextures[ 14 ]->loadFromFile( "hero/idleLeft2.png" );
       
idleTextures[ 15 ]->loadFromFile( "hero/idleLeft3.png" );
       
       
// run textures
       
for( int i = 0; i < 16; i++ )
           
 runTextures[ i ] = new sf::Texture();
       
       
runTextures[ 0 ]->loadFromFile( "hero/runTop0.png" );
       
runTextures[ 1 ]->loadFromFile( "hero/runTop1.png" );
       
runTextures[ 2 ]->loadFromFile( "hero/runTop2.png" );
       
runTextures[ 3 ]->loadFromFile( "hero/runTop3.png" );
       
       
runTextures[ 4 ]->loadFromFile( "hero/runRight0.png" );
       
runTextures[ 5 ]->loadFromFile( "hero/runRight1.png" );
       
runTextures[ 6 ]->loadFromFile( "hero/runRight2.png" );
       
runTextures[ 7 ]->loadFromFile( "hero/runRight3.png" );
       
       
runTextures[ 8 ]->loadFromFile( "hero/runBottom0.png" );
       
runTextures[ 9 ]->loadFromFile( "hero/runBottom1.png" );
       
runTextures[ 10 ]->loadFromFile( "hero/runBottom2.png" );
       
runTextures[ 11 ]->loadFromFile( "hero/runBottom3.png" );
       
       
runTextures[ 12 ]->loadFromFile( "hero/runLeft0.png" );
       
runTextures[ 13 ]->loadFromFile( "hero/runLeft1.png" );
       
runTextures[ 14 ]->loadFromFile( "hero/runLeft2.png" );
       
runTextures[ 15 ]->loadFromFile( "hero/runLeft3.png" );
       
       
bodySprite = new sf::Sprite();
       
bodySprite->setOrigin( 32, 58 );
       
       
collider = new sf::CircleShape( 16 );
       
collider->setFillColor( sf::Color( 128, 64, 64, 128 ) );
       
collider->setOrigin( 16, 16 );
       
collider->setScale( 1, 0.5f );
   
}
   
   
~Player() { }
   
   
   
   
void move( int direction ) {
       
state = states::run;
       
       
if( direction != this->direction ) {
           
this->direction = direction;
           
step = 0;
       
}
       
else
       
{ // directions are same
           
if( direction == 0 ) y -= 4.0f;
           
           
if( direction == 1 ) x += 4.0f;
           
           
if( direction == 2 ) y += 4.0f;
           
           
if( direction == 3 ) x -= 4.0f;
           
           
       
}
       
       
    }
   
   
void setPosition( float x, float y ) {
       
this->x = x;
       
this->y = y;
   
}
   
   
void update() {
       
       
step += 1;
       
if( step > 3 )
           
 step = 0;
       
       
if( state == states::run ) {
           
           
state = states::idle;
           
bodySprite->setTexture( * runTextures[ direction * 4 + step ] );
       
}
       
else if( state == states::idle ) {
           
           
bodySprite->setTexture( * idleTextures[ direction * 4 + step ] );
       
}
       
       
bodySprite->setPosition( x, y );
       
collider->setPosition( x, y );
       
   
}
   
   
void render( sf::RenderWindow * window ) {
       
       
window->draw( * bodySprite );
       
window->draw( * collider );
   
}
}
;

#endif

main.cpp
C/C++
#include <SFML/Graphics.hpp>

#include<iostream>

#include "Textures.hpp"
#include "GameObjects.hpp"
#include "NatureObjects.hpp"
#include "Player.hpp"

std::vector < GameObject * > gameObjects;
Player * player;

void loadTextures();
void createGameObjects();
bool collisions( GameObject * object, float dx, float dy );
bool collisionTwoElipses( float x1, float y1, float rx1, float ry1, float x2, float y2, float rx2, float ry2 );

int main()
{
   
sf::RenderWindow * window = new sf::RenderWindow( sf::VideoMode( 1280, 720 ), "RPG" );
   
window->setFramerateLimit( 60 );
   
   
loadTextures();
   
createGameObjects();
   
player = new Player();
   
player->setPosition( 70, 100 );
   
while( window->isOpen() )
   
{
       
sf::Event event;
       
while( window->pollEvent( event ) )
       
{
           
if( event.type == sf::Event::Closed )
               
 window->close();
           
           
if( event.type == sf::Event::KeyPressed ) {
               
// WASD
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::W ) && !collisions( player, 0, - player->stepSize ) ) player->move( 0 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::D ) && !collisions( player, player->stepSize, 0 ) ) player->move( 1 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::S ) && !collisions( player, 0, player->stepSize ) ) player->move( 2 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::A ) && !collisions( player, - player->stepSize, 0 ) ) player->move( 3 );
               
               
// UP DOWN LEFT RIGHT
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::Up ) && !collisions( player, 0, - player->stepSize ) ) player->move( 0 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::Right ) && !collisions( player, player->stepSize, 0 ) ) player->move( 1 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::Down ) && !collisions( player, 0, player->stepSize ) ) player->move( 2 );
               
               
if( sf::Keyboard::isKeyPressed( sf::Keyboard::Left ) && !collisions( player, - player->stepSize, 0 ) ) player->move( 3 );
               
           
}
           
           
        }
//while
       
        // UPDATES
       
cout << "cursor at: " << sf::Mouse::getPosition( * window ).x << "," << sf::Mouse::getPosition( * window ).y << endl;
       
       
for( auto & go: gameObjects )
           
 go->update();
       
       
player->update();
       
       
       
       
// RENDER
       
window->clear( sf::Color( 64, 128, 64 ) );
       
for( auto & go: gameObjects )
           
 go->render( window );
       
       
player->render( window );
       
window->display();
       
sf::sleep( sf::milliseconds( 150 ) );
       
   
} //while
   
return 0;
}

void loadTextures()
{
   
loadTexture( "assets/tree1.png", 58, 106 );
   
loadTexture( "assets/rocks1.png", 67, 83 );
   
}

void createGameObjects()
{
   
// trees
   
gameObjects.push_back( new NatureObject( 100, 50, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 150, 200, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 200, 300, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 300, 30, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 400, 70, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 50, 250, 16, getTexture( "tree1.png" ) ) );
   
   
gameObjects.push_back( new NatureObject( 420, 270, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 650, 100, 16, getTexture( "tree1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 600, 280, 16, getTexture( "tree1.png" ) ) );
   
   
   
// rocks
   
gameObjects.push_back( new NatureObject( 30, 150, 40, getTexture( "rocks1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 320, 170, 40, getTexture( "rocks1.png" ) ) );
   
gameObjects.push_back( new NatureObject( 350, 350, 40, getTexture( "rocks1.png" ) ) );
   
}

bool collisions( GameObject * object, float dx, float dy )
{
   
for( auto & go: gameObjects ) {
       
if( go != object && collisionTwoElipses( object->x + dx, object->y + dy, object->radius, object->radius / 2.f, go->x, go->y, go->radius, go->radius / 2.f ) )
           
 return true;
       
   
}
   
   
return false;
}

bool collisionTwoElipses( float x1, float y1, float rx1, float ry1, float x2, float y2, float rx2, float ry2 )
{
   
// Obliczamy odległość między środkami elips
   
float dx = x2 - x1;
   
float dy = y2 - y1;
   
float d = sqrt( dx * dx + dy * dy );
   
   
// Sprawdzamy czy suma promieni jest większa niż odległość między środkami elips
   
if( d <= rx1 + rx2 && d <= ry1 + ry2 )
       
 return true;
   
else
       
 return false;
   
}
P-180997
DejaVu
» 2024-05-02 16:43:45
Powinieneś skupić się na implementacji znanych/popularnych rozwiązań do kolizji takich jak prostokąty i okręgi, a nie wymyślać sobie 'własne' kształty, jeżeli nie jesteś w stanie sam sobie zaimplementować algorytmu do wykrywania kolizji.

Możesz sobie wygooglać, że ChatGPT 4 napisał znacznie sensowniejszą odpowiedź niż to co Ty wkleiłeś (zapewne z ChatGPT 3.5), co znajduje swe potwierdzenie na Stackoverflow:

https://stackoverflow.com/questions/2945337/how-to-detect-if-an-ellipse-intersectscollides-with-a-circle
P-180999
tBane
Temat założony przez niniejszego użytkownika
» 2024-05-03 02:32:27
Rozumiem, jednak w mojej grze potrzebowałem wykrywać kolizję elips. Sam zobacz jak to wygląda na screenie.

P-181001
DejaVu
» 2024-05-03 16:00:13
No to albo zrób kolizje pixel perfect, albo dostosuj rozwiązanie do prostokątów i okręgów.
P-181002
tBane
Temat założony przez niniejszego użytkownika
» 2024-05-03 17:34:05
pixel perfect jest powolne moim zdaniem, poza tym po co skoro można użyć prostych elips.


C/C++
#ifndef Collisions_hpp
#define Collisions_hpp

bool collisionTwoElipses( float x1, float y1, float rx1, float ry1, float x2, float y2, float rx2, float ry2 )
{
   
// obliczamy kat elipsy wzgledem elipsy
   
float len = sqrt( pow( x1 - x2, 2 ) + pow( y1 - y2, 2 ) );
   
float angle = atan2( y2 - y1, x2 - x1 );
   
   
// wyznaczamy punkt lezace na granicy jednej elipsy
   
float px = x1 + rx1 * cos( angle );
   
float py = y1 + ry1 * sin( angle );
   
   
// sprawdzamy czy punkt znajduje sie wewnatrz drugiej elipsy
   
if( pow(( px - x2 ) / rx2, 2 ) + pow(( py - y2 ) / ry2, 2 ) <= 1 )
       
 return true;
   
   
return false;
}

bool collisions( GameObject * object, float dx, float dy )
{
   
for( auto & go: gameObjects ) {
       
if( go != object && collisionTwoElipses( object->x + dx, object->y + dy, object->radius, object->radius / 2.f, go->x, go->y, go->radius, go->radius / 2.f ) )
           
 return true;
       
   
}
   
   
return false;
}

#endif  // !Collisions_hpp
P-181003
tBane
Temat założony przez niniejszego użytkownika
» 2024-05-07 21:44:00
Mam problem z kodem, a chat GPT nie pomógł. Wydaje mi się, że mam poprawny kod, ale nie działa. Funkcja playerInViewRange(); zawsze zwraca false
C/C++
// collision
bool intersectionsTwoElipses( float x1, float y1, float rx1, float ry1, float x2, float y2, float rx2, float ry2 )
{
   
// obliczamy kat elipsy wzgledem elipsy
   
float len = sqrt( pow( x1 - x2, 2 ) + pow( y1 - y2, 2 ) );
   
float angle = atan2( y2 - y1, x2 - x1 );
   
   
// wyznaczamy punkt lezace na granicy jednej elipsy
   
float px = x1 + rx1 * cos( angle );
   
float py = y1 + ry1 * sin( angle );
   
   
// sprawdzamy czy punkt znajduje sie wewnatrz drugiej elipsy
   
if( pow(( px - x2 ) / rx2, 2 ) + pow(( py - y2 ) / ry2, 2 ) <= 1 )
       
 return true;
   
else
       
 return false;
   
}

// ...

bool Beast::playerInViewRange() {
   
if( intersectionsTwoElipses( x, y, radius + viewRange,( radius + viewRange ) / 2.0f, player->x, player->y, player->radius, player->radius / 2.0f ) ) {
       
cout << "player is in range " << name << "\n";
       
return true;
   
}
   
   
cout << "player is not in range " << name << "\n";
   
return false;
   
}

void Beast::update() {
   
if( playerInViewRange() ) {
       
// TO-DO
       
target_x = player->x;
       
target_y = player->y;
       
state = states::run;
       
cout << name << " follow the player\n";
   
}
   
   
if( state == states::idle ) {
       
// ...
   
}
   
else if( state == states::run ) {
       
// ...
   
}
   
P-181034
DejaVu
» 2024-05-07 22:12:03
Czytałeś komentarze na temat wykrywania kolizji w elipsach?

Przecięcie się dwóch elips

Tam napisałem, że to nie jest takie proste, a Ty stwierdziłeś, że masz 'działającą' implementację.

W tym temacie również Ci napisałem co na ten temat uważają deweloperzy ze StackOverflow:

https://stackoverflow.com/questions/2945337/how-to-detect-if-an-ellipse-intersectscollides-with-a-circle
P-181035
tBane
Temat założony przez niniejszego użytkownika
» 2024-05-07 22:15:33
Ale dla innej funkcji to przecięcie się dwóch elips działa.

C/C++
void playerAttack() {
   
   
float x, y, rx, ry;
   
x = player->x;
   
y = player->y;
   
rx = player->radius + player->attackRange;
   
ry =( player->radius + player->attackRange ) / 2.0f;
   
   
for( auto & b: beasts )
   
{
       
if( b->type == gameObjectType::Beast ) {
           
if( intersectionsTwoElipses( x, y, rx, ry, b->x, b->y, b->radius, b->radius / 2.0f ) ) {
               
b->takeDamage( 2 );
           
}
        }
    }
}
P-181036
« 1 » 2
  Strona 1 z 2 Następna strona