Dueling/Aim Hero minigame

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Posts: 3
Joined: Thu Apr 05, 2018 4:22 pm

Dueling/Aim Hero minigame

#1 Post by PrinnyBaal » Thu Apr 19, 2018 12:43 pm

Hello! Long time lurker, felt guilty not giving at least something back. To give some background on the 'theme' this is supposed to evoke, I've been trying to cobble together some minigames that would work in a VN about dueling. With this one I'm trying to capture the fantasy of two duelists squaring off and trading quips while they cross swords.

Dialogue plays at the bottom of the screen while the minigame plays out. The dialogue will change for a moment whenever the player messes up and gets hit. The minigame itself is the common 'click a growing red circle at the right time'. With some minor tweaks you could reasonably mimic most of the minigames you see in Aim Hero (which was a bit of an inspiration) to have a shootout feel.

Anyhow I'll throw some closing thoughts at the end since I love to hear myself talk but let's get on with the code! I tried to clean it up but, fair warning, it's a little hacky. Any tips to improve readability, performance, or just sticking to convention are much appreciated!

So there are a couple of pieces you'll need for this. Let's go over them in this 10 steps list!

1: First you'll need to define a screen. For us this will basically be our main game loop. I usually just plop at the start of the label I'm going to use it in because I personally find it easier to playtest and iterate that way. I'd imagine best practices would be to define it with your other screens at game start or something though.

https://gist.github.com/PrinnyBaal/418e ... ac9d4de2c1

More on how screens work: https://www.renpy.org/doc/html/screens.html

2: You may have noticed that we called some calls to a function called growth at the end of that screen. Let's look at that function now, which handles randomizing where our targets spawn, randomizing when they spawn, and setting how quickly they grow.

https://gist.github.com/PrinnyBaal/af7d ... 7c83894c81

more on functions: https://www.datacamp.com/community/tuto ... orial#what

3: Another thing you may have noticed in our screen is that when drawing targets into view they're given a transform labeled number1. This is how we take the zoomed variable and actually apply it to our target image. The uh, the name of the transform isn't all that evocative but since I just wound up using a post here on the forums about transforms as a skeleton. This is that transform here. Put it in your init block:

Code: Select all

transform number1(z):
        zoom z
more on transforms: https://www.renpy.org/doc/html/transforms.html#

4: Finally all throughout you've been seeing blow0.x, blow3.path, etc. That's because all of our targets' personal information are stored in their own objects (for our purposes think of objects as a bundle of variables living under one roof). Let's make those now. First we'll make a blueprint for out objects in out init python block (we'll be telling our game to reference this when we're creating other objects so every object can have different variables tied together but their structure is always consistent):

Code: Select all

class blow:

        def __init__(self, X, Y, zoomed, target, blocked, parry, tick, clear, spawnTime, path):
            self.clear = clear
            self.spawnTime = spawnTime
more on python classes: https://docs.python.org/3/tutorial/classes.html

and even MORE on python classes in my personal favorite video tutorial on them: https://www.youtube.com/watch?v=apACNr7DC_s

5: Still sticking around that init python block let's now create four objects using our blueprint. One for each of our targets that can be on the screen at one time. I have them all being the exact same but creating some variety between targets can potentially be fun. You could for example make a few changes to have slow heavy hits with a green circle and quickly growing slashes with a blue circle, etc.

The order we pass in the values dictates which parameter of the blueprint we're assigning that value to. the first 0 is assigned to X, thesecond to Y, 1.0 is assigned to zoomed, etc. the 'self' parameter is skipped as its value is always just the object itself.

Code: Select all

blow0= blow(0,0, 1.0, target, blocked, \
                parry, 0, True, 0, "path")
    blow1= blow(0,0, 1.0, target, blocked, \
                parry, 0, True, 0, "path")
    blow2= blow(0,0, 1.0, target, blocked, \
                parry, 0, True, 0, "path")
    blow3= blow(0,0, 1.0, target, blocked, \
                parry, 0, True, 0, "path")
more on...actually just see the classes stuff linked above

6: We're almost done! Next we used some standard functions to keep track of time and a few other things. let's import them by pasting this code in our init python block as well

Code: Select all

    import random
    import time
    import pygame
more on Importing: viewtopic.php?t=38594

7: Next let's declare some of the variables we used like 'shieldBroken' and 'target'. Put these in your python init as well NOT like you would normally define images.

Code: Select all


    target = "redCircle.png"
    blocked =  "whiteCircle.png"
    parry =  "goldCircle.png"
    hit = 0

More on variables and init python: https://www.renpy.org/doc/html/python.html#one-line-python-statement

8: Now that we've got our code working how do we put it to work?  Easy!  Just slap this line wherever you want to begin your game, on the same lines you'd call in sprites or lines of dialogue in your script (note that we want show NOT call so that we'll be able to display the dialogue):

[code]show screen dueling
To turn off the minigame we'll just use this line

Code: Select all

 hide screen dueling 
More on...actually just see more on screens above.

9: Okay we've got the code for the minigame working but we don't have the dialogue playing! AND our player is stuck here, dueling foreeeeeever! Well you could have multiple win or loss conditions. In our case we're going to set up a win condition of lasting X amount of time (with a time penalty if you get hit) and a loss condition if you get hit three times. First let's put in the dialogue loop that'll run at the same time as our game loop. Place this immediately AFTER the "show screen dueling" line

The dialogue loop works by having various arrays filled with tuples of dialogue. You'll see them below as hitDialogue1, earlyBanter, etc. The dialogue loop uses renpy.say to display the dialogue saved in each tuple then ups a counter to work on the next one. Since we want the dialogue to work on its own while the player is doing he minigame we append "{w=2}{nw}" to the dialogue in each tuple. What this does is tell the dialogue to "W"ait for 2 seconds then the "nw" tag tells it to display the next line without user input. Since the counter is stored in a variable it's perfectly happy to stop churning through one array, display another array of dialogue when it gets hit, then go back to the original dialogue.

Code: Select all

#This while loop is set to true so it will keep cycling forever unless we forcibly break it somehow
        while True:
            #After every line of dialogue it will check with our logic again to see if it needs to change gears
            # If the player has been hit 3 or more times we'll use renpy.jump to break out of the while loop and into a label for the player losing
            #This won't end the minigame itself unless you also use the "hide screen dueling" line once you get to that new label
            if hit >=3:
             #If the player has been hit exactly once AND we haven't exhausted the dialogue for being hit once this will play
            elif hit ==1 and hitCounter1< len(hitDialogue):
                renpy.say(hitDialogue[hitCounter1][0], hitDialogue[hitCounter1][1]+"{w=2}{nw}")
                #If the player has been hit exactly once AND we haven't exhausted the dialogue for being hit once this will play
            elif hit ==2 and hitCounter2< len(hitDialogue2):
                renpy.say(hitDialogue2[hitCounter2][0], hitDialogue2[hitCounter2][1]+"{w=2}{nw}")
                #If either the player hasn't been hit OR the player has already worked through the hit dialogue the default banter is worked through.
            elif counter< len(earlyBanter):
                renpy.say(earlyBanter[counter][0], earlyBanter[counter][1]+"{w=2.5}{nw}")
             #Finally we'll fall to this if the player has seen all the available dialogue and hasn't been hit three times, breaking us out of the loop and into our player win
             #dialogue.  This works because the win dialogue is right after this code but you could use another renpy.jump(labelName) to bounce somewhere else
more on indexes, arrays, etc. (anyone have a better more focused link for this one?):  http://www.physics.nyu.edu/pine/pymanual/html/chap3/chap3_arrays.html
    hide screen dueling
    K "TIME!"
    K "Those of you Knights still on your feet, congratulations."
    K "Monsters, better luck next round." 
    if hit==0:
    	H "Wow I didn't get hit once!  I'm a beast!"
The dialogue at the end isn't necessary but I included it just to help make it a little clearer what's happening. Remember you still have that hit variable saved if you want to alter dialogue a bit to reflect how the player did.

10: Okay, this is the FINAL STEP. And it's just to define our dialogue arrays. This can technically go anywhere before the loop. I have it right before the loop for lazy tinkering but you probably want it in your init python block in which case just strip off the bling (dollar signs) before you slap it down there.

Code: Select all

  #Obviously you'll want your own dialogue but here's the dialogue I've been using to tinker.  Note that the first part of each tuple in the array is the 'who' while the 	
  #    second part is the 'what' of their dialogue.  One point of frustration is that I wasn't able to get multiple word variables in the first slot so \ H animated angry \ for  #example wouldn't work for side images.  Could probably just make sure all character definitions are all one word like HAnimatedMad though.  But still.
  $earlyBanter=[(H,'"Are you sure you\'re taking this seriously?"'), (M, '"I take this job VERY seriously!"'), (H, '"You sure have a funny way of showing it."'), 
  	(M, '"Look, I\'m sorry okay?"'), 
        (M, '"But don\'t you think if I wasn\'t..."'), (H, '"Left foot BEHIND your guiding hand Dragon Bait.  You {b}want{/b} a sprained ankle?"'), 
        (M, '"Oh.  Right, sorry."'),(M, '"...anyway if I wasn\'t serious I\'d be spending my freetime exploring Mewni."'), (M, '"You know, kicking back and adventuring!"'),
        (M, '"Instead, yeah I\'m late, but I\'m here!"'),(H, '"Yeah, well why are you here if you hate it so much?"'), (H, '"And for the love of corn, follow through!"'),
        (H, '"I can barely feel you with thrusts like that."'), (M, '"Well...I mean, like I\'ve been saying."'), (M, '"I want to be a good squire for Star."'),
        (H, '"Nuh-uh.  That\'s a goal, sure.  That\'s not a reason to follow it though."'),
         (H, '"Wait...geeze don\'t tell me this is some stupid way of trying to be her boyfriend."'),
        (M, '"I-uhm, what?!  What is it with you and bringing up weird things like that?!"'), (H, '"That\'s nooot an answer."'), 
        (M, '"NO!  Geeze, Star is...my best friend.  That\'s all!"'),
        (H, '"Uh-huh.  And you seem soooo fine with that."')]
    $hitDialogue=[(H, '"HIT!  Ah, Geeze, you rapped my knuckles there."'),(M, '"Ah, sorry should I...should I stop or..."'), 
    (H, '"Pfft, heck no.  I\'ve had worse.  Keep it going."')]
    $hitDialogue2=[(H, '"HIT!"'), (M, '"Haha!  That\'s two now."'), (H, '"Ugh.  Don\'t let that go to your head."')]

Step 11: AKA, I lied it's an 11 step process! Bwahaha! Anyhow just remember to have all the images you'll use in your image folder and all the sound files you'll use in your main game folder. Some of the sound files I used I just have lying around for personal testing and I'd feel weird sharing here since I didn't make them. But I've included all of the images. If you want you can just remove all the audio file related stuff and it'll still play well but I feel sfx as feedback make for a better user experience. Note that if you're changing the sprite for the target you'll want to tinker with the logic in the imagebuttons since they're keyed to the zoomed variable (for example it pops only when breaking past a certain threshold, it's valid to click only when touching the sweetspot, etc.).

If you run into any trouble (I don't THINK I missed anything but this is my first time trying to share my code and my experimental stuff tends to look pretty hacky/messy) or want any help altering the code to fit your needs feel free to let me know I'm glad to help if I can! If you see anything that should be done better DEFINITELY speak up. I know the dialogue could be wrapped up in a text box screen instead to avoid touching the regular renpy dialogue but I personally think this looks better with less fuss and has the benefit of showing up in the history for players who want to see the dialogue in a relaxed state.

I'll keep this updated with any suggestions that I wind up implementing including changes to the tutorial style/descriptions to make things clearer. Additionally in the near future I hope to expand this by having the player be able to make dialogue choices during all of this (using some additional variables and imagebuttons with it defaulting to a 'flustered' line of dialogue if the player chooses nothing) that I hope will really get the feel of trying to banter with divided attention. Could likely do something fancy with the background too though.
whiteCircle.png (3.19 KiB) Viewed 701 times
shieldUpNew.png (3.49 KiB) Viewed 701 times
shieldBroken.png (2.56 KiB) Viewed 701 times
redCircle.png (3.45 KiB) Viewed 701 times
goldCircle.png (3.13 KiB) Viewed 701 times

Post Reply

Who is online

Users browsing this forum: No registered users