Implemented some fixes to collider. Made pickables use collider. Partial implementation of stackable objects.

This commit is contained in:
Tiberiu Chibici 2016-12-21 21:49:10 +02:00
parent a7af100122
commit fc2597302b
12 changed files with 221 additions and 91 deletions

View File

@ -67,7 +67,12 @@ void Collider::dump(unsigned level)
for (unsigned i = 0; i < level; i++) for (unsigned i = 0; i < level; i++)
std::cout<<" "; std::cout<<" ";
std::cout << " .Component: Collider\n"; std::cout << " .Component: Collider ";
std::cout << "x=" << collisionRect.x << " ";
std::cout << "y=" << collisionRect.y << " ";
std::cout << "w=" << collisionRect.w << " ";
std::cout << "h=" << collisionRect.h << " ";
std::cout << "solid=" << solid << "\n";
} }
void Collider::onInitialize() void Collider::onInitialize()
@ -109,17 +114,18 @@ void Collider::onUpdateLogic()
if (m_sprite) if (m_sprite)
{ {
auto it1 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y); auto it1 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y);
auto it2 = qtree->find(this, oldBounds.x, oldBounds.y + oldBounds.h);
auto it3 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y + oldBounds.h);
qtree->move(it1, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y); qtree->move(it1, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y);
auto it2 = qtree->find(this, oldBounds.x, oldBounds.y + oldBounds.h);
qtree->move(it2, m_lastBounds.x, m_lastBounds.y + m_lastBounds.h); qtree->move(it2, m_lastBounds.x, m_lastBounds.y + m_lastBounds.h);
auto it3 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y + oldBounds.h);
qtree->move(it3, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y + m_lastBounds.h); qtree->move(it3, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y + m_lastBounds.h);
} }
} }
} }
Collider* Collider::checkCollision() void Collider::checkCollisions(std::unordered_set<Collider*>& results)
{ {
auto qtree = GameState::current().colliderTree; auto qtree = GameState::current().colliderTree;
Assert(qtree != nullptr, "Collider tree not set!"); Assert(qtree != nullptr, "Collider tree not set!");
@ -128,11 +134,9 @@ Collider* Collider::checkCollision()
for (auto it = qtree->lower_bound(searchArea); it != qtree->upper_bound(searchArea); it++) for (auto it = qtree->lower_bound(searchArea); it != qtree->upper_bound(searchArea); it++)
{ {
if (it->data != this && m_lastBounds.intersects(it->data->m_lastBounds)) if (it->data != this && m_lastBounds.intersects(it->data->m_lastBounds) && results.count(it->data) == 0)
return it->data; results.insert(it->data);
} }
return nullptr;
} }
bool Collider::canMove(float newX, float newY) bool Collider::canMove(float newX, float newY)

View File

@ -12,6 +12,8 @@
#include <model/Component.h> #include <model/Component.h>
#include <utils/QTree.h> #include <utils/QTree.h>
#include <unordered_set>
namespace farmlands { namespace farmlands {
namespace components { namespace components {
namespace basic { namespace basic {
@ -35,7 +37,7 @@ namespace basic {
/** /**
* Checks if this object collides with another Collider object. * Checks if this object collides with another Collider object.
*/ */
Collider* checkCollision(); void checkCollisions(std::unordered_set<Collider*>& results);
/** /**
* Tests if this object can move to the given coordinates. * Tests if this object can move to the given coordinates.

View File

@ -23,6 +23,9 @@ namespace basic {
virtual void dump(unsigned level) override; virtual void dump(unsigned level) override;
size_t slot; size_t slot;
bool stackable;
unsigned stackSize;
unsigned maxStackSize;
}; };
} /* namespace basic */ } /* namespace basic */

View File

@ -27,11 +27,16 @@ namespace items {
virtual ~Pickable() { } virtual ~Pickable() { }
virtual model::Component* clone() override; virtual model::Component* clone() override;
virtual void dump(unsigned level) override; virtual void dump(unsigned level) override;
bool stackable;
unsigned maxStack;
}; };
inline model::Component* Pickable::clone() inline model::Component* Pickable::clone()
{ {
return new Pickable(); Pickable* clone = new Pickable();
clone->stackable = stackable;
return clone;
} }
inline void Pickable::dump(unsigned level) inline void Pickable::dump(unsigned level)

View File

@ -127,7 +127,7 @@ void Player::onUpdateLogic()
energy = clamp(energy, 0, maxEnergy); energy = clamp(energy, 0, maxEnergy);
updateMovement(); updateMovement();
updatePickables(); checkCollisions();
} }
void Player::onPreRender() void Player::onPreRender()
@ -293,11 +293,30 @@ void Player::updateMovement()
// Compute movement positions // Compute movement positions
float vx = Input::instance().getX() * velMultiplier; float vx = Input::instance().getX() * velMultiplier;
float vy = Input::instance().getY() * velMultiplier; float vy = Input::instance().getY() * velMultiplier;
if (vx || vy)
{
bool res = canMove(gameObject->transform.x + vx, gameObject->transform.y + vy);
if (!res && vx != 0)
{
// Try moving on the X axis
res = canMove(gameObject->transform.x + vx, gameObject->transform.y);
if (res)
vy = 0;
}
if (!res && vy != 0)
{
// Try moving on the Y axis
res = canMove(gameObject->transform.x, gameObject->transform.y + vy);
if (res)
vx = 0;
}
if (res)
{
float newX = gameObject->transform.x + vx; float newX = gameObject->transform.x + vx;
float newY = gameObject->transform.y + vy; float newY = gameObject->transform.y + vy;
if ((vx || vy) && canMove(newX, newY))
{
gameObject->transform.x = newX; gameObject->transform.x = newX;
gameObject->transform.y = newY; gameObject->transform.y = newY;
@ -308,6 +327,7 @@ void Player::updateMovement()
lookY = newY; lookY = newY;
move(&lookX, &lookY, facingDirection, PlayerLookDistance); move(&lookX, &lookY, facingDirection, PlayerLookDistance);
} }
}
} }
Direction Player::getDirection(float vx, float vy) Direction Player::getDirection(float vx, float vy)
@ -363,36 +383,26 @@ bool Player::canMove(float x, float y)
return m_collider->canMove(x, y); return m_collider->canMove(x, y);
} }
void Player::updatePickables() void Player::checkCollisions()
{ {
// Don't do anything if inventory is full std::unordered_set<Collider*> objects;
if (m_inventory->emptySlots() <= 0) m_collider->checkCollisions(objects);
return;
std::vector<GameObject*> toPickUp; for (auto collider : objects)
for (auto it = m_pickables->childrenBegin(); it != m_pickables->childrenEnd(); it++)
{ {
GameObject* obj = *it; if (collider->gameObject->haveComponent<Pickable>())
Pickable* pickable = obj->component<Pickable>(); pickUpObject(collider->gameObject);
if (pickable)
{
if (checkCollision(gameObject, obj))
{
// We can't modify the container now, so queue the item for picking up
toPickUp.push_back(obj);
} }
// Compute distance from player }
// float dist = distanceSq(gameObject->transform.x, gameObject->transform.y, obj->transform.x, obj->transform.y);
} void Player::pickUpObject(GameObject* obj)
} {
Assert(obj != nullptr, "Can't pick up null.");
Assert(obj->haveComponent<Pickable>(), "Can't pick up a non-pickable.");
for (auto obj : toPickUp)
{
// Object no longer pickable // Object no longer pickable
obj->destroyComponent<Pickable>(); obj->destroyComponent<Pickable>();
obj->destroyComponent<Collider>();
// Make object a child of player // Make object a child of player
m_pickables->removeChild(obj); m_pickables->removeChild(obj);
@ -403,7 +413,6 @@ void Player::updatePickables()
obj->setEnabled(false); obj->setEnabled(false);
obj->transform.x = 0; obj->transform.x = 0;
obj->transform.y = 0; obj->transform.y = 0;
}
} }
} /* namespace player */ } /* namespace player */

View File

@ -77,8 +77,10 @@ namespace player {
void handleActionEvents(SDL_Event& event); void handleActionEvents(SDL_Event& event);
void performAction(model::GameObject* obj); void performAction(model::GameObject* obj);
void checkCollisions();
// Picking up // Picking up
void updatePickables(); void pickUpObject(model::GameObject* obj);
basic::Collider* m_collider; basic::Collider* m_collider;

View File

@ -96,6 +96,11 @@ void FarmlandsGame::onPostRender()
GameState::current().scene->root.onPostRender(); GameState::current().scene->root.onPostRender();
} }
void FarmlandsGame::onFrameEnded()
{
GameState::current().scene->root.onFrameEnded();
}
void FarmlandsGame::stop() void FarmlandsGame::stop()
{ {
m_running = false; m_running = false;
@ -124,6 +129,7 @@ int FarmlandsGame::run()
onPreRender(); onPreRender();
onRender(); onRender();
onPostRender(); onPostRender();
onFrameEnded();
} }
// Cleanup // Cleanup

View File

@ -30,6 +30,7 @@ namespace controller {
void onPreRender(); void onPreRender();
void onRender(); void onRender();
void onPostRender(); void onPostRender();
void onFrameEnded();
void stop(); void stop();

View File

@ -9,6 +9,8 @@
#include <model/GameObject.h> #include <model/GameObject.h>
#include <model/Component.h> #include <model/Component.h>
#include <model/Transform.h> #include <model/Transform.h>
#include <algorithm>
#include <iostream> #include <iostream>
namespace farmlands { namespace farmlands {
@ -18,10 +20,12 @@ GameObject::GameObject()
: name("unnamed"), : name("unnamed"),
visible(true), visible(true),
transform(this), transform(this),
m_components(), m_enabled(false),
m_children(),
m_parent(nullptr), m_parent(nullptr),
m_enabled(false) m_children(),
m_childRemoveQueue(),
m_components(),
m_componentRemoveQueue()
{ {
} }
@ -105,42 +109,48 @@ void GameObject::addChild(GameObject* obj)
GameObject* GameObject::removeChild(GameObject::ChildrenIterator it) GameObject* GameObject::removeChild(GameObject::ChildrenIterator it)
{ {
m_children.erase(it); ChildRemoveQueueItem item =
{
.data = *it,
.free = false
};
m_childRemoveQueue.push_back(item);
(*it)->m_parent = nullptr;
return *it; return *it;
} }
GameObject* GameObject::removeChild(GameObject* obj) GameObject* GameObject::removeChild(GameObject* obj)
{ {
for (auto it = m_children.begin(); it != m_children.end(); it++) ChildRemoveQueueItem item =
{ {
if (*it == obj) .data = obj,
{ .free = false
m_children.erase(it); };
m_childRemoveQueue.push_back(item);
obj->m_parent = nullptr; obj->m_parent = nullptr;
return *it;
}
}
return nullptr; return nullptr;
} }
void GameObject::destroyChild(GameObject::ChildrenIterator it) void GameObject::destroyChild(GameObject::ChildrenIterator it)
{ {
delete *it; ChildRemoveQueueItem item =
m_children.erase(it); {
.data = *it,
.free = true
};
m_childRemoveQueue.push_back(item);
} }
void GameObject::destroyChild(GameObject* obj) void GameObject::destroyChild(GameObject* obj)
{ {
for (auto it = m_children.begin(); it != m_children.end(); it++) ChildRemoveQueueItem item =
{ {
if (*it == obj) .data = obj,
{ .free = true
delete *it; };
m_children.erase(it); m_childRemoveQueue.push_back(item);
return;
}
}
} }
size_t GameObject::childrenSize() const size_t GameObject::childrenSize() const
@ -264,6 +274,18 @@ void GameObject::onPostRender()
for (auto child : m_children) for (auto child : m_children)
if (child->m_enabled && child->visible) if (child->m_enabled && child->visible)
child->onPostRender(); child->onPostRender();
// Process removals at end of frame
processRemovals();
}
void GameObject::onFrameEnded()
{
// Call children
for (auto child : m_children)
child->onFrameEnded();
processRemovals();
} }
void GameObject::onEnable() void GameObject::onEnable()
@ -332,6 +354,38 @@ void GameObject::dumpTree(unsigned level)
child->dumpTree(level + 1); child->dumpTree(level + 1);
} }
void GameObject::processRemovals()
{
// Remove components/children at end of frame
for (auto item : m_childRemoveQueue)
{
auto it = std::find(m_children.begin(), m_children.end(), item.data);
if (it != m_children.end())
{
m_children.erase(it);
if (item.free)
{
item.data->onDestroy();
delete item.data;
}
}
}
m_childRemoveQueue.clear();
for (auto item : m_componentRemoveQueue)
{
m_components.erase(item.index);
if (item.free)
{
item.data->onDestroy();
delete item.data;
}
}
m_componentRemoveQueue.clear();
}
} /* namespace model */ } /* namespace model */
} /* namespace farmlands */ } /* namespace farmlands */

View File

@ -15,9 +15,10 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <queue>
#include <typeindex> #include <typeindex>
#include <vector>
#include <unordered_map> #include <unordered_map>
#include <vector>
namespace farmlands { namespace farmlands {
namespace model { namespace model {
@ -37,8 +38,11 @@ namespace model {
// Constructors // Constructors
GameObject(); GameObject();
virtual ~GameObject(); virtual ~GameObject();
// Cloneable
virtual GameObject* clone() override; virtual GameObject* clone() override;
// Static methods
static GameObject* instantiate(GameObject* gameObject, std::string name, GameObject* parent); static GameObject* instantiate(GameObject* gameObject, std::string name, GameObject* parent);
// Components API // Components API
@ -83,6 +87,7 @@ namespace model {
void onPreRender(); void onPreRender();
void onRender(); void onRender();
void onPostRender(); void onPostRender();
void onFrameEnded();
void onDestroy(); void onDestroy();
void onEnable(); void onEnable();
@ -98,18 +103,39 @@ namespace model {
// Other properties // Other properties
std::string name; std::string name;
bool visible; bool visible;
bool persistent;
Transform transform; Transform transform;
private: private:
// Components struct ComponentRemoveQueueItem
ComponentContainer m_components; {
std::type_index index;
Component* data;
bool free;
};
// Tree struct ChildRemoveQueueItem
ChildrenContainer m_children; {
GameObject* m_parent; GameObject* data;
bool free;
};
typedef std::vector<ComponentRemoveQueueItem> ComponentRemoveQueue;
typedef std::vector<ChildRemoveQueueItem> ChildRemoveQueue;
void processRemovals();
// Properties // Properties
bool m_enabled; bool m_enabled;
// Tree
GameObject* m_parent;
ChildrenContainer m_children;
ChildRemoveQueue m_childRemoveQueue;
// Components
ComponentContainer m_components;
ComponentRemoveQueue m_componentRemoveQueue;
}; };
template <typename T> template <typename T>
@ -160,8 +186,14 @@ namespace model {
auto it = m_components.find(typeIndex); auto it = m_components.find(typeIndex);
if (it != m_components.end()) if (it != m_components.end())
{ {
comp = dynamic_cast<T*>(it->second); ComponentRemoveQueueItem item =
m_components.erase(it); {
.index = typeIndex,
.data = it->second,
.free = false
};
m_componentRemoveQueue.push_back(item);
// it->second->gameObject = nullptr;
} }
return comp; return comp;
@ -176,10 +208,13 @@ namespace model {
auto it = m_components.find(typeIndex); auto it = m_components.find(typeIndex);
if (it != m_components.end()) if (it != m_components.end())
{ {
T* comp = dynamic_cast<T*>(it->second); ComponentRemoveQueueItem item =
m_components.erase(it); {
.index = typeIndex,
delete comp; .data = it->second,
.free = true
};
m_componentRemoveQueue.push_back(item);
} }
} }

View File

@ -107,6 +107,7 @@ void ResourceManager::loadGame()
item->transform.x = x; item->transform.x = x;
item->transform.y = y; item->transform.y = y;
item->addComponent(new components::items::Pickable()); item->addComponent(new components::items::Pickable());
item->addComponent(new components::basic::Collider());
} }
for (auto prefab : GameState::current().seedsPrefabs) for (auto prefab : GameState::current().seedsPrefabs)

View File

@ -196,6 +196,9 @@ components::basic::InventoryItem* parse<components::basic::InventoryItem> (boost
components::basic::InventoryItem* item = new components::basic::InventoryItem(); components::basic::InventoryItem* item = new components::basic::InventoryItem();
item->slot = root.get<size_t>("<xmlattr>.slot"); item->slot = root.get<size_t>("<xmlattr>.slot");
item->stackable = root.get<bool>("<xmlattr>.stackable", false);
item->stackSize = root.get<unsigned>("<xmlattr>.stackSize", 1);
item->maxStackSize = root.get<unsigned>("<xmlattr>.maxStackSize", 1);
return item; return item;
} }
@ -367,6 +370,10 @@ components::items::Pickable* parse<components::items::Pickable> (boost::property
if (root.size() > 0 && root.front().first == "Pickaxe") if (root.size() > 0 && root.front().first == "Pickaxe")
root = root.front().second; root = root.front().second;
components::items::Pickable* pickable = new components::items::Pickable();
pickable->stackable = root.get<bool>("<xmlattr>.stackable", false);
pickable->maxStack = root.get<unsigned>("<xmlattr>.maxStack", 1);
// Parse // Parse
return new components::items::Pickable(); return new components::items::Pickable();
} }
@ -581,6 +588,7 @@ model::GameObject* parse<model::GameObject> (boost::property_tree::ptree& root)
model::GameObject* gameObj = new model::GameObject(); model::GameObject* gameObj = new model::GameObject();
gameObj->name = root.get<std::string>("<xmlattr>.name"); gameObj->name = root.get<std::string>("<xmlattr>.name");
gameObj->visible = root.get<bool>("<xmlattr>.visible", true); gameObj->visible = root.get<bool>("<xmlattr>.visible", true);
gameObj->persistent = root.get<bool>("<xmlattr>.persistent", false);
gameObj->setEnabled(root.get<bool>("<xmlattr>.enabled", true)); gameObj->setEnabled(root.get<bool>("<xmlattr>.enabled", true));
for (auto child : root) for (auto child : root)