A downloadable asset pack for Windows, macOS, and Linux
This project is made to demonstrate an effect I'm calling, Auto Highlight. Its primary purpose is to allow you to subtly change sprites based on who is speaking in the dialogue, drawing attention to the speaker's sprite. All handled automatically, saving you from having to write ATL on every line of the script. The default implementation of this I've provided makes use of a small zoom and darkening of the sprite. But can easily be expanded to make use of other ATL properties based on the desired effect.
The main file of interest is the00auto-highlight.rpy. It provides several comments to explain how it works and what you'll need to do to get it running.Be sure to read the setup comments near the top to know what you have to do to implement it.
Originally this was a minor effect that I created for Where the Demons Lurk. But it ended up being useful for both Beyond the Harbor: r and Burrows. As a result, I began to consider that it might be useful for other projects as well. So, I decided to release it so everyone has access to it.
Default implementation makes use of the matrixcolor ATL property, which is only available on Renpy 7.4 and above. But can be modified to work with earlier versions if you desire.
The code for this project is provided under the MIT license.
Credits:
Luke and Kota sprites used with permission from https://minoh.itch.io/minotaur-hotel-sfw I do not claim ownership of the sprites. The sprites belong to their respective owners.
Demo script written by members of FVN in a voice call: https://discord.gg/GFjSPkh - FVN is an 18+ discord server. Do not join if you are a minor.
Beyond the Harbor: r Example from: https://harmoyena.itch.io/beyond-the-harbor - BtH:r is an 18+ VN with strong themes that are inappropriate for children
Burrows Example is from: https://itch.io/search?q=Burrows - Burrows is an 18+ VN with strong themes that are inappropriate for children
Status | Released |
Category | Assets |
Rating | Rated 4.9 out of 5 stars (56 total ratings) |
Author | Wattson |
Genre | Visual Novel |
Made with | Ren'Py |
Tags | Asset Pack,Ren'Py,sourcecode |
Code license | MIT License |
Average session | A few seconds |
Languages | English |
Inputs | Keyboard,Mouse |
Click download now to get access to the following files:
Log in with itch.io to leave a comment.
Everything works perfectly for me. This is an amazing asset. I don't know if it was discussed before and I just missed it, but is there a way to have character voices play along with the sprite highlight? I have different audio beeps to play when certain characters talk but I got the error of a callback repeat. It's only when I took out one or the other is when the error is gone. I'm still new to coding, so I was wondering if having both was possible?
I'm having problems, I have everything perfect, i'm using layered images and i have the at sprite highlight thing and I copied and pasted the define character stuff, but it still can't find callback for some reason.
```
I'm sorry, but an uncaught exception occurred.
While running game code:
File "game/script.rpy", line 13, in script
define sa = Character(_('Sasha', callback = name_callback, cb_name = "Sasha"), color="#efe49c")
File "game/script.rpy", line 13, in <module>
define sa = Character(_('Sasha', callback = name_callback, cb_name = "Sasha"), color="#efe49c")
TypeError: _() got an unexpected keyword argument 'callback'
-- Full Traceback ------------------------------------------------------------
Full traceback:
File "game/script.rpy", line 13, in script
define sa = Character(_('Sasha', callback = name_callback, cb_name = "Sasha"), color="#efe49c")
File "I:\scoped_dir17580_1652525804\renpy-8.3.6-sdk\renpy\ast.py", line 2248, in execute
self.set()
File "I:\scoped_dir17580_1652525804\renpy-8.3.6-sdk\renpy\ast.py", line 2262, in set
value = renpy.python.py_eval_bytecode(self.code.bytecode)
File "I:\scoped_dir17580_1652525804\renpy-8.3.6-sdk\renpy\python.py", line 1208, in py_eval_bytecode
return eval(bytecode, globals, locals)
File "game/script.rpy", line 13, in <module>
define sa = Character(_('Sasha', callback = name_callback, cb_name = "Sasha"), color="#efe49c")
TypeError: _() got an unexpected keyword argument 'callback'
Windows-10-10.0.26100 AMD64
Ren'Py 8.3.6.25022803
Tmafangame 1.0
Sun Jun 29 14:25:22 2025
```
The problem is that you put everything inside the _() function. So you need to move the first ')'
define sa = Character(_('Sasha', callback = name_callback, cb_name = "Sasha"), color="#efe49c")
It should be:
define sa = Character(_('Sasha'), callback = name_callback, cb_name = "Sasha", color="#efe49c")
Also just commenting this for others that might want to use shaders with this, as it took me a bit to figure out --
if is_talking: # Apply the talking transformation trans.shader = "outline"else: # Apply the not-talking transformation trans.shader = None
Likely you will need to make a copy of the shader you're using with uniform variables, instead of them being set through a transform. This means you're stuck with one preset (unless you make it a variable potentially?), but it works like a charm!
As kinda the opposite of onebulb's comment below, for my use case I needed the sprites to transition between being tinted when speaking but left fully untouched at baseline. My modification ended up looking like the following:
if is_talking: # Apply the talking transformation trans.matrixcolor = SaturationMatrix(1.0 + (curr_ease * sat_change)) * BrightnessMatrix(0 + curr_ease * bright_change) else: # Apply the not-talking transformation trans.matrixcolor = SaturationMatrix(1.0 + ((1 - curr_ease) * sat_change)) * BrightnessMatrix((1 - curr_ease) * bright_change)
Thanks for making this - I customized it for my WIP to make the non-speaking sprites simply "shadowed". This is what I use:
if is_talking: # Apply the talking transformation trans.matrixcolor = IdentityMatrix()else: # Apply the not-talking transformation trans.matrixcolor = TintMatrix("#646464") * SaturationMatrix(1.0)
Hello! First off, thank you so much for this. It's a really neat effect, and I appreciate all the work you did on it.
For the most part, it seems very easy and straightforward. However, I have a few characters who have player-defined sprites. Right now I am handling this with DynamicImage statements.
For example:
image mc base = DynamicImage("images/sprites/mc/mc[avatar].png")
image mc sad = DynamicImage("images/sprites/mc/mc[avatar]_sad.png")
etc
In part 2 of the setup where you demonstrate how to set up the sprites, your image statements rely on the At() statement. Is there a way to make this compatible with DynamicImage, or is that a no go? Right now I don't have the sprites set up as layered, but I could switch to that if that helps in some way.
hey wattson!! fantastic tool!
currently working on a project for my thesis, and i'm struggling to understand what went wrong haha
the auto highlight will work for only the prologue, but when i continue adding in the sprites for other chapters, it just stops working (it'll show the sprite but the sprite highlight just doesn't seem to want to work). i've followed the steps you wrote out but maybe i did something wrong...
any ideas of how i can try to fix it? thank you!
I've never had it flat out refuse to work. My best guess would be maybe your sprites are sharing names that are used to look up which to highlight. Having two sprites referring to the same one can often make it act screwy. Could be something else. but without more information about what's going on, that's my best guess.
Thank you for the plugin, it has helped make our game a whole lot better!
https://store.steampowered.com/app/3250420/Talewarden_Riders_of_the_New_Day/
Hi Wattson, thanks for the content!
I have a question with making a callback to a character when the player has input their own name. I've been trying a few things but nothing is working. Any ideas what I need to put instead of the %(P)s ? Thanks anyone in advance.
This is my code:
(WORKING FINE)define t = Character('Topaz', image='i_topaz.png', color="#d2be0a", callback=name_callback, cb_name='t')image i_topaz =At('i_topaz.png', sprite_highlight('t'))
(NOT WORKING DUE TO NOT RECONGNISING INPUT NAME.)
define P = Character('%(P)s', image='i_mc1l.png', color="#e712b2", callback=name_callback, cb_name='%(P)s')image i_mc1l =At('i_mc1l.png', sprite_highlight('%(P)s'))
I was dumb, the answer was in the code's comments. If anyone else wants to know how to do this, it just takes 2 lines.
Use this line so that no one will be highlighted during narration:
define narrator = Character(callback = name_callback, cb_name = None)
Then just define who you want highlighted like this:
"Then CHARACTER laughed out loud." (cb_name = "CHARACTER")
Hello! I was wondering if there was a way to adjust a sprite's zoom levels globally, while also using this plugin. I don't mean the zoom_change, but rather a zoom for the sprite overall. For example, I have the following code:
transform small: zoom 0.8
And I can apply that to any of the character sprites to make them a little smaller. However, once I enable the auto-highlight effect on a character as follows, the zoom stops working and the sprite reverts to it's original size.
show h stunned: small function SpriteFocus('haka')
If I take out the "function SpriteFocus('haka')" part, the "small" effect works again and the sprite becomes smaller. Is there any way I can have zoom effects work on sprites while the auto-highlighter is active?
Hello, great plugin, but I encountered a problem, at the beginning of each scene, when the dialogue starts, the picture kind of “jumps”, first a darkened picture is shown, then immediately a normal one, after which the dialogue runs normally, as it should. Any ideas what could be causing this and how to fix it?
Sample code below:
define g1 = Character("Первый Стражник", color="#be4c4c", image="guard1",callback=name_callback, cb_name="guard1")
image guard1 guard1a = At('guard1', sprite_highlight('guard1'))
show guard1 at left2
with Dissolve(0.2)
g1 guard1a "В райских гущах, а ты как думаешь?"
If I had to guess I would presume it's because when the scene starts, the character hasn't been speaking, so the speaking_char is probably either None or someone else. Then when the line starts, the character is then set as the speaking_char, so he lights up to normal. If you want them to start off already focused, you can manually set the speaking char by doing:
$ speaking_char = "guard1"
either before the transition or sometime during it. Which would then have the sprite start off in the talking state.
Hopefully that helps. But if it's some other issue, let me know and I'd be happy to help correct it.
Hey there! Thanks for this tool, but I was wondering how to apply shaders to this tool. I tried applying it under line 185 as suggested in the comments, but I get a "ATLTransform object is not iterable".
if is_talking: trans.matrixcolor = ... .... trans.shader = outline() # <- this is defined as a transform
Implementing the shader by hand also didn't work...
if is_talking: trans.matrixcolor = ... ... trans.shader = "remix.smoothstep_outline" # there is no outline
Hi there! Thank you for making such a fantastic effect for visual novels. I wanted to ask about an issue I've been running into.
Following instructions, I placed the auto-highlight file into the game, and changed the definitions to match the instructions. It works like an absolute charm on certain characters (here: mary and vein), but not on others (here: charlie and mona).
## Define characters
define charlie = Character("Charlie",callback=name_callback, cb_name = "charlie", image="charlie")
define mary = Character("Mary", callback=name_callback, cb_name = "mary", image="mary")
define mona = Character("Mona", callback=name_callback, cb_name = "mona", image="mona")
define vein = Character("Vein", callback=name_callback, cb_name = "vein", image="vein")...
## Image definitionslayeredimage mona: #mona's SPRITES at sprite_highlight('mona')
group exp: attribute base: "images/MONA/mona.png" default attribute shadow: "images/MONA/monashadow.png" ....
There are brief moments when the character's name is changed ($ mona = "???"), and this might be the problem. Even reverting the name to its original ($ mona = "Mona") still doesn't work. Are there any solutions to this issue?
Got it fixed! I made a minor mistake of not capitalizing the name when converting it to a different one.
Incorrect: $ mona = "???"
Correct: $ Mona = "???"
EDIT: It broke again... a capital letter will not change the character's name now, so I would have to either decide to keep the autohighlight with no name changes or name changes with no autohighlight aughhhhh
I'm resurrecting this one because I am having the same issue. No layered images, just regular old pics:
define g = Character("Inspector Greenfield", color="#4b4efa", callback = name_callback, cb_name = 'gfield')image greenfield2 = At('greenfield2', sprite_highlight('gfield'))
I'm pretty sure it's the image definition since the character appears ("show greenfield2 at right") before he speaks. Should I be renaming the spright_highlighted image something else? But then what goes in the definition? I've gone around in circles on this a million times.
i tried changing the anim_length (to 1.0) and for some reason it only applies when the sprite is first shown, it goes back to the default (0.2) setting as i put another sprite over it.
i use individual sprites but all of them are defined.
i even set it to 0.5, wondering if it was too fast to no avail.
i use NVL, if that helps.
wondered if you could look into it.
thank you very much for this btw.
Hi. Sorry for the late reply. I tried replicating the issue on my end, but doing the same setup (or at least what I can suppose from your comment) yielded results that looked correct to me. My best guess for the issue is that something about how you defined the sprites is causing renpy to reset the anim_time value, which would cause the sprite_highlight function to snap to the highlighted value. Which maybe you're confusing for the .2 seconds given .2 is pretty fast. Though I could be wrong. This is just my best guess given the information.
I tested with NVL and that didn't seem to make much of a difference. If you'd want to show me your code so I could test it and see what the issue might be, I'd be happy to take a look when I have time. (My username on discord is wattson if you need to reach out). But yeah I'm not entirely sure what the problem might be beyond my guess. Hope this was helpful and a solution to your issue can be found!
thank you for replying!
i sent you a discord req, i'd greatly appreciate if you could take a look if you do have time for it.
i don't think it's the definition because it's the exact same for every sprite and it does work for whatever that i place on the screen first, though i'd be glad to send you the code to see for yourself.
Hello! I'm Zeil fromZeil Learnings, a YouTube channel about Ren'Py. Thank you for this! It was very helpful and easy to implement thanks to your awesome documentation! If you allow me, I would like your permission to upload a modified version of this on my itch.io (not using a layered image and more comments on script.rpy). I'll link it to this page, ofc. Additionally, I'd like to feature it in one of my future videos. (maybe 2-3 months from now). You may contact me on Discord if you have any questions: _zeil
For more context, an individual approached me seeking assistance with this. So I figured it's not easy for non-programmers to follow. Therefore, I want to make a modified version of it for people without programming experience.
Sincerely,
Zeil
Hi. Sorry for taking a bit to get back to you. But yeah I appreciate you wanting to edit it to be easier for non-programmers to read it. I will admit I did my best when writing the original code to try and make it approachable as I could. If you have ideas on how to make it more approachable, I'd be happy to take feedback and upload a modified version on here with credit to you for the help. But if you're not making significant changes to the functionality and just adding comments, I feel like it'd be best to just have one project up. Sent a friend request on discord so we can chat more about it. Thanks for your feedback.
- Wattson
Any advice on how to make this work on custom player names? It works for all of my other characters, but because I let the player choose their own name, the sprite doesn't highlight when they speak. Any thoughts?
define mc = Character("[mc_name]", callback=name_callback, cb_name="mc_light")layeredimage mc: at sprite_highlight('mc_light') always: "mc_base" group expressions: pos(70,62) attribute neutral default: "mc_neutral" attribute happy: "mc_happy" attribute sad: "mc_sad" attribute angry: "mc_angry" attribute surprised: "mc_surprised"
if blush: "mc_blush"layeredimage mchbrunch: at sprite_highlight('mc_light') always: "mc_brunch" group expressions: attribute neutral default: pos(163,125) "mc_neutral" attribute happy: pos(163,125) "mc_happy" attribute sad: pos(163,128) "mc_sad" attribute angry: pos(163,125) "mc_angry" attribute surprised: pos(163,125) "mc_surprised" if blush2: pos(165,115) "mc_blush2"
I cannot, for the life of me, get this to work with layered images. It only seems to work for me if I set the function SpriteFocus('sprite_name') per sprite call/update. Here's an example of one of my images and the associated character. I have no idea why this isn't working properly. It does not highlight at all (unless I manually set the sprite focus per call, which is tedious). Thoughts?
define Richard = Character("[Richard_name]", color="#5c00ff", callback = name_callback, cb_name = "ric", image='richard')
layeredimage richard: at sprite_highlight('richard') attribute normal default: "Richard" attribute angry: "Richard ANGRY" attribute annoyed: "Richard ANNOYED" attribute blush: "Richard BLUSH" attribute blush2: "Richard BLUSH 2" attribute crying: "Richard CRYING" attribute crying2: "Richard CRYING 2" attribute surprised: "Richard SURPRISED" attribute worried: "Richard WORRIED"
The highlight on the character "Mark" does not work here, but in all other dialogues with him it works. I tried changing the names of the files, as they only have differences in names, but it didn't help.
Right now I was thinking that it doesn't work because of the previous replica of this character. In front of her I have a sprite in the middle with two characters pushing each other and Mark is talking at this time. After that, the sprite of the two characters is removed and Mark speaks again. It is at this moment that highlight does not work on the character Mark.
It is here that the backlight and its animation on the character Mark do not work:
I tried to remove the "with dissolve" transition, but the highlight animation still didn't work.
As far as I can see from the sprites, the Van character is still darkened. And the highlight animation most likely does not work just because of the previous replica of the Mark character, since I noticed that after changing sprites with pushing two characters a little further than this moment, when the Van character has a replica, the highlight stays on it for a few milliseconds and goes to the Mark character when he speaks. I think I should come up with some kind of "stub" or something like that. Well, or leave it as it is. The rest works, I don't want to break everything because of this.
In any case, thank you for your time to this problem and many thanks for this script. This is very useful for people without Python programming skills.
In another .rpy file, I have a dictionary that maps character names (the string that is used for thecb_name parameter when defining a Character) to the path of their respective bleep files, which looks something like this:
## name_callback in game/00auto-highlight.rpy uses this dictionary ## to determine what bleeps to use per characterdefine bleeps_dict = {"ss" : "audio/bleep023.ogg", "vl" : "audio/bleep003.ogg", "ap" : "audio/bleep006.ogg"}
And in 00auto-highlight.rpy, I tweaked thename_callback function to look like this (please note that I defined a separate audio channel just for text bleeps):
def name_callback(event, interact=True, name=None, **kwargs): global speaking_char if event == "begin": speaking_char = name ## text bleeps if name in bleeps_dict: if event == "show": renpy.music.play(bleeps_dict[name], channel="bleeps", loop=True) elif event == "slow_done" or event == "end": renpy.music.stop(channel="bleeps", fadeout=1.0)
If there is a character who shouldn't use bleeps (I defined a separate Character object for my MC's thoughts, for example), then the "if name in bleeps_dict" line above can be changed to something like:
if name in bleeps_dict and name != "name_of_silent_character":
If there are several character who don't speak, it might be easier to define a list of names of silent characters and then use "...and name not in silent_characters".
I think this is all I needed to do. If you have any issues, please feel free to let me know!! ♡
(Also I'm really sorry if the code formatting above is a bit nasty to read with the indentations)
I'm happy to help!! I hope it works for your project.
I forgot to mention in my last reply that I defined a separate audio channel for the text bleeps (which affects the changes to the name_callback function). That's why I have a channel named "bleeps" in the code. This can be changed to any audio channel (premade or custom) where bleeps can work.
Please feel free to let me know if you have any more questions about my implementation!! <3 <3