写在前面的话:
鉴于大家对rpg制作的热情,我还是决定把它写下去。其实rpg真的是很考验技术的,因为它从剧本到美工,从程序到算法,都要研究一番。也就是说,如果能够成功地做出rpg,你就是一个全才了。而且rpg需要用到程序部分包含了许多其他类型游戏的成分,所以如果能够成功作出rpg,那么你几乎可以做任何其他类型的游戏了。不过现在随着各种rpg制作软件的加入,rpg的制作已经变得异常简单。我要说的是,使用rpg制作大师之类的软件,始终要在别人设计的条条框框中制作,这样制作出来的东西很难有自己的特色,也很不自由,许多创意是发挥不出来的,这也是rpg制作大师没有完全推广的重要原因。不过rpg也存在着缺陷,因为现在rpg繁多,导致rpg的可玩性下降,尤其是业余开发者,很难做出可玩姓高的作品来,所以可能也不会有多少人会尝试的你的游戏,而且,制作rpg又是一项浩大的工程(融合了各种类型的游戏),一个月是下不来的。所以希望大家在制作之前考虑清楚,一旦下决心做了,那就不要有后顾之忧,无论好坏,一定要做完它!阅读本教程时,请参看《梦的彼端》源代码。
第一节 rpg制作准备工作
VB+DX8从零开始轻松做游戏(第四章 2DRPG初级制作)现在我们开始做一个rpg了,需要做什么准备工作呢?
1、我们要下载一些优秀的rpg,我们来看看人家都有什么好的创意,好的效果,借鉴一下。当然最好在此之上超越他们,建议找些业余的rpg做参考就行,因为大公司做出来的东西想要超越它很困难,不要给自己这么大的压力,做游戏嘛就是为了享受。
2、写一个好剧本吧,rpg的剧本可以占到整个游戏分量的40%。想想看,当年的仙剑为何取得巨大的成功,除了因为是第一个国产rpg以外,正是因为当时人们还很少见这样的游戏情节,所以被深深地吸引。所以剧本的创意,对rpg来说可以起到灵魂的作用。
3、确认一下自己的美工实力。美工对任何游戏来说都可以占到30%以上的分量,它至少可以给人以很高的第一印象,至少不至于让人看一眼,就没有兴趣玩。不过其实也不需要太多的实力,网上的资源很多嘛,只要你稍微会一点Photoshop的抠图技巧和一点绘图技巧就可以了。网上对这方面有很多教程,学一下就可以了。对了,我好像也写了篇2drpg的CG制作教程,你们不妨参考一下。当然你也可以和你的美工师或伙伴一起做,这个就不多说了。
4、准备几个本子,记录开发进程和准备开发的内容。我当时开发《梦的彼端》时,用了三个本子:程序,剧本,美工。后来制作的时候还有一个本子是用来测试的。主要是在空余时间要多思考,把想出来的程序内容或创意写在本子上,把人物的造型定位,性格定位,地图设计也都先在本子上设计好,这样做程序就会方便很多了。测试的时候,哪里有bug,也记录好,空余的时间,再去想解决方案。多多利用你不能编程的时间是很有用处的。
5、想好rpg中的必要元素的设计,比如战斗,主角,敌人,Boss,宝箱,对话等等。这个多玩rpg自己就会总结了,我就不多说了。
先说这么多。
第二节 开始制作rpg
程序问题其实也就涉及到几个问题,我先列出来:
主循环
滚屏显示
人物走动
储存文件
读取文件
Npc走动
碰撞检测
对话框
触发
战斗引擎
战斗动画
升级系统
游戏菜单
买卖系统
锁定桢数
暂时想到这么多,如果你想到别的,请告诉我。
第三节 主循环
如果你还不会如何启动dx8,请参看前面的教程。
游戏需要动态的更新,而实现这种更新需要一个循环体,我们通常叫它主循环,放在Form_Load的最后。
rpg循环的结构:(以《梦的彼端》为例,简化了代码)
Private Sub MainLoop()
Do While Running <> 0'当游戏正常运行的时候,退出游戏只需让Running=0即可
Do While Running <> 0'当游戏正常运行的时候,退出游戏只需让Running=0即可
Select case Running
case 1
地图模式
case 2
战斗模式
case 3
游戏失败的画面
case 4
游戏通关的画面
case 5
显示制作信息
case 99
显示版权信息
case 98
显示标题画面
case 97
背景介绍页
End Select
DoEvents
Loop
End Select
DoEvents
Loop
退出游戏'当running=0时才会执行这个程序,把贴图从内存中清除
End Sub
End Sub
也就是说游戏的状态完全由running一个变量轻松控制,可随意转换。完成了上述工作,你只需要把各种情况下的代码加进去就可以了。
主循环就是这样,把程序主体方便的列出来,易于处理。
第四节 滚屏显示
很多游戏都用到了这种显示方式,原因是屏幕太小,地图太大,一个屏幕显示不出来。
现在很多游戏已经不再用小图块拼成地图的技术,取而代之的是一张完整的图片,这样视觉效果更好一些,代码编写也更容易一些。
所以我还是很推荐这种使用完整图片的做法,不过这里还是说明一下小图块的技术。
完整图片只需要改变贴图坐标即可实现滚屏效果,即按动方向键时改变贴图坐标即可,十分简单。
下面讲一下小图块技术,如果不感兴趣,大可以不用此方法。
Public Sub DrawTiles()'这个是贴地图的程序,建议好好研究一下
Dim i As Integer'行临时变量
Dim j As Integer'列临时变量
Dim intX As Integer'临时贴图坐标
Dim intY As Integer'临时贴图坐标
Dim Mapx As Integer'计算当前小块绝对坐标
Dim Mapy As Integer'计算当前小块绝对坐标
'画地面
For i = 0 To 20'这里用的是640*480的分辨率,而每个小块是32*32分辨率,所以每行有20个,每列有15个
For j = 0 To 15
intX = i * TILE - CenterX Mod TILE'这个是 贴图坐标=屏幕的绝对坐标-偏移坐标。所谓偏移坐标就是根据人物位置而算出来的CenterX,CenterY也就是屏幕偏移,这样就可以实现滚屏的效果。
intY = j * TILE - CenterY Mod TILE'同上
Mapx = (intX + TILE \ 2 + CenterX - SCREENWIDTH \ 2) \ TILE'计算出来实际的小图块,小图块是由2维数组定义的
Mapy = (intY + TILE \ 2 + CenterY - SCREENHEIGHT \ 2) \ TILE
'设置贴图区域
With RectNpc
.Left = Map1(Mapx, Mapy).X * TILE'在map1().X,map1().Y储存了该图块贴图源的坐标,也就是所贴的图块在原来的tile整图中的位置。
.Right = .Left + TILE
.Top = Map1(Mapx, Mapy).Y * TILE
.bottom = .Top + TILE
End With
'贴图
Tran.X = intX
Tran.Y = intY
Sprite.Draw TexTile, RectNpc, NoScaling, Center, NoRotation, Tran, -1
Next j
Next i
End Sub
Dim i As Integer'行临时变量
Dim j As Integer'列临时变量
Dim intX As Integer'临时贴图坐标
Dim intY As Integer'临时贴图坐标
Dim Mapx As Integer'计算当前小块绝对坐标
Dim Mapy As Integer'计算当前小块绝对坐标
'画地面
For i = 0 To 20'这里用的是640*480的分辨率,而每个小块是32*32分辨率,所以每行有20个,每列有15个
For j = 0 To 15
intX = i * TILE - CenterX Mod TILE'这个是 贴图坐标=屏幕的绝对坐标-偏移坐标。所谓偏移坐标就是根据人物位置而算出来的CenterX,CenterY也就是屏幕偏移,这样就可以实现滚屏的效果。
intY = j * TILE - CenterY Mod TILE'同上
Mapx = (intX + TILE \ 2 + CenterX - SCREENWIDTH \ 2) \ TILE'计算出来实际的小图块,小图块是由2维数组定义的
Mapy = (intY + TILE \ 2 + CenterY - SCREENHEIGHT \ 2) \ TILE
'设置贴图区域
With RectNpc
.Left = Map1(Mapx, Mapy).X * TILE'在map1().X,map1().Y储存了该图块贴图源的坐标,也就是所贴的图块在原来的tile整图中的位置。
.Right = .Left + TILE
.Top = Map1(Mapx, Mapy).Y * TILE
.bottom = .Top + TILE
End With
'贴图
Tran.X = intX
Tran.Y = intY
Sprite.Draw TexTile, RectNpc, NoScaling, Center, NoRotation, Tran, -1
Next j
Next i
End Sub
结合游戏源代码,可以方便的理解。
至于如何在程序中使用请参考2d动作游戏引擎中的DrawTiles。
第五节 人物走动
首先获取键盘信息,其实在程序中直接用DIState.Key也可以,我这样设计是为了以后实现一些其他功能用的。
Public Sub Checkkey()
''''''''''''''''''''''''''''''''获取键盘信息
DIDEV.GetDeviceStateKeyboard DIState
If DIState.Key(DIK_RIGHT) Then
KeyRight = True
Else
KeyRight = False
End If
If DIState.Key(DIK_LEFT) Then
KeyLeft = True
Else
KeyLeft = False
End If
If DIState.Key(DIK_UP) Then
KeyUp = True
Else
KeyUp = False
End If
If DIState.Key(DIK_DOWN) Then
KeyDown = True
Else
KeyDown = False
End If
End Sub
''''''''''''''''''''''''''''''''获取键盘信息
DIDEV.GetDeviceStateKeyboard DIState
If DIState.Key(DIK_RIGHT) Then
KeyRight = True
Else
KeyRight = False
End If
If DIState.Key(DIK_LEFT) Then
KeyLeft = True
Else
KeyLeft = False
End If
If DIState.Key(DIK_UP) Then
KeyUp = True
Else
KeyUp = False
End If
If DIState.Key(DIK_DOWN) Then
KeyDown = True
Else
KeyDown = False
End If
End Sub
下面是移动的方法,以八方向为例:
Public Sub MoveHero() '移动主角
Dim i%
Dim x1%, y1%, x2%, y2%, x3%, y3%, x4%, y4%'坐标
With Npcs(0)'主角为npc(0),剩下的为其他npc
'''''''选择方向,8个方向
If KeyUp Then .Direction = 7: .Moving = 1
If KeyDown Then .Direction = 8: .Moving = 1
If KeyLeft Then .Direction = 5: .Moving = 1
If KeyRight Then .Direction = 6: .Moving = 1
If KeyLeft And KeyDown Then .Direction = 1: .Moving = 1
If KeyRight And KeyUp Then .Direction = 2: .Moving = 1
If KeyLeft And KeyUp Then .Direction = 3: .Moving = 1
If KeyRight And KeyDown Then .Direction = 4: .Moving = 1
'''''''''''''''''主角移动''''''''''''''''''''''''''''''''''''''''''''''''''
If .Moving = 1 Then'运动了
Select Case .Direction
'左下
Case 1
.X = .X - .Speed / 14'进行坐标变更,这里除以不同的值是因为可以让竖直方向和斜方向保持相同的移动距离
.Y = .Y + .Speed / 14
'右上
Case 2
.X = .X + .Speed / 14
.Y = .Y - .Speed / 14
'左上
Case 3VB+DX8从零开始轻松做游戏(第四章 2DRPG初级制作)
.X = .X - .Speed / 14
.Y = .Y - .Speed / 14
'右下
Case 4
.X = .X + .Speed / 14
.Y = .Y + .Speed / 14
'左
Case 5
.X = .X - .Speed / 10
'右
Case 6
.X = .X + .Speed / 10
'上
Case 7
.Y = .Y - .Speed / 10
'下
Case 8
.Y = .Y + .Speed / 10
End Select
''''''障碍监测,检测主角周围4个点所在的图块区域是否.gif为0,非0就是有障碍,坐标恢复旧坐标。
x1 = Int(.X / TILE)
y1 = Int(.Y / TILE)
x2 = Int(.X + TILE) / TILE)
y2 = Int(.Y + TILE) / TILE)
If Map3(x1, y1).Gif <> 0 Or Map3(x2, y1).Gif <> 0 Or Map3(x1, y2).Gif <> 0 Or Map3(x2, y2).Gif <> 0 Then .X = Oldx: .Y = Oldy'碰撞, 坐标恢复旧坐标。
''''''''''''''''''''''''''''''
'贴图累加,实现人物走动的动画
.Stepnum = .Stepnum + 1
End If 'moving
Oldx = Npcs(0).X'储存旧坐标,以备碰撞时使用
Oldy = Npcs(0).Y
End With
End Sub
Dim i%
Dim x1%, y1%, x2%, y2%, x3%, y3%, x4%, y4%'坐标
With Npcs(0)'主角为npc(0),剩下的为其他npc
'''''''选择方向,8个方向
If KeyUp Then .Direction = 7: .Moving = 1
If KeyDown Then .Direction = 8: .Moving = 1
If KeyLeft Then .Direction = 5: .Moving = 1
If KeyRight Then .Direction = 6: .Moving = 1
If KeyLeft And KeyDown Then .Direction = 1: .Moving = 1
If KeyRight And KeyUp Then .Direction = 2: .Moving = 1
If KeyLeft And KeyUp Then .Direction = 3: .Moving = 1
If KeyRight And KeyDown Then .Direction = 4: .Moving = 1
'''''''''''''''''主角移动''''''''''''''''''''''''''''''''''''''''''''''''''
If .Moving = 1 Then'运动了
Select Case .Direction
'左下
Case 1
.X = .X - .Speed / 14'进行坐标变更,这里除以不同的值是因为可以让竖直方向和斜方向保持相同的移动距离
.Y = .Y + .Speed / 14
'右上
Case 2
.X = .X + .Speed / 14
.Y = .Y - .Speed / 14
'左上
Case 3VB+DX8从零开始轻松做游戏(第四章 2DRPG初级制作)
.X = .X - .Speed / 14
.Y = .Y - .Speed / 14
'右下
Case 4
.X = .X + .Speed / 14
.Y = .Y + .Speed / 14
'左
Case 5
.X = .X - .Speed / 10
'右
Case 6
.X = .X + .Speed / 10
'上
Case 7
.Y = .Y - .Speed / 10
'下
Case 8
.Y = .Y + .Speed / 10
End Select
''''''障碍监测,检测主角周围4个点所在的图块区域是否.gif为0,非0就是有障碍,坐标恢复旧坐标。
x1 = Int(.X / TILE)
y1 = Int(.Y / TILE)
x2 = Int(.X + TILE) / TILE)
y2 = Int(.Y + TILE) / TILE)
If Map3(x1, y1).Gif <> 0 Or Map3(x2, y1).Gif <> 0 Or Map3(x1, y2).Gif <> 0 Or Map3(x2, y2).Gif <> 0 Then .X = Oldx: .Y = Oldy'碰撞, 坐标恢复旧坐标。
''''''''''''''''''''''''''''''
'贴图累加,实现人物走动的动画
.Stepnum = .Stepnum + 1
End If 'moving
Oldx = Npcs(0).X'储存旧坐标,以备碰撞时使用
Oldy = Npcs(0).Y
End With
End Sub
下面来画主角的动画:
Public Sub Drawnpc()
Dim YY%
Dim XX%
With Npcs(0)
'计算在屏幕上的坐标坐标
YY = .Y - CenterintY + SCREEN_HEIGHT / 2
XX = .X - CenterintX + SCREEN_WIDTH / 2
''''''''''设定贴图区
If .Moving = 1 Then'移动时
If .Stepnum >= Stepmax Then .Steppic = .Steppic + 1: .Stepnum = 0'当Stepnum达到Stepmax时换图片,Stepmax是一个自己定义的数值,限定了播放动画的速度
If .Steppic > 3 Then .Steppic = 0'这里是设定4张图片一个动画,如果更多的图片,修改一下这里即可
'''''''''''设定贴图区域'''''
RectNpc.Left = .Steppic * TILE * 2
RectNpc.Right = RectNpc.Left + TILE * 2
RectNpc.Top = (.Direction - 1) * TILE * 2
RectNpc.bottom = RectNpc.Top + TILE * 2
Else
'静止时
RectNpc.Left = 0
RectNpc.Right = RectNpc.Left + TILE * 2
RectNpc.Top = (.Direction - 1) * TILE * 2
RectNpc.bottom = RectNpc.Top + TILE * 2
End If
'贴图
Tran.X = XX - 16
Tran.Y = YY - TILE
Sprite.Draw TexNpc(0), RectNpc, NoScaling, Center, NoRotation, Tran, -1
End With
End Sub
Dim YY%
Dim XX%
With Npcs(0)
'计算在屏幕上的坐标坐标
YY = .Y - CenterintY + SCREEN_HEIGHT / 2
XX = .X - CenterintX + SCREEN_WIDTH / 2
''''''''''设定贴图区
If .Moving = 1 Then'移动时
If .Stepnum >= Stepmax Then .Steppic = .Steppic + 1: .Stepnum = 0'当Stepnum达到Stepmax时换图片,Stepmax是一个自己定义的数值,限定了播放动画的速度
If .Steppic > 3 Then .Steppic = 0'这里是设定4张图片一个动画,如果更多的图片,修改一下这里即可
'''''''''''设定贴图区域'''''
RectNpc.Left = .Steppic * TILE * 2
RectNpc.Right = RectNpc.Left + TILE * 2
RectNpc.Top = (.Direction - 1) * TILE * 2
RectNpc.bottom = RectNpc.Top + TILE * 2
Else
'静止时
RectNpc.Left = 0
RectNpc.Right = RectNpc.Left + TILE * 2
RectNpc.Top = (.Direction - 1) * TILE * 2
RectNpc.bottom = RectNpc.Top + TILE * 2
End If
'贴图
Tran.X = XX - 16
Tran.Y = YY - TILE
Sprite.Draw TexNpc(0), RectNpc, NoScaling, Center, NoRotation, Tran, -1
End With
End Sub
第六节 储存文件
Sub SaveGame()'保存游戏
'输出2进制文件,格式为: Open 文件地址 For Binary Access Write Lock Write As 文件号
Open AppPath & "save\save" & Which & ".sav" For Binary Access Write Lock Write As #1
Open AppPath & "save\save" & Which & ".sav" For Binary Access Write Lock Write As #1
Put #1, 1, SMapInt '写入第一个数据,要写储存区域号,如1。写入地图数据,只要把要储存的变量写在后面就行了。
Put #1, , Npcc'储存数组,直接写数组名即可。
Put #1, , HP '写入主角数据
Put #1, , Continue '写入游戏数据
Close #1'关闭文件
End Sub
Put #1, , Npcc'储存数组,直接写数组名即可。
Put #1, , HP '写入主角数据
Put #1, , Continue '写入游戏数据
Close #1'关闭文件
End Sub
第七节 读取文件
Sub LoadGame()'读取游戏
'和上面只有一点不同
Open AppPath & "save\save" & Which & ".sav" For Binary Access Read Lock Write As #1
Open AppPath & "save\save" & Which & ".sav" For Binary Access Read Lock Write As #1
Get #1, 1, SMapInt '读取的时候要和储存的位置对应
Get #1, , Npcc
Get #1, , HP
Get #1, , Continue
Close #1'关闭文件
End Sub
Get #1, , Npcc
Get #1, , HP
Get #1, , Continue
Close #1'关闭文件
End Sub
第八节 Npc走动
npc的走动还是十分复杂的,不过它和主角的走动其实是一样的原理,只不过我们要求它要自动,智能。
这里只把大概提纲列出来,因为这里发挥的空间很大,大家可以自己把代码编写出来,有些内容后面会讲到。
Public Sub Movenpc()
Dim i%
Dim x1%, y1% '坐标
'''''''''''''''''''''''''''''''''''''''''''npc运动'''''''''''''''''''''''''
For i = 1 To MapInt.IntNpc'地图上的npc总数
With Npcs(i)
If .Enable Then'如果存在
碰撞检测'检测和主角是否碰撞
if .Enemy=True then'如果是敌人
Dim i%
Dim x1%, y1% '坐标
'''''''''''''''''''''''''''''''''''''''''''npc运动'''''''''''''''''''''''''
For i = 1 To MapInt.IntNpc'地图上的npc总数
With Npcs(i)
If .Enable Then'如果存在
碰撞检测'检测和主角是否碰撞
if .Enemy=True then'如果是敌人
if 离得很远 then
自由选取路径
else
追击主角
End if
else'enemy
自由选取路径
End if'enemy
If .Moving = 1 Then'如果在运动
坐标变更
路障检测
End if
End if'enable
End Sub
End Sub
接下来的工作,你可以按照主角的例子自己补全它,反正是如果你想让npc聪明些,你就多写点代码,如果npc不用那么聪明,你就少写点就行了。碰撞检测会在下一节讲到。
第八节 碰撞检测
碰撞检测一直都是最值得研究的课题,因为几乎每个游戏都会用到它,不过现在方法也有很多了。在2d游戏中,主要采用矩形碰撞和圆形碰撞。
矩形碰撞,第一个矩形中心坐标O1.x O1.y,长Y1,宽X1; 第二个矩形中心坐标O2.x O2.y,长Y2,宽X2,那么当他们碰撞的条件是,中心坐标的绝对值小于两个同方向的长度的和的2分之一,即
if abs(O1.x-O2.x)<X1/2+Y2/2 and abs(O1.y -O2.y)<Y1/2+X2/2 then
碰撞后的结果
End if
圆形碰撞的原理是,两个圆心距离小于半径和
if (O1.x-O2.x)*(O1.x-O2.x)+ (O1.y-O2.y)*(O1.y-O2.y) < (R1+R2)*(R1+R2) then
碰撞后的结果
End if
把碰撞的结果要写好,比如恢复旧坐标,触发战斗,触发对话等等,不然你的碰撞毫无意义。
VB+DX8从零开始轻松做游戏(第四章 2DRPG初级制作)第九节 对话框