(Solved)How to reduce lag predicting 2000 sprites in SpriteManager?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
User avatar
SONTSE
Regular
Posts: 95
Joined: Sun Nov 24, 2013 10:49 pm
Completed: 8 VN's so far
Contact:

(Solved)How to reduce lag predicting 2000 sprites in SpriteManager?

#1 Post by SONTSE » Tue Jun 05, 2018 11:26 am

Trying to make some cool pixellation mosaic effect tracking mouse pointer.
Actual code below

According to behavior i assume the engine suffering difficulties preloading so many sprites. Have you any ways/ideas to make it faster or maybe preload it separately in chunks, so i could sneak it in game without much notice.

Thanks!

Code: Select all

init python:
    import math
    
    def inrange(input,min,max): #ensures input numbers won't fall out of range
        if input<min:
            return min
        elif input> max:
            return max
        else:
            return input
    
    class MosaicStats():               #bunch of support global vars
        def __init__(self,child,od,td,pixel):
            self.sx = config.screen_width
            self.sy = config.screen_height
            
            self.od = od                      #radius of small circle of opaque squares 
            self.td = td                      #big circle of semi-transparent squares
            self.child = child                #blurred ver.of image pixellated
            self.pixel = pixel                #pixellation square size
            self.children = []                #list of Sprite objects
            self.predict = []                #list of predictable objects
            self.mx = 0                       #coordinates of mouse pointer
            self.my = 0
            
    m = MosaicStats('images/cg-blur.png',2,4,32)

    def predict_spawn(ix=None):             #takes an argument for verbose mode
        if ix==None:
            for ix in range(0,m.sx,m.pixel):
                for iy in range(0,m.sy,m.pixel):
                    ix_=inrange(ix,0,m.sx-1)  #get last pixel if beyond dimentions
                    iy_=inrange(iy,0,m.sy-1)
                    
                    t = im.Crop(m.child,(ix_,iy_,1,1))
                    t = im.Scale(t,m.pixel,m.pixel)    
                    m.predict.append(t)
                    t = im.Alpha(t,.5)
                    m.predict.append(t)
        else:
            for iy in range(0,m.sy,m.pixel):
                ix_=inrange(ix,0,m.sx-1)  #get last pixel if beyond dimentions
                iy_=inrange(iy,0,m.sy-1)
                
                t = im.Crop(m.child,(ix_,iy_,1,1))
                t = im.Scale(t,m.pixel,m.pixel)    
                                         #scaled 1pixel crop = pixellation square :)
                m.predict.append(t)
                t = im.Alpha(t,.5)
                m.predict.append(t)
        
    #predict_spawn()     
    
    
    def mosaic_spawn():
        i = 0
        for ix in range(0,m.sx,m.pixel):
            for iy in range(0,m.sy,m.pixel):
                ix_=inrange(ix,0,m.sx-1)  #get last pixel if beyond dimentions
                iy_=inrange(iy,0,m.sy-1)
                

                m.children.append(mosaic.create(Null()))
                o = m.children[-1]       #throw addition entities to just created Sprite        
                o.t = i   #opaque child of each squares
                i+=1
                o.t5 = i
                i+=1
                o.x = ix    #coordinates of each 
                o.y = iy
                o.a = 0    #alpha value of each

                 
                

        

    def mosaic_update(st):
        return .04

    def mosaic_event(ev, x, y, st):
        global m
        if m.children == []:
            mosaic_spawn()
        if True:       #delay to ensure mosaic_spawn() runs properly
        
            m.mx, m.my = (x,y)
            for i in m.children:
                r = math.hypot(i.x-m.mx,i.y-m.my)
                if r>m.td*m.pixel:     #hide all pixels if too far
                    if i.a == 0:
                        continue
                    else:
                        i.a = 0
                        i.set_child(Null())
                else:
                    i.a = 1 if r<m.od*m.pixel else .5   
                    i.set_child(m.predict[i.t] if r<m.od*m.pixel else m.predict[i.t5] )
                    #opaque pixel if near, semi-transparent if a bit farther
                    
    def mosaic_predict():
        return m.predict
    
                    
    mosaic = SpriteManager(update=mosaic_update,event=mosaic_event,predict=mosaic_predict)                
            


label main_menu:
    return
label start:
    scene cg
    $ ix = 0
    while ix<m.sx:
        $predict_spawn(ix)
        'LOADING [ix]{nw}'
        $ix+=m.pixel
    show expression mosaic as mosaic
    'TEST POINT 1'
    'TEST POINT 2'
    'RESTART POINT'
    return

Last edited by SONTSE on Tue Jun 05, 2018 7:39 pm, edited 1 time in total.
Look! It's moving. It's alive. It's alive... IT'S ALIVE! Oh, in the name of God! Now I know what it feels like to be God!(@Henry_Frankenstein. Sums up my coding style)

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: How to reduce lag predicting 2000 sprites in SpriteManager?

#2 Post by Remix » Tue Jun 05, 2018 5:47 pm

Though SpriteManager offers some useful functionality, in this instance I would tend toward using a dynamic displayable or container.

The optimum approach would likely be to have a dynamically composed image within a container that had pixel dipped the background at init and then directly blit colour squares at various points in that image and move it around using mouse position...
The following code does a semblance of that except it uses a full size transparent image and just puts children in wherever needed.

You will notice it ramps up CPU usage and, as such, I would not really advise using it for any long duration in a production release (more just posted for fun)

Code: Select all

init python:               

    class MousePosMosaic(renpy.display.layout.Container):
        """
        A dynamic container that draws a pixellated circle of the base
        around the mouse position
        """

        def __init__(self, *args, **kwargs):

            super(MousePosMosaic, self).__init__( **kwargs )

            self.image = renpy.display.im.Image(args[0]).load()

            self.w, self.h = self.image.get_size()

            self.mouse_pos = renpy.get_mouse_pos()

            self.pixel_size = kwargs.get('pixel_size', 32)

            self.radius = kwargs.get('radius', 160)

            hp = int( round( self.pixel_size / 2 ) )
            self.pixel_colours = []
            for y in range(0, self.h / self.pixel_size):
                colours = []
                for x in range(0, self.w / self.pixel_size):
                    colours.append(
                        self.image.get_at( ( 
                            ( (x * self.pixel_size ) + hp),
                            ( (y * self.pixel_size ) + hp) ) ) )
                self.pixel_colours.append(colours)
            # basically a map that says
            # [ [ (124, 76, 198, 255), (...), (...) ], ... ]

            self.box_size = self.radius // self.pixel_size



        def update(self):

            renpy.display.render.invalidate(self)

            x, y = self.mouse_pos

            for bx in range( max( 0, x-self.box_size), 
                             min(x+self.box_size,
                                 len(self.pixel_colours[0]) ) ):

                for by in range( max(0, y-self.box_size), 
                                 min(y+self.box_size,
                                          len(self.pixel_colours) ) ):

                    radius = 1.0 - ( ( ( 
                        (bx-x) ** 2 
                        + (by-y) ** 2 ) ** 0.5 ) / float(self.box_size) )

                    if radius < 0.0:
                        continue 

                    colour = list( self.pixel_colours[by][bx][:3] )
                    colour += [ int(self.pixel_colours[by][bx][3] * radius) ]

                    self.add( renpy.display.imagelike.Solid(
                        renpy.easy.color(tuple(colour)),
                        xysize=(self.pixel_size, self.pixel_size),
                        xpos=bx*self.pixel_size,
                        ypos=by*self.pixel_size) ) 


        def event(self, ev, x, y, st):

            x, y = [ int(k // self.pixel_size) for k in [x,y] ]

            if all( [ 0 <= x <= len(self.pixel_colours[0]),
                      0 <= y <= len(self.pixel_colours),
                      self.mouse_pos != (x,y) ] ):

                self._clear()

                self.mouse_pos = (x,y)

                self.update()

            return None


        def visit(self):
            
            return [ ]




label start:

    scene cg

    show expression MousePosMosaic( 'images/cg.png', pixel_size=24, radius=160 )

    "..."
    
    return
Frameworks & Scriptlets:

User avatar
SONTSE
Regular
Posts: 95
Joined: Sun Nov 24, 2013 10:49 pm
Completed: 8 VN's so far
Contact:

Re: How to reduce lag predicting 2000 sprites in SpriteManager?

#3 Post by SONTSE » Tue Jun 05, 2018 7:39 pm

It works... ohmy, it works! Thanks you so much, Remix, you are my hero! <3

Wow... just wow, this code is a treasure. Some part of it is like something I directly searched for long and to no avail, yet some other is just voodoo for me. Will study it thoroughly, actually would love to dig into some relevant manuals, RenPy docs just ¯\_(ツ)_/¯ me at all my queries.
Look! It's moving. It's alive. It's alive... IT'S ALIVE! Oh, in the name of God! Now I know what it feels like to be God!(@Henry_Frankenstein. Sums up my coding style)

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: (Solved)How to reduce lag predicting 2000 sprites in SpriteManager?

#4 Post by Remix » Wed Jun 06, 2018 4:24 pm

A quick overview:

The class is derived from a standard Ren'Py Container (much like your usual HBox, Frame, Composite and kin) which basically means it expects to have children displayables inside it. The parent Container object handles all the redrawing, styling, laying out etc, so all we need do is add a few children at set places and tell each one where it sits...

__init__
We pass in our image filename as first argument and possible keywords pixel_size and radius which hopefully are self explanatory
On init we load the image, store its size and then scamper through it dipping a pixel colour at various steps through the image

event()
This method is called whenever anything happens inside our object on the screen (Container makes that all happen) which includes moving the mouse
We basically just say, if the mouse is on screen and the recognized (x,y) is not our current one, call the update method to draw new version
Note: (x,y) is converted to be the integer floor of each divided by our pixel_size ( e.g int( 100 // 24 ) = 4 )

update()
We call this ourselves if we deem that a new image is needed in our event method
First we renpy.display.render.invalidate(self) which basically just tells Ren'Py not to just draw the old version of us
As event() cleared our children, we have a clean canvas to draw new ones on
We use the mouse pos to grab info from the __init__ colour map we dipped and just add Solids as children at set positions and alpha

Some of the functionality of event() could be in update() and vice versa... there is no rigidly 'correct' way to do it

Container handles the render and simply iterates the children and blits them to the canvas as needed.


Ren'Py Docs is part way through evolving from 6.99 to 7.0+ ... just give it some time and it will become more useful ;)
Frameworks & Scriptlets:

Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot]