Skip to content

Commit 7169983

Browse files
1 parent 249ddb8 commit 7169983

File tree

2 files changed

+253
-2
lines changed

2 files changed

+253
-2
lines changed

game/examples/glitch.rpy

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# https://github.com/Gouvernathor/renpy-ChromaGlitch
2+
init python:
3+
class glitch(renpy.Displayable):
4+
"""
5+
`randomkey`
6+
Follows the rules of the random modume's seed function.
7+
If not set, a random seed is generated when the transform is applied,
8+
and stays the same afterwards.
9+
If you want the effect to be random for each render operation, set to None.
10+
11+
`chroma`
12+
Boolean, whether to apply the chromatic aberration effect.
13+
14+
`minbandheight`
15+
Minimum height of each slice.
16+
17+
`offset`
18+
The offset of each slice will be between -offset and offset pixels.
19+
20+
`nslices`
21+
Number of slicings to do (the number of slices will be nslices + 1).
22+
Setting this to 0 is not supported.
23+
None (the default) makes it random.
24+
"""
25+
26+
NotSet = object()
27+
28+
def __init__(self, child, *, randomkey=NotSet, chroma=True, minbandheight=1, offset=30, nslices=None, **properties):
29+
super().__init__(**properties)
30+
self.child = renpy.displayable(child)
31+
if randomkey is self.NotSet:
32+
randomkey = renpy.random.random()
33+
self.randomkey = randomkey
34+
self.chroma = chroma
35+
self.minbandheight = minbandheight
36+
self.offset = offset
37+
self.nslices = nslices
38+
39+
def render(self, width, height, st, at):
40+
child = self.child
41+
child_render = renpy.render(child, width, height, st, at)
42+
cwidth, cheight = child_render.get_size()
43+
if not (cwidth and cheight):
44+
return child_render
45+
render = renpy.Render(cwidth, cheight)
46+
randomobj = renpy.random.Random(self.randomkey)
47+
chroma = self.chroma and renpy.display.render.models
48+
offset = self.offset
49+
minbandheight = self.minbandheight
50+
nslices = self.nslices
51+
if nslices is None:
52+
nslices = min(int(cheight/minbandheight), randomobj.randrange(10, 21))
53+
54+
theights = sorted(randomobj.randrange(cheight+1) for k in range(nslices)) # y coordinates demarcating all the strips
55+
offt = 0 # next strip's lateral offset
56+
fheight = 0 # sum of the size of all the strips added this far
57+
while fheight<cheight:
58+
# theight is the height of this particular strip
59+
if theights:
60+
theight = max(theights.pop(0)-fheight, minbandheight)
61+
else:
62+
theight = cheight-theight
63+
64+
slice_render = child_render.subsurface((0, fheight, cwidth, theight))
65+
66+
if offt and chroma:
67+
for color_mask, chponder in (((False, False, True, True), 1.25), ((False, True, False, True), 1.), ((True, False, False, True), .75)):
68+
chroma_render = slice_render.subsurface((0, 0, cwidth, theight))
69+
chroma_render.add_property("gl_color_mask", color_mask)
70+
render.blit(chroma_render, (round(offt*chponder), round(fheight)))
71+
72+
else:
73+
render.blit(slice_render, (offt, round(fheight)))
74+
75+
fheight += theight
76+
if offt:
77+
offt = 0
78+
else:
79+
offt = randomobj.randrange(-offset, offset+1)
80+
81+
return render
82+
83+
def visit(self):
84+
return [self.child]
85+
86+
class animated_glitch(glitch):
87+
"""
88+
Glitches in a way that changes over time, but consistently, unlike glitch(randomkey=None).
89+
Sets a timeout at the beginning. At the end of each timeout, sets a new one and changes the glitching.
90+
91+
`timeout_base`
92+
The time in seconds between two changes of the glitching.
93+
Can be either single float (or integer) value, or a tuple of two values between which the timeout
94+
will be chosen following a uniform distribution, respecting the randomkey.
95+
Defaults to .1 second.
96+
97+
`timeout_vanilla`
98+
The length in seconds of the periods of time over which the child will be shown without any glitch.
99+
Same values and meaning as `timeout_base`, except that if False, the child will never be shown without glitching.
100+
If `timeout_base` is passed, defaults to the same value. Otherwise, defaults to (1, 3).
101+
"""
102+
103+
def __init__(self, *args, timeout_base=None, timeout_vanilla=None, **kwargs):
104+
super().__init__(*args, **kwargs)
105+
if timeout_vanilla is None:
106+
if timeout_base is None:
107+
timeout_vanilla = (1, 3)
108+
else:
109+
timeout_vanilla = timeout_base
110+
if timeout_base is None:
111+
timeout_base = .1
112+
113+
self.timeout_base = timeout_base
114+
self.timeout_vanilla = timeout_vanilla
115+
self.set_timeout(vanilla=(timeout_vanilla is not False))
116+
117+
def set_timeout(self, vanilla, st=0):
118+
if vanilla:
119+
timeout = self.timeout_vanilla
120+
else:
121+
timeout = self.timeout_base
122+
123+
if not isinstance(timeout, (int, float)):
124+
timeout = renpy.random.Random(self.randomkey).uniform(*timeout)
125+
126+
self.timeout = timeout + st
127+
self.showing_vanilla = vanilla
128+
129+
def render(self, width, height, st, at):
130+
vanilla = self.showing_vanilla
131+
132+
if st >= self.timeout:
133+
randomkey = self.randomkey
134+
randomobj = renpy.random.Random(randomkey)
135+
self.randomkey = randomobj.random()
136+
137+
# determine whether to show vanilla or not
138+
if vanilla or (self.timeout_vanilla is False):
139+
# if we were showing it or if showing it is disabled
140+
vanilla = False
141+
else:
142+
vanilla = (randomobj.random() < .3)
143+
144+
self.set_timeout(vanilla, st)
145+
146+
renpy.redraw(self, st-self.timeout)
147+
148+
if vanilla:
149+
return renpy.render(self.child, width, height, st, at)
150+
else:
151+
return super().render(width, height, st, at)
152+
153+
class squares_glitch(renpy.Displayable):
154+
"""
155+
`squareside`
156+
The size, in pixels, of the side of the squares the child image will be cut to. This will
157+
be adjusted so that all the "squares" (rectangles, really) have the same width and the
158+
same height, and that none is cut at the borders of the image. Defaults to 20 pixels.
159+
160+
`chroma`
161+
The probability for each square to get a chromatic effect. Defaults to .25.
162+
163+
`permutes`
164+
The percentage of squares which will be moved to another square's place. If not passed,
165+
defaults to a random value between .1 and .4.
166+
"""
167+
168+
NotSet = object()
169+
170+
def __init__(self, child, *args, randomkey=NotSet, **kwargs):
171+
super().__init__()
172+
self.child = renpy.displayable(child)
173+
self.args = args
174+
if randomkey is self.NotSet:
175+
randomkey = renpy.random.random()
176+
self.randomkey = randomkey
177+
self.kwargs = kwargs
178+
179+
def render(self, width, height, st, at):
180+
cwidth, cheight = renpy.render(self.child, width, height, st, at).get_size()
181+
return renpy.render(self.glitch(self.child,
182+
cwidth, cheight, renpy.random.Random(self.randomkey),
183+
*self.args, **self.kwargs),
184+
width, height,
185+
st, at)
186+
187+
@staticmethod
188+
def glitch(child, cwidth, cheight, randomobj, squareside=20, chroma=.25, permutes=None):
189+
if not renpy.display.render.models:
190+
chroma = False
191+
if not (cwidth and cheight):
192+
return child
193+
194+
ncols = round(cwidth/squareside)
195+
nrows = round(cheight/squareside)
196+
square_width = absolute(cwidth/ncols)
197+
square_height = absolute(cheight/nrows)
198+
199+
lizt = []
200+
for y in range(nrows):
201+
for x in range(ncols):
202+
lizt.append(Transform(child,
203+
crop=(absolute(x*square_width), absolute(y*square_height), square_width, square_height),
204+
subpixel=True,
205+
))
206+
207+
if permutes is None:
208+
permutes = randomobj.randrange(10, 40)/100 # between 10% and 40%
209+
permutes = round(permutes*ncols*nrows)
210+
permute_a = randomobj.sample(range(ncols*nrows), permutes)
211+
permute_b = randomobj.sample(range(ncols*nrows), permutes)
212+
213+
for a, b in zip(permute_a, permute_b):
214+
lizt[a], lizt[b] = lizt[b], lizt[a]
215+
216+
for k, el in enumerate(lizt):
217+
if randomobj.random() < chroma:
218+
lizt[k] = Transform(el,
219+
gl_color_mask=(randomobj.random()<.33, randomobj.random()<.33, randomobj.random()<.33, True),
220+
# matrixcolor=HueMatrix(randomobj.random()*360),
221+
)
222+
223+
return Grid(ncols, nrows, *lizt)
224+
225+
def __eq__(self, other):
226+
return (type(self) == type(other)) and (self.args == other.args) and (self.kwargs == other.kwargs)
227+
228+
label glitch_example:
229+
230+
show expression glitch("eileen happy")
231+
232+
e "I'm glitched and happy!"
233+
234+
show eileen concerned at glitch
235+
236+
e "I'm glitched and concerned..."
237+
238+
# Use the randomkey parameter if you want to make the animation periodic
239+
image eileen glitched:
240+
glitch("eileen vhappy")
241+
pause 1.0
242+
glitch("eileen happy", offset=60, randomkey=None)
243+
pause 0.1
244+
repeat
245+
246+
show eileen glitched
247+
248+
e "I'm glitched and animated."
249+
250+
jump start

game/examples/start.rpy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ label start:
99
scene bg club
1010

1111
menu:
12-
"Which example do you want to see?"
13-
1412
"Creator-Defined Statements (CDS)":
1513
jump creator_defined_statements
1614

@@ -20,6 +18,9 @@ label start:
2018
"Drag and Drop":
2119
jump drag_and_drop
2220

21+
"Glitch":
22+
jump glitch_example
23+
2324
"Image":
2425
jump image_example
2526

0 commit comments

Comments
 (0)