AS3 tracking and using coordinates of a rotated object

人走茶凉 提交于 2019-12-11 11:15:59

问题


How does one track and use the coordinates of an object that is rotated on initialization?

Let's say I have a sword that is put on the stage in Main init(); and rotated (adjusted) so that it would look ok together with the character perspective. In another class however, I am making the sword rotate some more on a keypress timer event so to create a 'swing' animation.

All this is done through flashdevelop. I only used CS6 to create the symbols. And as this 'swing' is happening, I want to add another symbol onto the tip of the sword which is a collision point object. It's being added to the stage when the swing starts and removed after every swing. I want this object to follow the very tip of the sword, yet it seems like I can only achieve that it follows the coordinates of the original sword object, as if I hadn't initially modified the rotation of the said sword. I tried to implement GlobalToLocal() and LocalToGlobal() methods, but I don't think I fully understand what is happening with that.

I hope I'm being clear enough of what I'm trying to do. Thank you. This is the relevant code in question. The code is as was before I tried the two mentioned methods and the issue currently is exactly as described before that. Do I want any of those methods or am I just doing something else wrong?

Main initialization:

sword = new Sword();

sword.x = 53;
sword.y = 90;
addChild(sword);
sword.rotationZ = -150;
sword.rotationY = 25;
sword.rotationX = -15;

Coll_Point = new coll_point();

The class that deals with the swing has a method like this:

private function SwingTime(event:Event):void
{
    Main.Coll_Point.x = Main.sword.x + Main.sword.width;
    Main.Coll_Point.y = Main.sword.y + Main.sword.height;
    Main.MazeNr1.addChild(Main.Coll_Point);

    if (Main.sword.rotationZ > -330)
    Main.sword.rotationZ -= 20;

    if (Main.sword.rotationX < 15)
    Main.sword.rotationX += 10;

    if ((Main.sword.rotationZ == -330) && (Main.sword.rotationX == 15))
    {
        SwingTimer.stop();
        SwingBckTimer.start();
    }
}

Edit: A more holistic version of the code:

public class Main extends MovieClip 
{
    public static var from_point:Point = null;
    public static var to_point:Point = new Point();
    public function Main():void
    {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }

    // Puts everything on the stage here.
    private function init(e:Event = null):void
    {
    removeEventListener(Event.ADDED_TO_STAGE, init);
        PlayerInst = new Dorf();
        PlayerInst.x = 45;
        PlayerInst.y = 51;
        addChild(PlayerInst);

        sword = new Sword();
        sword.x = 53;
        sword.y = 90;
        sword.rotationZ = -150;
        sword.rotationY = 25;
        sword.rotationX = -15;
        from_point = new Point (Main.sword.width, Main.sword.height);
        to_point = sword.localToGlobal(from_point);
        addChild(sword);
        swordBD = new BitmapData(32, 32, true, 0x0000000000);
        swordBD.draw(sword);

        Coll_Point = new coll_point();
        Coll_PointBD = new BitmapData(2, 2, true, 0x0000000000);
        Coll_PointBD.draw(Coll_Point);
    }
}

This is how the Main looks like and literally every single object instantiation is added onto the stage this way. Including collision points, background, characters, gradient fills of line of sight radius, etc. And the relevant symbol class goes somewhat like this:

public class Creature extends MovieClip 
{
    protected var Swing:Boolean;
    private var SwingTimer:Timer = new Timer (5, 0);
    private var SwingBckTimer:Timer = new Timer (150, 1);
// Constructor.
    public function Creature()
    {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }

    // Initializer.
    private function init(event:Event = null):void
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);

        SwingTimer.addEventListener(TimerEvent.TIMER, SwingTime);
        SwingBckTimer.addEventListener(TimerEvent.TIMER, SwingBack);
    }
private function SwingAction():void
    {
        if (Swing == true)
        {
            SwingTimer.start();
        }
    }

    private function SwingTime(event:Event):void
    {
        Main.Coll_Point.x = Main.sword.localToGlobal(Main.from_point).x;
        Main.Coll_Point.y = Main.sword.localToGlobal(Main.from_point).y;
        Main.sword.addChild(Main.Coll_Point);
        trace(Main.Coll_Point.x);
        trace(Main.Coll_Point.y);

        if (Main.sword.rotationZ > -330)
        Main.sword.rotationZ -= 20;

        if (Main.sword.rotationX < 15)
        Main.sword.rotationX += 10;

        if ((Main.sword.rotationZ == -330) && (Main.sword.rotationX == 15))
        {
            SwingTimer.stop();
            SwingBckTimer.start();
        }
    }

    private function SwingBack(event:Event):void
    {
        Main.sword.rotationZ = -150;
        Main.sword.rotationX = -15;
        //Main.MazeNr1.removeChild(Main.Coll_Point);
    }

There is also a rather long update(); function that animates and moves every single object that needs moving.


回答1:


I think your problem might be in

    Main.Coll_Point.x = Main.sword.x + Main.sword.width;
    Main.Coll_Point.y = Main.sword.y + Main.sword.height;

Coll_Point expects global coordinates.

The parts + Main.sword.width and + Main.sword.height only work as expected if the sword is not rotated so that height is aligned with the y-axis and width with the x-axis.

You should use localToGlobal() on the position that is local to Main.sword ( Main.sword.width, Main.sword.height) to get the global position that represents the swords rotated tip before you add it as a child.



There are two ways you can approach this (you seem to have somewhat combined both). You can either


  • Add the Coll_Point as a child to something above the sword in hierarchy (Stage, MazeNr1, ...) and update the position manually every timer callback. You would have to recalculate the position everytime, so take the localToGlobal() from init to your timer function. It won't update if it doesn't get called.

For that you should have this kind of code in the timer callback:

var local:Point = new Point(Main.sword.width, Main.sword.height);
var global:Point = Main.sword.localToGlobal(local);
Main.Coll_Point.x = global.x;
Main.Coll_Point.y = global.y;



  • Add the point as a child to the sword. This might be a better approach as then the position will be updated automatically. What I did not remember before was that you then give the coordinates in "local" form, so do not use localToGlobal()

Run this once where you create the Collision_Point:

Coll_Point.x = <your x offset>;
Coll_Point.y = <your y offset>;
Main.sword.attachChild(Coll_Point);

Instead of sword height and width you might want to try something like -height and width/2.



Here is a quick (and not the prettiest) picture to demonstrate the problem. Local space is rotated with the object:




回答2:


The only thing I can imagine to help you with this problem is to have your collision object have the same registration point as the sword. What I mean is that the orientation points should match, and the collision graphic should be moved inside of the sprite so that it matches the position of the top of the sword.

This way you can put the collision object at the very same location of the sword and apply the very same rotation. This way it will move along with the top of the sword and still have hitTest working properly.

I cannot imagine any other way to figure this out as any code will get bounds and positions. But the real thing that matters is the registration point and the top of the sword, which is a graphic thing and cannot be dealt with coding.

I hope you can imagine what I mean - if now, just say and I will provide an image for you :)



来源:https://stackoverflow.com/questions/24046849/as3-tracking-and-using-coordinates-of-a-rotated-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!