使用 SpriteLibrary 开发角色扮演游戏





5.00/5 (6投票s)
使用 SpriteLibrary 来实现图形效果的一个角色扮演游戏示例
引言
该代码是一个演示游戏,使用
SpriteLibrary
编写。本文档旨在解释如何使用 SpriteLibrary
设计一款游戏,并通过代码展示最终成果。该游戏足够简单,易于修改,允许您绘制和填充自己的地图。
背景
不久前我制作了 SpriteLibrary
,但意识到人们可能需要更多的游戏制作示例。我希望发布一些这样的示例,希望能有所帮助。SpriteLibrary
的理念是使简单游戏的图形部分变得容易得多。但是,SpriteLibrary
有其优点和缺点,其中一些将在本文中重点介绍。
安装
下载 AdvDemo 包和 SpriteLibrary 包后,您需要解压它们并在 C# 中编译。您需要先构建 SpriteLibrary,然后构建 AdvDemo。AdvDemo 可能需要您打开解决方案资源管理器,右键单击“资源”并选择“添加资源”。然后,您可以浏览到 SpriteLibrary 的构建位置。如果您只想下载预编译的游戏,可以在 这里 找到。(免责声明:下载和安装随机陌生人的程序非常危险。但是,嘿,我来自互联网,我应该值得信赖且友好。如果可能,请使用源代码。这是个好习惯。)
使用 SpriteLibrary 设计 RPG
这款游戏有许多设计上的考虑。我的出发点是我想使用 SpriteLibrary
,并且想制作某种角色扮演游戏。我花了一些时间来决定是制作第一人称、第二人称还是俯视视角(我选择了后者)。
第一人称 / 第三人称
在这两种模式下,您四处走动,看到面前的场景的三维视图。
SpriteLibrary
虽然可以勉强处理一部分,但其遮挡效果不是最好的。如果精灵被其他精灵挡住,有时它们可能会穿透出来。如果您正在寻找的宝藏会穿透墙壁可见,那就太糟糕了。
第一人称比第三人称稍微容易一些。我们通常只会在前面有一个剑或武器,显示我们拿出的武器,而在第三人称中,您需要展示所有冒险者的背部。
我更像个程序员而不是美术人员。我最终在这款游戏的美术设计上花费的时间和我编程的时间一样多。
我之所以做出这个决定,是因为 SpriteLibrary
非常容易在 X/Y 网格中移动精灵,而且目前还没有为 3D 准备太多内置功能。我选择从上方俯视我们的冒险者。我也选择不设置光照/黑暗。这只会让编程变得更简单。这应该是一个“简单”的演示,而不是一项毕生事业。
屏幕布局
下一个主要的设计考虑是如何显示我们的队伍状态、敌人等。我选择在地图旁边设置一个面板,显示我们的队伍、生命值等。当我们面对敌人时,我们在同一个面板中看到它们。而且,当我们去商店时,我们也在此面板中看到可以购买的物品。
原因很简单。SpriteLibrary
处理的是单独的 PictureBox
。游戏中每个 PictureBox
都需要加载和初始化在该面板上使用的所有图形。我们可以将其设置为在首次需要时单独加载图像。但这需要比我当时想要的更多的基础结构。所以,我将敌人和物品的所有特写都显示在同一个 PictureBox
上。游戏加载时,会弹出一个窗口告诉您它正在准备游戏。此时,它正在准备精灵;读取图像并进行处理。精灵控制器在此过程中相当慢,但一旦完成,它在图像处理方面就相当高效。
界面
我使用 SpriteController
(SpriteLibrary
的一部分)来监听按键。我从窗体获取鼠标移动信息,并在拖动屏幕上的精灵时将其传递给 SpriteController
。这样做更多是为了展示如何进行精灵拖动,而不是因为这是最好的方法。
代码。
队伍设置
我希望在队伍中有几种不同的选择。一旦您有足够的钱,您最终可以招募所有人。但您只能开始时只有两个人。所以我创建了一个
PictureBox
,并创建了一个 SpriteController
来管理该框中的精灵。我在框的中间画了一条线,您可以将冒险者精灵拖到“选定”区域。当我们有两个冒险者准备好后,就可以开始游戏了。
由于角色扮演游戏鼓励您与您的数字自我建立更深的联系,因此我也允许您更改玩家的姓名。因此,在玩游戏时,我确保在“队伍设置”窗口和 AdDemoForm
(游戏进行的窗口)之间来回传递队伍成员。
AdvDemoForm
这是游戏核心进行的地方。所有在 SpriteLibrary
上运行的内容都使用“tick”来实现。这在 SpriteLibrary 页面上有详细解释,但我也会在这里简要描述一些内容,主要是为了解释工作原理。
DoTick 函数
SpriteLibrary
有一个非常快速的计时器“tick”。它大约每秒发生 100 次。每次 tick 发生时,“DoTick
”函数就会触发。(这在程序中设置,大约在 AdvDemoForm.cs 文件的第 151 行)。我们希望 DoTick
函数尽可能快速地完成,因此它不应太复杂。我们使用 CurrentMode 枚举来跟踪游戏期望的内容,以便知道如何处理。
如果您阅读 DoTick
代码,您会发现,如果需要,我们会立即退出函数。例如,我们检查地图上的精灵是否正在从 a 点移动到 b 点,如果是,我们就退出函数。当我们告诉冒险者向上、向下、向左或向右移动时,我们告诉构成游戏场景的每个精灵在地图上移动(使用 Sprite.MoveTo
函数)。我们不希望在到达下一个位置之前接受新的指令,因此如果我们正在等待 Sprites
,我们就直接退出 DoTick
。
如果没有任何精灵在移动,但代码知道我们刚刚移动过(IsMoving
= true),那么我们刚刚到达了新的位置。我们将一个布尔值(isMoving
=false)设置为表示我们不再移动,然后花费一些时间处理我们刚刚着陆的位置。如果停止了,并且该位置有一个敌人,我们就设置战斗。如果是门,我们就移动到该位置,如果是商店,我们就处理商店。您会发现游戏中的大部分内容(生命、死亡、战斗、购物)都发生在 DoTick
函数中。Tick 是 SpriteLibrary
的生命线,使用 Tick 是使用它进行编程的最佳方式。您可以使用窗体事件(窗体按钮点击)进行一些编程,但许多您期望发生的事情并不会如您所愿。
例如:如果您单击一个按钮并告诉 Sprite
移动,则直到 Click
函数完成后,精灵才开始移动。所有函数都使用相同的“线程”,并且窗体事件必须在精灵事件发生之前完成。有人试图告诉精灵做某事,然后进行 Thread.Pause
来等待精灵完成,这是非常常见的。但 Pause 会阻止精灵代码运行,因为它会暂停整个线程(包括精灵)。如果您使用 Tick 和一个简短的函数,那么该函数会触发很多次,并在它们之间留下大量时间供精灵完成它们的工作。
CheckKeypress
在 CheckKeypress
函数中,我们处理了大部分用户输入。在那里我们检查向上、向下、向左和向右移动。一次可以按下许多键。因此,使用 SpriteController
最简单的方法是询问它是否按下了某个特定键。该函数询问是否按下了“上”键或“W”键,如果是,它会设置一个布尔值表示我们要向上移动。稍后,我们会检查以确保我们没有同时设置向上和向下移动。如果我们朝两个冲突的方向移动,我们就不会移动。
该函数的其余部分处理按键。如果我们身处商店,我们可以做一些事情。如果我们正在进行遭遇战,我们不能随意移动,我们可以选择战斗、逃跑或使用治疗药水。所以这个函数中有一些逻辑。
DoFight
DoFight
是进行战斗的函数。角色扮演游戏最终依赖于几个关键部分,主要是战斗,才能发挥作用。这款游戏有一个非常简单的战斗算法;可能比任何有理智的人在制作 RPG 时都会使用的要简单得多。基本上,双方计算他们能造成的伤害,然后将其施加到选定的目标上。如果目标死亡,伤害就会施加到下一位生物身上。所以双方先造成伤害,然后再承受伤害。因此,双方都有可能杀死对方。我们并不真正关心坏人是死是活。如果玩家队伍死了,他们就死了,游戏就结束了。
我们使用随机数生成器。SpriteController
在实例化时会创建一个。您可以创建自己的随机数生成器,但如果您在游戏中不使用同一个生成器,那么随机数就不会那么随机。每个玩家“掷骰子”以决定是否命中,如果命中,则根据其武器的伤害进行掷骰子,以确定造成的伤害量。由此产生的伤害会添加到潜在伤害列表中。远程武器每回合可以命中两次,这使得它们成为非常强大的武器。但是,弓箭手类型的玩家不如近战玩家强壮。近战玩家承受的伤害减少,仅仅因为他们更强壮。
盔甲每回合吸收一定量的伤害。如果您穿戴板甲,那么它每回合吸收 20 次命中。如果对方在本回合造成 25 点累积伤害,那么玩家就承受 5 点伤害。
精灵
制作这款游戏时,我制作了很多精灵。我为我们最终要对抗的每个敌人制作了一个。我有一些简单的精灵,还有一些更复杂的。我在这里简要讨论一下我是如何制作这些精灵的。
制作战士
我不是艺术家,所以我从我很久以前制作的另一个游戏中窃取了我大部分图像。所有那些图像的大小都是 200x200,并且是单帧图像,而不是动画。所以,我需要做的是让它们稍微移动一下,使它们感觉更像动画而不是静态图像。
我使用 **GIMP**,一个免费开源的软件包,非常适合此类工作。第一步是制作一个网格,以便我知道在哪里放置我的图像。SpriteController
在创建 Sprite
时,会逐个读取网格的内容。最好有一个透明的背景,这样您就能看到它后面的任何东西。对于这款游戏,这不太重要,因为我们只有一个灰色背景。但对于其他游戏,这很重要。
然后我粘贴图像并调整大小。对于这款游戏,我将它们做成 100x100。这相当小,但它们很少会那么大。大多数时候它们在屏幕上都保持得很小。如果您做得太大,SpriteController 在打印它们时会变慢。如果您做得太小,
它们看起来会很模糊,质量很差。您需要为您的游戏确定最佳分辨率。
我复制了我的图像,这样我就可以获得想要的帧数。对于这一个,我只有几帧。然后,我考虑了它应该执行的动作。我主要只是让她的头和剑动起来。**GIMP** 有三个很棒的工具:选择工具、旋转工具和移动工具。我们用选择工具圈出我们要移动的区域。然后,我们按 ctrl-x 和 ctrl-y。这会复制并粘贴它。然后,在粘贴但未“锚定”(放回图像上)的情况下,我们可以对其进行一些处理。我们可以用移动工具稍微移动它,但我通常喜欢先旋转它,然后将其移动到我想要的位置。最后,您将其“锚定”回原位,这会将其粘贴到您当前打开的任何图层中。完成后,我需要做一点着色来填补切割和粘贴的地方,但要获得一点点动画效果并不需要太多。
关于旋转工具的一个好处是,它可以让您更改要旋转的项目的中心。如果我切了一个胳膊,我会将中心设置在胳膊会旋转的地方,无论是肩膀还是肘部。这样通常意味着我在将其放回原位时不需要过多使用移动工具,并且可以更好地预览完成后的效果。
精灵移动的目标是您需要有一个关于精灵应该做什么的想法,无论是行走、抽搐还是其他什么。然后您需要有几帧来完成它。您可以让您的精灵动画快速或缓慢,这取决于精灵应该做什么以及您有多少帧来完成它。使用很多帧,您可以让它相当流畅地移动,但这需要花费大量时间绘制每一帧,并且还需要更多的处理能力。
制作龙
既然我们已经制作了一个简单的三帧动画,让我们来看一个更复杂的动画:龙。当我设计这个 RPG 时,我脑海中第一个想法是,“酷!我可以制作一条喷火龙!”龙很棒。我想要它看起来很棒。因此,我希望它比我其他的精灵更复杂。
我所有的其他小怪都有一个动画。它们摆动头部,挥舞剑,挥舞斧头。但它们只会重复做这些。我希望我的龙与众不同。所以,我给了它们两个动画。它们大部分时间会执行正常的移动,但时不时会分支到它们的第二个动画。而且,它们还会时不时地喷火。
我以与制作其他小怪相同的方式制作动画。我从一张图片开始(这条龙最初是一本涂色书上的图像),然后我切下一条腿并旋转它,我切下它的头并旋转它,或者我切下它的脖子并旋转整个头。对于其中一个,我切下了龙的前半部分并旋转了它,使其看起来像它在腾空前腿……不是很复杂,但很有效。
但幕后发生的是,我告诉每个精灵动画一次。然后,当动画完成后,我有一个事件(PartyClass.SpriteAnimationEnd
)触发。此函数会检查精灵有多少个动画。如果只有一个,它会立即告诉精灵再次执行该动画。但是,如果它有多个动画,它会随机选择要使用的动画,并倾向于第一个动画。
而且,同一个事件(PartyClass.SpriteAnimationEnd
)会检查我们是否是龙。如果是,我们还会选择一个随机数,并决定是否喷火。火焰(再次强调,我不是艺术家)是第二个精灵,会被暂时添加。当龙在喷火时死亡,您可以稍微看到这一点。它变成了一个墓碑,而火焰球继续射出。这是一个小细节,本可以轻松编程移除,但我留下了它,主要是为了强调火焰是第二个精灵。我可以轻易地将一个木乃伊(易燃物)变成喷火野兽。现在我已经有了喷火精灵,我只需要决定什么应该喷火并让精灵动画化。
要使火焰效果很好,需要做一些额外的工作。火焰需要从它的嘴里喷出,而不是眼睛、耳朵或肚脐。我有几个不同的龙,它们的头部位置不同。所以我需要检查我有的龙,并为每条龙选择不同的火焰起始位置。所有这些代码都可以在
PartyClass.SpriteAnimationEnd
函数中看到。
您将看到的最后一件事是,当火焰完成动画时,该精灵就会被销毁。我们不会让火焰一遍又一遍地重复,它只发生一次,然后我们等待它再次喷火,然后再喷出。所以我们按需创建火焰精灵。
我想最后要提的一点是,您可以单击一个敌人来选中它。敌人会放大,并且是您的队伍的目标。当您想先杀死一个更危险的敌人时,这很方便。但是,对于龙来说有点奇怪。当您在喷火时单击龙,我需要找到对应的火焰精灵,并将其放大(同时将其移动到嘴部位置,因为当整个图像放大时,嘴部的位置会发生变化)。
修改游戏
CodeProject 的好处在于您可以获取代码并重复使用。这款游戏是一个功能齐全的游戏,但不是很长。我的故事情节非常弱。您一直玩到击败最终的龙。如果有人愿意,他们可以从此基础上制作一个更长、更有趣的游戏。有一些区域您可能想在此基础上进行工作。
更改地图
地图文件位于资源目录下的 AdvDemoMap.advx
文件。该文件内包含有关扩展或创建自己地图的相当不错的说明。地图文件是 XML 格式,但操作起来并不复杂。如果您弄错了,游戏会给您一个相当完整的错误消息。您可以将该地图复制到您的桌面或其他地方,并从中创建自己的地图。如果其他人安装了 AdvDemo,他们就可以打开您的地图文件并玩您的冒险。
主地图是通过 <line></line> 标签构建的。它们看起来像这样:
<Name>Town of Nod</Name>
<Line>wwwwww?wwwwwww</Line>
<Line>w h t t htw</Line>
<Line>w tt h w</Line>
<Line>wPh?t S w</Line>
<Line>w t h w</Line>
<Line>w h h h w</Line>
<Line>wwwwwwwwwwwwww</Line>
其中 W 是墙壁,H 是房屋,S 是商店。? 是一个标记,提醒我们以后在该位置上放置某些东西(我们需要手动将东西放在 ? 上;? 被直接忽略)。
更改武器
您可以在 ItemClass.cs
文件中更改武器的数值或伤害。
添加武器有点棘手。您不仅需要为物品创建精灵,还需要在几个地方硬编码新武器。
对于每个精灵,您都需要在 AdvDemoForm.cs
文件中的 LoadMultipurposeSprites
函数中加载它。LoadPlayingFieldSprites
函数用于地形(树木、商店等)。
您需要在同一个文件(AdvDemoForm.cs
)的 PossessionName
枚举中创建一个条目。
public enum PossessionName { potion_small, potion_large, armor_leather, armor_chain, armor_plate,
spear, club, scimitar, sword, dagger, battleaxe, crossbow, longbow, throwingstar,
sling, newWeaponName
}
您还需要在同一个文件中的 SpriteName
枚举中为精灵创建一个条目。
public enum SpriteName { wall, walldoordown, walldoorleft, tree_1, tree_2, tree_3, tree_4,
cavewall, cavedoordown, cavedoorleft, NewWeaponSpriteName
然后,在 item 类中,您需要创建一个新条目:
case PossessionName.newWeaponName:
Name = "Lightsaber";
WeaponDamage = 50;
Description = "A glowing saber of death";
Value = 7500;
WhatType = PossessionType.melee_weapon;
break;
武器将自动出现在商店中。目前,获得武器或盔甲的唯一方法是通过购买。(或者,在游戏开始时装备它们。您可以通过更改 player_knight1
或其他 player_*
生物的 CreatureClass
实例化来完成此操作。)
添加生物
您可以使用预先存在的精灵来添加生物,也可以添加更多精灵。
首先,在 AdvDemoForm.cs
文件中的 SpriteNames
枚举中添加它。在创建精灵图像并将其添加到资源后,请确保您也使用 LoadMultipurposeSprites
函数加载它。现在,您需要创建一个生物枚举,也位于 AdvDemoForm.cs
文件中,最后,在 CreatureClass
实例化函数中添加一小段代码。
case CreatureName.NewCreature:
SpriteImage = SpriteName.NewSpriteName;
size = .5; //Size in feet. 6.1 is just over 6 feet tall
strength = 1; //How tough this creature is
dexterity = 3; //How well it hits (out of 10)
intelligence = 1; //How smart it is. Smarter creatures are harder to run away from.
CreatureWepDamage = 5; //How much damage it is capable of doing
Name = "A ghastly tree elf.";
break;
结论
我希望您发现 SpriteLibrary
在制作有趣的游戏方面有所帮助。再次强调,SpriteLibrary
会让简单的游戏变得简单。但是,如果您想深入制作硬核游戏,您将需要使用其他图形系统。
历史
- 2016年8月4日 - 小错误修复,并增加了播放您自己制作或别人发给您的游戏文件(.rpgx)的能力。
- 2016年7月4日 - 创建第一个版本