-
-
Save methanoliver/98fe9304b63fff1f7d3419120e2c11f5 to your computer and use it in GitHub Desktop.
Neat RenPy rain effect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# This generates the rain images that serve as the base for the rain effect, | |
# and isn't expected to be used at runtime. | |
# | |
# You will need an installation of Python 3 with Pillow library in it. | |
from PIL import Image, ImageDraw, ImageFilter | |
import random | |
# Background color, serves as a shadow. | |
BLANK = (0, 0, 0) | |
# Foreground color. | |
RAIN = (255, 255, 255) | |
# Width and height must be even divisors of screen width and height, | |
# respectively, but they can (and should) be smaller than the screen. | |
# For my 1920x1080 screen, 6x3 makes a reasonable 320x360 texture. | |
WIDTH = 1920 // 6 | |
HEIGHT = 1080 // 3 | |
# Playing with these values and re-generating the images | |
# will be required to get good results for a specific use case. | |
# Thickness affects how dense the rain is. | |
THICKNESS = 0.3 | |
# Angle of rain: multiplier for y offset. | |
ANGLE = 8 | |
# --------------- | |
# These constants were arrived at through trial and error. | |
SHORT = 350 | |
MEDIUM = 250 | |
LONG = 150 | |
MARGIN = int((max(WIDTH, HEIGHT) / 100) * 20) | |
def drawlines(image, number, scale): | |
number = int(number) | |
draw = ImageDraw.Draw(image) | |
for line in range(0, int(number)): | |
x = random.randint(0 - MARGIN, WIDTH + MARGIN) | |
y = random.randint(0 - MARGIN, HEIGHT + MARGIN) | |
scaleoff = random.random() * 0.5 | |
xoff = x - int(5 * (scale + scaleoff)) | |
yoff = y + int(5 * ANGLE * (scale + scaleoff)) | |
# This trick makes the generated texture tile seamlessly | |
# by drawing each line four extra times, so that if | |
# it crosses any of the edges, it definitely has a match | |
# on the other end. | |
draw.line((x, y, xoff, yoff), fill=RAIN) | |
draw.line((x - WIDTH, y, xoff - WIDTH, yoff), fill=RAIN) | |
draw.line((x + WIDTH, y, xoff + WIDTH, yoff), fill=RAIN) | |
draw.line((x, y - HEIGHT, xoff, yoff - HEIGHT), fill=RAIN) | |
draw.line((x, y + HEIGHT, xoff, yoff + HEIGHT), fill=RAIN) | |
return image | |
def generate(multiplier, step): | |
plane = Image.new("RGB", (WIDTH, HEIGHT), BLANK) | |
# Shortest lines | |
if step == 1: | |
plane = drawlines(plane, SHORT * multiplier, 1) | |
# shorter lines | |
if step == 2: | |
plane = drawlines(plane, MEDIUM * multiplier, 3) | |
# Longer lines | |
if step == 3: | |
plane = drawlines(plane, LONG * multiplier, 6) | |
return plane | |
random.seed() | |
img = generate(THICKNESS, 1) | |
img.save("_rain-short.png") | |
img = generate(THICKNESS, 2) | |
img.save("_rain-medium.png") | |
img = generate(THICKNESS, 3) | |
img.save("_rain-long.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
init: | |
python: | |
# RainBlur is how much to blur rain. | |
RainBlur = 4 | |
# RainAlpha is the total alpha level of the entire rain sheet. | |
RainAlpha = 0.75 | |
# RainY is rain speed, basically how long does it take | |
# for the rain sheet to fall down by one tile | |
RainY = 0.16 | |
# RainX is the same for horizontal movement, | |
# and needs to be manually adjusted to fit the chosen raindrop angle | |
# for the rain to fall naturally. | |
RainX = RainY * 9 | |
# Total alpha of the rain is the sum of the alpha of all three layers, | |
# so each sheet has a third of it. | |
RainLayerAlpha = RainAlpha / 3 | |
# Speed of the medium sheet of rain is two times faster than the front sheet. | |
RainYM = RainY / 2 | |
RainXM = RainX / 2 | |
# Speed of the futhest sheet of rain is two times faster than that. | |
RainYF = RainYM / 2 | |
RainXF = RainXM / 2 | |
###################################### | |
# Automatically find our rain files, which are assumed to live in the module directory: | |
RainFiles = renpy.os.path.dirname(renpy.get_filename_line()[0]).split("game/")[-1] | |
# We're making three displayables, each bigger than the screen by the size of one rain | |
# tile, in both directions. | |
# | |
# Check generate-rain.py for how the raindrops are made. | |
RainTileSizeX, RainTileSizeY = renpy.image_size(RainFiles+"/_rain-long.png") | |
# Amazingly, using ATLs xtile/ytile to tile the rain images actually results | |
# in a lot more CPU usage. | |
RainsheetLong = Composite( | |
(config.screen_width + RainTileSizeX, config.screen_height + RainTileSizeY), | |
(0, 0), Tile(RainFiles+"/_rain-long.png")) | |
RainsheetMedium = Composite( | |
(config.screen_width + RainTileSizeX, config.screen_height + RainTileSizeY), | |
(0,0), Tile(RainFiles+"/_rain-medium.png")) | |
RainsheetShort = Composite( | |
(config.screen_width + RainTileSizeX, config.screen_height + RainTileSizeY), | |
(0,0), Tile(RainFiles+"/_rain-short.png")) | |
# This defines the far sheet of the rain. | |
# You show this one /behind/ character sprites. | |
# It has the shortest (more distant) and fastest moving raindrops. | |
# In theory you can split it and put sprites between each of the three sheets, | |
# but I didn't need that. | |
image rainback scroll: | |
# Distant drops | |
contains: | |
RainsheetShort | |
blur RainBlur | |
alpha RainLayerAlpha | |
subpixel True | |
parallel: | |
ypos -RainTileSizeY | |
linear RainYF ypos 0 | |
repeat | |
parallel: | |
xpos 0 | |
linear RainXF xpos -RainTileSizeX | |
repeat | |
# Medium drops | |
contains: | |
RainsheetMedium | |
blur RainBlur | |
alpha RainLayerAlpha | |
subpixel True | |
parallel: | |
ypos -RainTileSizeY | |
linear RainYM ypos 0 | |
repeat | |
parallel: | |
xpos 0 | |
linear RainXM xpos -RainTileSizeX | |
repeat | |
# This is the front sheet of the rain, it goes /above/ the | |
# character sprites. | |
image rainfront scroll: | |
contains: | |
RainsheetLong | |
blur RainBlur | |
alpha RainLayerAlpha | |
subpixel True | |
parallel: | |
ypos -RainTileSizeY | |
linear RainY ypos 0 | |
repeat | |
parallel: | |
xpos 0 | |
linear RainX xpos -RainTileSizeX | |
repeat | |
# Static sheets of rain for use when the rain does not need to animate, | |
# e.g. when the time has stopped. | |
image rainback static: | |
contains: | |
RainsheetShort | |
alpha RainLayerAlpha | |
blur RainBlur | |
contains: | |
RainsheetMedium | |
alpha RainLayerAlpha | |
blur RainBlur | |
image rainfront static: | |
contains: | |
RainsheetLong | |
alpha RainLayerAlpha | |
blur RainBlur | |
init -50 python hide: | |
# We also need to forbid build code from picking up our generator script, | |
# which I like to keep with my project. | |
build.classify("**/generate-rain.py", None) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment