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)
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
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 []
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"
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.