Forum Archive

LabelSprite rotate_by problem

amdescombes

Hello everyone,

Newbie question : I am trying to create an animation of a label moving like a pendulum. I tried using a SpriteLabel rotating around an arbitrary point using Action.rotate_by, but it always rotates around the lower left corner of the screen (0, 0) and not around the top center, is there any way around this ?
Here is the exact code I am using:

from scene import *
from math import *

class MyScene(Scene):

    def setup(self):
        self.background_color = 'black'
        self.anchor_point = (0.5, 0.0)
        self.timelabel = LabelNode(position=self.size / 2, text='time')
        self.add_child(self.timelabel)
        self.timelabel.rotation = -pi / 8.0
        self.run_action(Action.repeat(Action.sequence(Action.rotate_by(pi / 4.0, 1, TIMING_EASE_IN_OUT), Action.rotate_by(-pi / 4.0, 1, TIMING_EASE_IN_OUT), 0), 0))

run(MyScene(), PORTRAIT)

Cheers
Andre

omz

Is this close to the behavior you want?

from scene import *
from math import *

class MyScene(Scene):

    def setup(self):
        self.background_color = 'black'
        self.time_anchor = Node(position=(self.size.w/2, 0), parent=self)
        self.timelabel = LabelNode(position=(0, self.size.h/2), text='time')
        self.time_anchor.add_child(self.timelabel)
        rotate_action = Action.repeat(Action.sequence(Action.rotate_to(pi / 4.0, 1, TIMING_EASE_IN_OUT), Action.rotate_to(-pi / 4.0, 1, TIMING_EASE_IN_OUT), 0), 0)
        self.time_anchor.run_action(rotate_action)

run(MyScene(), PORTRAIT)

Things I've changed:

  • Added time_label to a container Node (time_anchor), so it can be rotated around an arbitrary point without rotating the whole scene (your approach of rotating the scene itself becomes problematic when you want to add more things to the scene; also, the anchor_point attribute is ignored for scenes)
  • Changed rotate_by to rotate_to -- this is just a guess, but I think the resulting effect may be closer to what you're after.
amdescombes

That is exactly what I wanted omz, thanks alot!

Cheers
Andre

amdescombes

Hi @omz,

is there a way to include a dynamic variable in an Action?

I was thinking of using the variable self.angle in the Action like this:

from itertools import cycle
from scene import *
from math import *

class MyScene(Scene):

    def setup(self):
        self.background_color = 'black'
        self.time_anchor = Node(position=(self.size.w/2, 0), parent=self)
        self.timelabel = LabelNode(position=(0, self.size.h/2), text='time')
        self.time_anchor.add_child(self.timelabel)
        self.angles = cycle([pi/4, -pi/4])
        rotate_action = Action.repeat(Action.rotate_to(self.angles.next(), 1, TIMING_EASE_IN_OUT), 0)
        self.time_anchor.run_action(rotate_action)

run(MyScene(), PORTRAIT)

This does not seem to work it stays at the same angle all the time, is there any way around it?
Thanks in advance.

Cheers
Andre

abcabc

One way to do is to write your own 'Action.repeat".. In the following code, the angles get changed when you touch. May be others could suggest better ways to this.

from scene import *
from math import *

class MyScene(Scene):
    def repeat(self):
        self.rotate_action = Action.sequence(
            Action.rotate_to(self.angles[0], 1, TIMING_EASE_IN_OUT),
            Action.rotate_to(self.angles[1], 1, TIMING_EASE_IN_OUT), 
            Action.call(self.repeat), 0)
        self.time_anchor.run_action(self.rotate_action)    

    def setup(self):
        self.background_color = 'black'
        self.time_anchor = Node(position=(self.size.w/2, 0), parent=self)
        self.timelabel = LabelNode(position=(0, self.size.h/2), text='time')
        self.time_anchor.add_child(self.timelabel)
        self.angles = (pi/8, -pi/8)
        self.rotate_action = Action.sequence(
            Action.rotate_to(self.angles[0], 1, TIMING_EASE_IN_OUT),
            Action.rotate_to(self.angles[1], 1, TIMING_EASE_IN_OUT), 
            Action.call(self.repeat), 0)
        self.time_anchor.run_action(self.rotate_action)
        self.toggle = True

    def touch_began(self, touch):
        if self.toggle:
            self.angles = (pi/4, -pi/4)
        else:
            self.angles = (pi/8, -pi/8)
        self.toggle = not self.toggle

run(MyScene(), PORTRAIT)



cvp

Try this code, you can easily define the fps (frame per second), you work in degrees and you can define min/max, and you can have more interaction than with run_action:

from scene import *
from math import *

class MyScene(Scene):
    def setup(self):
        self.background_color = 'black'
        self.timelabel = LabelNode(position=self.size/2, text='time')
        self.add_child(self.timelabel)
        self.ang = -45
        self.delta = 1
    def update(self):
        self.ang = self.ang + self.delta
        if abs(self.ang) > 45:
            self.delta = -self.delta
            self.ang = self.ang + self.delta
        self.timelabel.rotation = math.radians(self.ang)
        r = self.size[1]/2
        x = self.size[0]/2+r*sin(math.radians(-self.ang))
        y = r*cos(math.radians(self.ang))
        self.timelabel.position = (x,y)

run(MyScene(), PORTRAIT,frame_interval=1)
abcabc

You can change the first line of the update function to the following code if you want TIMING_EASE_IN_OUT like effect

self.ang = self.ang + self.delta*cos(radians(2*self.ang)) +.05*self.delta

Note that it assumes the maximum angle is 45. If it is different, you need to change 2*self.ang to appropriate expression.

amdescombes

Thank you @abcabc,
creating a new action is great, do I need to do a self.time_anchor.remove_all_actions ()before?
Cheers
Andre

amdescombes

Thanks @cvp,

I was trying to learn automatic actions so I had not considered your approach, I guess I was being lazy :-)

Cheers
Andre

cvp

No problem, I had never used scene thus I'm always happy to try and thus to learn something. Good luck

abcabc

I think that it is not needed since the previous action will be complete by that time. I assume that "run_action" call spawns a thread and
the Action.call does not wait for the thread to complete and it returns immediately. I hope that run_action call overheads are not much.I do not know the internal implementation details and may be omz can answer this.

abcabc

"run_action" may not be spawning a thread and it may just create action related data structures. which will be updated every frame. Anyway other experts ( I am just learning) could help you on this.

amdescombes

Well, it's running just fine!
Thanks to all!
Cheers
Andre