Forum Archive

[SPACE ESCAPE]-Game example to help with Game dev

stephen

Here is a little game to help new develpoers to game design get started. there is a tutorial in exmples but its very minimal and doesnt give examples of diferent styles of writing. this currently is not a tutorial. and it is not flooded with comments so that its easier to follow the code. any questions are more than welcomeand i will add annotations as needed.

the game itssrlf is not perfect and does have bugs that pop up here and there. this is mainly due to using multiple styles at once creating small conflicts but the game does play and should be easy to expand nd add aditional components. i tried to leave it in a way that made it to whoever was using it could add functionality to help learn and understand game development. all images are builtin and script should run without any dependancies as long as its ran within Pythonista.

Enjoy
Stephen, Frey

Script

screen_shot

stephen

@Karina @Drizzel sorry it took so long and i hop this helps

stephen

@cvp i had my baby brother play it and he got upset he couldnt keep it on his phone lol told him id have to build on a mac and i dont have one so he told me when he comes over he wants to play lol. i say baby brother but hes 28 lol

cvp

@stephen My upvote was for the script, not for the game ๐Ÿ˜€
I have always been to old for electronic games. I remember I had already this kind of discussion with my kids with Tandy, PC, Nintendo, PlayStation games. My only pleasure was to write, for instance in Basic on a Tandy, my own HangMan and so on. Now, my kids have them-selves their kids and they still play.
Some months ago, one of my grandchildren asks me to play tennis on their Wii and the only thing I have done is to break their chandelier while serving ๐Ÿ˜‚ and that isn't a joke.
Thus You can see my level (a lot better in real tennis than in virtual one)

stephen

i broke a tv trying the Wii sports lol never played again .. ๐Ÿ˜… as for the script, from your perspective it probably looks really messy lol

cvp

@stephen said:

from your perspective it probably looks really messy lol

Surely not, sincerely, I'm a very bad Python programmer, really honestly.
I agree that for @mikael, @ccc, @JonB and a lot of other ones, your code would be not perfect, exactly like for all my scripts, but is it so important ?

stephen

@cvp in game development it kind of is. generally you would have a team and inside that team you have graphic animators and world/level designers and they would have minimal coding abilities. so the programers would have to follow a style so that these team members can add their content without having to add cost to budget for time delay for rewriting or debugging. im sure this goes for any group project but its really important with video games โ˜บ๏ธ

it was a bit hard for me to not rewrite some parts where i show diferent coding styles lol but i stuck in there for the most part lol i do need to make this format for iphone though i always forget that it doesnt do this automaticaly

cvp

@stephen I agree. I have also programmed in very big projects of several man-years code and we were all very good in our programming language, and we had to follow some rules to be sure all our modules were coherent and understandable by others.
When I said that, I mean for my little own projects with only one user, like my wife, a friend or me.

stephen

ohhhh lol ok ya i used to have projects with my brother inlaw and i used to hide stuff in the code and he would find them as bugs and get real excited but then i stopped and he would just find my horrible code and get upset ๐Ÿ˜‚๐Ÿ˜‚

stephen

@JonB @mikael @ccc and anyone else that might be able to explain lol

I decided to setup some checks and value sets to format sizes and scales so that the game will run on iphone and i get a very odd syntax error on line 702 but this only appears when i run it on the phone... Any idea what is causing this? or does this not even happen for you?
the error gives no info other than annotating the line

cvp

@stephen same iOS, Pythonista, Python 2or3?

mikael

@stephen, runs on my iPhone, no syntax error.

stephen

@cvp said:

@stephen same iOS, Pythonista, Python 2or3?

had no idea my phone was set to 2.7... should of checked this when it said f' ' was incorect.. thank you @cvp @mikael

Karina

@stephen said:

@Karina @Drizzel sorry it took so long and i hop this helps

@stephen no problem. Donโ€™t know how I will deal with it now cause itโ€™s a lot of code for me, my brain is already overwhelmed๐Ÿคฏ

stephen

@Karina just play with it. i separated everything the way i did so you can change somthing then play it to see how your changes had effect. the best way to learn is to have fun with it. and if you break it past return lol just start over. i can answer any questions and at some point you can add to it. making new brushes and adding new levels ๐Ÿ™ƒ๐Ÿ˜‰๐Ÿ˜Ž๐Ÿค“

stephen

@Karina please dont hesitate with any questions. and if it will help i can break the code into hunks according to where you might be stuck on

Karina

@stephen

@stephen said:

@Karina please dont hesitate with any questions. and if it will help i can break the code into hunks according to where you might be stuck on
Okay i'll take a break from it and later. I have an idea of another game to write, in my head only for now

stephen

@Karina No Problem. its tough at first. just hang in there and it will be second nature before you know it

Karina

@stephen yes, not long ago oop was to me like ๐Ÿคฏ

stephen

@Karina said:

@stephen yes, not long ago oop was to me like ๐Ÿคฏ

i started roughly 6 years ago. first with C# then C++ then a mixture of java/html/css and now python lol until python it was all video game related. i started pyton because at the time i phly had an iPhone and i needed to write a program that generated WorkOrders for Excel that the place i was working and ive now made it my gol/mission to makevan RPG with decent quality and functionality expected from all platforms. mainly because of all the reviews saying Python was horible for creating games at the magnitude ive set my goal to. so far it has been wonderfull. my first project i ever finished was an inventory system writen in C# with MonoBehavior API on Unity3D. was a ๐Ÿคฏ crash course.. but still fun lol

Karina

@stephen so you've mainly focused on games?
Unity 3D is for 3D games as I think. Quite a difficult thing for beginning
And pythonista just for fun?

Karina

I tried to download postgresql in theese days and all the time problems...
And pythonista when I don't have pc with me

stephen

@katrina Pythonista and python at first was for my Work Order project. mainly because i needed mobility. After that project was finished i guess it became an educational mission to add Python to my Resume ๐Ÿ™ƒ i have alwys done Game Dev because i find its the best way to learn a new Language. at least for myself. how is your game coming along?

Karina

@stephen I began to read ui cause in many games need it. It seems more difficult than scene, maybe because there's no tutorial like in scene
I want to build four buttons up, down, right, left and when you tap it, it prints down, left and so on. I'm between your alien, mine and this, helps to change what I'm doing

stephen

@Karina yes it does help alot. ui is not too bad it does have more components such as:

  • Button
  • SegmentControl
  • TableView
  • WebView
  • NavigationView
  • ScrollView
  • Slider
  • Switch (Radeo buttons)
  • Label
  • TextView
  • DatePicker
  • ListDataSource
  • Image
  • ImageView
  • View

plus a few more but those seem to be the main ones.

where for scene you have:

  • SpriteNode
  • LabelNode
  • Texture
  • Scene
  • EffectNode
  • SceneView

scene is an extension of ui and the Node subclasses are View subclasses just enhanced for a VideoGame Environment.

i worded that last bit weird but im not sure how to write it lol sorry..

For example:

  • ui.Image โ‡’ sceneTexture

    loads the Image into the GPU PipeLine

  • ui.ImageView โ‡’ scene.SpriteNode

    Provides OpenGL functionality for custom shaders-

  • ui.Label โ‡’ scene.LabelNode

    Simple text to screen but you get more control of visual components

As for the rest you can still achieve, just takes more work to build from scratch. So to make a button in scene you could do the following for example:


import scene

class EventHandler:
    children=[]
    event_loop=None

    @classmethod
    def add_child(cls, node):
        cls.children.append(node)
        if cls.event_loop == None:
            cls.event_loop = node.scene


    @classmethod
    def touch_began(cls, touch):
        for child in cls.children:

            if cls.event_loop.point_from_scene(touch.location) in child.frame:
                child.Button_Tapped()


class ButtonNode(scene.SpriteNode):
    def __init__(
        self, name='ButtonNode', bgTexture='card:BackBlue1', 
        tint='white', border='pzl:Button1', text_color='white',
        borderColor='#000d98', accessoryColor='red', 
        icon='plf:Tile_BoxItem', accessory='emj:Exclamation_Mark_2',
        anchor_point=(0.5, 0.5), font_family='Helvetica', font_size=16, parent=None, 
        x=0, y=0, w=64, h=64, action=None, text='', enabled=True, *args, **kwargs):

        self.texture=self._init_textures(border)
        self.icon=self._init_textures(icon)
        self.accessory=self._init_textures(accessory)
        self.border=self._init_textures(bgTexture)
        self.name=name
        self.enabled=enabled
        self.text=text
        self.font_family=font_family

        self.color=tint
        self.text_color=text_color
        self.borderColor=borderColor
        self.showAccesory=False
        self.accessoryColor=accessoryColor
        self.components=dict({
            'border':None,
            'accessory':None,
            'icon':None,
            'label':None})
        self.font_size=font_size
        self.parentNode=parent
        self.x, self.y, self.w, self.h=x, y, w, h
        self.rect=scene.Rect(x, y, w, h)
        self.position=scene.Point(x, y)
        self.size=scene.Size(w/100*80, h/100*80)
        self.button_action=action
        self.anchor_point=anchor_point
        super().__init__(texture=self.texture, parent=parent, *args, **kwargs)
        self._setup(self.border, self.icon, self.accessory, self.components)

    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    def _setup(self, b, i, a, c):
        EventHandler.add_child(self)
        if b != None:
            c['border']=scene.SpriteNode(
                texture=b, size=self.size/100*95, 
                parent=self, z_position=0, color=self.borderColor)
        if a != None:
            c['accessory']=scene.SpriteNode(
                texture=a,
                size=self.size/5,
                position=scene.Point(-self.size[0]/2+20, self.size[1]/2-15),
                parent=self,
                z_position=4,
                color=self.accessoryColor)
        if i != None:
            c['icon']=scene.SpriteNode(
                texture=i,
                size=scene.Size(self.size[1]/3, self.size[1]/3),
                position=scene.Point(self.w - self.size[1]/4 , 0),
                parent=self, 
                z_position=9)
        if self.text:
            c['label']=scene.LabelNode(
                text=self.text,
                font=(self.font_family, self.font_size),
                position=scene.Point(-self.w/4 , 0),
                anchor_point=(0.5, 0.5),
                color=self.text_color,
                parent=self,
                z_position=10)

    def Button_Tapped(self):
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)


#---------------------------------------------------------------------------


def my_button_action(sender):

    print(sender.text)

class main(scene.Scene):
    def setup(self):
        self.my_button_up=ButtonNode(
                    text='up', 
                    parent=self,
                    position=scene.Point(self.size[0]/2, self.size[1]/2+100),
                    size=scene.Size(150, 75),
                    action=my_button_action)
        self.my_button_right=ButtonNode(
                    text='right', 
                    parent=self,
                    position=scene.Point(self.size[0]/2+100, self.size[1]/2),
                    size=scene.Size(150, 75),
                    action=my_button_action)
        self.my_button_down=ButtonNode(
                    text='down', 
                    parent=self,
                    position=scene.Point(self.size[0]/2, self.size[1]/2-100),
                    size=scene.Size(150, 75),
                    action=my_button_action)
        self.my_button_left=ButtonNode(
                    text='left', 
                    parent=self,
                    position=scene.Point(self.size[0]/2-100, self.size[1]/2),
                    size=scene.Size(150, 75),
                    action=my_button_action)

    def touch_began(self, touch):
        EventHandler.touch_began(touch)

scene.run(main())

ui.button is much faster.. but by using scene i had 100% control of the design of the button.

note: dont know if you recognise EventHandler class but i use a control class like this all the time. you can also just use your Scene's touch methods to and cut out the extra step. sorry if that created confusion

you can run both if you like to use ui you can create a scene.SceneView and link it to your Scene class then add it to your MainViews subviews then you have both ๐Ÿ˜Š.


Aside from explaining quickly about scene and ui the example for a ButtonNode should also help you with your curent situation ๐Ÿ˜Š๐Ÿ™ƒ Enjoy!

cvp

@stephen said:

ui is not too bad

I disagree. ui is wonderful ๐Ÿ˜€

stephen

@cvp said:

I disagree. ui is wonderful ๐Ÿ˜€

oh yes it is very womderful but ๐Ÿคซ dont tell scene i said anything... she has quite the temper.. ๐Ÿ˜ฌ

stephen

@Karina here is a link to a more standard style ButtonNode that i just put on my Github.

Karina

@cvp I didnโ€™t said itโ€™s bad

cvp

@Karina no problem, it was only a joke about @stephen sentence ๐Ÿ˜€

stephen

@Karina said:

@cvp I didnโ€™t said itโ€™s bad

i was refering to scene module shen i said she btw ๐Ÿ™ƒ. just incase there was any confusion lol ๐Ÿ˜‚๐Ÿ˜‚

Karina

What's wrong with the scene then?)

Karina

You have 3 touch_began in the game that refer to each other and it's brainstorming) can you explain how it works all together?

def touch_began(cls, touch):
        if(cls.needsTouch):
            #i didn't see where you add smth into needsTouch
            for node in cls.needsTouch:
                node.touch_began(touch)
        cls.player.touch_began(touch)
        cls.allTouches.append(touch)

And you have here "if cls.needsTouch" but I didn't find where you add smth to the list

Karina

@cvp said:

@Karina no problem, it was only a joke about @stephen sentence ๐Ÿ˜€

Ah, okay

she has quite the temper.. ๐Ÿ˜ฌ

What temper they both have?)

cvp

@Karina wow, if you check my post, the sentence about I spoke was "ui is not too bad", not at all something else. It was only a personal reaction about the fact that I find Pythonista's ui marvelous and not "not too bad". Nothing about scene.

stephen

@Karina said:

she has quite the temper.. ๐Ÿ˜ฌ

What temper they both have?)

lol it was just a little joke. i was saying to @cvp that i agreed about ui being a great module and to not tell scene i said this because scene would get Jealous and not work for me lol. sorry for th infusion ๐Ÿค“๐Ÿ™ƒ

stephen

@Karina Wonderfull Eye ๐Ÿ˜Ž๐Ÿค“ lets look t wht we got here.


player=None
needsTouch=[]
allTouches=[]

    def touch_began(cls, touch):
        if(cls.needsTouch):
            for node in cls.needsTouch:
                node.touch_began(touch)
        cls.player.touch_began(touch)
        cls.allTouches.append(touch)

    def touch_moved(cls, touch):
        if(cls.needsTouch):
            for node in cls.needsTouch:
                node.touch_moved(touch)
        cls.player.touch_moved(touch)

    def touch_ended(cls, touch):
        if(cls.needsTouch):
            for node in cls.needsTouch:
                node.touch_ended(touch)
        cls.player.touch_ended(touch)

First id like to start with the fact i did miss somthing here initially.
Another was left purposly as a social excersise and other is for future use for Menu buttons. Ill explain ๐Ÿ˜Š


cls.player.touch_began(touch)

this i completely missed... i forgot o do my Null check and if this were much large of game it would of crested issues. to fix this just add the check for it just like for needsTouch.


...

if(cls.player):
    cls.player.touch_ended(touch)

...


cls.needsTouch

here we are setting up touch to be used for objects such as Buttons for menus. since we do our null check it gets bypassed every time until we need it so in th future we can make a button object, just like we did yesterday with ButtonNode and here we used EventHandler, here instead we use EventManager exact same use so from your buttond __init__ you use EventManager.needsTouch.append(self) and insur your button has
def touch_began(self, touch): Method nd all done. set all your touch logic from the button class just to keep everything separated would be my suggestion. ๐Ÿค“


cls.allTouches.append(touch)

this one was left purposly to give an oprotunity for curiosity and get people to ask questions. exactly as you did.. ๐Ÿค  with out curiosity theres no advancement and without questions there no answers ๐Ÿ˜‰. so 3 โญ๏ธโญ๏ธโญ๏ธ !!! 1 for allTouches and two for my null check misshap.


from here you cand add the null chek if you like and remove both cls.allTouches.append(touch) and allTouches=[]. i would suggest keeping the cls.needsTouch if you plan to build more on it but its your project ๐Ÿค ๐Ÿ˜Š

Karina

Aa, I thought maybe the modules do bugs in serious programs. But they are perfect match, no place to those things๐Ÿ˜‰

Karina

So which of the touches needed for game or for future?

def touch_began(self, touch):
        self.ChangePose(1)
        self.idleJet.remove_from_parent()
        self.jetpack.add_child(self.liftJet)
        self.animate.Ascend(duration=0.15)

        if self.isDead != False:
            self.scene.gameOver=True
            self.isDead=True
            self.ChangePose(4)

And why you are ending the game in touch_began? He will be hit when touch began?

Karina

scene is an extension of ui and the Node subclasses are View subclasses just enhanced for a VideoGame Environment.

Then maybe it's better to learn first ui
@stephen those things you wrote are scene-ui features? Like gpu pipeline, custom shaders, openGL?

Karina

@stephen why in the scene you sometimes pass texture like a built in image, like SpriteNode(texture='plz:alien'), sometimes create texture object self.texture = Texture()? The same with position - sometimes pass it in tuple, sometimes create Point(). What's the difference?

stephen

@Karina said:

scene is an extension of ui and the Node subclasses are View subclasses just enhanced for a VideoGame Environment.

Then maybe it's better to learn first ui
@stephen those things you wrote are scene-ui features? Like gpu pipeline, custom shaders, openGL?

you can do both at the same time if you want. and all those are Examples of stuff that re Graphics card(GPU) focused not CPU

stephen

@Karina said:

@stephen why in the scene you sometimes pass texture like a built in image, like SpriteNode(texture='plz:alien'), sometimes create texture object self.texture = Texture()? The same with position - sometimes pass it in tuple, sometimes create Point(). What's the difference?

if your subclassing SpriteNode you must explicitly pass a Texture object. but if you are just creating a SpriteNode object like tree = SpriteNode(...) the SpriteNode class Implicitly Converts a string or ui.Image object to a Texture object but you can still Pass Texture object if you wish.

same goes with Point and Size. you dont have to expliitly create them but i try to. they are subclasses of scene.Vector2 i mainly do this because if i create a Size or Point object that normally would just be a normal 2 Tuple then that gets the extra attributes and thats checkout the Docs For them real quick.

stephen

@Karina said:

So which of the touches needed for game or for future?

if you want to use the one i placed in preperation its going to be,


    if(cls.needsTouch):
        for node in cls.needsTouch:
            node.touch_began(touch)

@Karina said:

```
def touch_began(self, touch):
self.ChangePose(1)
self.idleJet.remove_from_parent()
self.jetpack.add_child(self.liftJet)
self.animate.Ascend(duration=0.15)

  if self.isDead != False:
      self.scene.gameOver=True
      self.isDead=True
      self.ChangePose(4)

```

And why you are ending the game in touch_began? He will be hit when touch began?


    '''
        Step 1:
            if Main.gameOver has not been enabled
            send the Touch object to EventManager

        Main =โฏ EventManager
    '''
    # Main()
    def touch_began(self, touch):
        if(self.gameOver == False):
            EventManager().touch_began(touch)
        if(EventManager.canClose and self.gameOver):
            self.view.close()
    '''
        Step 2:
            send Touch object to Player

        EventManager =โฏ Player
    '''
    # EventManager
    def touch_began(cls, touch):
        if(cls.player):
            cls.player.touch_began(touch)
    '''
        Step 3:
            if Player.isDead has been enabled
            we insure Main.gameOver and 
            Player.isDead are set then run the 
            player.ChangePose() for game over.

            we insure Player.isDead is set even
            though wevjust got avtrue return because 
            anything could hapen in a frame or two and
            we dont want out player to end its run and 
            our game still run. 

        Player =โฏ End of Touch objects life.
    '''
    # Player
    def touch_began(self, touch):
        if self.isDead != False:
            self.scene.gameOver=True
            self.isDead=True
            self.ChangePose(4)
    '''
        Step 4:
            if Main.gameOver has been enabled
            and our EventHandlercloseTimer still
            has more than 0 sec we remove delta
            Time (time since last frames) every frame 

            once 0 we set EventHandler.canClose enabled
    '''
    # EventManager
    def Pass(cls, dt, game):
        if(game.gameOver):
            if(cls.closeTimer > 0):
                cls.closeTimer -= dt
                if(cls.closeTimer < 0):
                    cls.canClose=True
    '''
        Step 5:
            now that both EventManager.canClose
            and Main.gameOver are both True we have
            ran all our events and can close the game.
    '''
    #Main
    def touch_began(self, touch):
        if(self.gameOver == False):
            EventManager().touch_began(touch)
        if(EventManager.canClose and self.gameOver):
            self.view.close()
Karina

cls.player.touch_began(touch)

But the player is none. I looked for when you change it into Player() but ๐Ÿคทโ€โ™€๏ธ

Karina

But in the cls.player.touch_began(touch) player is None. I looked for Player() in the class but ๐Ÿคทโ€โ™€๏ธ

def touch_began(self, touch):
        if self.isDead != False:
            self.scene.gameOver=True
            self.isDead=True
            self.ChangePose(4)

And here๐Ÿ‘† it seems that you check if the player is dead, and if not, make him dead and end the game๐Ÿ˜ณ

stephen

@Karina said:

But in the cls.player.touch_began(touch) player is None. I looked for Player() in the class but ๐Ÿคทโ€โ™€๏ธ

def touch_began(self, touch): if self.isDead != False: self.scene.gameOver=True self.isDead=True self.ChangePose(4)

And here๐Ÿ‘† it seems that you check if the player is dead, and if not, make him dead and end the game๐Ÿ˜ณ

Essentially Iโ€™m doing the same as bellow.

def touch_began(self, touch):
         if self.isDead:
             self.scene.gameOver=True
             self.ChangePose(4)

You can do this anywhere really. Here we also do this in


def RemoveHP(self, val):
        hp=self.hitPoints - val
        if(hp > 0):
            self.hitPoints = hp
            Animation(self.scene.infoBox.hpBar).ResizeBar(hp/100)
        else:  
            self.hitPoints=0
            Animation(self.scene.infoBox.hpBar).ResizeBar(0)

            self.isDead=True
            self.ChangePose(4)

Then inside Player.ChangePose we also reset isDead

    ...
    elif(pose == 4):
        self.isDead=True
        self.shield.remove_from_parent()
        self.up.remove_from_parent()
        self.down.remove_from_parent()
        self.idle.remove_from_parent()
    ...

its a bit overkill but at least we know it gets set lol. in relity most of this woulf of been removed but i didnt go over as well as I should of. Only issue with how it sits is we only want one call to Player.ChangePose so i would remove the one inside touch_began

Karina

So if it's overkill, maybe I don't need to understand it now. I'm trying to get main sense of this, how it works

And what about the first part, self.player that is set None?

stephen

@Karina
yes our EventManager.player gets initialized as None. fom here we can set this property from anywhere we need. in our case we set it inside our Player.__init__.
This way anytime we create a new player we know the touch and update events ate going to the correct object.

---

I just started a group of Episodes for Tutorials that will run through a step by step of the game explaining each peice.

i will make a post aswell as ading to my GitHub Repository as each episode is finished. ๐Ÿ˜Š if you would like you can start with thoes. i will be removing bugs and redundent code(the overkill) and presenting a more profesional and consistant coding style/convention. ill have Episode One finished and posted today at the least.

but dont stop the questions! your questions along have helped debug and correct myself a few times already ๐Ÿ˜Ž

stephen

@Karina Posted the Into and first installment of the Tutorial. its not much yet but its going to explain in more detail and teach alot more than just throwing a bunch of messy code out there. if you can hangin there for me to rlease each one i think it will be much better for many

Karina

@stephen yes this will help a lot. Asking things one by one will take a year maybe๐Ÿ˜…. The problem is that after you explain I understand what you did, but not why exactly like that

stephen

@Karina i could tell i wrote it a bit confusing so i do better ๐Ÿ˜ im posting Episode two in justba min then probaby 3 tomorrow

Karina

@stephen don't worry you wrote and it's ๐Ÿ‘๐Ÿ‘๐Ÿ‘. I have lots of stuff to learn now)

Karina

We will only have one EventManager each game session

I understood that you explain here why to use class methods, but what this means? Game session is time from the start to when view is closed? And one EventManager means we have only one class like that

stephen

@mikael here is a better answer to your question earlier..

@Karina said:

We will only have one EventManager each game session

I understood that you explain here why to use class methods, but what this means? Game session is time from the start to when view is closed? And one EventManager means we have only one class like that

when i say Game Session i am indeed refering to start time until program termination.

when you use __init__(self) this returns a new instanse of the class object and each instance has its own block in memory. just as if you called Foo() but __init__ provides instance customizing before returned.

Here is a quick example of the memory blocks using Instances


class Bar(object):
    pass


b1=Bar()
b2=Bar()
b3=Bar()

print(f'b1 memory address: {id(b1)}\n',
    f'b2 memory address: {id(b2)}\n',
    f'b3 memory address: {id(b3)}')

output:

b1 memory address: 4518930920
b2 memory address: 4518803496
b3 memory address: 4532805080

Now just Using references to a single Class object


class Bar(object):
    pass


b1=Bar
b2=Bar
b3=Bar

print(f'b1 memory address: {id(b1)}\n',
    f'b2 memory address: {id(b2)}\n',
    f'b3 memory address: {id(b3)}')

output:

b1 memory address: 4996960280
b2 memory address: 4996960280
b3 memory address: 4996960280

now i have 3 references to the class in diferent places without using extra memory.

class Foo(object):
        baz = "Class Attribute baz"

    @classmethod
    def from_cls_obj(cls):
        print(cls.baz)


we still have access to class methods even from an instance but not the other way around. lets test if a class with __init__ still gets access to cls using self..

class Foo(object):
    baz = "Class Attribute baz"
    def __init__(self):
        self.bar="Instance Attribute bar"

    def from_self(self):
        print(self.bar)
    @classmethod
    def from_cls(cls):
        print(cls.baz)



b1=Foo
b2=Foo()

b1.from_self()
# TypeError: from_self() missing 1 required positional argument: 'self'

b2.from_self()
# Instance Attribute bar

b1.from_cls()
# Class Attribute baz

b2.from_cls()
# Class Attribute baz

print(f'b1: {b1.baz} / {b1.bar}')
# AttributeError: type object 'Foo' has no attribute 'bar'
print(f'b1: {b1.baz}')
# b1: Class Attribute baz

print(f'b2: {b2.baz} / {b2.bar}')
# b2: Class Attribute baz / Instance Attribute bar

As expected, only the instanced object had access to self.

so as long as you dont absolutly need the "pre-returned" customizing then using a standard class with cls seems to be the most effecient option.
and in the EventManagers case, its perfect.
hopfully i didnt go overboard or off topic. but this seemed to be the best way to explane the reasoning behind our use of cls

mikael

@stephen, why I asked is that it seems to me that you are paying a price by using the class-based approach:

  • Bother and clutter of writing @classmethod everywhere
  • Having to remember where to use self and where to use cls
  • Not able to use standard Python tools like @property, should you need them

The price might be small, but seems like you are getting nothing in return. It looks to me that you are using a class just because it is a global, when you could use a global variable instead.

To illustrate:

file1.py:

class EventManager:

    @property
    def address(self):
        return id(self)


event_manager = EventManager() # The global instance
print(event_manager.address)

file2.py:

from file1 import event_manager

def some_function():
    print(event_manager.address)

some_function()

If you run file2, you get the same object and the same numbers.

Since event_manager is an object, you can set it's values without needing to explicitly declare it as a global in the function.

If you want to be absolutely sure that there is only 1 EventManager instance ever, you can use a custom __new__ to make sure that EventManager() always returns the same instance.

I am not saying you should not use a class if it fits your style, but I guess in a tutorial you need to think about what you promote as best practice.

Karina

@stephen this tutorial will be like the game you wrote before in the code? Cause setup() in EventManager already differs a bit

```

 @classmethod
 def setup(cls, *args):
     cls.main=args[0]
     cls.hud=args[1]
     ```

Here you mean that args is dict or you access first and sec argument like that?
And you said it's called inside HUD.init() but I didn't found that HUD

stephen

@Karina the original was just a broad example where my current is intended to be a tutorial it will be very similar to the example only because for some people that used the example will feel more comfortable. and since im limiting it to built in assets It's limited on visual theme but that's ok ๐Ÿ˜Ž

@mikael this is Great information! one one of the reasons i wanted to do this tutorial is for feedback lime this ๐Ÿ˜Š im a strong believer on people should never stop trying to better no mater the topic.

I was using the global variable approach similar to how you present it but I was under the impression that scen's event loop was a bit "sketchy"on how global variables were handled/maintained.

which is why pretty much anything I post that uses scene doesn't use global variables unless it's just aliasing another object. Have you seen/heard any of this?

Karina
     def setup(cls, *args):
         cls.main=args[0]
         cls.hud=args[1]

@stephen Here the args is a dict or you access like that first and sec arg?

Karina

@stephen When I launch this it gives to areas of grey and black colors, but it should be blue๐Ÿค” Or not?

stephen

@Karina said:

def setup(cls, *args): cls.main=args[0] cls.hud=args[1]
@stephen Here the args is a dict or you access like that first and sec arg?

* is a wildcard list and ** is a wildcard key/val dict.

you can use any name you want but he standard is *args and **kwargs

here im assuming there is a list index 0 and 1. a better way would of jusr using none None parmeters.

stephen

@Karina i forgot to add that it doesnt have to be args you can almost use anything. for example:


def func(*abc):
    for x in abc:
        print(id(x))

and somewhere call func passing objects as arguments

stephen

@mikeal im working on a singleton solution for EventManager and found a few different implementations. One didnt work lol but i wanted to see if you had any inglton suggestions

cvp

@stephen said:

@mikeal

--> @mikael ๐Ÿ˜€

Karina

@stephen I read about args *kwargs things, but they didn't say what it is, like list or dict. Now you wrote couple words about, not article, and I got what is it๐Ÿ˜…

I'm reading now your buttons, and you use there ui, but didn't import it. You can access ui through scene?
Also they not so explain in docs what the sender param is

Karina

@stephen When I launch this it gives to areas of grey and black colors, but it should be blue๐Ÿค” Or not?

mikael

@stephen, Internet is indeed full of these.

Here is a very basic singleton implementation:

class EventManager:

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    # For demonstration only
    @property
    def address(self):
        return id(self)


assert EventManager().address == EventManager().address

You might need to be concerned about threads, in which case you would need a lock:

from threading import Lock

class EventManager:

    _instance = None
    _lock = Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
        return cls._instance

If you think of creating millions of these, a little optimization minimizes the cost of acquiring the lock:

class EventManager:

    _instance = None
    _lock = Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

Of course, you would also need to be aware of any threads you have when updating the event manager values.

stephen

@cvp said:

@stephen said:

@mikeal

--> @mikael ๐Ÿ˜€

lol idk y i spelled wrong its the same as my middle name oops ๐Ÿ˜‚

stephen

@Karina said:

@stephen I read about args *kwargs things, but they didn't say what it is, like list or dict. Now you wrote couple words about, not article, and I got what is it๐Ÿ˜…

im glad i was able to help with * and ** .


@Karina said:

I'm reading now your buttons, and you use there ui, but didn't import it. You can access ui through scene?

The scene modulewas built off of theui` module. both, if im correct, were created for Pythonista specifically. if you go into your

Python Modules => Standard Library (3.6) => site-packages => scene.py

you will see at the top:


#import 'pythonista'
#coding: utf-8

from _scene2 import *
import _scene2

from scene_drawing import *

import math
from numbers import Number
from io import BytesIO
import ui

  • _scene2 is the core module
  • scene_drawing is aditional functionality
  • then math, numbers.Number, io.BytesIO are standard
  • and the ui module

when we used from scene import * this means everything ๐Ÿ˜Ž so we dont need to import scene_drawing, math, numbers.Number, io.BytesIO or ui. scene does this for us.

If you want you can still do your own import but it will still be the same instance a that was imported inside scene so theres really no need.

@Karina said:

Also they not so explain in docs what the sender param is

wow im suprised the docs didnt cover this one.. but its really simple and ill show you an example i made of a ButtonNode i made to use with scene that hould be very close to how ui uses it.

When you create a ui.Button and set the action property and then Tap the button there are a few things that happends..

first it changes to the pressed state that changes the image to visually how you di tap it. then it calls the function/method that you set action to and passes self as the only argument.

class ui.Button(ui.view):
    ...

    def _action(self):
        # some cool stuff
        self.action(self)
        # more cool stuff
    ...

this is why your Custome action needs sender

now inside your custome action you can use sender to accesd the button you pressed

def button_tapped(sender):
    sender.superview.background_color = "blue"

this changes the color of the view that contains your button. ๐Ÿ˜Š

here is the example for a button in scene..

import scene

class EventHandler:
    children=[]
    event_loop=None

    @classmethod
    def add_child(cls, node):
        cls.children.append(node)
        if cls.event_loop == None:
            cls.event_loop = node.scene

    @classmethod
    def count(cls):
        return len(cls.children)

    @classmethod
    def touch_began(cls, touch):
        for child in cls.children:
            if cls.event_loop.point_from_scene(touch.location) in child.frame:
                child.Button_Tapped()

class ButtonNode(scene.ShapeNode):
    def __init__(self,
            action,
            name=f'ButtonNode',
            bg_color='#d4d2ca',
            accessoryColor='#e80000', 
            icon=None,
            accessory='emj:Exclamation_Mark_2',
            anchor_point=(0.5, 0.5),
            font_family='Helvetica',
            font_size=16,
            parent=None, 
            position=(0, 0),
            size=(120, 45),
            corner_radius=8,
            border_size=20,
            borderColor='#000000',
            text='',
            text_color='#000000',
            tint='white', 
            enabled=True,
            *args, **kwargs):

        self.x, self.y = position
        self.w, self.h = size

        super().__init__(
            path=scene.ui.Path.rounded_rect(
                self.x,
                self.y,
                self.w,
                self.h,
                corner_radius),
            fill_color=bg_color,
            stroke_color=borderColor,
            shadow=None,
            parent=parent,
            *args, **kwargs)

        EventHandler.add_child(self)
        self.icon=self._init_textures(icon)
        self.accessory=self._init_textures(accessory)
        self.border_size=border_size
        self.name=name + ' ' + str(EventHandler.count())
        self.enabled=enabled
        self.text=text if text != '' else self.name
        self.tint=tint
        self.text_color=text_color
        self.borderColor=borderColor
        self.corner_radius=corner_radius
        self.showAccesory=False
        self.accessoryColor=accessoryColor
        self.parentNode=parent
        self.position=position
        self.size=size
        self.button_action=action
        self.anchor_point=anchor_point
        self.font_family=font_family
        self.font_size=font_size
        self.components=dict({
            'accessory':None,
            'icon':None,
            'label':None,
            'width':None,
            'height':None})
        self._setup(self.icon, self.accessory, self.components)

    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    def _setup(self, i, a, c):

        if a != None:
            c['accessory']=scene.SpriteNode(
                texture=a,
                size=(self.size[1]/4, self.size[1]/5*1.5),
                position=scene.Point(-self.size[0]/2+7, self.size[1]/2-10),
                parent=self,
                z_position=4,
                color=self.accessoryColor)

        if i != None:
            c['icon']=scene.SpriteNode(
                texture=i,
                size=scene.Size(self.size[1]/2, self.size[1]/2),
                position=scene.Point(self.w/2 - self.size[1]/3 , 0),
                parent=self, 
                z_position=9)

        if self.text:
            c['label']=scene.LabelNode(
                text=self.text,
                font=(self.font_family, self.font_size),
                position=scene.Point(0 , 0),
                anchor_point=(0.5, 0.5),
                color=self.text_color,
                parent=self,
                z_position=10)

    def Button_Tapped(self):
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)
#---------------------------------------------------------------------------

def my_button_action(sender):
    print(f'{sender.name}: {sender.text}..')

class main(scene.Scene):
    def setup(self):
        self.background_color='#f2f2f2'
        self.my_button_up=ButtonNode(
                    text='', 
                    parent=self,
                    position=scene.Point(self.size[0]/2, self.size[1]/2+100),
                    action=my_button_action)
        self.my_button_right=ButtonNode(
                    text='right', 
                    parent=self,
                    position=scene.Point(self.size[0]/2+100, self.size[1]/2),
                    action=my_button_action)
        self.my_button_down=ButtonNode(
                    text='down', 
                    parent=self,
                    position=scene.Point(self.size[0]/2, self.size[1]/2-100),
                    action=my_button_action)
        self.my_button_left=ButtonNode(
                    text='left', 
                    parent=self,
                    position=scene.Point(self.size[0]/2-100, self.size[1]/2),
                    action=my_button_action)

    def touch_began(self, touch):
        EventHandler.touch_began(touch)

scene.run(main())
stephen

@Karina said:

@stephen When I launch this it gives to areas of grey and black colors, but it should be blue๐Ÿค” Or not?

im assuming your doing Episode 2 of the tutorial. if so your project should look like this at he end

menu

if you are talking about the tutorial we should post questions on the respective thread so anyone in the future can see Q and A wihout having to hunt it down ๐Ÿ˜

stephen

@mikael said:

@stephen, Internet is indeed full of these.

Here is a very basic singleton implementation:

class EventManager:

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    # For demonstration only
    @property
    def address(self):
        return id(self)


assert EventManager().address == EventManager().address

You might need to be concerned about threads, in which case you would need a lock:

from threading import Lock

class EventManager:

    _instance = None
    _lock = Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
        return cls._instance

If you think of creating millions of these, a little optimization minimizes the cost of acquiring the lock:

class EventManager:

    _instance = None
    _lock = Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

Of course, you would also need to be aware of any threads you have when updating the event manager values.

๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ i wasnt calling super().__new__(cls) thank you! one more question, im not too familier with threading just yet with python.. isnt each Animation or Action asynchronous and if so since im considering threading here should i make EventManager async aswell? or would the constant calls to update create conflict?

the size of this game wont matter too much but my next walkthrough game will be the RPG and the scale of that one will be much greater, limited to resource allocation from IOS. and i want to use the library created from this one on that to help others create thier personal cookbook or library

Karina

@stephen cool design๐Ÿ˜œ
And this is not thread of the tutorial?

Karina

@stephen how did you learn scene and ui? Cause to me docs is not enough

stephen

@Karina said:

@stephen cool design๐Ÿ˜œ
And this is not thread of the tutorial?

this thread for the Example Game ๐Ÿ˜€. though it is the Topic the code and questions wont be quite the same. thats why im doing the Tutorial in small chunks. that way there is a thread for each and they wont get flooded with conversation from all over the place. everything should stay based on each "Episode". people in the future, or even right now, that are doing the tutorial may never see the questions you have asked and the answer to even one of your questions could of helped many people. but they cant if they are in the wrong Thread.. and thats a shame because you usually have really good questions!

Links to Tutorial:

At the top of each there is a link to the the Files for each.

stephen

@Karina

For myself it was about 4 months of trial and error crash course. and every now and then i would find somthing on this forum (ill note that it was tough to find helpfull threads at the time because it was flooded with spam and general conversation Threads.. thanks to @ccc and a couple others they spent the time to filter the forums for everyone. so thats not an issue anymore โ˜บ๏ธ) and somtimes a thread on StackOverflow or GeeksForGeeks.. tdamdouni has a great repository of scripts that helped me alot and still do aswell.

That is why im doing this tutorial and why i made th Example.. To try and help th community Continue to grow and hopfully get some cool games along the way ๐Ÿ˜Š..

Karina

@stephen I tried to do like an arrow here

class ButtonNode(SpriteNode):
    def __init__(self, img, action=None, text=None, parent=None, *args, **kwargs):
        super().__init__(self, img, *args, **kwargs)

But he tells me that in super().init should be 2 numbers. I don't understand what he means at all๐Ÿคทโ€โ™€๏ธ

stephen

@Karina said:

@stephen I tried to do like an arrow here
```
class ButtonNode(SpriteNode):
def init(self, img, action=None, text=None, parent=None, args, kwargs):
super().init(self, img,
args, **kwargs)

```
But he tells me that in super().init should be 2 numbers. I don't understand what he means at all๐Ÿคทโ€โ™€๏ธ

when used in a classbdeffenition, in this case __init__, super will gather the needed data from the current environment that it needs to define the instance. so here you dont pass self and super for SpriteNode is expecting a string for texture(string is a sequence of characters) and not self. this automatic defining is why super is used. its called Cooperative Inheritance. as long as there is a super call in all base classes it allows for multiple inheritance without having to bother with explicitly defining who the parent or base class is. for example it would be more practical to made a button a ShapeNode that way you can add a border to the button without adding another sprite in the background by using path. now if we had put super().__init__(*args, **kwargs) and insured the correct args are passed we would just need to change the base class fom ButtonNode(SpriteNode): to ButtonNode(ShapeNode): and super would handle the rest. ๐Ÿ˜Ž๐Ÿ˜€

Karina

@stephen So here better to put ShapeNode instead of SpriteNode?

stephen

@Karina said:

@stephen So here better to put ShapeNode instead of SpriteNode?

i would personally. But there's nothing wrong with using SpriteNode. Check out this one I made not long ago.

Karina

@stephen Yes, I know, I copied it to my pythonista when you showed it. But there a lot of things, don't know from where to begin it. I just used to write more linear, so it's difficult for me to understand. But it goes slowly

Karina

@stephen said:

@Karina said:

@stephen So here better to put ShapeNode instead of SpriteNode?

i would personally. But there's nothing wrong with using SpriteNode. Check out this one I made not long ago.

Can you explain why, what ShapeNode has that we need?

Karina
class ButtonNode(SpriteNode):
    def __init__(self, img, action=None, text=None, parent=None, *args, **kwargs):
        super().__init__(img, *args, **kwargs)


    def setup(self):
        bg = ui.Path.rounded_rect(0, 0, 200, 200, 8) 
        bg.line_width = 7
        self.bg = ShapeNode(bg, stroke_color='blue', parent=self)
        self.add_child(self.bg)

@stephen here he tells me line_width is invalid arg, but Path obj has it, and in example they do the same. Why I can't do that?

stephen

@Karina said:

```
class ButtonNode(SpriteNode):
def init(self, img, action=None, text=None, parent=None, args, kwargs):
super().init(img,
args, **kwargs)

def setup(self):
bg = ui.Path.rounded_rect(0, 0, 200, 200, 8)
bg.line_width = 7
self.bg = ShapeNode(bg, stroke_color='blue', parent=self)
self.add_child(self.bg)
```
@stephen here he tells me line_width is invalid arg, but Path obj has it, and in example they do the same. Why I can't do that?

To start, im not too sure how your executing setup you dont make any calls to it. the code inside setup Is functional but I would like to point out that you are adding 2 children of this ShapeNode to self.. When you pass parent in any subclass of Node it will add that instance to the list of children for self. i would remove self.add_child(self.bg) since we know and who the parent will be at construction time ๐Ÿ˜Ž.

as for you question here i had zero issues with bg.line_width when i ran this setup method inside my Scene subclass. ill need to see how you cal calling your setup here

stephen

@Karina said:

@stephen said:

@Karina said:

@stephen So here better to put ShapeNode instead of SpriteNode?

i would personally. But there's nothing wrong with using SpriteNode. Check out this one I made not long ago.

Can you explain why, what ShapeNode has that we need?

you defenently do not need ShapeNode but there is no reason not to. I probably should of mentioned this earlier lol im sorry..

ShapeNode is a subclass of SpriteNode! ๐Ÿ˜ƒ๐Ÿ˜ƒ๐Ÿ˜ƒ So you have everything a SpriteNode has including texture as well as path. this means you can use a image for the background and use a Path for your Border..



# Just to show using Borders with a image Background
class ButtonNode(ShapeNode):
    def __init__(self, rect=(0, 0, 150, 75), corner_radius=6, *args, **kwargs):
        super().__init__(*args, **kwargs)
        x, y, width, height = rect

        if corner_radius > 0:
            self.path = ui.Path.rounded_rect(x, y, width, height, corner_radius)
        else:
            self.path = ui.Path.rect(x, y, width, height)

        lw=self.line_width=10
        self.fill_color=None
        self.stroke_color='blue'

        self.bg_texture=SpriteNode(
            texture=Texture('pzl:Blue8'),
            size=self.size-(lw, lw),
            parent=self)

If you were to try and do this with a normal SpriteNode you can never have path borders with a image background without covering the edges of your image. Also by using a SpriteNode as base class only the ShapeNode child has corner_radius so your texture images corners will show outside your rounded edges.

stephen

@Karina said:

@stephen Yes, I know, I copied it to my pythonista when you showed it. But there a lot of things, don't know from where to begin it. I just used to write more linear, so it's difficult for me to understand. But it goes slowly

I shortened my ButtonNode and removed rhe Event Manage. there is some short comments on this one too.. hopfully it will help you understand better?


import scene

class ButtonNode(scene.ShapeNode):
    def __init__(self,
                    action,
                    name=f'ButtonNode',
                    bg_color='lightgray',
                    accessoryColor='red',
                    icon=None,
                    accessory='emj:Exclamation_Mark_2',
                    anchor_point=(0.5, 0.5),
                    font_family='<system>',
                    font_size=16,
                    parent=None,
                    position=(0, 0),
                    size=(120, 45),
                    corner_radius=8,
                    border_size=20,
                    borderColor='black',
                    text='',
                    text_color='black',
                    enabled=True,
                    *args, **kwargs):

        # these mainly for call to super()
        self.x, self.y = position
        self.w, self.h = size
        super().__init__(
                path=scene.ui.Path.rounded_rect(
                    self.x, self.y, self.w, self.h, corner_radius),
                fill_color=bg_color,
                stroke_color=borderColor,
                shadow=None,
                parent=parent,
                *args, **kwargs)

        # Normal Properties for Instance()
        self.enabled=enabled
        self.button_action=action
        self.name=name
        self.position=position
        self.size=size
        self.anchor_point=anchor_point
        self.icon=self._init_textures(icon)

        # for border
        self.border_size=border_size
        self.borderColor=borderColor
        self.corner_radius=corner_radius

        # for accessory
        self.accessory=self._init_textures(accessory)
        self.showAccesory=False
        self.accessoryColor=accessoryColor

        # for Label
        self.text=text if text != '' else self.name
        self.text_color=text_color
        self.font_family=font_family
        self.font_size=font_size

        # Container to hold each component. 
        # is just a dict version of self.children but specific.
        self.components=dict({
                'accessory':None,
                'icon':None,
                'label':None})

        self._setup(self.icon, self.accessory, self.components)

    # Type Check to make sure img is a string or ui.Image
    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    # setup our Components
    def _setup(self, i, a, c):
        if a != None:
            # small indicator image in top left corner
            # if its enabled it will disable and hide when pressed
            # intended for stuff like new item in inventory...
            c['accessory']=scene.SpriteNode(
                    texture=a,
                    size=(self.size[1]/4, self.size[1]/5*1.5),
                    position=scene.Point(-self.size[0]/2+7, self.size[1]/2-10),
                    parent=self,
                    z_position=4,
                    color=self.accessoryColor)

        if i != None:
            # button image
            c['icon']=scene.SpriteNode(
                    texture=i,
                    size=scene.Size(self.size[1]/2, self.size[1]/2),
                    position=scene.Point(self.w/2 - self.size[1]/3 , 0),
                    parent=self,
                    z_position=9)

        if self.text:
            # button text..
            c['label']=scene.LabelNode(
                    text=self.text,
                    font=(self.font_family, self.font_size),
                    position=scene.Point(0 , 0),
                    anchor_point=(0.5, 0.5),
                    color=self.text_color,
                    parent=self,
                    z_position=10)

    # called when you tap the button
    def Button_Tapped(self):
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)

# custom action
def my_button_action(sender):
    print(f'{sender.name}: {sender.text}..')

class main(scene.Scene):
    def setup(self):
        self.buttons=list([])
        self.background_color='lightgray'
        self.my_button=ButtonNode(
            text='My Button', parent=self, action=my_button_action,
            position=scene.Point(self.size[0]/2, self.size[1]/2+100))
        self.buttons.append(self.my_button)

    def touch_began(self, touch):
        for btn in self.buttons:
            if self.point_from_scene(touch.location) in btn.frame:
                btn.Button_Tapped()

scene.run(main())
Karina

ShapeNode is a subclass of SpriteNode!

Yeah, they say it in docs, I just forgot๐Ÿคฆโ€โ™€๏ธ. Then it's more clever to make ShapeNode base class

Karina
 lw=self.line_width=10

Here why you're lw and self.line_width? One is not enough? And it seems it should be self.path.line_width cause Path has that attr.

self.bg_texture=SpriteNode(
            texture=Texture('pzl:Blue8'),
            size=self.size-(lw, lw),
            parent=self)

You're trying make here the form of Blue8, but with rounded angles?

Karina

@stephen in built in examples sometimes there are with pyui files. Do you use them?

stephen

@Karina said:

lw=self.line_width=10
Here why you're lw and self.line_width? One is not enough? And it seems it should be self.path.line_width cause Path has that attr.

ShapeNode has a few setting for path so that you dont need to set them for every path. when you pass a new path object to your ShapeNode it doesva check on the follwing Properties and if the value is not None it applies this to your new Path object.

with that said if you have say ShapeNode().line_width set to 3 and for just the next path object you want the width set to 6. you would need to set ShapeNode().path to the new path object and then set ShapeNode().path.line_width = 6 otherwise your ShapeNode().line_width will overide the path's predefined value. Only reason not to just change ShapeNode().line_width before creating the path is in this example you want it to only change once and would hate to forget to change it back lol.

ShapeNode Path Properties:
python ShapeNode().line_width ShapeNode().fill_color ShapeNode().path ShapeNode().shadow ShapeNode().stroke_color

stephen

@Karina said:

Here why you're lw and self.line_width? One is not enough?

Yes one is more than enough. I do this at times to shorten line length

Karina

as for you question here i had zero issues with bg.line_width when i ran this setup method inside my Scene subclass. ill need to see how you cal calling your setup here

@stephen here's the full code though I've changed a little reading your advices

from scene import *
import ui

def sw(): return get_screen_size()[0]
def sh(): return get_screen_size()[1]
def bw(): return 150
def bh(): return 140


class ButtonNode(ShapeNode):
    def __init__(self, rect=(0, 0, 150, 75), corner_radius=7, *args, **kwargs):
        super().__init__(img, *args, **kwargs)
        x, y, width, height = rect


    def setup(self):
        bg = ui.Path.rounded_rect(0, 0, 200, 200, 8) 
        bg.line_width = 7
        self.bg = ShapeNode(bg, stroke_color='blue', parent=self)
        self.add_child(self.bg)     




def button_tapped(sender):
    print(sender.text)



class Main(Scene):
    def setup(self):
        self.background_color = '#eaeaea'
        down = ButtonNode('iob:arrow_right_b_256', size=Size(bw(), bh()), 
                                line_width=7, position=Point(sw()-70, sh()/2))
        down.make_button()
        self.add_child(down)


run(Main(), LANDSCAPE)
Karina

Do you know how to make it in portrait orientation? No matter which attr I pass, it's still in landscape. Or that's just because of version,of my iPad?

stephen

@Karina said:

Do you know how to make it in portrait orientation? No matter which attr I pass, it's still in landscape. Or that's just because of version,of my iPad?

Heres the script back. i did a few corrections. you were ading the new code but not using it. lol but thats ok. this stuff can make you pull your hair out from time to time. Af for your error message for line_width, it's not part of the constructor parameters so this must be set after calling the super's Init. Interpreter didn't know the property existed yet. I'm assuming it has to do with execution inside ShapeNode itself.

Also you need to remove your fill color to see your texture. But we are getting there!

At the moment we can not control our Orientation on iPad. It has to do with ios13 and multi-tasking support for Pythonista. But a few of us are working on that

from scene import *
import ui

def sw(): return get_screen_size()[0]
def sh(): return get_screen_size()[1]
def bw(): return 150
def bh(): return 140


class ButtonNode(ShapeNode):
    def __init__(self,
            rect=(0, 0, 150, 75),
            corner_radius=8,
            bakground_color='lightgreen',
            line_width=0,
            *args, **kwargs):

        super().__init__(*args, **kwargs)
        self.tint = bakground_color
        x, y, width, height = rect
        self.position, self.size = get_screen_size()/2, (rect[2], rect[3])

#    def setup(self):
        bg = ui.Path.rounded_rect(x, y, width, height, corner_radius) 
        bg.line_width = line_width

        self.bg = ShapeNode(bg, stroke_color='blue', fill_color='clear',parent=self)
#        self.add_child(self.bg)     




def button_tapped(sender):
    print(sender.text)



class Main(Scene):
    def setup(self):
        self.background_color = '#b7b7b7'
        down = ButtonNode(
            texture=Texture('iob:arrow_right_b_256'),
            size=Size(bw(), bh()), 
            line_width=5, 
            position=Point(sw()/2, sh()/2),
            parent=self)

#        self.add_child(down)


run(Main(), LANDSCAPE)


stephen

@Karina said:

Do you know how to make it in portrait orientation? No matter which attr I pass, it's still in landscape. Or that's just because of version,of my iPad?

i made a note on last post but i wanted to add that it sounds like your orientation lock is on

Karina

@stephen Yes it was so. But when it's off, orientation changes when I tint. And I want it to be just portrait

Karina

@stephen I generally understood your code, but how do you count those positions, of icon, accessory, button? Cause size[1]/5*1.5 seems quite confusing
I changed a little your code, now the icon is music, and when you tap plays random sounds. But the button is in the corner, you even can't see it fully๐Ÿ˜“

stephen

@Karina said:

@stephen Yes it was so. But when it's off, orientation changes when I tint. And I want it to be just portrait

ah this currently an issue with ipad apps running support for multiscreen multitasking. and when you run your script in Pythonista yourvactually adding a model to the editors Main View and Pythonista supports IOS 13's multitasking. Right now there has been issues with XCode on the matter. Here in a couple hours I'm going to write an module to rotate your main view when the Device's window rotates to give a similar effect. Hopefully temporary but better than nothin lol me and a few others tried to fix it and a workaround seems to be needed. So hang in there we will get you fixed up

stephen

@Karina said:

@stephen I generally understood your code, but how do you count those positions, of icon, accessory, button? Cause size[1]/5*1.5 seems quite confusing
I changed a little your code, now the icon is music, and when you tap plays random sounds. But the button is in the corner, you even can't see it fully๐Ÿ˜“

May I see the code please

Karina

@stephen said:

this stuff can make you pull your hair out from time to time.

What that means? I'm not native speaker if what๐Ÿ™ƒ
You talk about args that I wrote but not used?
Multi tasking is thing that you can program on pythonista?

Karina
import scene
import sound
import random

sw = scene.get_screen_size()[0]
sh = scene.get_screen_size()[1]

music_sounds = ['piano:A3', 'piano:C3', 'piano:C4#', 'piano:D4', 'piano:E4', 'piano:F4', 'piano:G3#']


class ButtonNode(scene.ShapeNode):
    def __init__(self,
                    action,
                    name=f'ButtonNode',
                    bg_color='lightgray',
                    accessoryColor='red',
                    icon='emj:Musical_Notes',
                    accessory='emj:Exclamation_Mark_2',
                    anchor_point=(0.5, 0.5),
                    font_family='<system>',
                    font_size=16,
                    parent=None,
                    position=(0, 0),
                    size=(120, 45),
                    corner_radius=8,
                    border_size=20,
                    borderColor='black',
                    text='',
                    text_color='black',
                    enabled=True,
                    *args, **kwargs):

        # these mainly for call to super()
        self.x, self.y = position
        self.w, self.h = size
        super().__init__(
                path=scene.ui.Path.rounded_rect(
                    self.x, self.y, self.w, self.h, corner_radius),
                fill_color=bg_color,
                stroke_color=borderColor,
                shadow=None,
                parent=parent,
                *args, **kwargs)

        # Normal Properties for Instance()
        self.enabled=enabled
        self.button_action=action
        self.name=name
        self.position=position
        self.size=size
        self.anchor_point=anchor_point
        self.icon=self._init_textures(icon)

        # for border
        self.border_size=border_size
        self.borderColor=borderColor
        self.corner_radius=corner_radius

        # for accessory
        self.accessory=self._init_textures(accessory)
        self.showAccesory=False
        self.accessoryColor=accessoryColor

        # for Label
        self.text=text if text != '' else self.name
        self.text_color=text_color
        self.font_family=font_family
        self.font_size=font_size

        # Container to hold each component. 
        # is just a dict version of self.children but specific.
        self.components=dict({
                'accessory':None,
                'icon':None,
                'label':None})

        self._setup(self.icon, self.accessory, self.components)

    # Type Check to make sure img is a string or ui.Image
    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    # setup our Components
    def _setup(self, i, a, c):
        if a != None:
            # small indicator image in top left corner
            # if its enabled it will disable and hide when pressed
            # intended for stuff like new item in inventory...
            c['accessory']=scene.SpriteNode(
                    texture=a,
                    size=(self.size[1]/4, self.size[1]/5*1.5),
                    position=scene.Point(-self.size[0]/2+7, self.size[1]/2-10),
                    parent=self,
                    z_position=4,
                    color=self.accessoryColor)

        if i != None:
            # button image
            c['icon']=scene.SpriteNode(
                    texture=i,
                    size=scene.Size(self.size[0], self.size[1]),
                    position=scene.Point(self.w/2 - self.size[1]/3 , 0),
                    parent=self,
                    z_position=9)

        if self.text:
            # button text..
            c['label']=scene.LabelNode(
                    text=self.text,
                    font=(self.font_family, self.font_size),
                    position=scene.Point(0 , 0),
                    anchor_point=(0.5, 0.5),
                    color=self.text_color,
                    parent=self,
                    z_position=10)

    # called when you tap the button
    def Button_Tapped(self):
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)

# custom action
def my_button_action(sender):
    sound.play_effect(random.choice(music_sounds))

class main(scene.Scene):
    def setup(self):
        self.buttons=list([])
        self.background_color='lightgray'
        self.my_button=ButtonNode(size=scene.Size(100, 70),
            text=None, parent=self, action=my_button_action,
            position=scene.Point(sw/2, sh/2))
        self.buttons.append(self.my_button)

    def touch_began(self, touch):
        for btn in self.buttons:
            if self.point_from_scene(touch.location) in btn.frame:
                btn.Button_Tapped()

scene.run(main())

Now I found button pos, just need to fix sizes of button and music

stephen

@Karina said:

@stephen I generally understood your code, but how do you count those positions, of icon, accessory, button? Cause size[1]/5*1.5 seems quite confusing
I changed a little your code, now the icon is music, and when you tap plays random sounds. But the button is in the corner, you even can't see it fully๐Ÿ˜“

ive been trying to get back in the habbit of basing node sizing and positioning on the parent size. best way i can explain this is im cutting the parent's height into 5 peices then setting my nodes y to1 and a half peices so if the height of the parent was 50 my y would be 15

    |   | X |   |   |   |

i usually use percentages but buttons are pretty small generally and the icon is even smaller so i used Fifths instead

stephen

@Karina

its looking really good! heres a few spots i changed:

adjusted size and position:

if i != None:
    # button image
    c['icon']=scene.SpriteNode(
        texture=i,
        size=scene.Size(self.size[1]/100*80, self.size[1]/100*80), #changed
        position=scene.Point(0, 0),                                #changed
        parent=self,
        anchor_point=(0.5, 0.5),
        z_position=9)

added check for self.accessory to fix excption if None:

def Button_Tapped(self):
    if self.accessory:  #changed
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
    if self.enabled:
        self.button_action(self)

then added dditional button to close the loop.

stephen

@Karina said:

@stephen said:

this stuff can make you pull your hair out from time to time.

What that means? I'm not native speaker if what๐Ÿ™ƒ
You talk about args that I wrote but not used?
Multi tasking is thing that you can program on pythonista?

What that means? I'm not native speaker if what๐Ÿ™ƒ

im mean programming, especially in the begining, can get overwhelming and frustrating.

You talk about args that I wrote but not used?

nothing wrong with preparation ๐Ÿ˜Š

Multi tasking is thing that you can program on pythonista?

I'm referring to a Multitasking in IOS13 but yes Pythonista supports it

Karina

@stephen now I want a he notes to jump a little when you tap, but it doesn't move. Smth like that was in flappy alien

def pulsate(self):
        actions = [A.move_by(0, 10), ]
        icon_sprite = scene.SpriteNode(self.icon)
        icon_sprite.run_action(A.move_by(0, 50, scene.TIMING_LINEAR))
Karina

I also want to try make it pulse or shake a little when you tap it, to see what looks better. Maybe with ui.animate, there is example with that, but I don't understand it

import ui

# The button's action:
def button_tapped(sender):
    def animation():
        sender.alpha = 0.0 # fade out
    ui.animate(animation, duration=1.0)

# Set up and present a view with a single button:
v = ui.View(background_color='white')
button = ui.Button(title='Tap me!')
button.action = button_tapped
v.add_subview(button)
v.present('sheet')

How the interpreter understands that sender.alpha is animation?

stephen

@Karina said:

I also want to try make it pulse or shake a little when you tap it, to see what looks better. Maybe with ui.animate, there is example with that, but I don't understand it

```
import ui

The button's action:

def button_tapped(sender):
def animation():
sender.alpha = 0.0 # fade out
ui.animate(animation, duration=1.0)

Set up and present a view with a single button:

v = ui.View(background_color='white')
button = ui.Button(title='Tap me!')
button.action = button_tapped
v.add_subview(button)
v.present('sheet')
```
How the interpreter understands that sender.alpha is animation?

For ui the setup for animations are visually much different from scene, when they are prety much the same.

    def animation():
      sender.alpha = 0.0 # fade out

    ui.animate(animation, duration=1.0)

is the same as:

    Node.run_action(Action.fade_to(0.0, 1.0)

... or to match even closer ...

    def fade_out(node, progress):
        node.alpha -= progress


    Node.run_action(Action.call(fade_out, 1.0))

pretty much sender is the ui.button that was tapped.
and node in my example is the Node object that ran Action.call()

in ui it transitions the sender, aka ui.Button, alpha from its current value to the new value, 0.0, but does this gradually over duration time, 1.0 second.

in scene its the same where progress is the duration and you add or subtract by progress and Action will automatically spread the calls over set duration.
note: if no duration is passed to Action.call you dont need the paramiters node, progress the method/function will only be called once and you will need to get your own reference to the node

does this answer your question? ๐Ÿ™ƒ

stephen

@Karina i make a few upgrades to UIButton and then made some changes for this example. let me know if theres any questions

import scene
import sound
import random
import objc_util

sw = scene.get_screen_size()[0]
sh = scene.get_screen_size()[1]

music_sounds = ['piano:A3', 'piano:C3', 'piano:C4#', 'piano:D4', 'piano:E4', 'piano:F4', 'piano:G3#']


class ButtonNode(scene.ShapeNode):
    def __init__(self,
                    action,
                    name=f'ButtonNode',
                    bg_color='lightgray',
                    accessoryColor='red',
                    icon='emj:Musical_Notes',
                    accessory='emj:Exclamation_Mark_2',
                    anchor_point=(0.5, 0.5),
                    font_family='<system>',
                    font_size=16,
                    parent=None,
                    position=(0, 0),
                    size=(120, 45),
                    corner_radius=8,
                    border_size=20,
                    borderColor='black',
                    text='',
                    text_color='black',
                    enabled=True,
                    animated_icon=False,
                    icon_animation=None,
                    *args, **kwargs):

        # these mainly for call to super()
        self.x, self.y = position
        self.w, self.h = size
        super().__init__(
                path=scene.ui.Path.rounded_rect(
                    self.x, self.y, self.w, self.h, corner_radius),
                fill_color=bg_color,
                stroke_color=borderColor,
                shadow=None,
                parent=parent,
                *args, **kwargs)

        # Normal Properties for Instance()
        self.enabled=enabled
        self.button_action=action
        self.name=name
        self.position=position
        self.size=size
        self.anchor_point=anchor_point

        # for icon
        self.icon_animation=icon_animation
        self.animated_icon=animated_icon
        self.icon=self._init_textures(icon)

        # for border
        self.border_size=border_size
        self.borderColor=borderColor
        self.corner_radius=corner_radius

        # for accessory
        self.accessory=self._init_textures(accessory)
        self.showAccesory=False
        self.accessoryColor=accessoryColor

        # for Label
        self.text=text if text != '' else self.name
        self.text_color=text_color
        self.font_family=font_family
        self.font_size=font_size

        # Container to hold each component. 
        # is just a dict version of self.children but specific.
        self.components=dict({
                'accessory':None,
                'icon':None,
                'label':None})

        self._setup(self.icon, self.accessory, self.components)

    # Type Check to make sure img is a string or ui.Image
    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    # setup our Components
    def _setup(self, i, a, c):
        if a != None:
            # small indicator image in top left corner
            # if its enabled it will disable and hide when pressed
            # intended for stuff like new item in inventory...
            c['accessory']=scene.SpriteNode(
                    texture=a,
                    size=(self.size[1]/4, self.size[1]/5*1.5),
                    position=scene.Point(-self.size[0]/2+7, self.size[1]/2-10),
                    parent=self,
                    z_position=4,
                    color=self.accessoryColor)

        if i != None:
            # button image
            c['icon']=scene.SpriteNode(
                    texture=i,
                    size=scene.Size(self.size[1]/100*80, self.size[1]/100*80), #changed
                    position=scene.Point(0, 0),                                #changed
                    parent=self,
                    anchor_point=(0.5, 0.5),
                    z_position=9)

        if self.text:
            # button text..
            c['label']=scene.LabelNode(
                    text=self.text,
                    font=(self.font_family, self.font_size),
                    position=scene.Point(0 , 0),
                    anchor_point=(0.5, 0.5),
                    color=self.text_color,
                    parent=self,
                    z_position=10)

    # called when you tap the button
    def Button_Tapped(self):
        if self.components['icon']:
            if self.animated_icon and self.icon_animation:
                self.components['icon'].run_action(self.icon_animation())
        if self.components['accessory']:
            if self.components['accessory'].alpha > 0:
                self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)

# custom action
def my_button_action(sender):
    random_tone=random.choice(music_sounds)
    sound.play_effect(random_tone)
    return

def Animation_Shake(duration=1.5):
    action_list=[]
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.5, duration/10/2),
            scene.Action.rotate_to(0.25, duration/10/2)))
    action_list.append(
        scene.Action.repeat(
            scene.Action.sequence(
                scene.Action.rotate_to(-0.5, duration/50),
                scene.Action.rotate_to(0.5, duration/50)), 15))
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.0, duration/10/2),
            scene.Action.rotate_to(0.0, duration/10/2)))
    return scene.Action.sequence(action_list)

def Animation_Pulse(duration=1.5):
    action_list=[]
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.5, duration/10/2),
            scene.Action.scale_to(0.5, duration/10/2)))
    action_list.append(
        scene.Action.repeat(
            scene.Action.sequence(
                scene.Action.scale_to(0.5, duration/50),
                scene.Action.scale_to(1.5, duration/50)), 15))
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.0, duration/10/2)))

    return scene.Action.sequence(action_list)

def Animation_Spin(duration=1.5):
    action_list=[]
    action_list.append(
            scene.Action.scale_to(1.5, duration/10/2))
    action_list.append(
        scene.Action.repeat(
            scene.Action.sequence(
                scene.Action.rotate_by(10, duration/20),
                scene.Action.rotate_by(5, duration/20)), 10))
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.0, duration/10/2),
            scene.Action.rotate_to(0, duration/10/2)))

    return scene.Action.sequence(action_list)

class main(scene.Scene):
    def setup(self):
        self.buttons=list([])
        self.background_color='lightgray'

        self.my_button_shake=ButtonNode(size=scene.Size(100, 70),
            text=None, parent=self, action=my_button_action,
            position=scene.Point(sw/6*1, sh/2), animated_icon=True,
            icon_animation=Animation_Shake)
        self.buttons.append(self.my_button_shake)

        self.my_button_pulse=ButtonNode(size=scene.Size(100, 70),
            text=None, parent=self, action=my_button_action,
            position=scene.Point(sw/6*3, sh/2), animated_icon=True,
            icon_animation=Animation_Pulse)
        self.buttons.append(self.my_button_pulse)

        self.my_button_spin=ButtonNode(size=scene.Size(100, 70),
            text=None, parent=self, action=my_button_action,
            position=scene.Point(sw/6*5, sh/2), animated_icon=True,
            icon_animation=Animation_Spin)
        self.buttons.append(self.my_button_spin)

        self.quiter=ButtonNode(size=scene.Size(32, 32),
            text='X', parent=self, action=self.quit,
            position=scene.Point(sw-50, sh-50), icon=None,
            accessory=None)
        self.buttons.append(self.quiter)

    def quit(self, sender):
        self.view.close()

    def touch_began(self, touch):
        for btn in self.buttons:
            if self.point_from_scene(touch.location) in btn.frame:
                btn.Button_Tapped()

scene.run(main())
Karina

@stephen yes, this helped, esp that the sender is ui.Button. It is ui.Button or they just similar and somehow limited?

stephen

@Karina said:

@stephen yes, this helped, esp that the sender is ui.Button. It is ui.Button or they just similar and somehow limited?

Not just any button. It's the same one you tapped on. It just like saying this:

```python
my_button = ui.button(frame(0, 0, 150, 75), title='this is my button')

sender = my_button

stephen

@Karina said:

It is ui.Button or they just similar and somehow limited?

i wanted to add that sender is just a positional argument. just like self and cls. you can put any word you want. for example:


def button_tapped(sender):
    and
def button_tapped(button_that_was_tapped):

they both are the same outcome ๐Ÿ˜€

Karina

@stephen what is scale really? I thought it's measure of an angle. When I played with shaking button and leaved only the scale, it just grew in size

Karina

In the def Button_Tapped() why do you set the conditions if self.animated_icon and self.icon_animation? And if self.enabled?
I didn't see where you fix them, there were just set in init. And the animation and sound both play when the button is tapped, so why don't put them in one condition?

stephen

@Karina said:

In the def Button_Tapped() why do you set the conditions if self.animated_icon and self.icon_animation? And if self.enabled?
I didn't see where you fix them, there were just set in init. And the animation and sound both play when the button is tapped, so why don't put them in one condition?

What do you mean by fix them?


# called when you tap the button
def Button_Tapped(self):
    if self.components['icon']:
        if self.animated_icon and self.icon_animation:
            self.components['icon'].run_action(self.icon_animation())
    if self.components['accessory']:
        if self.components['accessory'].alpha > 0:
            self.components['accessory'].alpha = 0
    if self.enabled:
        self.button_action(self)

i see here i could of done this much better. but for each component we do a null check then if not None then run codes for needs.

As for icon i have separate properties like this so i can store and hold an Action Object from icon_animation while being able to use the Boolean animated_icon to enable/disable animation. Otherwise i would have to set icon_animation to None to disable and create a new one to Enable again. my goal with UIButton is to have it so there is very little need to change or create anything once Developer creates thier button.

note: The check on if there is a value for icon_animation is to avoid an excepton if there isn't one.

For the improvment lets move the enabled check one level above everything else so we skip all the animation stuff if its not enabled:

    # called when you tap the button
    def Button_Tapped(self):
        if self.enabled:
            if self.components['icon']:
                if self.animated_icon and self.icon_animation:
                    self.components['icon'].run_action(self.icon_animation())
            if self.components['accessory']:
                if self.components['accessory'].alpha > 0:
                    self.components['accessory'].alpha = 0
            if self.button_action:
                self.button_action(self)
Karina

What do you mean by fix them?

I mean that you set them once and didn't change. But your explanations helped though you didn't understood my question๐Ÿ˜„
Also it looks strange to me that you pass icon_animation as variable and then use it as a method

Karina

I think I'll use all the animations, just instead of spin I tried to do jump

def Animation_Jump(duration=1.5):
    action_list=[]
    action_list.append(
            scene.Action.sequence(
                scene.Action.move_by(0, 20, duration/20),
                scene.Action.move_to(sw/6*5, sh/2, duration/20)))

The icon just disappears if use move_to, and move_by - if tap to often on the button, icon gets out from the frame (why this happens I understand). And when I check by print(self.my_button.position) it prints the right pos

stephen

@Karina said:

Also it looks strange to me that you pass icon_animation as variable and then use it as a method

hey sorry for the delay..

bestway to explain this is that functions, methods, classes and variables are objects. But not all are Callable i.e. Function and Method. a class can be a callable also if you Define the __call__ magic method.

what im doing with icon_animation is im creating a reference to the function and then when i need to call the function i use () with any paraniter arguments thats needed.

example:


def func(arg1, arg2):
    print(f'{arg1} and {arg2}')

func_ref = func

if __name__=='__main__':
    func_ref("foo", "bar")

stephen

@Karina said:

I think I'll use all the animations, just instead of spin I tried to do jump

```python
def Animation_Jump(duration=1.5):
action_list=[]
action_list.append(
scene.Action.sequence(
scene.Action.move_by(0, 20, duration/20),
scene.Action.move_to(sw/6*5, sh/2, duration/20)))

```
The icon just disappears if use move_to, and move_by - if tap to often on the button, icon gets out from the frame (why this happens I understand). And when I check by print(self.my_button.position) it prints the right pos

everything is great here but one small ... detail..

scene.Action.move_by(0, 20, duration/20) is perfectly fine, your saying to move the node +20pts on the y axis.

scene.Action.move_to(sw/6*5, sh/2, duration/20) Here is the problem.. lets say Originally your node is at (0, 0) after first Action, your Node is now at (0, 20).. when you run this Action, you move the Node to 5/6 the screen width and 1/2 screen height. so if screen size is 600X800 you move the Node to (500, 400)

Animation Position Steps:

  1. (0, 0)
  2. (0, 20)
  3. (500, 400)

heres a fix:

scene.Action.move_by(0, 20, duration/20),
scene.Action.move_by(0, -20, duration/20)
Karina

@stephen no problem, anyway thank youโ˜บ๏ธ

Karina
def Button_Tapped(self):
        if self.components['icon']:
            if self.animated_icon and self.icon_animation:
                self.components['icon'].run_action(self.icon_animation())
        if self.components['accessory']:
            if self.components['accessory'].alpha > 0:
                self.components['accessory'].alpha = 0
        if self.enabled:
            self.button_action(self)

It wasn't a good idea to put self.enabled in the beginning cause then the action didn't work - the button quit not working, sounds not playing
But I don't understand for what is it. You set it once and don't change

Karina

I thought nodes measure their position in the parent's coordinate system

scene.Action.move_by(0, 20, duration/20),
scene.Action.move_by(0, -20, duration/20)

I did it in the beginning, but if you tap to often, notes begin to exit the frame, because don't finish the last action

stephen

@Karina said:

@stephen no problem, anyway thank youโ˜บ๏ธ

not a problem, im just glad i can help ๐Ÿ˜Š

@Karina said:

I thought nodes measure their position in the parent's coordinate system

yes they do but when you use sw you are getting the screen width not the width of the parent.you could of used Node.parent.size[0] to get the local width.

@Karina said:

def Button_Tapped(self): if self.components['icon']: if self.animated_icon and self.icon_animation: self.components['icon'].run_action(self.icon_animation()) if self.components['accessory']: if self.components['accessory'].alpha > 0: self.components['accessory'].alpha = 0 if self.enabled: self.button_action(self)
It wasn't a good idea to put self.enabled in the beginning cause then the action didn't work - the button quit not working, sounds not playing
But I don't understand for what is it. You set it once and don't change

Try replacing the methods with this..

 # called when you tap the button
    def Button_Tapped(self):
        if self.enabled:
            if self.components['icon']:
                if self.animated_icon and self.icon_animation:
                    self.components['icon'].run_action(self.icon_animation())

            if self.components['accessory']:
                if self.components['accessory'].alpha > 0:
                    self.components['accessory'].alpha = 0

            self.button_action(self)

i think because our Animation function doesnt return a value when not called using (). just a guess. lol

Karina

@stephen I thought super() is responsible for frame only, but I occasionally deleted it, and got empty screen. So what super() does?
P S I fixed that, now it's nearly how I want it to be

Karina

I want to add some playing instruments and allow multi touch. But there's not many embedded pictures in pythonista, so I'll need somehow to work with files. In images I already did it, uploaded drums pict, only left with sounds

Karina

Now it's like that

import scene
import sound
import random
import objc_util

sw = scene.get_screen_size()[0]
sh = scene.get_screen_size()[1]

piano_sounds = ['piano:A3', 'piano:C3', 'piano:C4#', 'piano:D4', 'piano:E4', 'piano:F4', 'piano:G3#']
guitar_sounds = ['8ve:8ve-beep-warmguitar']
drum_sounds = ['drums:Drums_01', 'drums:Drums_04', 'drums:Drums_07', 'drums:Drums_10', 'drums:Drums_13', 'drums:Drums_16']

class ButtonNode(scene.ShapeNode):
    def __init__(self,
                    action,
                    icon,
                    text_color,
                    sounds,
                    corner_radius=8,
                    border_size=20,
                    text='',
                    name=f'ButtonNode',
                    bg_color='lightgrey',
                    anchor_point=(0.5, 0.5),
                    borderColor=None,
                    parent=None,
                    position=(0, 0),
                    size=(120, 45),
                    enabled=True,
                    animated_icon=False,
                    icon_animation=None,
                    *args, **kwargs):

        # these mainly for call to super()
        self.x, self.y = position
        self.w, self.h = size
        super().__init__(
                path=scene.ui.Path.rounded_rect(self.x, self.y, self.w, self.h, corner_radius),
                fill_color=bg_color,
                stroke_color=borderColor,
                shadow=None,
                parent=parent,
                *args, **kwargs)

        # Normal Properties for Instance()
        self.sounds=sounds
        self.enabled=enabled
        self.button_action=action
        self.name=name
        self.position=position
        self.size=size
        self.anchor_point=anchor_point

         # for border
        self.border_size=border_size
        self.borderColor=bg_color
        self.corner_radius=corner_radius

        # for icon
        self.icon_animation=icon_animation
        self.animated_icon=animated_icon
        self.icon=self._init_textures(icon)

        # for Label
        self.text=text 
        self.text_color=text_color

        # Container to hold each component. 
        # is just a dict version of self.children but specific.
        self.components=dict({
                'icon':None,
                'label':None})

        self._setup(self.icon, self.components)

    # Type Check to make sure img is a string or ui.Image
    def _init_textures(self, img):
        if type(img) == str or type(img) == scene.ui.Image:
            return scene.Texture(img)
        else:
            return None

    # setup our Components
    def _setup(self, i, c):
        if i != None:
            # button image
            c['icon']=scene.SpriteNode(
                    texture=i,
                    size=scene.Size(self.size[1]/100*80, self.size[1]/100*80), 
                    position=scene.Point(0, 0),                                
                    parent=self,
                    anchor_point=(0.5, 0.5),
                    z_position=9)

        if self.text:
            # button text..
            c['label']=scene.LabelNode(
                    text=self.text,
                    position=scene.Point(0 , 0),
                    anchor_point=(0.5, 0.5),
                    color=self.text_color,
                    parent=self,
                    z_position=10)

    # called when you tap the button
    def Button_Tapped(self):
        if self.components['icon']:
            if self.animated_icon and self.icon_animation:
                self.components['icon'].run_action(self.icon_animation())
        if self.enabled:
            self.button_action(self)

# custom action
def my_button_action(sender):
    random_tone=random.choice(piano_sounds)
    play = random.choice(sender.sounds)
    sound.play_effect(play)
    return


def Animation_Shake(duration=1.5):
    action_list=[]
    action_list.append(
            scene.Action.rotate_to(0.25, duration/10/2))
    action_list.append(
            scene.Action.sequence(
                scene.Action.rotate_to(-0.5, duration/50),
                scene.Action.rotate_to(0.5, duration/50)))
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.0, duration/10/2),
            scene.Action.rotate_to(0.0, duration/10/2)))
    return scene.Action.sequence(action_list)


def Animation_Pulse(duration=1.5):
    action_list=[]
    action_list.append(
        scene.Action.sequence(
            scene.Action.scale_to(1.2, duration/10/2),
            scene.Action.scale_to(0.5, duration/10/2)))
    action_list.append(
                scene.Action.scale_to(1.2, duration/50))
    action_list.append(
        scene.Action.group(
            scene.Action.scale_to(1.0, duration/10/2)))

    return scene.Action.sequence(action_list)

def Animation_Jump(duration=1.5):
    action_list=[]
    action_list.append(
            scene.Action.sequence(
                scene.Action.move_by(0, 20, duration/20),
                scene.Action.move_to(0, 0, duration/20)))

    return scene.Action.sequence(action_list)

class main(scene.Scene):
    def setup(self):
        self.buttons=list([])
        self.background_color='lightgray'

        self.guitar=ButtonNode(size=scene.Size(160, 112), icon='emj:Guitar',
            text=None, text_color=self.background_color, parent=self, action=my_button_action, sounds=guitar_sounds,
            position=scene.Point(sw/6*1.5, sh/3), animated_icon=True,
            icon_animation=Animation_Shake)
        self.buttons.append(self.guitar)

        self.my_button_pulse=ButtonNode(size=scene.Size(100, 70), icon='emj:Musical_Notes',
            sounds=drum_sounds, text=None, text_color=self.background_color, parent=self, 
            action=my_button_action, position=scene.Point(sw/6*3, sh/2), 
            animated_icon=True, icon_animation=Animation_Pulse)
        self.buttons.append(self.my_button_pulse)

        self.my_button_jump=ButtonNode(size=scene.Size(100, 70), icon='emj:Musical_Keyboard',
            text=None, text_color=self.background_color, parent=self, action=my_button_action, sounds=piano_sounds,
            position=scene.Point(sw/6*5, sh/2), animated_icon=True,
            icon_animation=Animation_Jump)
        self.buttons.append(self.my_button_jump)

        self.quiter=ButtonNode(size=scene.Size(32, 32),
            text='X', text_color='black', parent=self, action=self.quit, sounds=None,
            position=scene.Point(sw-50, sh-50), borderColor='black', icon=None)
        self.buttons.append(self.quiter)

    def quit(self, sender):
        self.view.close()

    def touch_began(self, touch):
        for btn in self.buttons:
            if self.point_from_scene(touch.location) in btn.frame:
                btn.Button_Tapped()

scene.run(main())
stephen

@Karina

Very well done!

Bumbo Cactoni

I have noticed a slight error in the game. Occasionally, I will be playing the game, and face a solid wall of gray asteroids. How do I avoid the wall? I canโ€™t. Please, fix this?

stephen

@Bumbo-Cactoni said:

I have noticed a slight error in the game. Occasionally, I will be playing the game, and face a solid wall of gray asteroids. How do I avoid the wall? I canโ€™t. Please, fix this?

There should always be a gap. What I think is happening here is the gap is being placed above the View Frame and we just cannot see it. I ran some test after changing the following inside Brush class


def __init__(self, game):
        self.game=game
        self.crateMin=200
        self.crateMax=780
        self.gaps=list()
        self.gapSize=4
        self.gapMin=4
        self.gapMax=6
        self.gameObjects=self.game.gameObjects
        self.startPos=Point(Screen.Width()+128, self.gameObjects.Floor)
        self.ShieldBoost()

i did not see this happen again but there could still be a chance somehow. all you need to do is limit the hieght for spawning the objects. you could also do a last check to insure there is a gap pressent before returning.

Bumbo Cactoni

@stephen
When I tried to run the code with your updated script, it gave me this error:

TypeError
__init__() got an unexpected keyword argument โ€˜isObsticleโ€™

Karina

@stephen hereโ€™s I did something relying on your code, itโ€™s very similar to the music buttons

from scene import *
import sound


def sw(): return get_screen_size()[0]
def sh(): return get_screen_size()[1]
def bw(): return 100
def bh(): return 100

icons = {
         'iob:arrow_down_b_256' : (sw()/2, 60),
         'iob:arrow_up_b_256' : (sw()/2, bh() + 60),
         'iob:arrow_left_b_256' : (sw()/2 - bw(), bh()),
         'iob:arrow_right_b_256' : (sw()/2 + bw(), bh())
          }


class Arrow(ShapeNode):
    def __init__(self,
                 picture,
                 path=None,
                 size=Size(120, 120),
                 corner_radius=8,
                 border_size=20,
                 borderColor='#3f0917',
                 position=(0,0),
                 parent=None,
                 *args, **kwargs):

                    #for border
                    self.picture = picture
                    self.corner_radius = corner_radius
                    self.border_size = border_size
                    self.borderColor = borderColor

                    self.position = position
                    self.size = size

                    #for super()
                    self.x, self.y = position
                    self.w, self.h = size

                    super().__init__(fill_color='white',
                                    path=ui.Path.rounded_rect(self.x, 
                                                               self.y,
                                                               self.w/1.5, 
                                                               self.h/1.5,
                                                               self.corner_radius),
                                    stroke_color=borderColor,
                                    parent=parent,
                                    *args, **kwargs)

                    self._setup(self.picture)

    def _setup(self, pict):
        if self.picture:
            arrow = SpriteNode(self.picture,
                               position=Point(0, 0), 
                               size=(100, 100),
                               parent=self)


class Main(Scene):                               
    def setup(self):
        fill_color = self.background_color
        self.background_color = 'white'
        self.arrows = []
        for i in icons:
            arw = Arrow(picture=i, position=icons[i], parent=self)
            self.arrows.append(arw)



    def touch_began(self, touch):
        tapped = True
        for arw in self.arrows:
            if touch.location in arw.frame:
                sound.play_effect('rpg:Chop')
                arw.fill_color = '#b2b2b2'


    def touch_ended(self, touch):
        for arw in self.arrows:
            if arw.fill_color == '#b2b2b2':
                arw.fill_color = 'white'




run(Main(), PORTRAIT) 

I think it will be a base for Tetris

ccc

https://docs.python.org/3/library/functions.html#enumerate is your friend.

        self.arrows = []
        for i in icons:
            arw = Arrow(picture=i, position=icons[i], parent=self)
            self.arrows.append(arw)

# -->

        self.arrows = [Arrow(picture=i, position=icon, parent=self)
                       for i, icon in enumerate(icons)]
Karina

@cvp with enumerate doesnโ€™t work cause with this for i, icon in enumerate(icons) you get keys of icons and nums 0, 1, 2. So i is the number and icon picture
I did the same but without enumerate

self.arrows = [Arrow(i, position=icons[i], parent=self) for i in icons] 

Looks better than it was before๐Ÿ‘

cvp

@Karina I suppose that you wanted to answer to @ccc ๐Ÿ˜€

Karina

@cvp yes youโ€™re right ๐Ÿ˜