Page 1 of 3

Drawing in Ren'Py

Posted: Tue Aug 14, 2018 12:17 pm
by Kinmoku
Okay, this is a big question. I don't think I've seen it done in a Ren'Py game before but I'm sure it's possible –somehow– with python...

Can a player draw a picture in Ren'Py? I want to add a feature where the player can draw and submit/ wipe clean a simple black and white drawing. The idea is that the drawing will be crude/ funny, and purely there just for a bit of fun, hence I'm not too concerned about people using a mouse.

If it's possible, then I think I'll bring in a programmer to do this for me (as I'm terrible at it myself!).

I thought it's best to ask first, to know if it's even a possibility, before getting someone involved!

Re: Drawing in Ren'Py

Posted: Tue Aug 14, 2018 12:32 pm
by Per K Grok
There is something called renpy.render.canvas https://www.renpy.org/doc/html/udd.html ... der.canvas
on which you should be able to use some draw functions. I have not got that work myself yet though. :)

Re: Drawing in Ren'Py

Posted: Tue Aug 14, 2018 7:26 pm
by Imperf3kt
This was asked once before and the conclusion at that time was that it was not possible in any feasible way.

A recent thread prompted me to conside building a grid, made up of 1*1 solids. Upon mouseover (perhaps, only when holding down a key), any hovered grid section would swap colors.

It might work, it might not. I expect, if it does work, it would be fairly GPU intensive.

Re: Drawing in Ren'Py

Posted: Tue Aug 14, 2018 7:58 pm
by MaydohMaydoh
Imperf3kt wrote: Tue Aug 14, 2018 7:26 pm This was asked once before and the conclusion at that time was that it was not possible in any feasible way.

A recent thread prompted me to conside building a grid, made up of 1*1 solids. Upon mouseover (perhaps, only when holding down a key), any hovered grid section would swap colors.

It might work, it might not. I expect, if it does work, it would be fairly GPU intensive.
It's a good idea in theory, but I agree, feels like it would lag due to the 1000s of buttons on screen. A window size of 1280* 720 would have 921600 buttons... You could increase the size of each to 2*2, to half the amount of buttons but who knows.
I'm going to try it out at some point and see how it goes.

Re: Drawing in Ren'Py

Posted: Wed Aug 15, 2018 12:48 pm
by Kinmoku
Hmm okay, good to know. My idea was to have the draw area a lot smaller (probably around 700 x 400), although it still maybe too big.

If I get chance, I may try it out and see how it runs on a lower-end device. I still really want to do it as I think it'd add quite a charming little feature to my game :)

If anyone else tries it in the meantime, please comment back on this thread so I know how it goes :D

Re: Drawing in Ren'Py

Posted: Wed Aug 15, 2018 5:02 pm
by MaydohMaydoh
So I gave it a go and it is pretty laggy. Max size I could do before it lagged unbearably on my PC was 40x40. 50x50 was bad and once you start getting towards 100x100 and beyond, it lagged so bad I kept getting has stopped responding messages.

Code: Select all

init python:
    def fSetVal(max):
        l = []
        for i in range(max):
            l.append(False)
        return l

screen drawing():

    tag menu
    
    predict False
    
    default rows = 40
    default cols = 40
    default lDraw = fSetVal(cols * rows, False)
    default tPixelSize = (10, 10)
    default bDraw = False
    default bErase = False

    grid cols rows:
    
        for i in range(cols * rows):

            button xysize (tPixelSize):
            
                action NullAction()
                
                hovered [ If(bDraw, SetDict(lDraw, i-1, True)), If(bErase, SetDict(lDraw, i-1, False)) ]
                
                if lDraw[i-1]:
                    background '#000000'
                else:
                    background '#FFFFFF'

                key 'mousedown_1' action SetScreenVariable('bDraw', True)
                key 'mouseup_1' action SetScreenVariable('bDraw', False)
                key 'mousedown_3' action SetScreenVariable('bErase', True)
                key 'mouseup_3' action SetScreenVariable('bErase', False)
One issue is that if you hover over a button and click, it won't change. You need to get a new hover event to change a button. Only way to do it is to change the button_select keymap to mousedown_1 and add SetDict(lDraw, i-1, True) to the action because for some reason changing

Code: Select all

key 'mousedown_1' action SetScreenVariable('bDraw', True)
to

Code: Select all

key 'mousedown_1' action [ SetScreenVariable('bDraw', True), SetDict(lDraw, i-1, True) ]
will only change the last button in the bottom right corner, no matter where you click. But I'm sure I'm just missing something and it's relatively simple to fix.
I also messed about and made a color version.

Code: Select all

init python:
    def fSetValColors(max, value):
        l = []
        for i in range(max):
            l.append(value)
        return l
        
screen drawing_color():
    
    tag menu
    
    predict False
    
    default rows = 40
    default cols = 40
    default lDraw = fSetValColors(cols * rows, '#FFFFFF')
    default tPixelSize = (10, 10)
    default bDraw = False
    default bErase = False
    default color_list = [ '#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF' ]
    default draw_color = color_list[1]
    
    vbox:
    
        grid cols rows:
        
            for i in range(cols * rows):
                
                button xysize (tPixelSize):
                    
                    action NullAction()
                    
                    hovered [ If(bDraw, SetDict(lDraw, i-1, draw_color)), If(bErase, SetDict(lDraw, i-1, color_list[0])) ]
                    
                    background lDraw[i-1]
                    
                    key 'mousedown_1' action SetScreenVariable('bDraw', True)
                    key 'mouseup_1' action SetScreenVariable('bDraw', False)
                    key 'mousedown_3' action SetScreenVariable('bErase', True)
                    key 'mouseup_3' action SetScreenVariable('bErase', False)
                    
        grid 4 2:
        
            for i in range(len(color_list)):
            
                button xysize (20, 20):
                
                    action SetScreenVariable('draw_color', color_list[i])
                    
                    background color_list[i]
                    
        text draw_color color '#FFFFFF'
And have some picture I drew for you all.
Image
Image

End result, it probably won't run on low end devices.

Re: Drawing in Ren'Py

Posted: Fri Aug 17, 2018 3:50 am
by Kinmoku
MaydohMaydoh wrote: Wed Aug 15, 2018 5:02 pm So I gave it a go and it is pretty laggy. Max size I could do before it lagged unbearably on my PC was 40x40. 50x50 was bad and once you start getting towards 100x100 and beyond, it lagged so bad I kept getting has stopped responding messages.

Code: Select all

init python:
    def fSetVal(max):
        l = []
        for i in range(max):
            l.append(False)
        return l

screen drawing():

    tag menu
    
    predict False
    
    default rows = 40
    default cols = 40
    default lDraw = fSetVal(cols * rows, False)
    default tPixelSize = (10, 10)
    default bDraw = False
    default bErase = False

    grid cols rows:
    
        for i in range(cols * rows):

            button xysize (tPixelSize):
            
                action NullAction()
                
                hovered [ If(bDraw, SetDict(lDraw, i-1, True)), If(bErase, SetDict(lDraw, i-1, False)) ]
                
                if lDraw[i-1]:
                    background '#000000'
                else:
                    background '#FFFFFF'

                key 'mousedown_1' action SetScreenVariable('bDraw', True)
                key 'mouseup_1' action SetScreenVariable('bDraw', False)
                key 'mousedown_3' action SetScreenVariable('bErase', True)
                key 'mouseup_3' action SetScreenVariable('bErase', False)
One issue is that if you hover over a button and click, it won't change. You need to get a new hover event to change a button. Only way to do it is to change the button_select keymap to mousedown_1 and add SetDict(lDraw, i-1, True) to the action because for some reason changing

Code: Select all

key 'mousedown_1' action SetScreenVariable('bDraw', True)
to

Code: Select all

key 'mousedown_1' action [ SetScreenVariable('bDraw', True), SetDict(lDraw, i-1, True) ]
will only change the last button in the bottom right corner, no matter where you click. But I'm sure I'm just missing something and it's relatively simple to fix.
I also messed about and made a color version.

Code: Select all

init python:
    def fSetValColors(max, value):
        l = []
        for i in range(max):
            l.append(value)
        return l
        
screen drawing_color():
    
    tag menu
    
    predict False
    
    default rows = 40
    default cols = 40
    default lDraw = fSetValColors(cols * rows, '#FFFFFF')
    default tPixelSize = (10, 10)
    default bDraw = False
    default bErase = False
    default color_list = [ '#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF' ]
    default draw_color = color_list[1]
    
    vbox:
    
        grid cols rows:
        
            for i in range(cols * rows):
                
                button xysize (tPixelSize):
                    
                    action NullAction()
                    
                    hovered [ If(bDraw, SetDict(lDraw, i-1, draw_color)), If(bErase, SetDict(lDraw, i-1, color_list[0])) ]
                    
                    background lDraw[i-1]
                    
                    key 'mousedown_1' action SetScreenVariable('bDraw', True)
                    key 'mouseup_1' action SetScreenVariable('bDraw', False)
                    key 'mousedown_3' action SetScreenVariable('bErase', True)
                    key 'mouseup_3' action SetScreenVariable('bErase', False)
                    
        grid 4 2:
        
            for i in range(len(color_list)):
            
                button xysize (20, 20):
                
                    action SetScreenVariable('draw_color', color_list[i])
                    
                    background color_list[i]
                    
        text draw_color color '#FFFFFF'
And have some picture I drew for you all.
Image
Image

End result, it probably won't run on low end devices.
Thanks for letting us know! I love the little pixel arts you did :)

It's a shame the lag was too much, I'd have really liked to try this for my game :(

I can't figure another way around it, apart from having a character creator-like screen for the drawings... But that would require more work from me and be nowhere near as fun. I think I'll drop the idea and try something else.

Re: Drawing in Ren'Py

Posted: Fri Aug 17, 2018 11:56 am
by MaydohMaydoh
This thread got me interested in trying to make a drawing function, so like was mentioned before, I tried making a UDD. It works a lot better than the button method but it's still far from perfect. Never used UDD before as never needed it so it's probably a bit messy but here it is.

Code: Select all

    lDraw_Pos = [] ## List of displayable colors positions
    
    class Draw(renpy.Displayable):
        
        def __init__(self, color, size, width, height):
            super(Draw, self).__init__()
            
            self.col = color ## Brush color
            self.size = size ## Brush size tuple
            
            self.canvas_w = width ## Width of drawing space
            self.canvas_h = height ## Height of drawing space
            
            self.drawing = False
            self.erasing = False

            self.pos = (0, 0) ## Cursor position tuple
            
        def render(self, width, height, st, at):
            global lDraw_Pos
            
            ## Create drawing space
            canvas = renpy.Render(self.canvas_w, self.canvas_h)
            canvas.canvas().rect('#FFF', (0, 0, self.canvas_w, self.canvas_h))
            
            ## Set brush location
            x, y = self.pos
            w, h = self.size
            
            x -= w / 2
            y -= h / 2
            
            ## Prevent brush from leaving drawing space
            if x < 0:
                x = 0
            elif x > self.canvas_w - w:
                x = self.canvas_w - w
            
            if y < 0:
                y = 0
            elif y > self.canvas_h - h:
                y = self.canvas_h - h
            
            ## Create brushes
            t_brush = Transform(child=Solid(self.col), size=self.size)
            brush = renpy.render(t_brush, width, height, st, at)
            
            t_eraser = Transform(child=Solid('#FFF'), size=self.size)
            eraser = renpy.render(t_eraser, width, height, st, at)
            
            ## Add displayables to lists
            if self.drawing and (brush, (x, y)) not in lDraw_Pos:
                lDraw_Pos.append( (brush, (x, y)) )
            elif self.erasing and (eraser, (x, y)) not in lDraw_Pos:
                lDraw_Pos.append( (eraser, (x, y)) )
                
            ## Add the list of displayables to the drawing space
            for v in lDraw_Pos:
                canvas.blit(v[0], v[1])
                
            return canvas
                
        def event(self, ev, x, y, st):
        
            # Set variable to true when key is pressed and set to false when released to create a pseudo key hold
            if renpy.map_event(ev, 'mousedown_1'):
                ## Start drawing
                self.drawing = True
            elif renpy.map_event(ev, 'mouseup_1'):
                ## Stop drawing
                self.drawing = False
                
            if renpy.map_event(ev, 'mousedown_3'):
                ## Start erasing
                self.erasing = True
            elif renpy.map_event(ev, 'mouseup_3'):
                ## Stop erasing
                self.erasing = False
                
            renpy.redraw(self, 0.0)
            
            ## Get cursor position
            self.pos = (x, y)
Have to define the displayable list as global to allow for color and size changes as when you click a button the screen updates, resetting the UDD. I suppose you could set it as a screen variable and pass it as an argument to the UDD so it resets when you leave and return to the screen.
One big issue is that when you move the cursor too fast, it can't keep up with the mouse tracking and you end up with spaces between each pixel but not sure that can be fixed.

If you change it a bit and have it display an image of a circle instead of a solid block of color, you could get a "cheap" antialiasing effect, using matrix color to change the color etc. but I haven't tried it yet.

But the code is there if anyone wants to use or improve it.

Sample screen to show UDD with buttons to change color/size and clear the canvas:

Code: Select all

screen canvas():

    tag menu
    
    default color_list = [ '#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF' ]
    default draw_color = '#000000'
    default brush_size = (2, 2)
    
    vbox:
    
        add Draw(draw_color, brush_size, 500, 500)
            
        grid 4 2:
            for v in color_list:
                button xysize (20, 20):
                    action SetScreenVariable('draw_color', v)
                    
                    background v
                    
        hbox:
            textbutton "Size 2" action SetScreenVariable('brush_size', (2, 2))
            textbutton "Size 4" action SetScreenVariable('brush_size', (4, 4))
            textbutton "Size 10" action SetScreenVariable('brush_size', (10, 10))
            
            textbutton "Clear Canvas" action SetVariable('lDraw_Pos', []) selected False
And of course, here's your complimentary snail pic.
Image

Re: Drawing in Ren'Py

Posted: Sat Aug 18, 2018 10:24 am
by Human Bolt Diary
Here's an quick example that won't melt your CPU. Won't leave spaces between pixels if you draw quickly, either.

https://github.com/jsfehler/renpy-freehand-draw

Re: Drawing in Ren'Py

Posted: Mon Aug 20, 2018 7:53 pm
by Imperf3kt
Imperf3kt wrote: Tue Aug 14, 2018 7:26 pm This was asked once before and the conclusion at that time was that it was not possible in any feasible way.
Found the thread I was referencing.
viewtopic.php?f=8&t=47773

Although what we currently have seems to work well enough.

Re: Drawing in Ren'Py

Posted: Mon Aug 20, 2018 11:37 pm
by Human Bolt Diary
I've added the ability to draw straight lines so that it's easier to draw Gundams. I know how important that is to the kids. Go forth, and draw the polygonal shapes of your dreams.

Re: Drawing in Ren'Py

Posted: Tue Aug 21, 2018 11:05 am
by ComputerArt.Club
Wow! This looks awesome!

Re: Drawing in Ren'Py

Posted: Tue Aug 21, 2018 9:06 pm
by MaydohMaydoh
Also just to add, adding a new method that pops the last child from the list and you've got yourself an undo button and putting the removed child into a new list, you now also have a redo button.

Code: Select all

def undo(self):
    self.old_children.append(self.children.pop())
    renpy.redraw(self, 0)

def redo(self):
    self.children.append(self.old_children.pop())
    renpy.redraw(self, 0)

Re: Drawing in Ren'Py

Posted: Tue Aug 21, 2018 9:13 pm
by Imperf3kt
I like where this is going!

The big question is: can you draw an Eileen with it?
Just kidding, I wonder if there's any way to use the generated image.

Re: Drawing in Ren'Py

Posted: Wed Aug 22, 2018 6:52 am
by Alex
This drawing-thingy looks worth to be in a cookbook - as a stand alone feature and as useful sample of UDD..;)