Here is the code if you are interested. This is as simplified as I could make and thus you should replace it with what you need yourself. (Such as a more complicated hash function)
#include <hash_map>
struct hash_int_pair : public std::hash_compare< std::pair<int,int>>
{
const size_t operator() ( const std::pair<int,int> &p ) const
{
return p.first * 10000 + p.second;
}
bool operator() ( const std::pair<int,int> &a, const std::pair<int,int> &b) const
{
if (a.first == b.first)
{
return a.second < b.second;
}
else
{
return a.first < b.first;
}
}
};
typedef std::hash_map<std::pair<int,int>,std::vector<CUnit>, hash_int_pair> UnitMap;
struct hash_int_pair : public std::hash_compare< std::pair<int,int>>
{
const size_t operator() ( const std::pair<int,int> &p ) const
{
return p.first * 10000 + p.second;
}
bool operator() ( const std::pair<int,int> &a, const std::pair<int,int> &b) const
{
if (a.first == b.first)
{
return a.second < b.second;
}
else
{
return a.first < b.first;
}
}
};
typedef std::hash_map<std::pair<int,int>,std::vector<CUnit>, hash_int_pair> UnitMap;
Anyhow, we move into the world of the basic RTS UI and forget about isometric engine performance right now. We can go back to that when it becomes too slow again. Right now we want to be able to move the mouse around, click, capture the click and translate it into a unit selection box. Nothing fancy. Anything inside the box gets selected. But, there is the standard RTS selection algorithm.
- Select your own units first
- Select enemy units if any, but only the first
- Select animals if any, but only the first
- Select a piece of terrain if it was just a click
Quickly explained, the code basically selects a box along a zig-zagging top, iterate downward to reach the bottom zig-zagging row and then move to the next column toward the right. I'm quite sure there's lots of problems with this right now but I'm just trying to get mouse picking working initially.
void CFaction::getUnitsByArea(std::pair<int,int> firstCorner, std::pair<int,int> secondCorner, std::vector<CUnit*> &unitsInArea, bool getFirstUnitOnly)
{
//convert coordinates into (x+y),(y-x)
std::pair<int,int> convertedFirstCorner = std::pair<int,int>(firstCorner.first + firstCorner.second, firstCorner.second - firstCorner.first);
std::pair<int,int> convertedSecondCorner = std::pair<int,int>(secondCorner.first + secondCorner.second, secondCorner.second - secondCorner.first);
int leftX = convertedFirstCorner.first < convertedSecondCorner.first ? firstCorner.first : secondCorner.first;
int rightX = convertedFirstCorner.first < convertedSecondCorner.first ? secondCorner.first : firstCorner.first;
int topY = convertedFirstCorner.second < convertedSecondCorner.second ? secondCorner.second : firstCorner.second;
int bottomY = convertedFirstCorner.second < convertedSecondCorner.second ? firstCorner.second : secondCorner.second;
//this is actually too fine grain
int currColumnX = leftX;
int currColumnY = topY;
bool moveDownRight = true;
for (int i = 0; i < (Ogre::Math::Abs(convertedFirstCorner.first - convertedSecondCorner.first) + 1); i++)
{
int jMax = Ogre::Math::Abs(float(convertedFirstCorner.second) - float(convertedSecondCorner.second)) / 2.0f;
for (int j = 0; j <= jMax; j++)
{
//shift down
int x = currColumnX + j;
int y = currColumnY - j;
std::pair<int,int> currLocation = std::pair<int,int>(x,y);
for (int i = 0; i < m_unitsByLocation[currLocation].size(); i++)
{
unitsInArea.push_back(&m_unitsByLocation[currLocation][i]);
if (getFirstUnitOnly)
{
return;
}
}
}
if (moveDownRight)
{
currColumnX++;
moveDownRight = false;
}
else
{
currColumnY++;
moveDownRight = true;
}
}
}
What do in Ogre3d land? We create a ray based on the mouse position. Ogre provides an API to do so. We then calculate it's intersection with the XY plane. Ogre also has code to do this. Then we translate the x, y coordinates to a game coordinate (I have a constant called UNIT_DISTANCE in my code).
if (btn == OIS::MB_Left)
{
Ogre::Ray mouseRay = m_nCamera->getCameraToViewportRay(evt.state.X.abs/float(evt.state.width), evt.state.Y.abs/float(evt.state.height));
std::pair<bool, Ogre::Real> intersectionValues = mouseRay.intersects(Ogre::Plane(Ogre::Vector3::UNIT_Z, 0));
Ogre::Vector3 intersectionPoint;
if (intersectionValues.first)
{
intersectionPoint = mouseRay.getPoint(intersectionValues.second);
//translate the intersection point into gameworld coordinates
m_firstCorner = std::pair<int,int>(intersectionPoint.x / UNIT_DISTANCE, intersectionPoint.y / UNIT_DISTANCE);
}
}
And now let's see it!
Okay, so a static image doesn't really help show it. :P
In any case, it will have to be improved with several features. First, I need to draw the selection box. Second, make sure the box works no matter how you define the two corners. Third, add in the ability to use shift and alt clicking which is a pretty standard UI interface. The selection is somewhat inaccurate due to the weird isometric layout, but I'll just leave it alone for now (also because I box based on actual grid squares instead of something more fine).
Side note: std::unordered_map should probably be used in favor of std::hash_map since it's part of the standard and the implementations of std::hash_map are typically built as compiler-specific extensions.
ReplyDeleteThankfully that change could be made without doing anything to my code. I was curious as to why I needed to include hash_map if it was part of std but I suppose I mistook the msdn article as it being part of std lib when it was not.
Delete