我一直在努力制作我的游戏引擎,并且在处理相对于容器旋转精灵时遇到了很多麻烦。(它用于覆盖多个精灵并能够轻松更改它们相对于对象中心的位置。我需要能够旋转对象并保持它们的相对位置正确)。
我已经得到了它的工作。我可以旋转精灵,所有精灵的相对位置保持不变。我什至可以在移动精灵的同时做到这一点(它会同时移动和旋转)。除非精灵在旋转时再次开始移动。发生这种情况时,精灵会继续保持其相对于容器的方向,但会跳回到相对于容器的原始 X/Y 位置。
我剥离了我的代码以提供一个独立的示例程序,并且错误仍然存在。要对其进行测试,您需要 SFML 2.1 和以下源代码
主文件
#include<SFML\Graphics.hpp>
#include<vector>
#include"sprite_holder.h"
int main()
{
sf::RenderWindow window(sf::VideoMode(640, 640), "Things don't work so gewd :<");
std::vector<sprite_holder> sprite_holders;
sprite_holders.push_back(sprite_holder(50,50));
sprite_holders.push_back(sprite_holder(150,100));
sprite_holders.push_back(sprite_holder(250,50));
sprite_holders.push_back(sprite_holder(350,50));
sprite_holders.push_back(sprite_holder(450,100));
sprite_holders.push_back(sprite_holder(550,50));
float move1 = -100;
float move2 = -100;
float move3 = -100;
float move4 = -100;
sf::Clock timer;
sprite_holders[3].adjust_child_pos(1,0,20,0);
sprite_holders[4].adjust_child_pos(-1,0,20,0);
sprite_holders[5].adjust_child_pos(-1,0,20,0);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear(sf::Color(255,0,255));
for(unsigned int i = 0; i < sprite_holders.size(); i++) {
sprite_holders[i].update(timer.getElapsedTime().asMilliseconds());
for(unsigned int j = 0; j < sprite_holders[i].sprites.size(); j++) {
window.draw(sprite_holders[i].sprites[j].first);
}
}
// This block of code will make the left-most sprite_holder move up and down constantly
if(sprite_holders[0].transform_complete(POSITION)) {
move1 *= -1;
sprite_holders[0].adjust_pos(0,move1,1200);
}
// This block of code will make the sprite_holder second from the left spin constantly
if(sprite_holders[1].transform_complete(ANGLE)) sprite_holders[1].adjust_angle(360,800);
// This block of code will make the sprite_holder third from the left move up and down AND spin constantly
if(sprite_holders[2].transform_complete(ANGLE)) sprite_holders[2].adjust_angle(360,800);
if(sprite_holders[2].transform_complete(POSITION)) {
move2 *= -1;
sprite_holders[2].adjust_pos(0,move2,1200);
}
// This block of code will make the sprite_holder third from the right move up and down constantly
if(sprite_holders[3].transform_complete(POSITION)) {
move3 *= -1;
sprite_holders[3].adjust_pos(0,move3,1200);
}
// This block of code will make the sprite_holder second from the right spin constantly
if(sprite_holders[4].transform_complete(ANGLE)) sprite_holders[4].adjust_angle(360,800);
// This block of code will make the right-most sprite_holder move up and down AND spin constantly
if(sprite_holders[5].transform_complete(ANGLE)) sprite_holders[5].adjust_angle(360,800);
if(sprite_holders[5].transform_complete(POSITION)) {
move4 *= -1;
sprite_holders[5].adjust_pos(0,move4,1200);
}
window.display();
}
return 0;
}
sprite_holder.cpp
#include "sprite_holder.h"
sprite_holder::sprite_holder(float x, float y)
{
xPos = x;
yPos = y;
cAngle = 0;
lastUpdateTime = -1;
// These are here so I could remove some functions to make tracking the error down much simpler.
// Ignore everything from here till the end of the constructor. It's just loading the sprites and texures
// into memory, initializing the offsets to 0, and setting the initial positions
sf::Sprite sprite1;
sf::Texture texture1;
texture1.loadFromFile("heart_back.png");
textures.push_back(texture1);
sprite1.setTexture(textures.back());
sprite1.setPosition(xPos,yPos);
sf::Vector2u spriteSize = textures.back().getSize();
sprite1.setOrigin(spriteSize.x/2, spriteSize.y/2);
offset offset1;
offset1.xOffset = 0;
offset1.yOffset = 0;
offset1.angleOffset = 0;
offset1.tiltOffset = 0;
sf::Sprite sprite2;
sf::Texture texture2;
texture2.loadFromFile("heart.png");
textures.push_back(texture2);
sprite2.setTexture(textures.back());
sprite2.setPosition(xPos,yPos);
spriteSize = textures.back().getSize();
sprite2.setOrigin(spriteSize.x/2, spriteSize.y/2);
offset offset2;
offset2.xOffset = 0;
offset2.yOffset = 0;
offset2.angleOffset = 0;
offset2.tiltOffset = 0;
sprites.push_back(std::make_pair(sprite1,offset1));
sprites.push_back(std::make_pair(sprite2,offset2));
}
// This is called every frame the window is redrawn.
// (note, the reason I calculate everything based on the timeElapsed since last draw is because
// I don't want the application's speed to be bound to it's framerate)
void sprite_holder::update(int timeElapsed)
{
// Because SFML hates textures for some reason, I'd normally have classes containing the sf::Texture, and keep pointer instances of
// said classes. But since that'd complicate the code a little, I'm just re-loading the image every time I need to draw it. That
// keeps the sprites from being white boxes.
for(unsigned int i = 0; i < sprites.size(); i++) sprites[i].first.setTexture(textures[i]);
int transformToRemove = -1;
for(unsigned int i = 0; i<transforms.size(); i++) {
if(transforms[i].first.startTime == -1) transforms[i].first.startTime = timeElapsed; // This makes anything transforms created before the first update use the proper start time
float percentComplete = (float)(timeElapsed - transforms[i].first.startTime) / transforms[i].first.duration; // this calculates what percentage of the transform's time duration has passed
if(transforms[i].first.type == POSITION) {
if( calculate_transform_pos(percentComplete,i) == true ) transformToRemove = i; // This will calculate the x and y value for the current completion percentage of the transform and remove it if it's 100% complete
render_children_pos(); // This sets all the sf::Sprites to their proper location reletive to the sprite_holder
} else {
// This will calculate the angle value for the current completion percentage of the transform and remove it if it's 100% complete
if( calculate_transform_angle(percentComplete,i) == true ) transformToRemove = i;
render_children_angle(); // This sets all the sf::Sprites to their proper location reletive to the sprite_holder
}
}
lastUpdateTime = timeElapsed;
if(transformToRemove != -1) transforms.erase(transforms.begin()+transformToRemove);
}
// This is a function that will let you know if a given transformation has finished.
// It's used so you can do something as soon as a transformation is complete.
// (an example would be to make a sprite rotate forever by telling it to spin by 360 degrees,
// whenever it's finished spinning by 360 degrees)
bool sprite_holder::transform_complete(transform_type type, int spriteIndex)
{
if(lookup_transform(type,spriteIndex) == -1) return true;
return false;
}
// This will move the sprite_holder's center point by
// the x and y values provided. The argument "duration"
// provides a time in miliseconds the movement will take.
// (note, children will keep their reletive position to the sprite_holder)
void sprite_holder::adjust_pos(float x, float y, int duration)
{
int transformIndex = lookup_transform(POSITION);
if(transformIndex == -1) {
transformIndex = transforms.size();
transform newTransform;
newTransform.type = POSITION;
transforms.push_back(std::make_pair(newTransform,-1));
}
transforms[transformIndex].first.startingA = xPos;
transforms[transformIndex].first.startingB = yPos;
transforms[transformIndex].first.targetA = x + xPos;
transforms[transformIndex].first.targetB = y + yPos;
transforms[transformIndex].first.duration = duration;
transforms[transformIndex].first.startTime = lastUpdateTime;
}
// This will rotate the sprite_holder by the angle
// value provided. The argument "duration" provides
// a time in miliseconds the rotation will take.
// (note, children will keep their reletive position and angle to the sprite_holder)
void sprite_holder::adjust_angle(float angle, int duration)
{
int transformIndex = lookup_transform(ANGLE);
if(transformIndex == -1) {
transformIndex = transforms.size();
transform newTransform;
newTransform.type = ANGLE;
transforms.push_back(std::make_pair(newTransform,-1));
}
transforms[transformIndex].first.startingA = cAngle;
transforms[transformIndex].first.targetA = angle + cAngle;
transforms[transformIndex].first.duration = duration;
transforms[transformIndex].first.startTime = lastUpdateTime;
}
// This will move the sf::Sprite specified by spriteIndex
// by the x and y values provided. This movement will last
// as long as the provided duration argument.
void sprite_holder::adjust_child_pos(int spriteIndex, float x, float y, int duration)
{
if (spriteIndex == -1) spriteIndex = sprites.size()-1;
int transformIndex = lookup_transform(ANGLE,spriteIndex);
if(transformIndex == -1) {
transformIndex = transforms.size();
transform newTransform;
newTransform.type = POSITION;
transforms.push_back(std::make_pair(newTransform,spriteIndex));
}
transforms[transformIndex].first.startingA = sprites[spriteIndex].second.xOffset;
transforms[transformIndex].first.startingB = sprites[spriteIndex].second.yOffset;
transforms[transformIndex].first.targetA = x + sprites[spriteIndex].second.xOffset;
transforms[transformIndex].first.targetB = y + sprites[spriteIndex].second.yOffset;
transforms[transformIndex].first.duration = duration;
transforms[transformIndex].first.startTime = lastUpdateTime;
}
// This will rotate the sf::Sprite specified by spriteIndex
// by the angle value provided. This movement will last
// as long as the provided duration argument.
void sprite_holder::adjust_child_angle(int spriteIndex, float angle, int duration)
{
if (spriteIndex == -1) spriteIndex = sprites.size()-1;
int transformIndex = lookup_transform(ANGLE,spriteIndex);
if(transformIndex == -1) {
transformIndex = transforms.size();
transform newTransform;
newTransform.type = ANGLE;
transforms.push_back(std::make_pair(newTransform,spriteIndex));
}
transforms[transformIndex].first.startingA = sprites[spriteIndex].second.tiltOffset;
transforms[transformIndex].first.targetA = angle + sprites[spriteIndex].second.tiltOffset;
transforms[transformIndex].first.duration = duration;
transforms[transformIndex].first.startTime = lastUpdateTime;
}
// This function is called every time a transform updates either an sf::Sprite or the sprite_holder's position
// note: the reason angleOffset is set here is because the reletive angle to the sprite_holder only changes when it's
// reletive position does. It doesn't matter how much you rotate the sprite_holder, the reletive angle of the sf::Sprite is the same
void sprite_holder::render_children_pos()
{
for(unsigned int i = 0; i < sprites.size(); i++) {
sprites[i].first.setPosition(xPos+sprites[i].second.xOffset, yPos+sprites[i].second.yOffset);
sf::Vector2f spritePos = sprites[i].first.getPosition();
sprites[i].second.angleOffset = atan2(spritePos.y - yPos, spritePos.x - xPos);
}
}
void sprite_holder::render_children_angle()
{
for(unsigned int i = 0; i < sprites.size(); i++) {
sf::Vector2f spritePos = sprites[i].first.getPosition();
float radius = sqrt(pow((xPos - spritePos.x), 2) + pow((yPos - spritePos.y), 2)); // xPos and yPos are the container's x and y position
float angle = sprites[i].second.angleOffset + (cAngle /180*3.14); // cAngle is the container's angle
float newX = xPos + radius * cos(angle);
float newY = yPos + radius * sin(angle);
sprites[i].first.setPosition(newX, newY); // this sets the sprite's x and y coordinates
sprites[i].first.setRotation(cAngle + sprites[i].second.tiltOffset); // this sets the spries rotation around it's local axis. it only affects the sprite's orientation, not position
}
}
// This function returns the index of the transform.
// If you give it a spriteIndex value, it'll look for transforms
// bound to an sf::Sprite contained within the sprite_holder.
// Otherwise, it'll look for transforms bound to the sprite_holder.
int sprite_holder::lookup_transform(transform_type type, int spriteIndex)
{
int result = -1;
for(unsigned int i = 0; i < transforms.size(); i++) {
if(transforms[i].first.type == type) {
if(transforms[i].second == spriteIndex) result = i;
break;
}
}
return result;
}
// This returns the value a given percent between the start and end value
// It's so I can calculate what is 33.2687% between 36.2 and 55, or some other such
// nonesense that occurs when moving things around
float sprite_holder::percentage_along_distance(float startValue,float endValue, float percentComplete)
{
return ((1-percentComplete) * startValue) + (percentComplete * endValue);
}
// This function will set either an sf::Sprite or a sprite_holder (the transform provided by transformIndex tells it which to set)
// to the proper location a given percentage through the transformation.
// For example, if the start point of the transform is 0,0, the end point is 0,10, and we tell it we're 50% done with the transform
// it'll set the proper entity to 0,5
bool sprite_holder::calculate_transform_pos(float percentComplete, int transformIndex) // returns true when the transform is finished
{
float x;
float y;
if(percentComplete < 1) {
x = percentage_along_distance(transforms[transformIndex].first.startingA, transforms[transformIndex].first.targetA, percentComplete);
y = percentage_along_distance(transforms[transformIndex].first.startingB, transforms[transformIndex].first.targetB, percentComplete);
} else {
x = transforms[transformIndex].first.targetA;
y = transforms[transformIndex].first.targetB;
}
if(transforms[transformIndex].second == -1) {
xPos = x;
yPos = y;
} else {
int spriteIndex = transforms[transformIndex].second;
sprites[spriteIndex].second.xOffset = x;
sprites[spriteIndex].second.yOffset = y;
}
if(percentComplete >= 1) return true;
return false;
}
// This does the same as calculate_transform_pos, but with angles. I'm planning on merging the two functions, but haven't yet come up with a good way to go about it.
bool sprite_holder::calculate_transform_angle(float percentComplete, int transformIndex) // returns true when the transform is finished
{
float angle;
if(percentComplete < 1) {
angle = percentage_along_distance(transforms[transformIndex].first.startingA, transforms[transformIndex].first.targetA, percentComplete);
} else {
angle = transforms[transformIndex].first.targetA;
}
if(transforms[transformIndex].second == -1) {
cAngle = angle;
} else {
int spriteIndex = transforms[transformIndex].second;
sprites[spriteIndex].second.tiltOffset = angle;
}
if(percentComplete >= 1) return true;
return false;
}
偏移量.h
#ifndef _LBMOON_OFFSET_H
#define _LBMOON_OFFSET_H
struct offset
{
float xOffset;
float yOffset;
float angleOffset;
float tiltOffset;
};
#endif
sprite_holder.h
#ifndef _LBMOON_SPRITE_H
#define _LBMOON_SPRITE_H
#include<SFML\Graphics.hpp>
#include<vector>
#include<string>
#include<utility>
#include <math.h>
#include "transform.h"
#include "offset.h"
class sprite_holder
{
public:
// General Interface Functions
sprite_holder(float x, float y);
void update(int timeElapsed);
bool transform_complete(transform_type type, int spriteIndex=-1);
// Shell functions (transform the whole sprite)
void adjust_pos(float x, float y, int duration);
void adjust_angle(float angle, int duration);
// Child functions (transform a specific drawable)
// note: when passing drawIndex, -1 means the top drawable
void adjust_child_pos(int spriteIndex, float x, float y, int duration);
void adjust_child_angle(int spriteIndex, float angle, int duration);
std::vector<std::pair<sf::Sprite,offset>> sprites;
std::vector<sf::Texture> textures;
protected:
int lookup_transform(transform_type type, int spriteIndex = -1);
void render_children_pos();
void render_children_angle();
float percentage_along_distance(float startValue,float endValue, float percentComplete);
float xPos;
float yPos;
float cAngle;
int lastUpdateTime;
std::vector<std::pair<transform,int>> transforms;
private:
bool calculate_transform_pos (float percentComplete, int transformIndex); // returns true when the transform is finished
bool calculate_transform_angle(float percentComplete, int transformIndex); // returns true when the transform is finished
};
#endif
变换.h
#ifndef _LBMOON_TRANSFORM_H
#define _LBMOON_TRANSFORM_H
enum transform_type{UNKNOWN=-1,POSITION=0,SCALE,ANGLE};
struct transform
{
transform_type type;
int startTime;
int duration;
float startingA;
float startingB;
float targetA;
float targetB;
};
#endif
您还需要将以下图像放入程序中:
http://legacyblade.com/images/heart.png
http://legacyblade.com/images/heart_back.png
我一直在努力让这个类工作几个星期,在测试了一整天以找到这个特别令人困惑的错误的来源之后,我已经干了。所以感谢您阅读本文!你能提供的任何帮助都会很棒。