相对于它的容器旋转的精灵在移动时有错误

时间:2013-11-14 03:55:34

标签: c++ geometry sfml

我一直致力于制作我的游戏引擎,而且我在处理相对于容器旋转精灵方面遇到了很多麻烦。 (它用于覆盖多个精灵,并且能够轻松地改变它们相对于物体中心的位置。我需要能够旋转物体并保持它们的相对位置正确。)

我得到了相当多的工作。我可以旋转精灵,所有精灵相对位置都保持不变。我甚至可以在移动精灵时执行此操作(它将同时移动和旋转)。除非精灵在精灵转动时再次开始移动。当发生这种情况时,精灵继续保持相对于容器的方向,但会跳回到它相对于容器的原始X / Y位置。

我剥离了我的代码以提供一个独立的示例程序,并且该错误仍然存​​在。要测试它,您需要SFML 2.1和以下源代码

的main.cpp

#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;
}

offset.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

transform.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

我一直在努力让这堂课工作几周,经过一整天的测试,找到这个特别令人困惑的错误的根源,我已经干了。所以感谢您阅读本文!你能提供的任何帮助都会令人惊叹。

0 个答案:

没有答案