Простой иерархии игровых класса с обнаружением столкновений


Я пытаюсь сделать переход от 'понять, как автономные функции работают поэтапно писать.. более 'сложные' код, который действительно может видеть использование в реальной жизни, поэтому я пытаюсь найти какой-аккуратный, простой проект, который использует как указатель наследования и связанные с ней функции (то, что я боролся с тем самым). Я придумал, что я представляю себе очень простой 2D платформер игры будет использоваться в качестве класса-архитектурой лица, и хорошо, он работает, но я понятия не имею, если то, как я относился очень хорошо.

Так что в принципе у меня есть Entity тип, который имеет 2 производных классов, Player и Enemy. Каждый Entity также есть коллизии коробка тип Shape, которое может быть Rectangle или Circle (оба производные классы Shape). Основная цель проекта-быть в состоянии вызвать функцию (collides()) от Entity что может обнаружить столкновения с любым другим Entity любые формы столкновения коробки.

После оклейки каждого файла обязательно делать пост слишком длинным, я решил не включать Circle/Enemyпоскольку они практически такие же, как Rectangle/Playerвот остальное правда:

Лица.ч

#pragma once
#include "Shape.h"
#include <memory>

class Entity
{
    public:
        Entity();
        Entity(const double& weight, const double& x, const double& y,
               const double& width, const double& height);
        Entity(const double& weight, const double& x, const double& y, const double& radius);

        virtual void printLine() = 0;
        bool collides(Entity* other);

        inline Shape* getCollisionBox() 
        {
            return m_collisionBox.get();
        }

        inline double getWeight() const
        {
            return m_weight;
        }
    protected:
        double m_weight;
        std::unique_ptr<Shape> m_collisionBox;
};

Entity.cpp

#include "Entity.h"
#include "Rectangle.h"
#include "Circle.h"

Entity::Entity():
    m_weight(0)
{ }

Entity::Entity(const double& weight, const double& x, const double& y,
    const double& width, const double& height) : //Rectangle-shaped entity
    m_weight(weight),
    m_collisionBox(std::make_unique<Rectangle>(Rectangle(x, y, width, height)))
{ }

Entity::Entity(const double& weight, const double& x, const double& y, const double& radius) : 
    m_weight(weight), //Circle-shaped entity
    m_collisionBox(std::make_unique<Circle>(Circle(x, y, radius)))
{ }

bool Entity::collides(Entity* other)
{
    return m_collisionBox->collides(*other->getCollisionBox());
}

Плеер.ч

#pragma once
#include "Entity.h"
#include <string>

class Player: public Entity
{
    public:
        Player();
        Player(const double& weight, std::string name, const double& x, const double& y,
               const double& width, const double& height);

        void printLine() override;

        inline std::string getName() const
        {
            return m_name;
        }
    private:
        std::string m_name;
};

Player.cpp

#include "Player.h"
#include <iostream>

Player::Player():
    Entity(0, 0, 0, 0, 0)
{ }

Player::Player(const double& weight, std::string name, const double& x, const double& y,
    const double& width, const double& height):
    Entity(weight, x, y, width, height),
    m_name(name)
{ }

void Player::printLine() 
{
    std::cout << "Well met." << '\n';
}

Формы.ч

#pragma once

enum class ShapeType
{
    RECTANGLE,
    CIRCLE,
    NONE
};

class Shape
{
    public:
        Shape();
        Shape(const ShapeType& type);

        virtual bool collides(const Shape& other) = 0;

        inline ShapeType getType() const
        {
            return m_type;
        }
    protected:
        ShapeType m_type;
};

Shape.cpp

#include "Shape.h"

Shape::Shape():
    m_type(ShapeType::NONE)
{ }


Shape::Shape(const ShapeType& type):
    m_type(type)
{ }

Прямоугольник.ч

#pragma once
#include "Shape.h"

class Rectangle: public Shape
{
    public:
        Rectangle();
        Rectangle(const double& x, const double& y, const double& width, const double& height);
        bool Rectangle::collides(const Shape& other) override;

        inline double getLeft() const
        {
            return m_x;
        }

        inline double getRight() const
        {
            return m_x + m_width;
        }

        inline double getTop() const
        {
            return m_y;
        }

        inline double getBottom() const
        {
            return m_y + m_height;
        }
    private:
        double m_x, m_y, m_width, m_height;
};

Rectangle.cpp

#include "Rectangle.h"
#include "Circle.h"
#include <algorithm>

Rectangle::Rectangle():
    Shape(ShapeType::RECTANGLE),
    m_x(0), m_y(0), m_width(0), m_height(0)
{ }


Rectangle::Rectangle(const double& x, const double& y, const double& width, const double& height):
    Shape(ShapeType::RECTANGLE),
    m_x(x), m_y(y), m_width(width), m_height(height)
{ }

bool Rectangle::collides(const Shape& other)
{
    switch (other.getType())
    {
        case ShapeType::RECTANGLE:
        {
            auto rec = static_cast<const Rectangle&>(other);
            return (getLeft() <= rec.getRight() && getRight() >= rec.getLeft() &&
                getTop() <= rec.getBottom() && getBottom() >= rec.getTop());
        }
        case ShapeType::CIRCLE:
        {
            auto circle = static_cast<const Circle&>(other);
            auto clamp = [](const double& val, const double& min, const double& max)
            {
                return val < min ? min : (max < val ? max : val);
            };
            double nearestX = clamp(circle.getX(), getLeft(), getRight());
            double nearestY = clamp(circle.getY(), getTop(), getBottom());
            double dX = circle.getX() - nearestX;
            double dY = circle.getY() - nearestY;
            double distanceSquared = (dX * dX) + (dY * dY);
            return distanceSquared <= (circle.getRadius() * circle.getRadius());
        }
        default:
        {
            return false;
        }
    }
}

Так что я думаю, то, что мне наиболее любопытно, как я обработал весь проходящий m_collisionBox вокруг, приведения типов в collides()использование перечислимого класса и встраивание некоторые функции (это действительно стоит делать, если это просто компилятор предложение?), но любую критику/замечания приветствуются. Я думал, что большинство функций довольно тривиально, так что я не беспокоить с комментариями (наверняка бы иначе). Одна вещь, что я, вероятно, следует Прокомментировать использование перечислимого класса, его в основном только для Rectangle и Circle чтобы определить, какой тип Shape параметр в collides().



Комментарии
1 ответ

Есть несколько-передовая техника называется двойной диспетчеризации , которая используется для разрешения ситуаций, когда у вас есть более чем одной неизвестной (виртуальных) тип.

В этом случае, вы используете shared_ptr<Shape> в Entity Так вы будете слепы, как к истинным видам на обе стороны столкновения.

Подход к посетителю-массив в C++ может использоваться, который работает так:


  1. Осуществлять слепой collides(const Entity& other) const метод. Этот метод
    не знает ничего о других сущностей, но не знаю, что это
    собственный тип:

    bool Circle::  // <-- NOTE: Circle 
    collides(const Entity & other) const
    {
    return other.collides_with_circle(*this); // <-- NOTE: circle
    }

    bool Square:: // <-- NOTE: Square
    collides(const Entity & other) const
    {
    return other.collides_with_square(*this); // <-- NOTE: square
    }

    Эти функции не "ничего". Но они рассказать другим, что
    их тип, вызывая различные методы. Поэтому при проверке на
    универсальный "сталкивается", они отвечают: "Вы не знаете, что я
    меня, и я не знаю кто ты, но я круг. Так что я позвоню
    ваш with_circle способ и рассказать вам это".


  2. Реализовать два метода в каждом классе, что ручка столкновения с
    тот или иной класс:

    bool Circle::
    collides_with_circle(...) {...}

    bool Circle::
    collides_with_square(...) {...}

    bool Square::
    collides_with_circle(...) {...}

    bool Square::
    collides_with_square(...) {...}


Обратите внимание, что если столкновения симметричны, возможно, вы захотите делегировать еще один раз в одном классе. То есть, квадрат с кругом может просто вернуться other.collides_with_square так, чтобы только поддерживать кросс-форма столкновения кода в одном классе.

Отметим также, что вы можете использовать перегрузку форм и слепков, чтобы получить тот же результат, определение collides(Circle&) и collides(Square&)тогда звоню collides(static_cast<Circle>(...)) вместо collides_with_circle(). Но это на самом деле проще просто написать название, чем тип из чугуна (который я уверен, был фактор в имена, выбранные для отливки операций)...

3
ответ дан 14 февраля 2018 в 03:02 Источник Поделиться