Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / Tutorial: Tile Based RPG

Author
Message
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 02:52
Welcome all,

I decided I would work up a simple tutorial to help people get their feet wet in making a 2D RPG.

What I plan to cover.

Step 1: Getting a character moving around in the world.
Step 2: Creating a map for the character to interact with and setting up the map collision.
Step 3: Adding a few NPC's to the game world and getting them to move around.
Step 4: Interacting with NPC's.
Step 5: Creating Objects and interacting with them.
Step 6: Adding monsters, moving them and simple combat.
Step 7: Adding the UI.
Step 8: Inventory Control.
Step 9: Setting Zones in the gameworld.
Step 10: Swapping Maps.
Step 11: Expanding combat.
Step 12: Magic
Step 13: Character Experience and "leveling Up".
Step 14: Setting Quests.
Step 15: Wrapping it all up.

If there is anything else you would like to see me cover let me know we can see where I can fit it in.

I will post a step at a time give people a little time to absorb it, answer any questions then move on to the next step. I encourage you to modify the code I present (tell me if you found a better way) and start good dialog.

As a note I am trying to write this as if the reader has never made a DB pro program before.

So without further adieu(??)

Step 1:
Ok, the approach I am taking here is a map made of 16x24 pixel tiles. Why 16x24? Well because the character sprite I am using for this is just that size and I like to keep the edges real tight since DBpro does not support pixel perfect collision.

So lets start with the basics

sync on <----We do this so the screen only updates when we want it to
sync rate 60 <-- We will set the rate to 60, I like this speed and anything over 40 is good
Hide Mouse<-- don't want that little cursor in our way we will make our own later.

next we grab some media
disect("link2.bmp",9,4,1)<- this function breaks up the image for us, i will explain it a little more when we get to it.
load image "grass.bmp",500<- a nice green medow
`load music "aolpalaceremix.mid",1<-- music I am using, i remmed this out to keep the media minimal but you can replace this with any music.

make the framework for our character, Monsters, and NPC's
some of the fields we are not using yet, like health, but eventually we will.
I can not express enough that UDT's are very important for easy data management
and making your code easier to read.
type bot
xpos as integer
ypos as integer
image as integer
facing as integer
name as string
health as integer
maxhealth as integer
animate as integer
frame as integer
endtype



set globals, a global variable can be used anywhere and we want that so we can call them in our functions.
Global xscroll as integer<--this will hold the scroll amount for our x plane
Global yscroll as integer<-- and this will do the same for y

set player data. Here we will set our starting values for our character, where he starts, what direction he is facing, his animation frame etc. I use screen width() and height() so that we can change the resoultion and not need to recode.
dim player(0) as bot
player(0).xpos=screen width()/2-8
player(0).ypos=screen height()/2-12
player(0).facing=3
player(0).animate=1
player(0).image=1
player(0).frame=timer()


launch items. here we will launch anything we want to start with the main loop. right now i am starting the music.

loop music 1

MAIN LOOp
do
`We want to keep the Main Loop easy to read and clutter free so we will move
`almost all of the game into seperate functions

Draw_map()<--these three commands are calling the functions we will define to control our game.
player_controls()
map_scroll()

sprite 1,player(0).xpos,player(0).ypos,player(0).image<--here we are putting our little shmo onto the screen, you will note that the x,y, and image number are all called from the player object

sync<--at the end of the loop after we have updated everything we want it to draw to the screen
loop<-return to the top of the main loop

`This function turns our bitmap into multiple images so we can animate it. the format for the function is name of the image,frames across, frames down, and what image number to start at.
function disect(filename as string,across,down,imagestart)

load bitmap filename,1
x=bitmap width(1)/across
y=bitmap height(1)/down
for a = 0 to down-1
for b = 0 to across-1
get image imagestart+a*across+b,x*b,y*a,x*b+x,y*a+y
next b:next a
delete bitmap 1
endfunction


`Here we determine what direction if any our player is moving. Since I did not want diagonal
`movement to be an option it is set up to allow only one direction at a time.

function player_controls()
choice=0
`Find out which key, if any, our player is pressing
if upkey()=1 then choice =1
if rightkey()=1 and choice=0 then choice=2
if downkey()=1 and choice=0 then choice=3
if leftkey()=1 and choice=0 then choice=4

`Use the players input to move and animate our character.
select choice
case 0: exitfunction:endcase<- if no key is pressed exit the function
case 1:
player(0).image=9+player(0).animate
`This sets what frame we are on
dec player(0).ypos,2 `This changes the characters position on the map
endcase
case 2:
player(0).image=18+player(0).animate
inc player(0).xpos,2
endcase

case 3:
player(0).image=0+player(0).animate
inc player(0).ypos,2
endcase
case 4:
player(0).image=27+player(0).animate
dec player(0).xpos,2
endcase
endselect

if player(0).frame<timer()`We are using the system timer to determine when to progress to the next frame.
inc player(0).animate
player(0).frame=timer()+50`by changing this value we can increase or decrease the animation speed.
endif
if player(0).animate>9 then player(0).animate=1 ` since our character animation has 9 frames we reset the frame to 1 if we pass the end
endfunction

function map_scroll()
`this group of commands limits the player from walking off the edge of the screen
`I chose to let the player come very close to the edge of the map before scrolling it
`but by changing the value you can set up a larger or smaller area for the character to
`move in before the map scrolls.
if player(0).xpos<32 then player(0).xpos=32:xscroll=xscroll+2
if player(0).xpos>screen width()-32 then player(0).xpos=screen width()-32:dec xscroll,2
if player(0).ypos<32 then player(0).ypos=32:yscroll=yscroll+2
if player(0).ypos>screen height()-32 then player(0).ypos=screen height()-32:dec yscroll,2

`Here we are setting up to roll the scroll back to its start position to keep the map
`movement fluid.
if xscroll>14 then xscroll=0
if xscroll<-14 then xscroll=0
if yscroll>22 then yscroll=0
if yscroll<-22 then yscroll=0
endfunction

function draw_map()
`At the moment we are continuously scrolling the same tile in all directions. Later on we
`will expand this function to draw the correct tiles according to the soon to be added map
`xposition/yposition.
cls
for a = -1 to screen height()/24+1
for b = -1 to screen width()/16+1
paste image 500,(b*16)+xscroll,a*24+yscroll
next b
next a
endfunction


here it all is in straight code form

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 02:53
Ignore

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 06:02
Hmm been trying to edit the above posts but keep getting an error. the grass texture is attached to the second post in case you did not notice sorry about that.
Jedi Lord
19
Years of Service
User Offline
Joined: 11th Jun 2004
Location: Jedi Temple
Posted: 2nd Jun 2006 06:54
Pretty good
I like your tutorial it teach me how to manage the inventory.
Have a enjoyable day!
oh... this is... 10 OUT DA 10

Whoa... I like a shiny thing... let's touch it .
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 09:52
Quote: "oh... this is... 10 OUT DA 10"


A little early for that statement but I hope it will be. I hope after the tutorial is done that I see twenty WIP RPG's based in some way off this. I am glad however that at least one person is looking into this.

Step two is completed code wise just want to clean it up a little and comment it properly. I will be posting the code for a very simple map editor to make the maps for the tutorial, but since this is not an editor tutorial it will be very basic.

Until the next chapter
Cash Curtis II
19
Years of Service
User Offline
Joined: 8th Apr 2005
Location: Corpus Christi Texas
Posted: 2nd Jun 2006 12:21
I'll keep an eye on this. I always enjoy learning different management theories in RPGs. The game mechanics might be very different, but the basics are the same. Good luck!


Come see the WIP!
Chris Franklin
18
Years of Service
User Offline
Joined: 2nd Aug 2005
Location: UK
Posted: 2nd Jun 2006 12:24
Awesome i'll be able to stop using Rpg maker Xp for my 2d rpgs now

Mailback is now on for this thread

Sergey K
20
Years of Service
User Offline
Joined: 4th Jan 2004
Location:
Posted: 2nd Jun 2006 13:46
you also could use the Word and Dword data types. that could be more useful =]

MyNewSite:http://gogetax.com
Forums(About BLO and more):http://gogetax.com/forum
ConorH
21
Years of Service
User Offline
Joined: 5th Feb 2003
Location:
Posted: 2nd Jun 2006 15:31
Holla!

Im making an RPG type engine. Should come in handy

its my new rat tail dog!
Chris Franklin
18
Years of Service
User Offline
Joined: 2nd Aug 2005
Location: UK
Posted: 2nd Jun 2006 15:40
lol

I can't wait to start on this my old 2d rpg i'm making in rpg maker xp can go in the bin for a hand coded one w00t

x1bwork
18
Years of Service
User Offline
Joined: 9th Nov 2005
Location:
Posted: 2nd Jun 2006 15:49
awsome. looking forward to this.

Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 18:58
Here is the Code for the map maker and attached is the media for it. I only set it up to use 10 tiles but that is easily modified I would just suugest that if you add more you limit it to 255 tiles since it is saving bytes. currently the maps are set up to be 150 wide and 100 high and a map file is 30k(till we add more later)

The controls are pretty easy arrow keys scroll the map, the LMB selects a tile in the lower area or places it in the upper. the RMB makes a tile immpassable or changes it back to passable. "s" saves a map, "l" loads a map, "g" turns the grid on or off, and "p" shows/hides passability.

It's not a great editor I just threw it together kinda quick to make some maps to work with. With a little tweaking it could be pretty good, but I am more concerned with the rest of the tutorial. Make some maps have some fun till the next installment.

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 19:08
A sample map I was making (Image only)

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 2nd Jun 2006 20:21 Edited at: 3rd Jun 2006 18:17
STEP TWO:





disect("link2.bmp",9,4,1001)<-- changed where these images rest so no conflict with the mapeditor
disect("terrain.bmp",10,1,1) <--Now we are loading our whole tileset


load music "aolpalaceremix.mid",1<--remember to rem this out or replace it with your own music


type tile <-- Added a UDT for our tiles, right now we are only using image and
image as integer passable but the others will be handy later.
passable as integer
openable as integer
locked as integer
doodad as integer
endtype

dim maptile(100,150) as tile <-- set up our map this is 15,000 tiles but you can resize this to whatever you want just make sure to do so in the editor also.
load_map("test.map") <--This loads our test map

`set globals
Global xscroll as integer
Global yscroll as integer

global xpos as integer =0 <--This keeps track of the maps x positon
global ypos as integer =0 <--This keeps track of the maps y position
global max_x <-- This is the max number of tiles that can fit across the screen at current resolution
global max_y <-- This is the max number of tiles that can fit with the screens height resoultion
global scrollrate=2 <--Added this to make it easier to adjust how fast our map scrolls and player moves, perhaps in the future our player will gain a speed burst and this will come in handy.
global xcord as integer <--This is the true xposition (tile wise) of our player
global ycord as integer <--This is the true yposition (tile wise) of our player
max_x=screen width() /16 <-- this we use to set our max x
max_y=screen height() /24 <--same for y



player(0).xpos=screen width()/2-8-16 <--offset this a bit to keep from spawning in a tree
player(0).ypos=screen height()/2-12-24 <-- Same here


Draw_map() <-- Updated this to scroll the map tiles now

function player_controls() <-- Here I made quite a few changes.
choice=0

if upkey()=1 then choice =1
if rightkey()=1 and choice=0 then choice=2 <--This part is the same
if downkey()=1 and choice=0 then choice=3
if leftkey()=1 and choice=0 then choice=4

xcord=((player(0).xpos+3)-xscroll)/16+xpos
ycord=((player(0).ypos+5)-yscroll)/24+ypos
xcord2=((player(0).xpos+13)-xscroll)/16+xpos
ycord2=((player(0).ypos+19)-yscroll)/24+ypos


Ok lets talk about the calculations above.
To determine where the player is on the map we need to know quite a few things since
he can be literally on 4 different tiles at the same time. So I am going to break them down.

XCORD
First we want to take the player screen position ---player(0).xpos

Now I add 3 to that, its an arbitrary number it is used to give us a little wiggle room in movement
otherwise our player is going to need to be dead-on with their controls and that can be very frustrating. --(player(0).xpos+3)

Next we subtract xscroll from it. Why? Because not only does our player move in small increments but so does the map
this helps us get an accurate calculation of his position. --(player(0).xpos+3)-xscroll

Now we are going to divide the result by 16, this lets us know how many tiles the player is in from the edge of the map.-- ((player(0).xpos+3)-xscoll)/16

And last we add the maps xposition. we do this because there are a load of tiles to the left that exist but we cant see. --((player(0).xpos+3)-xscoll)/16 +xpos

The other 3 calculations work the same way, but why 2 different x and 2 different y you ask. Simple actually since our player can
be on up to 4 tiles at the same time we need to find what tile each of his corners rests in, sometimes they will all be in the same tile other times they won't. So by using all four calculations we get a much more acurate location for our little dude. Just as --(player(0).xpos+3) was used to give the player a little wiggle room on the left we use +13 for the right +5 for above and +19 for below. So now he can walk up part way onto a tile to give him a little depth and he will not have to be perfectly accurate to walk down a dirt road. I got these values with some playtesting you may find you want the person to half to be more accurate and decrease them or make it a little more loose and increase them just never set them to half of the width or height (or more) as this will allow the player to walk through some tiles.


`Use the players input to move and animate our character.
select choice
case 0: exitfunction:endcase
case 1:
player(0).image=1009+player(0).animate
ycord2=((player(0).ypos+12)-yscroll)/24+ypos <-- here i make some fine tuned adjustments to our collison calculations depending on what direction the player is moving. in this case since a player can not move in more than one direction at a time I am a little more liberal with the wiggle room to make sure the player never becomes stuck. we will do this with all the directions but will recalculate differently for each one.

xcord=((player(0).xpos+5)-xscroll)/16+xpos
xcord2=((player(0).xpos+11)-xscroll)/16+xpos

if maptile(ycord,xcord).passable=0 and maptile(ycord2,xcord2).passable=0 <--This checks to make sure the tiles we designated with our xcord/ycord calculations are passable.
dec player(0).ypos,scrollrate `This changes the characters position on the map<-- if they are we move the player this is the same in all our directions
endif
endcase

case 2:
player(0).image=1018+player(0).animate
xcord=((player(0).xpos+5)-xscroll)/16+xpos
ycord=((player(0).ypos+7)-yscroll)/24+ypos
ycord2=((player(0).ypos+17)-yscroll)/24+ypos
if maptile(ycord,xcord).passable=0 and maptile(ycord2,xcord2).passable=0
inc player(0).xpos,scrollrate
endif
endcase
case 3:
player(0).image=1000+player(0).animate
ycord=((player(0).ypos+12)-yscroll)/24+ypos
xcord=((player(0).xpos+5)-xscroll)/16+xpos
xcord2=((player(0).xpos+11)-xscroll)/16+xpos
if maptile(ycord,xcord).passable=0 and maptile(ycord2,xcord2).passable=0
inc player(0).ypos,scrollrate
endif
endcase
case 4:
player(0).image=1027+player(0).animate
xcord2=((player(0).xpos+8)-xscroll)/16+xpos
ycord=((player(0).ypos+7)-yscroll)/24+ypos
ycord2=((player(0).ypos+17)-yscroll)/24+ypos
if maptile(ycord,xcord).passable=0 and maptile(ycord2,xcord2).passable=0
dec player(0).xpos,scrollrate
endif
endcase
endselect
if player(0).frame<timer()
`We are using the system timer to determine when to progress to the next frame.
inc player(0).animate
player(0).frame=timer()+50
endif
if player(0).animate>9 then player(0).animate=1 ` endfunction

function map_scroll()

if player(0).xpos<16 <-- I changed this a little bit to allow the player to get within one square of the edge of the map
player(0).xpos=16
if xpos>1 then xscroll=xscroll+scrollrate <-- This makes sure we cannot scroll into an illegal number in our array and stops the map at its edge
endif
if player(0).xpos>screen width()-32
player(0).xpos=screen width()-32

if xpos<150-max_x then dec xscroll,scrollrate <--This makes sure we cannot scroll into an illegal number in our array and stops the map at its edge

endif
if player(0).ypos<24
player(0).ypos=24
if ypos>0 then yscroll=yscroll+scrollrate <--Same
endif
if player(0).ypos>screen height()-32
player(0).ypos=screen height()-32
if ypos<100-max_y then dec yscroll,scrollrate <--Same
endif


`Here we are setting up to roll the scroll back to its start position to keep the map
`movement fluid.
if xscroll>16-scrollrate then xscroll=0:xpos=xpos-1 <-- once we scroll past a whole tile we want to update the maps x/y position
if xscroll<-16+scrollrate then xscroll=0:xpos=xpos+1<----
if yscroll>24-scrollrate then yscroll=0:ypos=ypos-1
if yscroll<-24+scrollrate then yscroll=0:ypos=ypos+1
if ypos<0 then ypos=0
<--And this is a final error trap to make sure we do not call an illegal element in our map array
if xpos<1 then xpos=1
endfunction


function draw_map2()
for a = ypos-1 to ypos+max_y <--This now calculates what part of the map to draw to the screen we start with one less than the maps x,y so we can scroll it onto the screen if need be . we then draw to the the maps x,y + 1 more than the maximum number of tiles in each direction so we can scroll from that side also.
for b = xpos-1 to xpos+max_x <-- same here
if a>=0 and b>=0 <--this just makes sure we don't draw no existant tiles since when the map x/y is at 0 it would try to draw from -1 and that would be bad.
if maptile(a,b).image>0 <-- Just incase we somehow forgot to assign an image to the tile an error trap
paste image maptile(a,b).image,(b-xpos)*16+xscroll,(a-ypos)*24+yscroll <--This places our tiles on the screen, note we subtract xpos and ypos from the calculation since they are needed to determine what tile to draw but not where to draw it.
endif
endif
next b:next a


endfunction

And lastly for this edition loading our map, It is very important to make sure that you load your data the same way you entered it. this function was taken right out of the map editor to ensure i did not mess it up. What we are going to do is open our file (which by the way I am just noticing I did not set to ensure the file exists but that is something we will add later), after we open the file we are going to read the files data into our maptile array. I set this file to write bytes to keep it as small as possible.


function load_map(file$)
open to read 1,file$
for a = 0 to 100
for b = 0 to 150
read byte 1,DD
maptile(a,b).image=DD
read byte 1,DD
maptile(a,b).passable=DD
next b
next a
close file 1
endfunction

And the code in a nice easy to read box



attached is the map file I have been using.

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 3rd Jun 2006 01:18
A preview of part 3. executable

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 3rd Jun 2006 19:38
PART 3

Ok now we are going to look at adding some NPC's to the map, and setting a script for their movement. Attached are the additional files you will need for this part of the demo.

Scripting NPC's
There are alot of ways to do this from the simpilest form of hardcoding their activities, to advanced A* pathing and AI calculations. For this demo I am taking a point somewhere in the middle. To create the scripts I just modified the map maker with a very poor path maker (more about this later.

So lets start by adding a new set of animation for our NPC's, for this demo I am going to use the same images for all the NPC's.

disect("link3.bmp",9,4,2001)

Now lets create a way to store our NPC's movement paths.

type route
xpos as integer
ypos as integer
current as integer
direction as integer
endtype
dim path(100,300) as route


This simple type will hold all the data we need for now to get these little guys running around. With the array I have set up we can store up to 100 paths, each with 300 steps in them (more than enough for now.


Now lets update our bot type from the previous demos to be capable of handling the NPC's as well as the player character.

type bot
xpos as integer
ypos as integer
image as integer
facing as integer
name as string
health as integer
maxhealth as integer
animate as integer
frame as integer
path_number as integer
max_path as integer
path_point as integer
endtype

What I have added in is a place to store which path routine the NPC will use with PATH_NUMBER, a way to store the number of steps in the path with MAX_PATH, and a way to store which step of the path the NPC is on with PATH_POINT. These will all be essential to keep good fluid movement.

Let's Initialize our NPC's

dim npc(-1) as bot <- establish an array to hold them.

array insert at bottom npc()
npc(0).image=2001
array insert at bottom npc()
npc(1).image=2001
array insert at bottom npc()
npc(2).image=2001


Above we insert 3 elements into the NPC() array for our guys, and set each of their starting images (if we don't set these they will be a 0 and could cause an illegal image call later.)

Here we will load the scripts for our NPC's, at the moment all the scripts do is handle the movement paths (we can add more to that later.)

load_npc_script("test3.npc",0,0)
load_npc_script("test2.npc",1,1)
load_npc_script("test1.npc",2,2)


The format for this function is (Script name,NPC to assign it to, and path number). I will discuss it more when we get to the function.

In our main loop we need to set up to run these little guys so we will add.

manage_npc()


Now lets talk about loading in the scripts. first lets take a look at the function. This is short and straight to the point, first we load in the script file. the first byte of the file needs to tell the rest of the function the number of steps in the routine.

we will set the NPC's max_path to this amount so that it knows when to start the routine over. Now we will set the Npc's path number to the value we passed in. and last read the x/y position (on the map) and assign them to each step in the routine.

Also in the function I have set the the NPC's .frame so that it can start animating correctly.

function load_npc_script(file$,number,current)
open to read 1,file$
read byte 1,DD
npc(number).max_path=DD
npc(number).frame=timer()+200
npc(number).path_number=current
for a = 0 to npc(number).max_path
read byte 1,DD
path(current,a).xpos=DD
read byte 1,DD
path(current,a).ypos=DD
next a
close file 1

path(current,0).current=0
endfunction


So just to review this file format it is

Moves in path
Map x,y position for each move in the path.

These a written as bytes but you could change this to use WORD or DWORD if you wanted really long paths.

all steps in the path are just like a players 1 direction at a time and 1 space at a time so a progression of 1,1 1,2 2,2 would be good but a progression of 1,1 2,2 would not. I could make this more advanced but this simple method means no need to ever check collision for the NPC's and speeds things up for us.

Now lets look at how we are moving the NPC's around.

This function is almost identicle to our player movement routine but instead of getting the input from the player we get it from the NPC's pathing script.

function manage_NPC()
for a = 0 to array count(npc())
sprite 10+a,(path(npc(a).path_number,npc(a).path_point).xpos-xpos)*16+xscroll+npc(a).xpos,(path(npc(a).path_number,npc(a).path_point).ypos-ypos)*24+yscroll+npc(a).ypos,npc(a).image
choice=5
if npc(a).path_point<npc(a).max_path
if path(npc(a).path_number,npc(a).path_point+1).xpos<path(npc(a).path_number,npc(a).path_point).xpos then choice =4
if path(npc(a).path_number,npc(a).path_point+1).xpos>path(npc(a).path_number,npc(a).path_point).xpos then choice =2
if path(npc(a).path_number,npc(a).path_point+1).ypos<path(npc(a).path_number,npc(a).path_point).ypos then choice =1
if path(npc(a).path_number,npc(a).path_point+1).ypos>path(npc(a).path_number,npc(a).path_point).ypos then choice =3
endif
select choice
case 0: exitfunction:endcase
case 1:
npc(a).image=2009+npc(a).animate
dec npc(a).ypos,scrollrate

endcase
case 2:
npc(a).image=2018+npc(a).animate
inc npc(a).xpos,scrollrate

endcase

case 3:

npc(a).image=2000+npc(a).animate
inc npc(a).ypos,scrollrate

endcase
case 4:

npc(a).image=2027+npc(a).animate
dec npc(a).xpos,scrollrate

endcase
case 5:
npc(0).image=2001
if npc(a).frame>=8 then npc(a).xpos=16
endcase

endselect
if npc(a).ypos>22 then npc(a).ypos=0:inc npc(a).path_point
if npc(a).xpos>14 then npc(a).xpos=0:inc npc(a).path_point
if npc(a).ypos<-22 then npc(a).ypos=0:inc npc(a).path_point
if npc(a).xpos<-14 then npc(a).xpos=0:inc npc(a).path_point

if npc(a).frame<timer()
inc npc(a).animate
npc(a).frame=timer()+50
endif
if npc(a).animate>9 then npc(a).animate=1
if npc(a).path_point>npc(a).max_path then npc(a).path_point=0
next a

endfunction


As you can see the code is very similar we just need to determine what direction the animation should face so we do this by finding out where the next path point lies. Since we designed the path to only move in the four cardinal directions we know that if the next points xpos<the current xpos we have to be going left and the same for the other directions.

Next we determine how far the npc is across the current tile with these calculations
if npc(a).ypos>22 then npc(a).ypos=0:inc npc(a).path_point
since we know the size of the tile we know that once the tile size has been reached the NPC has moved on to the next tile.

and that is all there really is to this part of the demo. just some advice on creating the paths now in case you want to make your own.

If you want a path to be continous make sure the last point in the path is the same as the first.

If you want a path to be broken, say a fireball that shoots from point a to point b then resets then you only need to set the two end and it will "warp" back to its staring point once it reaches the end.

In the future we will be modifying the code to be more universal and handle different size animations. At the moment alot of the numbers are "harcoded" in to make it easier to understand.

These movement routines are going to work for our monsters also as we will just add an element to our UDT called "Hostile"

So here is the full code



You will note that there are 2 functions added into the code that I have not covered they make a simple snow effect for the game, I didn't document it since weather was not something I had initially planned to discuss. You can activate the snow by pressing S and stop it by pressing C. This is something we may explore more later.

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 4th Jun 2006 06:45
Hello Again,

Just wanted to get a little feedback here since I have not written a tutorial before.

Are you finding the explanations clear enough?
Am I moving forward too fast?
Are there any questions as to why I have done things certain ways?
Am I posting too much and just need to shut up?

Any feedback would be good so I have a better idea moving forward of what people expect.

Thank You
wildbill
18
Years of Service
User Offline
Joined: 14th Apr 2006
Location:
Posted: 5th Jun 2006 19:58
I've been following along, mostly to see how you implemented scripting.
Don't let the lack of response stop you. I think a lot of people, including myself read and enjoy, but don't respond. I've made a promise to start participating more in the forums.

Keep up the good work.........
SkyPryer
19
Years of Service
User Offline
Joined: 12th Dec 2004
Location:
Posted: 7th Jun 2006 10:24
I just started up DarkBasic again, (though I didn't do much before).. and I found this. Awesome tutorial so far. I hope you keep up with it.
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 11th Jun 2006 00:21
Sorry I have been away for a bit here, Really into working on the retro competition at the moment. more to follw soon I promise.
Chris Franklin
18
Years of Service
User Offline
Joined: 2nd Aug 2005
Location: UK
Posted: 11th Jun 2006 00:24
I hope so this is just what i need i'll be waiting good luck with the compo

sadsack
20
Years of Service
User Offline
Joined: 27th Nov 2003
Location: here
Posted: 21st Jun 2006 01:09
hello Hobgoblin Lord ,
How is the rest of the tut. coming along? I am very happy with it so far.
renny
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 21st Jun 2006 01:13
Kind of killing two birds with one stone at the moment. In the retro remake competition I had to change the game I am remaking, so I picked one that will work well with this exact engine. The rest of the Tutorial will begin rolling out soon. In the meantime if you want an extra tileset to make maps with here is one I just posted in the artists challenge.

Para _Charlie
17
Years of Service
User Offline
Joined: 31st May 2006
Location: Wisconsin
Posted: 21st Jun 2006 04:04
Hobgoblin Lord,
I'm following along very contently trying to soak it all in. I'm extremely interested in your structure of the code. I'm finding it is a great learning lesson for myself being so new, but I find your explanations extremely helpful. Alot of things that I did not understand before are starting to become more clear now.
Thanks for taking the time to do this...

DBP 6.1
3.2Gig Proc, 2Gigs Ram, SATA HD, Gigabyte MB, 6800GT Vid
sadsack
20
Years of Service
User Offline
Joined: 27th Nov 2003
Location: here
Posted: 22nd Jun 2006 02:57
yes, thanks you very much.
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 22nd Jun 2006 07:12 Edited at: 22nd Jun 2006 08:39
Ok All as promised here is the next installment of the tutorial.
This one is kinda long so I suggest you download the project file first and execute it play around a little then come back to read this it may help make things a little bit clearer.

Step 4 : Interacting with NPC's
What I aim to show you with this step is progressively speaking to the NPC's in the game. By that I mean NPC B may not have much to say to you until you first speak to NPC A and so on, so we are basically creating a story tree. First lets look at the text file we will use to script the npc's



Now that you have given it a glance let me explain my thinking here and why I have done what I have done.

I have created a tagging system using < and > this is how we will let our parser know when to execute certain commands.

lets look at the first NPC Horgan

<start_npc> <--this tells our parser we are describing a new NPC
Name:Horgan <--Here is his name
image:700 <--What image is assigned to him
animated:-1 <-- is he animated (-1 means no)xpos:12 <-- what is his x position in tiles
ypos:10 <-- his y position in tiles
path:-1 <-- if he has a path assigned to him (-1= none)
speak:1 <-- can he speak<start_npc_text> <-- since he can speak we now tell the parser what he says and when he says it.
text_number:1 <-- This assigns his text to an array

Declaring <new> advises us of a new entry
<new>Welcome to the town of Fangrul my young friend, my name is Horgan. <pause> You look like an adventurer,
you should seek out Melanie, she can get you started. <pause> <advance_horgan> <advance_melanie>
<new>Seek out Melanie. <pause>

lets talk about the other tags in this string.

<pause> tells the parser that even though this is all one speech this is a point to pause and make a text balloon out of the text prior to the pause.

<Advance_xxx> Each "new" text for an npc is assigned a number from 1 to whatever we set the upper limit at. advancing tells the parser to set the named npc to the next point in their dialog tree, so for example the <advance_horgan> tag tells it that Horgan has moved on to the next branch in his tree. so in the future if you talk to him again all he will say is "seek out melanie"

<end_npc_text> <--this tells the parser that we have finished assigning text to this npc.
<end_npc> <-- and this tells it that we have finished with this npc.

You will notice a few other tags under Nobeard. <Update_quest> and <add_page>, though I have not actually used them in this demo we will use them to advance the quest tree and a journal tree later in the game.

So why scripting you ask. Though we could hard code all this we of course want the engine to be as modular as possible so that in the future to make a new game all we will need to do is change the game data files and not the code itself.

Why put tags at the front of the data like Name when you could just read them in in order and not have to break up the string later?

There are a few reasons you want to do this the first is when you are looking at/editing your data it is alot easier to know what you are changing if there is a tag in front of it and it makes it alot easier to find something that is causing a problem in the game.
The second reason is that by doing this you do not have to enter all the data for each NPC if that data is irrelevant. Why code in blank text dialog for a character that cant speak or add a name to one that does not need it.

OK lets talk about a few changes since the last lesson. I have changed the movement keys to the typical WASD and added using the mouse for talking to the NPC's left click starts a dialog, right click advances it.

Also you will notice that I have moved alot of the functions into seperate files in project if you downloaded it. previously we did not have alot of code so one file was fine, but as the code increases we want to seperate it into relevent files both for reuse in other programs and to make it easier to read.

Now on to the code.


Main file:

disect("npc2.bmp",3,1,700) <-- added this for the images of the NPC's we will be talking to.

text_group as integer
text_point as integer
<--These two variables have been added to our bot UDT, the first will assign a specific text tree to our NPC the second tells us what branch on the tree we are on.

type text_for_game
saying as string
endtype
<-- I have added this UDT for our text, it only has one variable now but we may add more to it later.

dim game_text(100,20) as text_for_game<--here is where we will store the game text I have dimensioned it out for 100 npc's to have 20 branches on their text trees each, more than enough for our tutorial but you will probably want to increase this later for a full game.

array insert at bottom npc()
npc(3).image=2001
array insert at bottom npc()
npc(4).image=2001
load_npc_script("new1.npc",3,3)
load_npc_script("new2.npc",4,4)
<-- added two more random walking NPC's to the map these are still hardcoded but we wil eventually move them into the main npc scriptfile.

initialize_npcs("npcscripts.txt")<--this function calls up the NPC file above and sets all our guys up, the paramater for input is the filename.

npcol=check_npc_collision() <-- this function sees if the player is colliding with an NPC sprite and returns its value.

if npcol>0 then run_text(npcol):redraw=1<--if a collision happened this check to see if the npc can speak and if so begins a dialog. Redraw variable is so that we do not accidentally syn twice in a row without an update and get that nasty blue flash


NPCscripts function (new file)

function initialize_npcs(file$)
open to read 1,file$
repeat
read string 1,x$
if x$="<start_npc>" then inc count

until x$="<end_all>" or file end(1)=1

close file 1 <-- this part of the function counts the number of NPC's in the file.

open to read 1,file$
repeat

read string 1,x$
if x$="<start_npc>"
repeat
read string 1,x$

if lower$(left$(x$,5))="name:"
npcnum=construct_npc()

npc(npcnum).name=right$(x$,len(x$)-5)
endif

if lower$(left$(x$,6))="image:"
npc(npcnum).image=val(right$(x$,len(x$)-6))
endif

if lower$(left$(x$,9))="animated:"
npc(npcnum).animate=val(right$(x$,len(x$)-9))
endif

if lower$(left$(x$,5))="xpos:"
npc(npcnum).xpos=val(right$(x$,len(x$)-5))
endif

if lower$(left$(x$,5))="ypos:"
npc(npcnum).ypos=val(right$(x$,len(x$)-5))
endif

if lower$(left$(x$,5))="path:"
npc(npcnum).path_number=val(right$(x$,len(x$)-5))
endif

if lower$(left$(x$,6))="speak:"
rr=val(right$(x$,len(x$)-6))
if rr>0 then npc(npcnum).text_point=1
endif


if lower$(x$)="<start_npc_text>"
repeat
read string 1,x$
if lower$(left$(x$,12))="text_number:"
npc(npcnum).text_group=val(right$(x$,len(x$)-12))
endif

if lower$(left$(x$,5))="<new>"
inc linecount
game_text(npc(npcnum).text_group,linecount).saying=right$(x$,len(x$)-5)
endif


until x$="<end_npc_text>"
linecount=0

endif

until x$="<end_npc>"
endif

until x$="<end_all>"

endfunction


Well this is doing alot, I am not going to go into to much detail on how we are breaking the strings down other than to say we are using mid$,right$,left$ to find the tags in the code and then assign the data to the appropriate variable in our NPC. I think it is pretty easy to see what each tag is doing and you can of course add whole new tags to your npc file (like health, time; like perhaps this npc is only drawn at night or in the day) there is no limit to what you can add to these files and it is here you want to do it then create an appropriate function if neccessary to handle it.




function construct_npc()
array insert at bottom npc()
count=array count(npc())
npc(count).image=700
npc(count).name="Citizen"
npc(count).animate=1
endfunction count


This is a base constructor for our NPC's it will set some standards whenever we call it. first it adds a new element to our npc array sets a base image so we never forget to do that, a base name and number of animation frames. You can add alot to this constuctor to save data later, say you want every npc to have 10 health unless otherwise stated you add in npc(count).health=10 and now unless you specify it in your data every person you create has 10 health.

Moving on

NPC Controls (new file)

Changes to the Manage_NPC() function
if npc(a).animate>=0 <-- added this so that if the npc is not animated it will skip the entire path update animate character routine. as you may remember from above a non animated character has an .animate value of -1

else
sprite 10+a,(npc(a).xpos-xpos)*16+xscroll,(npc(a).ypos-ypos)*24+yscroll,npc(a).image
endif
<- so now at the end of our manage_npc function we have this to save processing time and from having to develop a path of no steps for the NPC.

function check_npc_collision()
if sprite collision(1,0)>0
for a = 0 to array count(npc())
if sprite collision(1,10+a)=1
contact=a
endif
next a
endif
endfunction contact


Ok this function is pretty straight forward it first check to see if the player is colliding with anything if not it does not run through the NPC's to see if it is one of them. If there is a collision with an npc sprite it returns that value.

There are a few methods of doing this in a future lesson we will be adding a variable to our NPC's that give them a sprite number so we do not count through from 10+, it will make it easier to add and delete sprites for the npcs.

Here is the biggie added to this section. the following function is what parses the NPC text into text balloons and commands.

function run_text(npcol)

ink rgb(255,255,255),0
set text size 10
set text font "arial"
if npc(npcol).text_point>0 and mouseclick()=1
<--here we see if the NPC can even talk and if they can has the player pressed the left mouse button.
local dim sentence$(-1) <-setting up an array to hold lines of text so we can pause between statements made by the NPCs'



xx$= game_text(npc(npcol).text_group,npc (npcol).text_point).saying
<-- assigned this to xx$ so we dont have to type in game_text(npc(npcol).text_group,npc (npcol).text_point).saying
every time
for x = 1 to len(xx$)

r$=mid$(xx$,x)
if r$="<"
<--looking to find any instances of the <, once we do that we look till we find a > and figure out the word, or words inbetween. SEE BELOW

testline$=""
testline$=right$(xx$,len(xx$)-x)
if lower$(left$(testline$,6))="pause>"
inc x,6
array insert at bottom sentence$()
sentence$(array count(sentence$()))=word$
word$=""
endif
<-- so if we find a <pause> we add to our sentence array and later we can cycle though the sentences one at a time


if lower$(left$(testline$,8))="advance_"
z=x+9
npname$=""
repeat
rr$=mid$(xx$,z)
npname$=npname$+rr$
inc z
until rr$=">"
npname$=left$(npname$,len(npname$)-1)
for find=0 to array count(npc())
if lower$(npc(find).name)=lower$(npname$)
inc npc(find).text_point
endif
next find
x=z
endif
<-- this section looks for anything that has <advance_ in it, right now we are just next checking names against the npcs to see if we need to update someones dialog tree but later we will add updating the quest tree here as well.


else
word$=word$+r$

endif
next x
`array insert at bottom sentence$()
`sentence$(array count(sentence$()))=word$



for a = 0 to array count(sentence$()) <--now we count through the sentences we have made.

balloon(sentence$(a),100,300) <-- make a balloon out of each one
sprite 6000,-200,-200,300 <-set this sprite to the ballons image
set sprite priority 6000,50 <-- make sure it appears over the NPC's and player
sprite 6000,sprite x(npcol+10)-(sprite width(6000)/2)+14,sprite y(npcol+10)-sprite height(6000)-3,300 <-- and then center it correctly


repeat

draw_map()
set cursor 0,0

print array count(sentence$())<-- this is a debug line oops
sync

until mouseclick()=2
repeat:until mouseclick()=0
next a
<-- so now we mait for the right mouse button to be pushed and once it is we wait till it is released so the ballons do not fly by on the player to fast to read. then if there is another balloon to display we do so.

empty array sentence$() <- I always try to do this with lists because once in a while even when we undim it after the data sticks around

undim sentence$()
text 0,0,"in loop"<--another debug line
else
sync
endif

if sprite exist(6000) then delete sprite 6000
<-- get rid of the balloon and I put an error trap in there just in case for some reason a balloon was not created.
endfunction



In the player controls function you will find the arrow keys have been changed to the following

if scancode()=17 then choice =1
if scancode()=32 and choice=0 then choice=2
if scancode()=31 and choice=0 then choice=3
if scancode()=30 and choice=0 then choice=4


just the way I decided to set it up feel free to go back to the arrow keys, or any other keys you want.

and in a new file named Graphics you will find the function that makes our Balloons (this was actually the first post I ever made on the forums, I have updated it so it works a little better now.


Similar to the script parser this function takes in the text to be displayed, the maximum width you want the balloon to be, and the image number to assign it to. It then checks to see if the text is longer than the max width and if it is it breaks it down into chunks that will fill the space correctly by adding new lines and breaking at spaces.


function balloon(new$,maxwidth,imnumber)

local dim word$(-1)
set text font "arial"
set text size 12
if text width(new$)>maxwidth


for a = 1 to len(new$)
if mid$(new$,a)=" "
if text width(sentence$+" "+current$)<maxwidth
sentence$=sentence$+" "+current$
current$=""
else
array insert at bottom word$()
word$(array count(word$()))=sentence$
sentence$=current$
current$=""
endif

else
current$=current$+mid$(new$,a)
endif
next a
array insert at bottom word$()
word$(array count(word$()))=sentence$
else
array insert at bottom word$()
word$(array count(word$()))=new$
endif
bsize=((array count(word$())+1)*text height(new$))+6

ink rgb(255,255,255),0
x=0
for a=0 to array count(word$())
if x<text width(word$(a)) then x=text width(word$(a))
next a
x=x+4
create bitmap 1,x+50,bsize+100
box 0,0,x+1,bsize
dot (x/2),bsize+1:dot (x/2)+1,bsize+1:dot (x/2)+2,bsize+1
dot (x/2)-1,bsize+2:dot (x/2),bsize+2
dot (x/2)-2,bsize+3:dot (x/2)-1,bsize+3
dot (x/2)-3,bsize+4
ink 0,0
for a = 0 to array count(word$())

center text (x/2),a*text height(new$),word$(a)
next a

dot 0,0:dot 0,bsize:dot x-1,0:dot x-1,bsize
dot 0,1:dot 0,bsize-1:dot x-1,1:dot x-1,bsize-1
dot 1,0:dot 1,bsize:dot x-2,0:dot x-2,bsize
get image imnumber,0,0,x,bsize+5,1
delete bitmap 1
undim word$()
endfunction


our box and dot commans are what make the balloon shape. If you notice the get image command when we capture the balloon has a ,1 at the end of it. This is essential to make sure the text does not get all fuzzy on us and keeps the image much clearer.

So that is all of the new material in this lesson. Your mission is to play with the code and once you think you have it grasped well enough write a few of your own npc scripts and see if you can get 10 or so working out a large dialog tree together (it is a skill you will need further into the tutorial if you want to make your own game.)


A note on the download, there are likely some files in there that are not needed yet, or at all. I think that the path editor I made is in there as well.

I would appreciate any feedback on this section, such as your thoughts, questions, other things you would like to see in the NPC scripts, rants, whatever.

Till Next Time
Excelsior

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 22nd Jun 2006 20:06
Just thought I would put up a few more npc sprites for you. consists of 2 guards and a shopkeep type.

Attachments

Login to view attachments
wildbill
18
Years of Service
User Offline
Joined: 14th Apr 2006
Location:
Posted: 22nd Jun 2006 20:49
I'm getting a lot out of your scripting system. Keep going.
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 22nd Jun 2006 23:04
will do wild bill, will start work on the next part tonight probably.
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 23rd Jun 2006 02:55
Better Scrolling

In the Map scroll function change the first block of code to the code below. Now it will scroll sooner and once the edge of the map is reached it will allow movement to the very edge. I was finding it difficult to avaoid obstacle that were just out of scroll sight since they were appearing almost exactly as you hit them. If there are any questions about where to put this code let me know.




Your Work:

Anyone have a demo of something they may have made with the tutorial so far, I would love to see it if you want to post it here.
WebCobra
17
Years of Service
User Offline
Joined: 19th Jun 2006
Location: CC, USA
Posted: 23rd Jun 2006 07:01
So can this RPG game be 3D?

http://icubesoftware.info/
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 24th Jun 2006 10:25 Edited at: 24th Jun 2006 11:21
Yes, the same basic principles apply but you would need to modify the code to use 3d objects over 2d images.

Now On to the next part:

There is an attachment with all the current files and media, you may want to run that first before going on.

Objects

SImilar to the NPC's we are going to script all of our objects. This will take place with 2 files, one that defines them and one that creates them. Why you ask, well we may want to place 200 of the same item in the game and the last thing we want to do is have to define all the stats for those 200 items, better to instead define it once and then create 200 copies of it.

Lets take a look at the Object data (this is where we will define our objects).



so what do these mean...

<new_object> <-- this tells our parser we are about to describe a new item
name:Bush <-- this gives the item a name
takeable:0 <-- this lets us know if the item can be added to our inventory 0 means no and 1 (or more) means yes
image:809 <-- this gives the object its starting image.
used_image:808 <-- this tells the parser if the image changes after it has been used, in this case the base image is a bush with berries on it and the used image is a bare bush for after those berries are picked.
give:berries <-- this tells the parser what to give to the player if the item is used. This will not be defined for all items because some may not give something else, but in this case using the bush gives the player a pile of berries.
<end_object> <--This tells the parser we are done defining this object

A few other commands you may notice on the other items.

zpos:0 <-- this defines if the player can walk "behind" the item most items, like the bush, you can, but they key we are defining with this is tiny and should rest on the ground, our player should not walk behind it. so a zpos of 0 means the player is always drawn "over" the item.

Locked:1 <-- This tells the parser that this object blocks the players progress in some way, in this case a door. but it could also be applied to a chest, cabinet or anything else you want locked.

open:gold key <-- This defines the name of the object required to unlock this object, so in the case of our wooden door it is unlocked by a gold key.

as we get farther into the tutorial we will add some more fields such as a description for the inventory screen, value, attack damage etc. this is the part of the script where you will most likely create alot of your own commands to fit your game.

Now lets look at the Object Key



This is alot more straight forward. Here we create the instances of our objects, as you can see I have made two doors, bushes, and keys and by doing it this way we only needed to define them once. Each entry has a Start, the name of an object we created in our object data file, its map xposition and its yposition followed by the <end> command. This is the sort of thing you will eventually want to build an editor for to speed up placing you objects on the map.


Our new UDT's



The first one is to hold our objects it should be very easy to understand as it is just holding the data we just discussed.

the possessions type will be used for our characters inventory. it holds the items name, an inventory image, and the items value (which we are not using yet) we wont need to much more than that in this type because we will use it to reference the base objects as defined in our object data file.

New Global Variables:

global old_x as integer
global old_y as integer


these two variables will be used to hold our players position before an attempted move so we can bounce him off locked objects.

New Arrays

dim objects(-1) as thing <-- this stores the data for our base objects.
dim game_objects(-1) as thing <-- this stores the data for our instanced objects.
dim inventory(-1) as possessions <-- and this defines what is in our players inventory.


Initialization:

initialize_objects("objectdata.txt") <-- this function loads our data into our base objects
make_objects("objectkey.txt") <-- and this one creates our instances.

Additions to the main loop.

draw_objects()<-- this draws our objects to the screen.
sprite_depth()<-- this sets the imagined z position of the objects based on their screen y position to give the illusion of depth.

if array count(inventory())>-1
for a = 0 to array count(inventory())
text 0,a*text height("test"),inventory(a).name
next a
endif


Right now we have not yet addedd in an interface for our players inventory so this is printing the items our player is carrying to the left side of the screen. Later we will change this to a full inventory system.

obcol=object_collision()
if obcol>-1 and mouseclick()=1 then work_objects(obcol)


This is almost identicle to the checks we make for our NPC's we first check to see if the player is colliding with an object, and next we check to see if the player is attempting to use an object he is colliding with.

Additions to the Graphics sub file



The first of these two functions, Free_sprite(), is checking for the next unused sprite starting from whatever parameter we give it. This will be essential to bjects as we will be creating and deleting them constantly.

The second function is cycling through the player sprite and all object and NPC sprites and assigning them priority according to their y position. This will make it so items lower on the screen are drawn over items higher on the screen to add an imagined z dimension to our game.



Our new Object Functions:




lets discuss these one by one. most of them are very similar to our NPC functions, especially our parsers.

Initialize_objects()

Just like our initialize NPC function this is taking our data dile and breaking the text down into its parts and assigning them to the variables in our Objects() array.

Make_objects()
Another Parser, but instead of reading in all the objects data it reads only the name and where to position it, it then cycles through all the base objects we made with our Initialize_objects() function and copies all the objects pertinant data from there.

Construct_base_object() and Construct_object()
JUst like our NPC's I made a base constructor, this will add an item to the appropriate array and assign it some base values. and by assigning these it will help with debugging our data since an object not properly defined will still be created but should be obviously wrong.

draw_objects()
If any objects exist this function makes sure to draw them all to the screen.

object_collision()
A little more to this function. it starts by checking to see if the player is colliding with any of the game_objects. I set count to -1 since it is outside the range of our array and will be used as a check later. If the player is colliding with an object it sets our count to the current object.

count=-1
for a = 0 to array count(game_objects())
if sprite collision(1,game_objects(a).sprite_number)=1
count=a


Now if a collision did happen we first want to see if the object is a "locked" object. If it is we check idf there are any items in the players inventory. and if there are we cycle through them to see if any of them are an item that can "unlock" the object we are colliding with. If it is we set the object to unlocked (locked=0), and set the objects image from starting (image) to used (Used_image). we then delete the object used to unlock the object we are colliding with, and exit the loop (if we don't it will delete every copy of that item in inventory). You may however want to add some code where an item can "unlock" many things and will not be deleted.

if game_objects(a).locked>0
if array count(inventory())>-1
for incount=0 to array count(inventory())
if lower$(inventory(incount).name)=lower$(game_objects(a).open)
game_objects(a).locked=0
game_objects(a).image=game_objects(a).used_image
array delete element inventory(),incount
exit
endif
next incount
endif


After all this we check again to see if the item is locked (if it is the player obviously did not have an item to unlock it). if it is we reset the players x-y position to his position before the collision thus keeping him from passing.

if game_objects(a).locked>0 then player(0).xpos=old_x:player(0).ypos=old_y

finally we return the number of any objects the player is colliding with.

Next we have the function Work_objects(). It is called only idf the player is colliding with an object and is pressing the left mouse button.

if game_objects(obcol).takeable=1
add_to_inventory(obcol)
delete sprite game_objects(obcol).sprite_number
array delete element game_objects(),obcol

exitfunction
endif


what this part of the code does is see if the item can be taken by the player, if it can it adds it to inventory, deletes the objects sprite to free it up and then deletes the object instance from the game. then following that it exits the function.

if game_objects(obcol).image<>game_objects(obcol).used_image and game_objects(obcol).used=0 and game_objects(obcol).locked=0
game_objects(obcol).used=1:game_objects(obcol).image=game_objects(obcol).used_image
if game_objects(obcol).give<>""
if array count(inventory())<20
add_to_inventory_by_name(game_objects(obcol).give)
endif
endif

exitfunction
endif


Now if we get to this part of the function then it is obvious that the object cannot be taken, so now we will check if the object can be used, and if it can does it give something to the player. if the objects base image and used image are not the same then it has not already been used, we make sure the item is not locked then update the image to the used image and give anything to the player that they are supposed to get.

The next function Add_to_inventory() is used when a player picks up an object that is takeable. we passed the game_objects number into the function. it will copy the appropriate info into the inventory array after making sure the palyer can fit it into their inventory.

function add_to_inventory(obnum)
if array count(inventory())<20
array insert at bottom inventory()
inum=array count(inventory())
inventory(inum).name=game_objects(obnum).name
inventory(inum).image=game_objects(obnum).inventory_image
else
exitfunction
endif
endfunction


and our last function.

function add_to_inventory_by_name(name$)
for a = 0 to array count(objects())
if lower$(objects(a).name)=lower$(name$)
array insert at bottom inventory()
inum=array count(inventory())
inventory(inum).name=objects(a).name
inventory(inum).image=objects(a).inventory_image
endif
next a
endfunction


Sometimes the player is given an object that has not been instanced yet, as in the berries from the bush, or a quest item from an NPC. so in this case rather than have it copy the information from a GAme_object it instead looks up the objects name and clones the information from a Base_object. This will be useful for alot of things, like random treasure from a chest, quest rewards, items bought from a shop etc.

And that is it for this part of the tutorial. You now have a near complete engine (though not refined) and could make alot of games with the info you have already. Now it is time to really start to put all of this together and start creating the data, maps, etc you want for your game. Next up Monsters.



I have noticed very little activity in this thread and really I would like to hear your thoughts (if anyone is reading this) your questions, your gripes, just something Especially like to hear opinions from those who have been around here a long time or those who have writtten tutorials before for some advice on how to perhaps make this better.

Attachments

Login to view attachments
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 24th Jun 2006 11:22
editted out sorry
wildbill
18
Years of Service
User Offline
Joined: 14th Apr 2006
Location:
Posted: 27th Jun 2006 21:40
This may seem funny/strange, but one of the difficulties I've had with DB is dealing with objects. I love the syntax of the basic language, I just wish it was more OOPish. . It would be so nice for a object to be able to take care of itself. Your really helping me organize my objects better.
sadsack
20
Years of Service
User Offline
Joined: 27th Nov 2003
Location: here
Posted: 29th Jun 2006 03:53
Hey this is great, I made a game in gamemaker 6 call "The Monk Story" I am doing it in DBP now and with your help It is coming along fine.
Thanks
renny
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 29th Jun 2006 05:46
Quote: "This may seem funny/strange, but one of the difficulties I've had with DB is dealing with objects."


I like OOP as you may be able to tell. I have tried to structure my stuff to somewhat simulate it. remember old basic languages when I had to set up all kinds of arrays and harcode alot of stuff, thank goodness for progress. Glad I am helping.

Sadsack, I'd love to see the "The Monk Story".

http://www.cafepress.com/blackarrowgames
Check out my great stuff here
sadsack
20
Years of Service
User Offline
Joined: 27th Nov 2003
Location: here
Posted: 3rd Jul 2006 03:03
What program do I use to open the npc files?
renny
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 3rd Jul 2006 03:05
Quote: "What program do I use to open the npc files?"


Notepad

http://www.cafepress.com/blackarrowgames
Check out my great stuff here
ZDavis26
17
Years of Service
User Offline
Joined: 22nd May 2006
Location:
Posted: 9th Aug 2006 21:58
this seems great, but i am having trouble putting the parts of the tutorial together i am stuck on where to put certain parts of the code. maybe you could upload the entire project for me to take a look at

thanks,
Redviper 15
17
Years of Service
User Offline
Joined: 13th Aug 2006
Location: UK
Posted: 20th Aug 2006 17:50
Hi, first of all great tutorial, i had no idea whatsoever how to start a project like this and your tutorial has certainly helped. However like ZDavis i'm having some trouble trying to place the code. Would it be possible to post the entire source file?

Thanks,

Red

Login to post a reply

Server time is: 2024-04-27 19:55:30
Your offset time is: 2024-04-27 19:55:30