Page 1 of 7

Dungeon Crawl RPG Framework

Posted: Wed Feb 06, 2013 1:52 am
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"

Re: Pseudo 3D dungeon crawling frame work

Posted: Wed Feb 06, 2013 6:58 am
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. :)

Re: Simple dungeon crawling RPG frame

Posted: Wed Feb 06, 2013 12:10 pm
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.

Re: Simple dungeon crawling RPG frame

Posted: Thu Feb 07, 2013 7:53 am
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.

Re: Simple dungeon crawling RPG frame

Posted: Thu Feb 07, 2013 9:26 am
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.

Re: Simple dungeon crawling RPG frame

Posted: Thu Feb 14, 2013 7:03 am
by nyaatrap
[/merged in the top code]

Re: Simple dungeon crawling RPG frame

Posted: Fri Feb 15, 2013 2:50 am
by DaFool
I have got to try this and combine it with Battle Engine.

Now I have an excuse to join Nanoreno.

Re: Simple dungeon crawling RPG frame

Posted: Fri Feb 15, 2013 1:11 pm
by TrickWithAKnife
The mediafire link isn't working. Something about an alleged ToS violation.

Re: Simple dungeon crawling RPG frame

Posted: Fri Feb 15, 2013 7:54 pm
by nyaatrap
I didn't met that issue. If it's not just a connection error, maybe your provider or browser shut it arbitrarily?

Re: Simple dungeon crawling RPG frame

Posted: Fri Feb 15, 2013 8:47 pm
by TrickWithAKnife
Okay, I'll try it with a different browser tonight and post the result.

Re: Simple dungeon crawling RPG frame

Posted: Sat Feb 16, 2013 9:36 pm
by nyaatrap
[/merged in the top code]

Re: Simple dungeon crawling RPG frame

Posted: Sat Feb 16, 2013 10:21 pm
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.

Re: Dungeon crawling RPG frame

Posted: Sun Feb 17, 2013 9:53 am
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.

Re: Dungeon crawling RPG frame

Posted: Mon Feb 18, 2013 4:06 pm
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.

Re: Dungeon crawling RPG frame

Posted: Mon Feb 18, 2013 9:12 pm
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.