[Tutorial] Moving from Pygame to Renpy

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
maximinus
Newbie
Posts: 5
Joined: Sat Nov 14, 2009 10:04 am
Location: China
Contact:

[Tutorial] Moving from Pygame to Renpy

#1 Post by maximinus »

I've seen a few tutorials on this forum that show you how to make a custom Displayable object so that you can have complete control over the scene graphics. This tutorial will approach that from the other way around. We will start with some Pygame code, and move that into Renpy.

I am aware that there is a Pyrengame module, but that seems to be broken right now because of changes to SDL. But we don't need that anyway, there is enough functionality in Renpy to do everything we need.

The only assumed prerequisite for this tutorial is an understanding of Python to some degree. Knowledge of Pygame is not really needed, but if you wish to build upon this code then you will need to know Pygame. But Pygame is easy enough to work with if you already know Python.

So let's first look at what we want to achieve. For this first, I am going to make a simple scrolling starfield. This is easy simple code:

Code: Select all

#!/usr/bin/python

# simple example of side scrolling pixels for renpy game

import pygame, sys, random

TOTAL_STARS = 500
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

class Star(object):
	color = [0,0,0]
	position = [0,0]
	speed = 1

	def __init__(self):
		self.generateStartPosition(xrandom=True)
	
	def generateStartPosition(self, xrandom=False):
		# start at right of screen, scroll left
		if xrandom:
			xpos = random.randint(1, SCREEN_WIDTH - 1)
		else:
			xpos = SCREEN_WIDTH - 1
		self.position = [xpos, random.randint(1, SCREEN_HEIGHT - 1)]
		brightness = random.randint(1, 255)
		self.color = [brightness, brightness, brightness]
		self.speed = float(brightness / 400.0)
	
	def update(self):
		self.position[0] -= self.speed
		if(self.position[0] < 0):
			# generate new star
			self.generateStartPosition()
	
	def draw(self, screen):
		xpos = int(self.position[0])
		ypos = int(self.position[1])
		screen.fill(self.color, pygame.Rect(xpos, ypos, 1, 1))

def setup():
	pygame.init()
	screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
	return(screen)

def checkEvents():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			sys.exit()
		# or exit on spacebar being pressed
		if event.type == pygame.KEYDOWN:
			if event.key == 32:
				sys.exit()

def loop(screen):
	stars = [Star() for x in range(TOTAL_STARS)]
	while(True):
		screen.fill([0,0,0])
		for star in stars:
			star.draw(screen)
			star.update()
		pygame.display.flip()	
		checkEvents()

if __name__ == '__main__':
	screen = setup()
	loop(screen)

The standard boilerplate is up at the top, together with some globals.

Then there is a simple star class. This will handle the position, color and will draw itself. Basically, the color and the speed of the star are related: darker stars will move slower, giving the illusion of a 3D depth field.

After the class, we then have some simple functions that are all fairly obvious. there is a setup(), a checkEvents() that just looks to see if we should quit, and a simple loop that just goes round updating the stars.

If you have python and pygame installed on your system, run this and you should see some lovely scrolling stars.

Now we want to port this to Renpy. The key element we need to do this is use a custom displayable, in which our code will run.

To show you this, let's start a new, very simple, Renpy game. Follow these steps:

1: Run Renpy
2: Choose "Create New Project", with a name of "DisplayTest" or similar. The language, theme, and color can be anything you like.
3: Choose "script.py" under "Edit File" and replace the contents of that file with this:

Code: Select all

   label start:
        "This is our test game"
        return
4: Just quickly run the code to make sure there are no errors. It will be the worst VN ever, but everybody has to start somewhere.

Now that the boilerplate has been done, we can start to add our details. Create a file star_display.rpy in the directory DisplayTest/game (which will be found wherever Renpy creates your game). This file will be loaded and run by Renpy automatically. Inside that file, we need to create a custom Python object that will be the display renderable. However, we need to tell Renpy to look at this file before the others, and to tell it that we are using Python code. So this new file will have to start with this line:

init -1 python:

Below this code we must have our Python code indented. What we will do is derive a new class from the renpy displayable class. Here is what I have did:

Code: Select all

init -1 python:

    # need some imports
    import random, pygame

    # and some constants
    TOTAL_STARS = 500
    SCREEN_WIDTH = 800
    SCREEN_HEIGHT = 600

    # and the Star class
    class Star(object):
        color = (0,0,0)
        position = [0,0]
        speed = 1

        def __init__(self):
            self.generateStartPosition(xrandom=True)

        def generateStartPosition(self, xrandom=False):
            # start at right of screen, scroll left
            if xrandom:
                xpos = random.randint(1, SCREEN_WIDTH - 1)
            else:
                xpos = SCREEN_WIDTH - 1
            self.position = [xpos, random.randint(1, SCREEN_HEIGHT - 1)]
            brightness = random.randint(1, 255)
            self.color = (brightness, brightness, brightness)
            self.speed = float(brightness / 400.0)

        def update(self):
            self.position[0] -= self.speed
            if(self.position[0] < 0):
                # generate new star
                self.generateStartPosition()

        def draw(self, canvas):
            xpos = int(self.position[0])
            ypos = int(self.position[1])
            canvas.rect(self.color, pygame.Rect(xpos, ypos, 0, 0))

    # now we add out own custom Displayable
    class StarDisplay(renpy.Displayable):
        def __init__(self, *args, **kwargs):
            super(StarDisplay, self).__init__(*args, **kwargs)
            self.stars = [Star() for x in range(TOTAL_STARS)]

        def render(self, width, height, st, at):
            """Called when renpy needs to get the image to display"""
            # make a screen to draw on
            screen = renpy.Render(SCREEN_WIDTH, SCREEN_HEIGHT)
            canvas = screen.canvas()
            # fill with black and then put our stars on
            canvas.rect((0,0,0), pygame.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT))
            for star in self.stars:
                star.draw(canvas)
                star.update()
            # just drawing once if not good enough. Tell Renpy to call this function again as soon as possible.
            renpy.redraw(self, 0)
            # now we just have to return this render
            return screen

        def visit(self):
            """This function needs to return all the child displayables.
               We have none, so just return the empty list."""
            return []
You can see that the original Star class we used is almost unchanged, and most of the functions outside this class have now been moved into the StarDisplay class. Some of the functionality has been removed; we no longer have to setup Pygame or check the events to see if we exit.

Note that we had to do a couple of things that were extra work. We had to ask for a render area, and then for a canvas of that render area. To get a real pygame surface you need to call canvas.get_surface(), but I've gone the easy way in this tutorial. The colors had to turned into tuples and not just lists (Pygame is a little less picky). other than that the rest of the code is quite similar.

The next piece of the puzzle is to tell Renpy that this in fact is the display we want to use for the background of our game. So go back to the script.rpy file. Change it so that now it reads like this:

Code: Select all

# we need the displayable to be attached to a screen
screen star_screen:
    add StarDisplay()

label start:
    # now we need to add the display to the background of our game
    show screen star_screen
    "This is our test game"
Save this and then run the game. You should see scrolling stars in the background of the screen if you've done everything right.

Now one of the things you might notice is that the Renpy version is slower than the original Python. This is due to many factors, the main one being that Renpy is also doing other things in the background (for example, overlaying the text). Also we haven't covered how to handle events, or how to just make the displayable part of the background and not the entire background. I hope to add those tutorials myself in the future.

Anyway, I hope some of you found this of use; if you find any bugs in this tutorial, or can think of ways to improve it, then let me know in the comments below.

Post Reply

Who is online

Users browsing this forum: No registered users