How to draw an image through a function?

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
span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

How to draw an image through a function?

#1 Post by span4ev » Mon Aug 15, 2022 6:32 pm

Hi all. Again I hope for your help.

I want to arrange objects on the screen using an array, which is a map.
Objects on the map are defined by different values. So far, there are only 2 of them in the example: "+" and "-"
Each of them can be defined by some kind of image, such as grass and stone (as an example).

This is a working option.
2022-08-16_012858.png

Code: Select all

init python:
    class Level:

        self.level_map          = [
            '++++++++++++++++++++',
            '+-------------+----+',
            '+----++-----------++',
            '+---------+--------+',
            '++++++++++++++++++++',
        ]

screen show_map:

    $ x = 0
    $ y = 0

    for row in level.level_map:
        for col in row:
            if col == '+':
                $ img = level.stone
            if col == '-':
                $ img = level.grass

            image Transform(img, size=(level.sizes), pos=(x, y))
            $ x += level.width
        $ y += level.height
        $ x = 0
I don't want to loop in "screen" and want to move this to a class method, but I couldn't find any example how to do it...
I did not find an analogue of image Transform () for the function.

It should somehow work, because there is this:

Code: Select all

init python:
    def some_func(var):
        renpy.show_screen('test', var)
        # You can show the screen through functions.
and there is also this:

Code: Select all

renpy.image(name, d) # But how do you draw an image through a function?
But I couldn't figure out how to use renpy.image(Transform()) in a function

Ultimately it should look like this:

Code: Select all

class Level:

    # Method for drawing the map.
    def draw(self):

        x = 0
        y = 0

        for row in self.level_map:
            for col in row:
                if col == '+':
                    img = self.stone
                if col == '-':
                    img = self.grass

                renpy.image(Transform(img, size=(self.sizes), pos=(x, y)) )
                x += self.width
            y += self.height
            x = 0



screen show_map:

    $ level.draw() # I only call the class method.

User avatar
m_from_space
Veteran
Posts: 302
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: How to draw an image through a function?

#2 Post by m_from_space » Tue Aug 16, 2022 7:31 am

Why don't you want to loop inside a screen? Since all those map tiles have the same size, this calls for a grid in my opinion.

https://www.renpy.org/doc/html/screens.html#grid

span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

Re: How to draw an image through a function?

#3 Post by span4ev » Tue Aug 16, 2022 1:24 pm

m_from_space wrote:
Tue Aug 16, 2022 7:31 am
Why don't you want to loop inside a screen? Since all those map tiles have the same size, this calls for a grid in my opinion.

https://www.renpy.org/doc/html/screens.html#grid
For many reasons:
1. It can be removed in a function
2. This will unload the code inside 'screen'
3. I get a performance drop when I move a character, he moves with a delay. As I understand it, this is due to the fact that the loop is triggered every time. In the example, I have to use a loop inside the screen because I don't know how to draw a picture inside the functions.
But I need the function to work only 1 time, in which case the loop will be executed only 1 time and this will not affect performance.
If I figure out how to draw a grid inside functions, I will call the function outside the screen only 1 time. It makes no sense to call the loop more than once if its task is to draw objects and finish the work.
4. It is easier and more convenient to work with functions. A function is a universal piece of code. I can use the function multiple times to draw other maps on other levels.
5. If I need to redraw the map (add new objects, remove unnecessary ones), I can call the function again and pass its modified array.
I think that I have not written all the reasons why loops should be moved to the function body, but these arguments should be enough

Why do you want to use a loop inside 'screen'? It will fire every time and draw objects on top of the same drawn objects.
Do you know how to draw an image inside a function? Do I just need an example of this syntax?

User avatar
Ocelot
Eileen-Class Veteran
Posts: 1843
Joined: Tue Aug 23, 2016 10:35 am
Github: MiiNiPaa
Discord: MiiNiPaa#4384
Contact:

Re: How to draw an image through a function?

#4 Post by Ocelot » Tue Aug 16, 2022 2:01 pm

a) RenPy does not allow to draw "image" on display. You need to use Displayables.
b) You can show single displayable using show command (providing unique tag for each), or have displayables grouped together as part of screen.
c) Grid of several hundred small repeating images should not cause any perceivable slowdown as long as you are not creating new Displayables on each evaluation.
d) You can improve perfomance further by making a Creator Defined Displayable and drawing everything in its render method. https://www.renpy.org/dev-doc/html/cdd.html
< < insert Rick Cook quote here > >

User avatar
Alex
Lemma-Class Veteran
Posts: 2978
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: How to draw an image through a function?

#5 Post by Alex » Tue Aug 16, 2022 3:07 pm


User avatar
m_from_space
Veteran
Posts: 302
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: How to draw an image through a function?

#6 Post by m_from_space » Wed Aug 17, 2022 12:25 am

span4ev wrote:
Tue Aug 16, 2022 1:24 pm
3. I get a performance drop when I move a character, he moves with a delay. As I understand it, this is due to the fact that the loop is triggered every time.
Since this is a map, you should be able to draw it in a separate screen that has a lower zorder and your character images on top of it. Of course you don't put everything in one screen. The map should not be redrawn every time.

span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

Re: How to draw an image through a function?

#7 Post by span4ev » Wed Aug 17, 2022 2:08 pm

m_from_space wrote:
Wed Aug 17, 2022 12:25 am
span4ev wrote:
Tue Aug 16, 2022 1:24 pm
3. I get a performance drop when I move a character, he moves with a delay. As I understand it, this is due to the fact that the loop is triggered every time.
Since this is a map, you should be able to draw it in a separate screen that has a lower zorder and your character images on top of it. Of course you don't put everything in one screen. The map should not be redrawn every time.
I recorded a 25 second video of how it works. Forgive me for the terrible pictures - for training I always use the first thing that catches my eye, because as long as I write the logic, the appearance is not important.

https://youtu.be/6iDFFemC8cY

I took out the function of drawing the character and moving it to a separate screen. In another screen, I have a loop in which the map is drawn.
As you can see, the character moves with a delay. It's not noticeable on the video, but the character continues to move after I stop holding the key, for about 0.5 - 1 second.
I don't know what exactly is the problem:
1. Renpy is designed for visual novels, so it doesn't do well with such tasks.
2. Renpy draws screen and all objects in a loop too often every time.

Therefore, I wanted to put it in a function and call it only 1 time.

In general, drawing objects and a character is the easiest thing. The most difficult thing is to write the program logic for the intersection of objects. But I ran into difficulties even at the simplest stage.

I know that RPGM or Unity (Construct 3, Godot) is used for such tasks, but I love python and I like Renpy, so I wanted to try to do everything in Renpy.
Last edited by span4ev on Wed Aug 17, 2022 2:22 pm, edited 2 times in total.

span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

Re: How to draw an image through a function?

#8 Post by span4ev » Wed Aug 17, 2022 2:11 pm

Alex wrote:
Tue Aug 16, 2022 3:07 pm
This will be useful - viewtopic.php?f=51&t=29964#p352984
Oh thanks! I haven't looked at how it works yet and just read the description quickly. I will definitely look into this in more detail a little later. I'm very curious if he could do:
1. Good performance
2. Ability to change map
3. Collision with objects

span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

Re: How to draw an image through a function?

#9 Post by span4ev » Wed Aug 17, 2022 2:14 pm

Ocelot wrote:
Tue Aug 16, 2022 2:01 pm
a) RenPy does not allow to draw "image" on display. You need to use Displayables.
b) You can show single displayable using show command (providing unique tag for each), or have displayables grouped together as part of screen.
c) Grid of several hundred small repeating images should not cause any perceivable slowdown as long as you are not creating new Displayables on each evaluation.
d) You can improve perfomance further by making a Creator Defined Displayable and drawing everything in its render method. https://www.renpy.org/dev-doc/html/cdd.html
I again ran into something that is not in Renpy. It's a pity. Until recently, I hoped that this could be done somehow, because if you can display the screen through a function, then why can't you display an image? ..
Now at least I know I was on the wrong track. Thanks for the answer and the right direction, what should I use.
Now I will read about Displayables and try to put it into practice. Looks complicated. But now I know it's probably the only option.

In the post I wrote above, I have attached a link to a 25 second video so you can see how much performance drops, although I use very little logic in the code.

span4ev
Regular
Posts: 65
Joined: Fri Jul 08, 2022 9:29 pm
itch: rpymc
Contact:

Re: How to draw an image through a function?

#10 Post by span4ev » Wed Aug 17, 2022 2:20 pm

all the code i use
I put the code in the spoiler but it didn't work. I apologize

Code: Select all

init python:

    class Hero:
        def __init__(self):

            self.screen_width       = config.screen_width 
            self.screen_height      = config.screen_height
            self.width              = 40
            self.height             = 40
            self.sizes              = (self.width, self.height)

            self.start_x            = self.screen_width  // 2 - self.width  // 2
            self.start_y            = self.screen_height // 2 - self.height // 2
            self.edge_x             = self.screen_width  - self.width
            self.edge_y             = self.screen_height - self.height
            self.x                  = self.start_x
            self.y                  = self.start_y
            self.speed              = 10

            self.direction          = 'front'
            self.path               = 'images/hero_' + self.direction + '.png'

        def update(self, direction):

            self.direction = direction

            if direction == 'left' and self.x > 0:
                self.x -= self.speed
            elif direction == 'right' and self.x < self.edge_x:
                self.x += self.speed
            elif direction == 'back' and self.y > 0:
                self.y -= self.speed
            elif direction == 'front' and self.y < self.edge_y:
                self.y += self.speed

            self.path = 'images/hero_' + self.direction + '.png'

        def draw(self):

            return Transform(self.path, size=(self.sizes), pos=(self.x, self.y))


    class Level:
        def __init__(self):
            
            self.screen_width       = config.screen_width 
            self.screen_height      = config.screen_height
            self.width              = 40
            self.height             = 40
            self.sizes              = (self.width, self.height)

            self.max_tiles_x        = self.screen_width  // self.width 
            self.max_tiles_y        = self.screen_height // self.height 

            self.grass              = 'images/grass.png'
            self.stone_1            = 'images/stone.png'
            self.stone_2            = 'images/stone_2.png'
            self.tree_1             = 'images/tree_1.png'
            self.tree_2             = 'images/tree_2.png'


            self.level_map          = [
                '++++++++++++++++++++++++++++++++',
                '+-------l----------------------+',
                '+-!----------!--------!--------+',
                '+-----=---------------------!--+',
                '+----l---------=--------=------+',
                '+--!------=------!-=------!----+',
                '+-----l------------------------+',
                '+-------l----------------------+',
                '+-!----------!--------!--------+',
                '+-----=------------------------+',
                '+----l---------=--------=------+',
                '+-----------------l------------+',
                '+-----------l------------------+',
                '+-----l------------------------+',
                '+------------=-----------=-----+',
                '+--!------=--------=------!--!-+',
                '+-----l----------!-------------+',
                '++++++++++++++++++++++++++++++++',
            ]

            self.sprites = {
                '+' : self.stone_1,
                '-' : self.grass,
                '=' : self.stone_2,
                '!' : self.tree_2,
                'l' : self.tree_1,
            }

        def get_sprite(self, sign):
            if sign in self.sprites:
                img = self.sprites[sign]
                return img

init:

    define hero = Hero()
    define level = Level()


screen show_hero:
    image hero.draw()

    key 'K_RIGHT'           action Function(hero.update, 'right')
    key 'repeat_K_RIGHT'    action Function(hero.update, 'right')
    key 'K_LEFT'            action Function(hero.update, 'left')
    key 'repeat_K_LEFT'     action Function(hero.update, 'left')
    key 'K_UP'              action Function(hero.update, 'back')
    key 'repeat_K_UP'       action Function(hero.update, 'back')
    key 'K_DOWN'            action Function(hero.update, 'front')
    key 'repeat_K_DOWN'     action Function(hero.update, 'front')

screen show_map:

    $ x = 0
    $ y = 0

    for row in level.level_map:
        for sign in row:
            $ img = level.get_sprite(sign)
            image Transform(img, size=(level.sizes), pos=(x, y))
            $ x += level.width
        $ y += level.height
        $ x = 0


    vbox:
        vbox:
            text 'x: ' + str(hero.x) size 20 color '#FFF'
            text 'y: ' + str(hero.y) size 20 color '#FFF'
            text hero.direction
            text 'scr w: ' + str(hero.screen_width)
            text 'scr h: ' + str(hero.screen_height)

    

label game:

    show screen show_map
    show screen show_hero
    pause

label start:
    
    $ Game_running = True
    while Game_running:

        jump game

    return

Post Reply

Who is online

Users browsing this forum: Google [Bot], LuckyT, Ocelot