Drawing in Ren'Py

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.
Message
Author
User avatar
Kinmoku
Veteran
Posts: 413
Joined: Mon Aug 11, 2014 9:39 am
Completed: One Night Stand
Projects: Love IRL, Memories
Tumblr: gamesbykinmoku
itch: kinmoku
Location: Germany
Contact:

Drawing in Ren'Py

#1 Post by Kinmoku » Tue Aug 14, 2018 12:17 pm

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!

User avatar
Per K Grok
Veteran
Posts: 462
Joined: Fri May 18, 2018 1:02 am
Completed: the Ghost Pilot, Sea of Lost Ships, Bubbles and the Pterodactyls, Defenders of Adacan Part 1
Projects: Defenders of Adacan Part 2
Deviantart: pekj
itch: per-k-grok
Location: Sverige
Contact:

Re: Drawing in Ren'Py

#2 Post by Per K Grok » Tue Aug 14, 2018 12:32 pm

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. :)
Image Visual Story Telling, DeviantArt Group for animations, comics, games and illustrations https://www.deviantart.com/acgi

User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 2490
Joined: Mon Dec 14, 2015 5:05 am
Location: Your monitor
Contact:

Re: Drawing in Ren'Py

#3 Post by Imperf3kt » 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.
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Todo list:
Actually finish a project

User avatar
MaydohMaydoh
Regular
Posts: 101
Joined: Mon Jul 09, 2018 5:49 am
Projects: Fuwa Fuwa Panic
Tumblr: maydohmaydoh
Location: The Satellite of Love
Contact:

Re: Drawing in Ren'Py

#4 Post by MaydohMaydoh » Tue Aug 14, 2018 7:58 pm

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.

User avatar
Kinmoku
Veteran
Posts: 413
Joined: Mon Aug 11, 2014 9:39 am
Completed: One Night Stand
Projects: Love IRL, Memories
Tumblr: gamesbykinmoku
itch: kinmoku
Location: Germany
Contact:

Re: Drawing in Ren'Py

#5 Post by Kinmoku » Wed Aug 15, 2018 12:48 pm

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

User avatar
MaydohMaydoh
Regular
Posts: 101
Joined: Mon Jul 09, 2018 5:49 am
Projects: Fuwa Fuwa Panic
Tumblr: maydohmaydoh
Location: The Satellite of Love
Contact:

Re: Drawing in Ren'Py

#6 Post by MaydohMaydoh » 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.

User avatar
Kinmoku
Veteran
Posts: 413
Joined: Mon Aug 11, 2014 9:39 am
Completed: One Night Stand
Projects: Love IRL, Memories
Tumblr: gamesbykinmoku
itch: kinmoku
Location: Germany
Contact:

Re: Drawing in Ren'Py

#7 Post by Kinmoku » Fri Aug 17, 2018 3:50 am

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.

User avatar
MaydohMaydoh
Regular
Posts: 101
Joined: Mon Jul 09, 2018 5:49 am
Projects: Fuwa Fuwa Panic
Tumblr: maydohmaydoh
Location: The Satellite of Love
Contact:

Re: Drawing in Ren'Py

#8 Post by MaydohMaydoh » Fri Aug 17, 2018 11:56 am

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

Human Bolt Diary
Regular
Posts: 94
Joined: Fri Oct 11, 2013 12:46 am
Contact:

Re: Drawing in Ren'Py

#9 Post by Human Bolt Diary » Sat Aug 18, 2018 10:24 am

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
Attachments
freehand.png

User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 2490
Joined: Mon Dec 14, 2015 5:05 am
Location: Your monitor
Contact:

Re: Drawing in Ren'Py

#10 Post by Imperf3kt » Mon Aug 20, 2018 7:53 pm

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.
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Todo list:
Actually finish a project

Human Bolt Diary
Regular
Posts: 94
Joined: Fri Oct 11, 2013 12:46 am
Contact:

Re: Drawing in Ren'Py

#11 Post by Human Bolt Diary » Mon Aug 20, 2018 11:37 pm

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.
Attachments
freehand.png
freehand.png (11.7 KiB) Viewed 937 times

User avatar
ComputerArt.Club
Veteran
Posts: 393
Joined: Mon May 22, 2017 8:12 am
Completed: Famous Fables, BoPoMoFo: Learn Chinese, Santa's workshop, Cat's Bath, Computer Art Club
Location: Taiwan
Contact:

Re: Drawing in Ren'Py

#12 Post by ComputerArt.Club » Tue Aug 21, 2018 11:05 am

Wow! This looks awesome!

User avatar
MaydohMaydoh
Regular
Posts: 101
Joined: Mon Jul 09, 2018 5:49 am
Projects: Fuwa Fuwa Panic
Tumblr: maydohmaydoh
Location: The Satellite of Love
Contact:

Re: Drawing in Ren'Py

#13 Post by MaydohMaydoh » Tue Aug 21, 2018 9:06 pm

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)

User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 2490
Joined: Mon Dec 14, 2015 5:05 am
Location: Your monitor
Contact:

Re: Drawing in Ren'Py

#14 Post by Imperf3kt » Tue Aug 21, 2018 9:13 pm

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.
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Todo list:
Actually finish a project

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

Re: Drawing in Ren'Py

#15 Post by Alex » Wed Aug 22, 2018 6:52 am

This drawing-thingy looks worth to be in a cookbook - as a stand alone feature and as useful sample of UDD..;)

Post Reply

Who is online

Users browsing this forum: Bing [Bot], Majestic-12 [Bot]