Dungeon Crawl RPG Framework

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
Message
Author
User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Dungeon Crawl RPG Framework

#1 Post by nyaatrap »

Clipboard 1.png
This framework allows to implement Dungeon Crawl RPG elements into your ren'py game.
Sample game is available at http://www.mediafire.com/download/9fli0 ... .0-all.zip

This framework contains 3 scripts: script.rpy, dungeon.rpy and battle.rpy. You can omit dungeon.rpy or battle.rpy if you don't need it.
This framework only works in ren'py 6.17 or later. Rewriting styles allows to work in renpy 6.16 or before, but not recommended because of performance reason.
Basic python knowledge is highly recommended.
This framework is in the public domain.

script.rpy

Code: Select all

# This file is in the public domain.

label start:
    
    # Initializing data
    python:
        
        # Create skills (name, type, hit, power)
        attack = Skill("Attack", "attack", 70, 20)
        escape = Skill("Escape", "escape")
        
        # Create battle actors (name, max_hp, skills)
        hero = Actor("Hero",100, [attack,escape])
        goblin = Actor("Goblin",40, [attack])
        
        # Create a dungeon stage (map,enemy)
        # "1" means wall, "0" means path. 
        stage1=Stage([
            "1111111111",
            "1111011001",
            "1000000001",
            "1110111101",
            "1000000001",
            "1111111111",
            ],
            enemy=goblin)
            
    # The game starts here.
    
    # Place a player position on a dungeon stage (stage,y,x,dy,dx).
    # dx,dy means direction. If dy=1, it's down. If dx=-1, it's left.
    $ here=Coordinate(stage1,2,2,0,1) 
    
    # To start exploring, call or jump to the label dungeon. 
    call dungeon
    
    # To start battling, call the label battle with 2 actor objects: player and enemy.
    call battle(hero,goblin)
dungeon.rpy

Code: Select all

# This file is in the public domain.

init -1 python:
    
    class Stage(object):
        
        '''
        Class which contains map itself, auto mapping record, and encounter enemy.
        '''
        
        def __init__(self, map, enemy=None):
            self.map=map
            self.enemy=enemy
            self.mapped=[]
            for n,i in  enumerate(map):
                self.mapped.append([])
                for j in i:
                    self.mapped[n].append(0)
                    
    class Coordinate(object):
        
        '''
        Class used for calculating relative coordinate.   
        '''
        
        def __init__(self, stage=None, y=0, x=0, dy=0, dx=0):
            self.stage=stage
            self.y=y
            self.x=x
            self.dy=dy
            self.dx=dx 
            
    class Minimap(object):
        
        '''
        A minimap. Minimap(current_coordinate).sm is a displayable to show this minimap.
        '''
        
        def __init__(self,child):
            self.sm = SpriteManager(ignore_time=True)
            for i in xrange(len(child.stage.map)):
                for  j in xrange(len(child.stage.map[0])):
                    if child.stage.mapped[i][j]==1:
                        if child.stage.map[i][j] in ["1"]:
                            d = Solid("#666", xysize=(12,12))
                        else:
                            d = Solid("#fff9", xysize=(12,12))
                    else:
                        d = Solid("#0000", xysize=(12,12))
                    self.add(d,i,j)
            if child.dy==-1:
                self.add(Text("↑",size=12),child.y,child.x)
            elif child.dx==1:
                self.add(Text("→",size=12),child.y,child.x)
            elif child.dy==1:
                self.add(Text("↓",size=12),child.y,child.x)
            else:
                self.add(Text("←",size=12),child.y,child.x)
                    
        def add(self, d,n,m):
            s = self.sm.create(d)
            s.x = m*12+12
            s.y = n*12+12
            
screen move:
    # Screen which shows move buttons and a minimap 
    
    fixed style_group "move":
        if front1.stage.map[front1.y][front1.x] is not "1":
            textbutton "↑" action Return(value=front1)  xcenter .2 ycenter .7
        textbutton "→" action Return(value=turnright) xcenter .3 ycenter .8
        textbutton "↓" action Return(value=turnback) xcenter .2 ycenter .9
        textbutton "←" action Return(value=turnleft) xcenter .1 ycenter .8
    
    add Minimap(here).sm
    
style move_button_text:
    size 60
        
# Assign background images.    
# "left0" means a wall on the lefthand, "front2" means a further wall on the front, and so on.

# left2, front2, right2
# left1, front1, right1
# left0,  here , right0 

image floor = "floor.png"
image left0 = "left0.png"
image right0 = Transform("left0.png", xzoom=-1)
image front1 ="front1.png"
image left1 = "left1.png"
image right1 = Transform("left1.png", xzoom=-1)    
image front2 = "front2.png"
image left2 = "left2.png"
image right2 = Transform("left2.png", xzoom=-1)    
    
label dungeon:
    # To start exploring, call or jump to this label
    # To exit, create an event which has return or jump statement.
    
    while True:        
        # Calculate relative coordinates
        python:            
            turnright=Coordinate(here.stage, here.y,here.x, here.dx,-here.dy)
            turnleft=Coordinate(here.stage, here.y, here.x, -here.dx,here.dy)
            turnback=Coordinate(here.stage, here.y,here.x, -here.dy,-here.dx)
            right0=Coordinate(here.stage, here.y+here.dx,here.x-here.dy, here.dy,here.dx)
            left0=Coordinate(here.stage, here.y-here.dx,here.x+here.dy, here.dy,here.dx)
            front1=Coordinate(here.stage, here.y+here.dy,here.x+here.dx, here.dy,here.dx)
            right1=Coordinate(here.stage, front1.y+front1.dx,front1.x-front1.dy, here.dy,here.dx)
            left1=Coordinate(here.stage, front1.y-front1.dx,front1.x+front1.dy, here.dy,here.dx)
            front2=Coordinate(here.stage, front1.y+front1.dy,front1.x+front1.dx, here.dy,here.dx)
            right2=Coordinate(here.stage, front2.y+front2.dx,front2.x-front2.dy, here.dy,here.dx)
            left2=Coordinate(here.stage, front2.y-front2.dx,front2.x+front2.dy, here.dy,here.dx)                    
        
        # Composite background images. Try-except clauses are used to prevent the List Out of Index Error
        scene
        show floor
        python:
            for i in ["left2", "right2", "front2", "left1", "right1", "front1", "left0", "right0"]:
                try:
                    j=globals()[i]
                    if j.stage.map[j.y][j.x]=="1":
                        renpy.show(i)
                except:
                    pass
                
        # Record maps
        python:
            for i in [left1, right1, front1, left0, right0, here]:
                here.stage.mapped[i.y][i.x]=1
                
        # Check events. If it happens, call a label or jump out to a label.
        if here.stage.enemy is not None and renpy.random.random()< .2:
            call battle(player=hero, enemy=here.stage.enemy)
                
        # Otherwise, call the move screen
        $ renpy.block_rollback()
        call screen move
        $ here=_return
battle.rpy

Code: Select all

# This file is in the public domain.

init -1 python:
    
    from copy import copy
        
    class Skill(object):        
        
        '''            
        Class used for battle skills
        '''    
        
        def __init__(self, name, type, hit=0, power=0):
            self.name = name
            self.type = type
            self.hit = hit
            self.power = power        
            
    class Actor(object):        
        
        '''
        Class used for battle characters.
        '''        
        
        def __init__(self, name, max_hp=0, skills=[]):
            self.name=name
            self.max_hp=max_hp
            self.hp=max_hp
            self.skills = skills
                        
        def attack(self,skill,target):
            if self.skill.hit < renpy.random.randint (0,100):
                narrator ("{} dodged {}'s attack".format(target.name,self.name))
            else:
                target.hp -= self.skill.power
                narrator ("{} got {} damage".format(target.name, self.skill.power))                
                
screen battle_ui:    
    # Screen which shows battle status
    
    use battle_frame(char=player, position=(.95,.05))
    use battle_frame(char=enemy, position=(.05,.05))
    
screen battle_frame(char,position):
    # Screen included in the battle_ui screen
    
    frame xysize(180, 80) align position:
        vbox yfill True:
            text "[char.name]"
            hbox xfill True:
                text "HP"
                text "[char.hp]/[char.max_hp]" xalign 1.0
                
screen command_screen:    
    # Screen used for selecting skills
    
    vbox style_group "skill" align (.1,.8):
        for i in player.skills:
            textbutton "[i.name]" action Return (value=i)

style skill_button_text:
    size 40
    xminimum 200
                
label battle(player, enemy):
    # To start battling, call this label with 2 actor objects: player and enemy.
    
    # Preparation
    # Copying enemy object prevents modifying an original data.
    $ enemy=copy(enemy) 
    $ _rollback=False
    show screen battle_ui
    "[enemy.name] appeared"
    
    # Main phase
    call _battle(player, enemy)
    
    # Result
    if _return is "lose":
        "Gameover"
        $renpy.full_restart()
    elif _return is "win":
        "You won"
    elif _return is "escape":
        "You escaped"        
    hide screen battle_ui
    $ _rollback=True
    return
    
label _battle(player, enemy):
    # A sub label used in the battle label.
    
    while True: 
        $ player.skill = renpy.call_screen("command_screen")
        $ enemy.skill = renpy.random.choice(enemy.skills)
        if player.skill.type=="escape":
            return "escape"
        $ player.attack(player.skill, enemy)
        if enemy.hp < 1:  
            return "win"
        $ enemy.attack(enemy.skill, player)
        if player.hp < 1:
            return "lose"
Last edited by nyaatrap on Thu Sep 04, 2014 10:43 pm, edited 12 times in total.

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: Pseudo 3D dungeon crawling frame work

#2 Post by CaseyLoufek »

This looks great. I'm really curious about this and I'm willing to help out if you need anything.

With this and the RPG engine you could do some really nice games in the style of the old Gold Box D&D games like Pool of Radiance and the early Ultima series. Nothing beats a good conversation engine welded to a good dungeon crawling engine. :)

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#3 Post by nyaatrap »

Rewrite the first post a bit. It's easy to add a RPG element so I mixed them while keeping codes short and simple as possible as I can.

Ryue
Miko-Class Veteran
Posts: 745
Joined: Fri Nov 02, 2012 8:41 am
Projects: Red eyes in the darkness
Contact:

Re: Simple dungeon crawling RPG frame

#4 Post by Ryue »

It is a very neat framework.
One thing I just saw that could enhance it even (as it will be something that will have to be added there when the scenes,... follow) would be to make another array there
which holds event locations (thus jump locations).
stageEvents=[[null,null,null,"event1",null,null,"event2",...],...]

If then when the player moves after the move it is looked if the location on the map has a stageEvent associated even the planed events can be used dynamically thus
the whole code can be used easily for multiple parts of the game again and again.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#5 Post by nyaatrap »

Yes. It should have external lists of events outside of maps.
However, I think the top post should have the minimum and simplest code. So I might not add more functions on the top post (other than bug fixes. I also trimmed some additional codes from the top post to make the code look clear. Full code is still available in the sample game).
Anyway It's welcome to discuss more ideas, functions, e.t.c here. I wrote this code as a reference more than framework, so I'm happy if you get some hints for your original game from this code.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#6 Post by nyaatrap »

[/merged in the top code]
Last edited by nyaatrap on Sun Feb 17, 2013 9:55 am, edited 1 time in total.

User avatar
DaFool
Lemma-Class Veteran
Posts: 4171
Joined: Tue Aug 01, 2006 12:39 pm
Contact:

Re: Simple dungeon crawling RPG frame

#7 Post by DaFool »

I have got to try this and combine it with Battle Engine.

Now I have an excuse to join Nanoreno.

TrickWithAKnife
Eileen-Class Veteran
Posts: 1261
Joined: Fri Mar 16, 2012 11:38 am
Projects: Rika
Organization: Solo (for now)
IRC Nick: Trick
Location: Tokyo, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#8 Post by TrickWithAKnife »

The mediafire link isn't working. Something about an alleged ToS violation.
Last edited by TrickWithAKnife on Sat Dec 06, 2014 10:23 am, edited 1 time in total.
"We must teach them through the tools with which they are comfortable."
The #renpy IRC channel is a great place to chat with other devs. Due to the nature of IRC and timezone differences, people probably won't reply right away.

If you'd like to view or use any code from my VN PM me. All code is freely available without restriction, but also without warranty or (much) support.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#9 Post by nyaatrap »

I didn't met that issue. If it's not just a connection error, maybe your provider or browser shut it arbitrarily?

TrickWithAKnife
Eileen-Class Veteran
Posts: 1261
Joined: Fri Mar 16, 2012 11:38 am
Projects: Rika
Organization: Solo (for now)
IRC Nick: Trick
Location: Tokyo, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#10 Post by TrickWithAKnife »

Okay, I'll try it with a different browser tonight and post the result.
"We must teach them through the tools with which they are comfortable."
The #renpy IRC channel is a great place to chat with other devs. Due to the nature of IRC and timezone differences, people probably won't reply right away.

If you'd like to view or use any code from my VN PM me. All code is freely available without restriction, but also without warranty or (much) support.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#11 Post by nyaatrap »

[/merged in the top code]
Last edited by nyaatrap on Sun Feb 17, 2013 9:56 am, edited 1 time in total.

TrickWithAKnife
Eileen-Class Veteran
Posts: 1261
Joined: Fri Mar 16, 2012 11:38 am
Projects: Rika
Organization: Solo (for now)
IRC Nick: Trick
Location: Tokyo, Japan
Contact:

Re: Simple dungeon crawling RPG frame

#12 Post by TrickWithAKnife »

The download link worked, even though I used the same browser. Must have been a temporary hiccup on the server's end.
This looks fantastic. I've had something in mind for about 6 months, and this could provide the ideal framework to make it a reality. Many thanks.
"We must teach them through the tools with which they are comfortable."
The #renpy IRC channel is a great place to chat with other devs. Due to the nature of IRC and timezone differences, people probably won't reply right away.

If you'd like to view or use any code from my VN PM me. All code is freely available without restriction, but also without warranty or (much) support.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Dungeon crawling RPG frame

#13 Post by nyaatrap »

Shortened and Simplified the code, and added some comments a bit. I removed some functions I think they're not important. The demo is also replaced.

User avatar
facadepapergirl
Regular
Posts: 46
Joined: Thu Jan 31, 2013 7:27 pm
Projects: Pokemon: Final Evolution; Galactic Lust Crisis
Contact:

Re: Dungeon crawling RPG frame

#14 Post by facadepapergirl »

I'm trying to extract just the battle elements from the map, and I got this error:

I'm sorry, but an uncaught exception occurred.

While running game code:
ScriptError: could not find label '(u"C:\\Users\\Dick Chappey\\Desktop\\Ren'py Projects\\Pokemon Final Evolution/game/script.rpy", 1361216315, 1)'.

-- Full Traceback ------------------------------------------------------------

Full traceback:
File "C:\Program Files\renpy-6.14.1-sdk\renpy\execution.py", line 266, in run
File "C:\Program Files\renpy-6.14.1-sdk\renpy\ast.py", line 1149, in execute
File "C:\Program Files\renpy-6.14.1-sdk\renpy\execution.py", line 353, in lookup_return
File "C:\Program Files\renpy-6.14.1-sdk\renpy\script.py", line 477, in lookup
ScriptError: could not find label '(u"C:\\Users\\Dick Chappey\\Desktop\\Ren'py Projects\\Pokemon Final Evolution/game/script.rpy", 1361216315, 1)'.

Windows-7-6.1.7601-SP1
Ren'Py 6.14.1.366
A Ren'Py Game 0.0


Do you know what I did wrong and how to fix it?

Edit* When I rolled back, it disapeared and asked me instead about the 'name', saying 'function' object has no attribute to 'name'. It refers me to this line: "[enemy.name] appeared". Although I have changed Goblin to Skitty, I have not changed any instance of 'enemy' from the original script.


I replaces all instances of Goblin and enemy with my designated enemy (Skitty) and it worked fine. I'll leave this post here in case someone lse makes a similar mistake.

*Edit again* It only worked right once. It went back to the function problem, which moved on to complain about hp.

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Dungeon crawling RPG frame

#15 Post by nyaatrap »

The above code is using simple variables/classes/instances names which can cause contradiction easily. It's more safe to rename more complicated names.

Post Reply

Who is online

Users browsing this forum: No registered users