Codes: Achievements

I actually haven’t seen a full writeup of using RenPy’s achievements module. Thankfully, it’s super easy to set up, but I have no idea if this will actually integrate into Steam and such properly.

In any case: The game had collectibles. Yay. But I wanted some larger accomplishments (such as finding lots of collectibles) to be noted in a more prominent way.

The first thing to note is that RenPy’s achievements module is part of core RenPy and not its game scripts. (This is probably obvious, but I figured I’d point it out anyway.) As such, you have to call it from python code blocks.

To simplify things, I defined two kinds of achievements: Ones in a set list that are just boolean (you have it or you don’t) toggles, and ones that have a bit more complicated tracking logic. (I believe the latter will matter more if you actually integrate into Steam, but again, I have not actually tested any of this.)

First off: Defining your achievements. They just need a name, but I put a small description with them for UI display. Note the tuple instead of map, this time, so ordering is maintained:

python early:
basic_achievements = (
("First Steps", "Starting your college life", "Starting your college life"),
("Nothing But Love", "???", "Coming out to your parents"),
("Achievement Name", "Description when not unlocked", "Description when unlocked"),
…other achievements…
)

Then, you’ll want to register them with the backend. I believe this isn’t strictly necessary, but it doesn’t hurt to do.

init python:
for a, lockdesc, unlockdesc in basic_achievements:
achievement.register(a)
achievement.register("Growing Pains", stat_max=100, stat_modulo=1)
achievement.register("Compulsive Hoarder", stat_max=100, stat_modulo=1)

Note the manually-registered ones that include stat maxes. You could probably just register them with the actual number of collectibles, but I figured it’d be cleaner to deal with completion in terms of percentage, especially as I was still increasing the number of collectibles in the game.

Next, you actually want to grant these achievements in the game. This is, thankfully, straightforward.

"You feel an immense sense of relief, as well."
$ achievement.grant("Nothing But Love")
show adam grin
"Adam turns to you, grinning."

So this grants achievements (silently, to the player). How do you display them? I added them to the collectibles screen I defined in the previous post.

screen mycollection():
tag menu
default collectshow = "collectibles"
use game_menu(("Collection"), scroll="viewport"): vbox: spacing 10 hbox: spacing 20 textbutton ("Collectibles") action SetScreenVariable("collectshow", "collectibles")
textbutton _("Achievements") action SetScreenVariable("collectshow", "achievements")
if collectshow == "achievements":
for aname, lockdesc, unlockdesc in basic_achievements:
if achievement.has(aname):
text aname + ": {color=#777}" + unlockdesc + "{/color}"
else:
text "{color=#ccc}???: " + lockdesc + "{/color}"
if achievement.has("Growing Pains"):
text "Growing Pains: {color=#777}Finding " + str(int(len(collectitems)/2)) + " collectibles{/color}"
else:
text "{color=#ccc}???: Finding " + str(int(len(collectitems)/2)) + " collectibles{/color}"
if achievement.has("Compulsive Hoarder"):
text "Compulsive Hoarder: {color=#777}Finding all " + str(int(len(collectitems))) + " collectibles{/color}"
else:
text "{color=#ccc}???: Finding all " + str(int(len(collectitems))) + " collectibles{/color}"
else:
text "Found " + str(len(persistent.collection)) + "/" + str(len(collectitems)) + " collectibles" id "collectiblescount"
for k in sorted(persistent.collection):
if k in collectitems:
textbutton collectitems[k][0] action Show("itemdesc", itemid=k)

The last part is to increment progress as you acquire collectibles. This is also straightforward.

    def execute_collect(what):
if what in persistent.collection:
return
formatmsg = "{color=#090}{b}You found a collectible:{/b} "
if what in collectitems:
formatmsg += collectitems[what][0]
else:
renpy.error("Invalid collectable " + what)
persistent.collection.add(what)
totitemcount = len(collectitems)
curitemcount = len(persistent.collection)
if curitemcount >= (totitemcount):
if not achievement.has("Compulsive Hoarder"):
achievement.grant("Compulsive Hoarder")
else:
achievement.progress("Compulsive Hoarder", int(totitemcount100/curitemcount)) if curitemcount >= (totitemcount/2): if not achievement.has("Growing Pains"): achievement.grant("Growing Pains") else: achievement.progress("Growing Pains", int(totitemcount50/curitemcount))
if len(persistent.collection) == 1:
formatmsg += "\n(Access your collection from the main menu or pause menu. Collectible items will persist between games.)"
formatmsg += "{/color}"
renpy.say("", formatmsg)

As usual, I make no claims that this code is the best way to do things, or even a good way to do things. But it works. And, assuming achievements work the way I assume they will, doing it this way should also hook properly into things like Steam.

I was expecting the achievement framework to already put up some sort of notification, but it looks like RenPy doesn’t do this by default, so using this doesn’t really have any immediate advantages over directly writing persistent data.

This could be improved by adding a custom command to wrap achievement.grant() that can lint against the known list of achievements, and that can also show a notification to the player that an achievement was unlocked (some new screen that displays temporarily in some corner). This is perhaps an enhancement I will make at some point. 

Edit: Improved achievements, building on this code, is here.

Join the Conversation

1 Comment

Leave a comment

Your email address will not be published. Required fields are marked *