游戏编程入门(byDavidAstle)
经常有人问我,没有编程经验的人该如何开始开发游戏。在此之前,我总是一个个的尽力回答。然而,
后来提相同问题的人数增长到难以处理的地步。我决定,是时候把我所有的建议写成文章,作为一个大概。
这篇文章是针对那些想要开发自己游戏,但几乎没有编程经验的人。事实上,我假设读者没有任何编
程经验。我主要讨论游戏开发的程序和设计方面,而不是艺术性。我也不准备讲述如何进入游戏行业(这
方面已经有足够的资料),而只是让你逐步的开始开发自己的游戏。最后,我所指出的这条道路也并不能
作为唯一的,或是最好的路径来学习开发游戏,但至少对我和一些人很有用。
选择一门语言
你要做的第一件事就是选择一门开发语言。你有很多选择,包括Basic,Pascal,C,C++,Java,等等。也经
常会有人争论对于初学者那一门语言是最好的。对于这一系列流行语言的讨论,你可以参看JohnHattan
的著作,WhatLanguageDoIU?(我用什么语言?)
我的建议是以C和C++开始。有些人会说这些语言对初学者来说太高级了,但因为我自己就是学C++,
我并不同意这一说法。而且,C/C++是当今使用最广泛的语言(译者认为应该是汉语。。。),所以你可以找
到大量学习资料和帮助。你先学C或C++都无所谓,因为只要学了一个,再学另外一个就很容易。但是,
如果你先学C++,请保证在学习面向对象编程之前能理解和使用过程编程(等编程熟练再去学习类)。(译
者:C是过程性语言,C++是面向对象语言)
如果你开始学习C/C++,发现太难,那再学一个简单一点的也没关系,比如Basic或Pascal。但是我真
的认为,如果你坚持努力,而且有好的资料,学C/C++应该没有太大问题。
你的下一个问题可能会是:“我该怎么学C/C++?”我很高兴你这样问。最好的办法是上课。有老师可以
回答你的问题,帮助你产生很大进步,编程练习作业也可以保证你能用到所学的东西。
如果你不觉得上课是个好主意,那最好的办法就是买一些好书。不要花太多时间去选一本什么“超级
宝典”或“万用全书”,因为你最终可能会买几本。我建议你去一家书店,然后拿几本比较入眼的C或C++
书看,直到找到一本或几本你能看懂,并且可以拿来学习的。同时,你可能会想要一些更深入的,或者一
些材料,但是你一旦对于这门语言有了一些了解,我相信你应该有自己更好的选择。
在这里,我有必要花一些时间,来说我看到很多初学者所关心的一个事情,特别是年轻人:没有钱买
书和其他东西。首先,有很多免费资源可以利用,图书馆,MacmillanComputerPublishing,有成千上百的编
程书籍。但是如果你真的想要成为一个好的程序员,还是应该投入一部分资金。应当想方设法(合法的)
帮助你弄到一些钱。
网上也有很多C/C++的学习指南。但是我认为那只能作为补充而不是你自学的主要资源。
选择正确的编译器
你写的程序,或者代码,是以文本方式储存的,你甚至可以用记事本写C/C++程序。但是总需要有东
西把他们转换成为可执行文件。对于C和C++,那就是编译器。
可用的编译器有好多种,包括很多免费的。选择一款自己适合的编译器很重要,免费的编译器就有这
样的好处,你可以把它们试个遍,然后从中选择自己最喜欢的。然而,免费编译器比起商业版,可能会缺
失一些功能和大部分服务。幸运的是,多数商业版编译器也兼售介绍版或学习版,这要便宜得多,通常功
能却不见得少,唯一的限制是你不能发布用它编译的程序(短时间内你也根本用不着)。
总之,选择编译器取决于你能花多少钱,用什么操作系统,和为什么平台开发。如果要为windows开
发,我强烈推荐MicrosoftVisualC++。他强大的开发环境使得很多事做起来更方便,毫无疑问没有其他编
译器更适合开发windows应用程序。如果你是个学生,你还可以折价买到。(译者:爽!)如果你在DOS
平台开发,你最好的选择可能是DJGPP,免费的哦~。
选择开发平台
尽管最终你很可能为好几个平台开发,总要先选择一个来学。当你在学这门语言,还没有接触到图像的
时候,你可能会想使用非图形用户界面的操作系统,比如DOS,Unix.这样可以避免接触高层,比如windows
编程,让你集中精力学习语言本身。
一旦你做好开发游戏的准备,那么,应该考虑是否改变平台,让我们来看看每个选项的特征。
windows:如果你想成为游戏行业的专家,或者如果你想让许多人来玩你开发的游戏,那么,这就是
你要选择的平台。因为多数你的用户使用windows,而且现在我也看不出有什么改变的迹象。当今大多数
的windows游戏都是由一种你可能听说过技术---DirectX---开发的。你可以DirextX这个库直接访问硬件,
这意味着你可以开发高性能的游戏。
DOS:DOS过去是占统治地位的游戏平台,但是已经一去不复返了。尽管可能有一些特殊爱好者还
在为DOS开发游戏,现在没有一个为DOS开发的商业游戏,他也将继续衰落,直到微软不再支持。如果
你只是想开发游戏,还是不要选择DOS,如果你非要这么做,也不要太久。记住:由于存在大量DOS游
戏开发的书,可能还有人辩护从这些书中学习DOS游戏开发。但是,windows游戏开发的书越来越多,那
些辩解也变得越来越无力。
Linux:Linux是Unix的一种,由于很多原因后来变得流行,包括稳定性,价格,和反微软情绪。尽管
Linux用户还是相当少,但是围绕着他的热情和不断增长的市场潜力使其也成为不错的选择。
Macintosh:MAC有大量忠实粉丝并不能说明什么,几乎每一个和我讨论的MAC狂热者都需要更多更
好的游戏。我没有见过多少MAC游戏开发资源,但我相信还是有的,因此这也是一个选择。
consoles:console(就是PS,N64,DC等等)的游戏市场十分巨大,当然console游戏开发的前景也不错。
然而以非商业的形式开发console游戏,出于各种原因,在现在似乎行不通。如果你开发console,很可能是
在被商业游戏开发组雇用之后。
开始进入主题
现在是时候讨论开发游戏了。简单起见,我假设你选择用C/C++在windows平台开发,可能你选择别
的,但大多数我说的还是有用的。
首先,甚至在你打算开始开发游戏之前,你必须很好掌握C和C++.你应该懂指针,数组,结构体,
函数,可能还有类。如果你精通他们,就可以开始做游戏了。
这篇文章可能不能教会你所有关于开发游戏的东西。幸运的是,也没有这个必要。这方面有很多书,
网上也有很多指南。应该有所有你想要的东西,这里我建议你怎么开始:
找一本或几本书。对于windows游戏开发初学者,TricksoftheWindowsGameProgrammingGurus是
个完美的开始。除此之外,还有许多好书.读透这些书,试试所有的例子,不懂得部分多读几遍。
网上指南作为补充。除了弄清书上读到的,他们还涉及一些书上没有谈及的主题。
向专家寻求帮助。如果你不能在书上或指南找到问题的答案,好好利用我们的论坛和聊天室。那里有
好多专家愿意帮助别人。
这并不是要你按照顺序执行的,而是可以同时并且不断的重复执行。
光学习还是不够的,你必须运用你所学到的。从一个小游戏开始,然后在这个基础上前进。你可以看
一下GeoffHowland's的著作:HowdoIMakeGames?APathtoGameDevelopment.
开始,自己独立工作。不要急着加入团队,那样只会减缓学习过程。而且一旦你有了自己的几个游戏,
你可以为团队做出更大的贡献。
还有关于书,你应该不仅仅看游戏开发的书。为了能够开发出你看到商店出售的游戏,你应该钻研比
大多数游戏开发书籍更高级的一些主题。有一些可以在网上找到,但你也应该选一些图形学,人工智能,
网络,物理学等方面的书。计算机科学学位看来唾手可得,但因为你被迫上这些课的时候可能认为他们和
游戏开发无关----你错了!
包装
这里有一些提示很有用
不要只积累知识,用它
你永远不会知道会理解一些东西知道你是用他们。用你学的东西作些演示。做作书上的练习。
多玩游戏
这样做会使你做出更好的游戏。而且可以减轻编程的枯燥。
帮助别人
能帮别人的地方尽量帮助别人,教别人的过程中自己会学到更多。
有始有终
不要有这样的想法:“我能够完成这个游戏了,但是我又有一个新的想法,那就直接做下一个。”你可
以学得更多如果你完成他,你也可以用事实证明你不是只会空谈。所以,尽量不要做很大很复杂的游戏,
直到你有了一定经验。
开始吧!你现在可以开始准备QUAKE4了。可能你不是不是很了解,但是至少应该知道如何开始这
条道路,找多些资料,加上多年努力工作,他一定会实现!
MMORPG开发入门
原著:RaduPrivantu
翻译:pAnic
2005年5月11日
原文出处:ABeginner’’sGuidetoCreatingaMMORPG
-------------------------------------------------------------------
译者序:这是一篇讲解如何开发一款MMORPG的入门文章,作者本人也是一款游戏的开发者,文中
的内容源于实践,有很高的参考价值。很多人都想拥有自己的游戏,这篇文章对那些想自己开发游戏的人
来说可能是一纸福音,也可能是一盆冷水。无论如何,开发游戏都不是一件简单的事情。以下是翻译正文:
-------------------------------------------------------------------
文章的中心是如何起步开发你自己的大型多人在线角色扮演游戏(原文:MassiveMultiplayerOnline
RolePlayingGames)(MMORPG)(译者注:俗称:网络游戏,网游)。针对的读者是经验和资源有限的开发
者。读完文章之后,你应该懂得如何起步,还有一些关于什么是应该做的和不应该做的忠告。第一步是
评估你的能力和资源。你必须对自己诚实,因为做你力不从心的事情会浪费你的时间并让你心灰意冷。
第一步:评估你的能力
必须的技能:
懂至少一种编程语言。迄今为止,C++因为性能和效率的优越性成为游戏开发者的首选。VisualBasic,
Java或者C#可能也是不错的选择;
熟悉一种图形库。通常的选择是SDL,OpenGL,或者DX/D3D。(译者注:网上也有很多免费/付费引
擎下载和出售);
选择一种网络通讯库。你可以从WinSock,SDL_net,或DirectPlay中选择。(译者注:很多人喜欢开发
自己独特的网络库,这并不复杂,似乎ACE也是一种选择);
对游戏开发有大体的经验。例如,事件循环,多线程,GUI设计,等等。
强烈推荐的技能:
C/S结构通讯;
多平台开发。你可能希望设计一个MMORPG,尤其是服务器能运行在多种操作系统。为此,我推荐
使用SDL,OpenGL和SDL_net;
网站开发。如果你想让用户通过网站查看玩家统计,服务器信息和其他信息,这是必须的。(译者注:
其实网站可以交给其他人开发,如果有必要的话);
安全管理。你当然不想因为有人攻击你的服务器而浪费时间!
团队组织能力。你需要一个你能成功领导和管理的团队;
第二步:初步规划
我注意到很多人在不同的论坛发帖子寻找团队开发MMORPG。他们中的大部分是这样:“我们成立了
一个公司/游戏工作室,需要3个美工,两个程序,1个音乐制作,等等。为了创新,不要看过去的MMORPG,
你有全部的自由用来创造你想要的世界,等等。我们会在项目完成并赚到钱的时候付给你酬劳,等等”。
不幸的是,以现有的技术和带宽,你无法拥有一个动态的世界。朝向无法到达的目标前进只会导致失败。
正确的做法是拿出一些小规模的,功能性强的,可扩展的设计和构架。,
基本软件构架
首先,尝试创建一个简单的C/S模型,有如下功能:
创建一个新角色;
保存那个角色(服务器端);
用那个角色登陆;
能够和其他人交谈;
能在3D空间游览;
保存角色看起来简单,其实不然。例如,有两种方式保存角色:使用数据库服务或者使用文件。两
者有各自的优缺点:
请点击查看详细优缺点对比
现在你决定了如何存储角色,你还得选择C/S通讯的网络协议:TCP还是UDP?,我们都知道TCP
速度慢,但是更准确,并且需要额外带宽。我实际使用TCP并没有遇到什么问题。如果你有充足的带宽,
TCP是个好选择,至少对初学者是这样。UDP会很麻烦,尤其是对新手。记住,游戏或引擎的初步测
试会在你的局域网进行,所有的包都会按顺序依次抵达。在Internet上无法保证这一点。虽然包会按顺序
到达,但是有时候会丢包,这通常是个麻烦事。当然,你可以设计你的协议使得C/S能够从丢包中恢复。
但这对初学者来说很痛苦,不值得推荐。
第三步:选择数据传输协议
又是看起来很简单,其实不然。你不能只是发送’’0’’结尾的串。因为你需要一个通用的协议,能同时
适用字符串和二进制数据。用0(或其他字符)做结束符是不明智的,因为那个结束符可能是你要发送的数据
的一部分。此外,如果你发送20字节,然后再20字节,服务器极有可能收不到两个20字节的包。取而代之
的是,它会一次性收到40字节,为了避免浪费带宽在不必要的头上。而且,你可以发送1KB的包,但服
务器会以两个小包的形式收到它。所以你必须知道哪里是一个包的开始,哪里是结束。在“永恒大陆”(译
者注:原文:EternalLands,本文的作者正在开发的一款MMORPG)中,我们用如下的方法:
Offt0:1字节表示传输的命令;
Offt1:2字节,传输的数据长度;
Offt3:变长,消息内容;
这种方法有一致的优点:所有的数据传输有统一的标准。缺点是有些命令有固定已知的长度,浪费
了一些带宽。以后我们会改成混合的方法。
下一件事是决定服务器模型:“非阻塞soket,不使用线程”,或者“阻塞soket,使用线程”。两种方法(使
用线程vs不使用线程)各有优缺点。
线程:
服务器响应会更加平滑,因为如果一个玩家需要大量时间(例如从数据库中读取数据),这会在它自己
的线程中完成,不会影响其他人。(译者注:也许作者的意思是每个玩家都有独立的线程,但这对MMORPG
不太现实);
难以恰当的实现和调试:你可能需要大量同步,并且一个小疏忽就会导致灾难性的后果(服务器瘫痪,
物品复制,等等);
可以利用多处理器;
无线程:
实现和调试更简单;
响应速度慢;
在我的公司,我们使用无线程的方法,因为我没有足够的资源和人力处理线程模式。
第四步:客户端
你打算做2D还是3D游戏?有些人认为2D游戏做起来简单。我两者都做过,并且我倾向于3D游戏更
简单。容我解释。
2D下,你通常有一个帧缓冲,也就是一个巨大的象素点数组。象素点的格式会因显卡的不同而不同。
有些是RGB模式,另一些是BGR模式,等等。每种颜色的bit数也会不同。只有在16bpp模式才有这个
问题。8-bit和24-bit模式简单一些,但有他们各自的问题(8-bit颜色数太少(256),而24-bit速度更慢)。同时,
你需要制作你的精灵动画程序,不得不自己排序所有对象,以便他们以正确的顺序绘制。当然,你可以
用OpenGL或者D3D制作2D游戏,但通常这并不值得。并不是所有人都有3D加速卡,所以使用3D库开
发2D游戏一般会带给你两者的缺点:不是所有人都能玩,你也不能旋转摄像机,拥有漂亮的阴影,和3D
游戏炫目的效果。
(译者注,目前绝大部分显卡都支持565的16bpp格式,这个也成为目前16位色的业界通用格式,有不
少文章和代码都是讲述这一格式下图像处理的,尤其是使用MMX技术)
3D的途径,正如我所说,更简单。但是需要一些数学(尤其是三角)的知识。现代的图形库很强大,
免费提供了基本的操作(你不需要从后到前排列对象,改变物体的色彩和/或帖图都十分简单,对象的光照
会按照光源和它的位置计算(只要你为它们计算了法向量),还有更多)。并且。3D给了你的创作和运动更
多的自由度,缺点就是不是所有人都能玩你的游戏(没有3D卡的人数可能会让你大吃一惊的),并且,预渲
染的图片总是比实时渲染的更漂亮。
(译者注:市面上想买不支持3D的显卡目前很困难,只是高性能的3D卡价格也不低)
第五步:安全
显然,不能相信用户。任何时候都不能假设用户无法破解你精巧的加密算法(如果你使用了的话)或者
协议,用户发送的任何信息都要通过验证。极有可能,在你的服务器上,你有固定的缓冲区。例如,通常
有一个小(可能是4k)缓冲区用来接收数据(从soket)。恶意用户会发送超长数据。如果不检查,这会导致缓
冲区溢出,引起服务器瘫痪,或者更坏的,这个用户可以hack你的服务器,执行非法代码。每个单独的消
息都必须检查:缓冲区是否溢出,数据是否合法(例如用户发送“进入那扇门”,即使门在地图的另一端,或
者“使用治疗药水”尽管用户没有那种药水,等等)。我再次强调,验证所有数据非常重要。一旦有非法数
据,把它和用户名,IP,时间和日期,和非法的原因记录下来。偶尔检查一下那个记录。如果你发现少量
的非法数据,并且来自于大量用户,这通常是客户端的bug或者网络问题。然而,如果你发现从一个用户
或者IP发现大量非法数据,这是明显的迹象表明有人正在欺骗服务器,试图hack服务器,或者运行宏/
脚本。同时,决不要在客户端存储数据。客户端应该从服务器接收数据。换句话说,不能发送这样的消息
“OK,这是我得物品列表”或者“我的力量是10,魔法是200,生命值是2000/2000”。而且,客户端不应收
到它不需要的数据。例如:客户端不应该知道其他玩家的位置,除非他们在附近。这是常识,给每个人
发送所有玩家会占用大量带宽,并且有些玩家会破解客户端从中获取不公平的利益(像在地图上显示特定玩
家的位置)
(译者注:就像传奇的免蜡烛外挂)。所有这些似乎都是常识,但,再次,你会惊奇的发现有多少人不
知道这些我们认为的常识。
另一个要考虑的问题,当涉及到安全:玩家走动的速度必须在服务器计算,而不是客户端。
(译者注:这是重要的原则,但是会耗费大量服务器资源。魔兽世界没有这样做,它采用类似其他玩家
揭发的形式掩盖这个事实,导致加速外挂可以用,但是在有其他玩家的时候会暴露)。
服务器应该跟踪时间(以ms为单位)当客户最后一次移动的时候,并且,移动的请求如果比通常的极限
更快到来,这个请求应该被抛弃。不要记录这类虚假请求,因为这可能是因为网络延迟(也就是玩家延迟,
过去的10秒内发送的数据同时到达了)。
检查距离。如果一个玩家试图和100亿公里以外的玩家交易(或者甚至在另一张地图上),记录下来。如
果一个玩家试图查看,或者使用一个遥远的地图对象,记录它。小心假的ID。例如,正常情况下每个玩家
都会分配一个ID(ID在登陆的时候分配,可以是持久的(唯一ID)。如果ID在玩家登陆的时候赋予9或怪
物被创建的时候),显然可以用玩家数组(保存玩家)的位置(索引)作为ID。
所以第一个登陆的玩家ID是0,第二个是1,依此类推。现在,通常你会有一个限制,比如说2000个
索引在玩家列表里。所以如果一个客户端发送一条命令类似:“查看ID200000的角色”,这会使服务器当机,
如果没有防备的话,因为服务器会访问非法的内存区域。所以,一定要检查,就像这样:"ifactorid<0or
ifactorid>maxplayers然后记录非法操作并且断开玩家。如果你使用C或者C++,注意或者定义索引
为’’unsignedint’’并且检查上限,或因为某些原因定义为int(int,默认是有符号的),记得检查<0and>max。
没有做这些会严重挫伤你和其他用户。类似的,要检查超出地图坐标。如果你的服务器有某种寻路算法,
并且客户端通过点击地面来移动,确保他们不要点击在地图外部。
第六步:获得一个团队
制作游戏需要大量的工作(除非是个PongandTetris游戏)。尤其是MMORPG。你无法单靠自己。理论
上,一个完整的团队组成是这样:
至少3个程序员:1个做服务器,两个客户端(或者一个客户端,一个负责工具,例如美术插件,世
界编辑器,等等)。有6个程序员是最好的,更多就没必要了。这取决于你的领导能力。最少一个美工,2
到3个更合适。如果这是个3D游戏,你需要一个3D美工,一个2D美工(制作帖图,界面,等等),一个动
画师,和一个美术部负责人。美术部应该由有经验的人组织和安排,除非你就是个艺术家。
少数世界构建者:创建所有地图是个漫长的过程,并且直接关系到游戏的成败。再次,你需要一个世
界构建部的负责人。你的世界需要协调一致,所以不能只有一个意气用事的人。
一个网站管理员是必须的,除非你精通网站设计,并且愿意花时间做网站。音效和音乐不是必须的,
但是有音效和音乐的游戏比没有的会更吸引人。
一个游戏经济系统设计师.。你也许觉得那很简单,可以自己来做,但事实上那是最复杂的工作之一。
如果经济系统设计不良(比如物品没有平衡,资源在地图上随意放置,等等。)玩家会觉得无聊并且退出游
戏。我们早期的进展存在很大的问题,尤其是因为经济系统主要是由我(一个程序员)设计的,它没有被恰
当的计划。于是,我们花费了两个月来重新思考和建立一整个新的经济系统。这需要一次完全的物品清
除。我告诉你,玩家会很不乐意你删除他们的物品。幸运的是,大部分玩家赞同这个想法,但是这么多小
时的争论,妥协,解释和时间的浪费还是让我们丧气。以后会更多。
如前所说,你需要一个10~15人的团队,不包括协调员和管理者。这10~15人必须是有经验的。如果都
是新手就不值得,因为你需要花大量时间解释要做什么,怎样做,为什么他现在的做法不好,等等。
一开始就凑齐10~15人几乎是不可能的。不管你在不同的论坛发多少帖,你也无法找到合适的团队成
员。毕竟,如果一个人熟练于他/她的领域,为什么在你无法拿出任何东西的时候他/她要加入你的团队?
很多人有远大的想法,但是实现它们需要大量时间和努力,所以他们宁可从事自己的工作也不会加入你。
那如果你需要10~15人,但是无法让他们加入你的团队,你如何才能制作一款MMORPG呢?好,事实上,
你一开始不需要所有人都到位。你真正需要的是一个程序员和一个美工。如果你是个程序员,只要找个美
工就可以了。请求懂美术的朋友帮忙,花钱请大学生/朋友做一些美术或者其他工作。
现在你有了一个美工,你期待的游戏的样子,现在可以开始实现了。一旦你有了可以运行的C/S引擎,
一些用来展示的截图(或者更好,玩家可以登陆你的世界,四处走动,聊天),更多的人会愿意加入你的团
队。更恰当的是,除非你使用独有的技术,否则你的客户端可以开源。许多程序员会加入(作为志愿者)一
个开源工程而不是非开源项目。而服务器不应该开源(除非你打算做一款完全开源的MMORPG)。
其他一些忠告:在有东西可展示之前,不要夸大你的游戏。最惹人烦的事情之一就是一个新手发一个
“需要帮助”的请求,要求一个巨大的团队加入他的游戏制作,解释这个游戏到底有多酷。一旦你拥有了网
站广告(通常是在一个免费主机),你会看到一个吸引人的导航条,包含“下载”,“截图”,“原画”(译者注,
原文:Conceptart,概念艺术,在游戏应该指美工的原始设计),“论坛”。你点击下载链接,然后看到美妙
的“建设中”页面(或者更糟糕,一个404错误)。然后你点击截图,得到同样的结果。如果你没有东西给人下
载,就不要放下载链接。如果没有截图展示,不要放截图链接。然而更好的是,在工程进展10%(程序和美
工)之前,不要浪费时间在网站上。
第七步:打破某些神话
你无法制作MMORPG,只有大公司才可以。
我不同意。虽然制作一款像魔兽世界(WorldofWarcraft),无尽任务2(EverQuest2),亚瑟王的召唤
2(Asheron’’sCall2),血统2(Lineage2),和其他一些游戏对一个小的自发团队是不可能的,但是做一款像样
的游戏还是可以的,只要你有经验,动机,和时间。,你需要1000小时的编程来制作一个可运行的测试版,
大概10~15k小时完成几乎完整的客户端和服务器。。但是作为团队领导者,你不能只编程。保持团队团结,
解决争执,维护公共关系(PR),技术支持,架设服务器,惩罚捣乱分子,自由讨论,等等都是你的职责。
你可能会被非编程的任务淹没。你很可能需要上班/上学,这减少了你花费在项目上的时间。我们很幸运,
没有成员离开团队,但是如果这种事情发生,那的确是大问题。假设你的美工半途离开。或者更糟糕,他
/她没有给你使用他/她作品的许可。当然这可以通过和他们签订合同来解决,但找另外一个美工仍然很麻
烦。一个工程中有两种不同的美术风格也是问题。
需要大笔金钱(通常4-6位数)用来架设一个MMORPG服务器.
当然,这不是真的。我见过专业服务器,1000GB/月,不到100美元/月(2~300美元的初装费)。除非你
的数据传输协议设计非常不合理,1000GB/月对一个1000玩家在线(平均)的服务器来说足够了。当然,你还
需要另一个服务器做网站和客户端下载(客户端下载会占用大量流量,当游戏变得流行的时候)。我们的客
户端有22MB,有时候会有400GB/月的传输量。而我们还没有很流行(仍然)。另一件事,我们不需要另一台
专用服务器开启这个工程。ADSL/cable服务器可以胜任,直到你的同时在线人数达到20~30。然后要么找
一个友好的主机公司,用广告交换免费主机,要么就只能自己掏腰包了。
制作一个MMORPG很有趣。
这不是真的。你可能认为每个人都会赏识你,玩家会支持你,你标新立异,并且,当然,很多玩家都
玩你的游戏。玩家可能让人讨厌。即使是完全免费的游戏,他们也能找到理由抱怨。更糟糕的是人们经常
会抱怨矛盾的事。战士会抱怨升级太难,商人会对战士掠夺大量钱财很失望。如果你减少怪物掉落物品,
有些玩家就会威胁说要退出游戏。如果你增加,同样的一群人会不满新手能更简单赚钱的事实。真是左
右为难。改革和改进是必须的。如果你决定改变某些东西,例如给加工物品增加挑战性,有些人会说太难
了。如果你不做,他们又会说太简单无味。你会发现满意的玩家通常不会说什么并且感到满意,同时破坏
者会怨声载道。
MMORPG的经济比单机版难以平衡的多。在单机游戏,你可以逐渐改良武器,只要玩家进展,他/她
可以使用更好的装备,丢弃(或者卖掉)旧的。另一方面,在多人游戏里,这种观点不成立,因为每个人都
试图得到最好的武器,而跳过低等级武器。大部分玩家宁可空手省钱,直到他们能买游戏中最好的武器。
经济系统设计要参考相关的文章。
迄今为止我列举的所有事情,加上额外的工作和挑战,足以让你在决定涉足这个工程之前三思而行。
你必须知道你的决定意味着什么。
总结
希望这篇文章能给你足够的知识。我的下一篇文章将介绍如何建立一个经济系统(更明确的,要避免哪
些错误),还有一些调试服务器和客户端的信息。
关于作者
这篇文章作者是RaduPrivantu,永恒大陆(EternalLands)的主程序和项目规划,
永恒大陆是一款免费,客户端开源的MMORPG。作者可以通过chaos_rift@联系。
斜45度游戏开发(一)
作者:Flysky2005
注:写的十分粗糙,如果看不懂请对照KgameV1.0源代码浏览。
最好的游戏末过于RPG游戏了,但如果赢得大众的好评,那么必须要采用(甚至说现在是一种标准)
斜45度地图、人物游戏引擎,下面我们分别展开分析。
一.地图数据结构
Soft的《圣剑英雄传二》定义的就不错,可以参考它的。
定义地图结构以前,我们要先定义Tile结构,分两种情况。
(1).物品、景物等按NPC处理(不规则处理,如“魔力宝贝”):
typedefstruct{
boolIsGround;//是否显示地表
shortGroundPicNum;//地表图片页面编号(0~59),,,(0-55)页为静态,(56~59)页为动态
shortGroundPicX;//地表材料在地表图片上的横坐标(以格子为坐标)
shortGroundPicY;//地表材料在地表图片上的纵坐标(以格子为坐标)
shortBlock;//阻碍标志(ID_BLOCK_T,ID_BLOCK_F)
shortHook;//陷阱标志(ID_HOOK_F,ID_HOOK_T…………)
charHookScriptName[28];
charRerve;//保留位,(我估计我们肯定还有想不到的一些信息,以后可以在这里添加,以
免地图编辑完成之后,再修改cell结构时可避免重新编辑地图文件)
}stCell;
具体我也不说什么了,注释都很清楚!
(2).物品、景物也按Tile处理但NPC按不规则处理(如:“仙剑奇侠转”,强烈不推荐!)
typedefstruct{
boolIsGround;//是否显示地表
shortGroundPicNum;//地表图片页面编号(0~59),,,(0-55)页为静态,(56~59)页为动态
shortGroundPicX;//地表材料在地表图片上的横坐标(以格子为坐标)
shortGroundPicY;//地表材料在地表图片上的纵坐标(以格子为坐标)
boolIsObject1;//是否显示物品1
shortObject1PicNum;//地表图片页面编号(0~59),,,(0-55)页为静态,(56~59)页为动态
shortObject1PicX;//物体材料1在物体图片上的横坐标(以格子为坐标)
shortObject1PicY;//物体材料1在物体图片上的纵坐标(以格子为坐标)
boolIsObject2;//是否显示物品2
shortObject2PicNum;//地表图片页面编号(0~59),,,(0-55)页为静态,(56~59)页为动态
shortObject2PicX;//物体材料2在物体图片上的横坐标(以格子为坐标)
shortObject2PicY;//物体材料2在物体图片上的纵坐标(以格子为坐标)
shortBlock;//阻碍标志(ID_BLOCK_T,ID_BLOCK_F)
shortHook;//陷阱标志(ID_HOOK_F,ID_HOOK_T…………)
charHookScriptName[28];
charRerve;//保留位,(我估计我们肯定还有想不到的一些信息,以后可以在这里添加,以
免地图编辑完成之后,再修改cell结构时可避免重新编辑地图文件)
}stCell;
太清楚了,我也不解释了!
下一步就是地图了,地图的处理不管是什么方法大同小异.
intID;//地图编号
charName[32];//地图名称(地图文件名)
intWidth;//宽度(以格子为坐标)斜
intHeight;//高度(以格子为坐标)斜
intMapStartX,MapStartY;//左上角坐标
stCell**Cell;//动态格子
charFileName[32];//当前地图文件名
charScrFName[32];//地图初始化脚本文件名
charRerve[4];//保留位,(我估计我们肯定还有想不到的一些信息,以后可以在这里添加,
以免地图编辑完成之后,再修改Map_struct格式,破坏原先编好的地图文件)
//这里先不用看,到了那里再说。
LPDIRECTDRAWSURFACE7lpDDS_MapBack;//地图临时保存点
LPDIRECTDRAWSURFACE7lpDDS_TMou;//Tile鼠标
intMapBx,MapBy;//用来优化机器
这样不就介绍完了吗?简单吧,不过后面的越来越难,做好心理准备。
二
.
坐标转换
各大文章在这里都快讲疯了,我不想说推理方法了,你记住以下函数就可以:
//斜45度的坐标转换成屏幕坐标
inlinevoidMIToMD(intDx,intDy,int&Ix,int&Iy)
{
Ix=(TileWidth>>1)*(Dx-Dy);//转换为绝对坐标x
Iy=(TileHeight>>1)*(Dx+Dy);//转换为绝对坐标y大菱形
}
//屏幕坐标转换成斜45度的坐标
inlinevoidMDToMI(intIx,intIy,int&Dx,int&Dy)
{
Dx=int(0.5*((Iy<<1)+Ix)/(TileWidth>>1));
Dy=int(0.5*((Iy<<1)-Ix)/(TileWidth>>1));
}
其中
#defineTileWidth32//每个Tile的宽
#defineTileHeight16//每个Tile的高
三.绘制地图
绘制地图有什么难的,错!地图绘制好了FPS能提高数倍。
传统的地图绘制方法:
for(inti=0;i
{
for(intt=0;t
{
绘制。。。。。
}
}
经过改进我这样做
if(MapBx!=x&&MaxBy!=y)
{
for(inti=0;i
{
for(intt=0;t
{
绘制。。。。。
更新MaxBx和MaxBy
备份地图(存成图片)
}
}
}
el
{
还原地图(取出图片)
}
这样空闲状态的FPS就会提升,什么?还想提升,这样办吧!
推理方法从略(其实我也不记得的,春天写的):
#defineMapDMXint(0.5*((ScreenHeight<<1)+ScreenWidth)/(TileWidth>>1))+1//最大画出点X
#defineMapDMYint(0.5*(ScreenHeight<<1)/(TileWidth>>1))+1//最大画出点Y
#defineMapDSYint(0.5*(-ScreenWidth)/(TileWidth>>1))-1//最小画出点Y(特殊)
然后
if(MapBx!=x&&MaxBy!=y)
{
for(intY=IntSizeL(MapStartY+MapDSY,0);Y
{
for(intX=IntSizeL(MapStartX-1,0);X
{
绘制。。。。。
更新MaxBx和MaxBy
备份地图(存成图片)
}
}
el
{
还原地图(取出图片)
}
其中IntSizeL取两个数大的,IntSizeS取两个数小的!
速度优化到极限了,好了,先写到着,下一次我再说遮挡。
斜45度游戏开发(二)
作者:Flysky2005
上一篇我们讲了地图的结构和地图的基本的绘制,这个属于较简单的步骤,我做这些也不是特别的麻
烦,如果你是老手,那么1天对你来说已经足够,我们这一组文章也没有讲任何的优化技巧,如果你想优
化,还是需要在实际中摸索,当然,我有时间也会写的。
这一篇我们主要研讨地图的物品、景物、NPC建立,并说一下遮挡的简单实现方法。
NPC主角结构:
主角和NPC在一起比较好,也便于管理(再次声明:作者已经不使用这些方法,这些方法只适用于初学
者!),结构如下:
structstRoleC
{
//判断
boolIsNPC;//是不是NPC
//----------Role基本--------------
char*Name;//NPC名字
boolVS;//是否有这个人物(是否可见)
intx,y;//人物的XY坐标值
RECTroler;//人物矩形
intface;//NPC面向的方向0.下1.上2.左3.右
char*facePic;
//----------属性值-------------
intHP,MP;//目前的HP,MP
intMaxHP,MaxMP;//满HP和MP
intLevel;//目前的等级
intExp;//目前的经验
intMaxExp;//满血的经验
//----------寻路相关--------------
boolmove;
intmovex,movey;//移动目标点
//----------NPC相关---------------
char*EScrFName;//当触发人民的脚本文件名
};
structstRole
{
//判断
boolIsNPC;//是不是NPC
//----------Role基本--------------
charName[32];//NPC名字
boolVS;//是否有这个人物(是否可见)
intx,y;//人物的XY坐标值
intoldx,oldy;//人物上一步的XY值
RECTroler;//人物矩形
POINTPicS;//距像素坐标的偏移
intface;//NPC面向的方向0.下1.上2.左3.右
boolfacejd;//上一步的站向
charfacePic[64];
intfacej;//NPC方向的脚步0123
vector
//----------属性值-------------
intHP,MP;//目前的HP,MP
intMaxHP,MaxMP;//满HP和MP
intLevel;//目前的等级
intExp;//目前的经验
intMaxExp;//满血的经验
//----------寻路相关--------------
boolmove;
vector
intPathSteps;//步数
intPathCSteps;//已经走过步数
intmovex,movey;//移动目标点
//----------NPC相关---------------
char*EScrFName;//当触发人民的脚本文件名
boolWalkLoop;//是向前走还是倒退
//----------Surface--------------
LPDIRECTDRAWSURFACE7lpDDS_ROLEP;//Role的图片
LPDIRECTDRAWSURFACE7lpDDS_ROLEL;//Role的logo
};
这两个结构其中stRole是内部储存的结构,而stRoleC是对外的接口,其实你完全不必那样,用一个
也可以,具体的完全随你便。
注意:以下的代码和KGameSrc一点关系也没有。
景物的建立:
structst_scene
{
intx,y;//房子等东西右下角的坐标
intWidth,Height;//物品的大小,利用这个可以选出房子等左上角的坐标
LPDIRECTDRAWSURFACE7lpDDS_SCENEPIC;//房子等的图片
unsignedshortb_isablock;//0:不阻碍1:全部阻碍2:使用阻碍表
struct{unsignedintdata;}**v_block;//阻碍表,比如房子,门那里是不遮挡的
//注意:因为我们构件的是<<魔力宝贝>>式,所以陷阱并不是归这里
};
我也注释了,应该很好看吧。
遮挡问题:
这个确实比较棘手,如果是单格遮挡,可以看云风的遮挡算法,可我们是多个格的,怎么办呢?
根据几位前辈的讨论,他们想出了使用X+Y算法(画家算法),但当时不是很成功,我就做一下补充。
首先,把地图上的一切东西(不管是物品还是主角)串成一个链表(遮挡链表),然后根据X+Y的值有
小到大(左上到右下)排列,最后绘制,这样就完成了,很简单吧(哪位可以帮我画一下图,感激不尽.)
优化方法也有:那就是这个景物的排列让地图编辑器排列,然后这个不动了,光人物在链表中的位置
移动,这样就完成了快速遮挡。
好了,基本的东西我也写完了,由于现在我在做操作系统,语言说的太急,下一次我会把更好的优化
代码和例子程序给大家,谢谢大家观看!
脚本系统的实现
作者:Flysky2006
终于又有时间写文章了,这回我们要实现一个简单的脚本系统(很简单的)!
说实话,原来《电脑报2004年合订本》有个很好的直角90度游戏开发系列,里面的游戏脚本系统讲
得还可以,如果你对本文还存在疑惑,可以看那一个游戏开发系列。
和以前一样,我在脚本系统中没有掺合任何关于DirectX的内容,只讲理论!
看看KGameV1.0(2005年5月初次在gameres中出现,就是13岁开发游戏的那个文章:))的脚本系统实现.
首先是脚本类:
classCScript
{
charName[64];
vector
boolScrIsFill;//脚本运行时是否使用黑屏
stringstr;//一行完整的指令
stringM_CMD;//M_CMD命令,M_CAN临时储存参数
stringM_CANX[MAX_CANX];//参数数组
stringM_CAN,M_RIGHT;
stringM_VAR;//变量
stringM_value;//变量,如果为空,就为"="右边去掉";"的数据,否则为"="右边脚本的返回值
stringM_IFO,M_IFT;
intCanB[MAX_CANX];//参数是布尔的话在这里储存
public:
CScript();
~CScript();
boolLoadScript(char*FileName);
voidScriptXCanC();
voidShowERROR(stringM_CMD);//显示错误
voidFenJScript();
voidRunScript();
voidFenJVar();
boolBScriptBg(char*FileName);
boolAddVar(stringName,stringvalue);
boolClearVar();
};
其实就只有这些东西,读取一个脚本,弄出一行的内容,分析一行的的内容(这时候边分析边执行相应
的函数),添加一个变量,删除一个变量,等等.....
读取脚本的实现(其实这个KGameV1.0写得并不好,我自己承认的,但起码做出来了,呵呵!)
boolCScript::LoadScript(char*FileName)
{
ifstreamis;
intZS;
intsta,end;
if(IsFile(FileName)==fal)returnfal;//直接返回,因为不存在
IsNextLine=true;
strcpy(Name,FileName);
(FileName);
if(IsNextLine==true)getline(is,str);
ZS=int(("//"));
str=(0,ZS);//去掉注释语句
replace_all_distinct(str,"'","");//自己写的替换函数
replace_all_distinct(str,syh,"");//自己写的替换函数,syh是双引号的ASCII码
if(stricmp(str.c_str(),"return;")==0||stricmp(str.c_str(),"return();")==0)break;
elRunScript();
//IF语句的实现
if(stricmp(M_CMD.c_str(),"If")==0)
{
intIfP=2;
//If(X(Y))肯定条件,相符运行语句
if(M_CANX[0].find("(")!=-1)
{
sta=int(M_CANX[0].find("("));//比如IF(XX[(]XX))
end=int(M_CANX[0].find_last_of(")"));//比如IF(XX(XX[)])
M_IFO=M_CANX[0].substr(0,sta);//获取IF里的命令
M_IFT=M_CANX[0].substr(sta+1,end-sta-1);//获取if里的变量
//strcpy(SMessage,M_IFO.c_str());
if(M_IFO==M_IFT)IfP=0;//0为通过
elIfP=1;//1为不通过
}
//If(X[Y])否定条件,不符运行语句
if(M_CANX[0].find("[")!=-1)
{
sta=int(M_CANX[0].find("["));//比如IF(XX[XX])
end=int(M_CANX[0].find_last_of("]"));//比如IF(XX[XX])
M_IFO=M_CANX[0].substr(0,sta);//获取IF里的命令
M_IFT=M_CANX[0].substr(sta+1,end-sta-1);//获取if里的变量
if(M_IFO!=M_IFT)IfP=0;//0为通过
elIfP=1;//1为不通过
}
if(IfP!=2)
{
if(IfP==0)
{
while(str!="}")//什么时候结束
{
if(IsNextLine==true)getline(is,str);
ZS=int(("//"));
str=(0,ZS);//去掉注释语句
replace_all_distinct(str,"'","");
replace_all_distinct(str,syh,"");
if(("return;")!=||("return();")!=){break;break;}//结束
脚本
elRunScript();
//二级IF
//完成
}
}
el
{
while(str!="}")//什么时候结束
{
getline(is,str);//到"{"这一行
}
}
}
IfP=2;
M_();
M_();
}//IF结束
//结束
}
M_();
M_();
();
returntrue;
}
其实IF语句的实现占了代码比较多的比例,简单的方法就是ifstream这个东西有一个读取一行的函数
substr,具体的自己研究一下吧,也可以用KGameV1.0的代码学习.
运行了这段代码后,脚本就被切成一行一行的(此时"//"和这行后面的内容早已删去),下一个函数:
voidCScript::RunScript()//运行一行脚本
{
intVarVa=int(_first_of("="));
intsta=int(_first_of("("));
intend=int(_last_of(")"));
M_VAR=(0,VarVa);//获取变量
M_CMD=(VarVa+1,sta-VarVa-1);//获取命令
M_CAN=(sta+1,end-sta-1);//获取全部参数
M_RIGHT=(VarVa+1,strlen(str.c_str()));
for(inti=0;i
{
replace_all_distinct(M_CAN,"["+VARA[i].Name+"]",VARA[i].value);
replace_all_distinct(M_CAN,VARA[i].Name,VARA[i].value);
}
for(inti=0;i
{
sta=int(M_(","));
M_CANX[i]=M_(0,sta);
M_e(0,sta+1,"");
if(stricmp(M_CANX[i].c_str(),"Fal")==0)CanB[i]=0;
elif(stricmp(M_CANX[i].c_str(),"True")==0)CanB[i]=1;//布尔变量读取
}
if(M_()!=0)FenJScript();//执行脚本
if(M_()!=0)FenJVar();//分析变量,因为脚本有的有返回值
}
我的代码风格不是很好,凑合看看吧!
这段就是把这一行给拆开,M_CMD就是gggg(1fs,fs)的gggg,M_CAN是1fs,fs的字符串,M_CANX就是
一个数组,里面存贮着一个个参数,比如这里M_CANX[0]="1fs",M_CANX[1]="fs",注意我使用的是STL的
string,具体的资料可以上网查.
把命令和参数分开了,下一步就是执行了.
if(stricmp(M_CMD.c_str(),"Clo")==0)RunMessage(MS_Clo,0,NULL,0,0,0,0,NULL,0);//命令2,关闭
引擎
elif(stricmp(M_CMD.c_str(),"NewMap")==0)//创建地图
{
strcpy(Tempc,M_CANX[0].c_str());t5=atoi(Tempc);
strcpy(Tempc,M_CANX[2].c_str());t1=atoi(Tempc);
strcpy(Tempc,M_CANX[3].c_str());t2=atoi(Tempc);
strcpy(Tempc,M_CANX[4].c_str());t3=atoi(Tempc);
strcpy(Tempc,M_CANX[5].c_str());t4=atoi(Tempc);
strcpy(Tempc,M_CANX[6].c_str());t6=atoi(Tempc);
strcpy(Tempc,M_CANX[1].c_str());
strcpy(tmpc,M_CANX[7].c_str());
RunMessage(MS_NWMAP,t5,Tempc,t1,t2,t3,t4,tmpc,t6);
}
elif(stricmp(M_CMD.c_str(),"ReadATile")==0)//读取一个TILE资源到表面
{
strcpy(Tempc,M_CANX[0].c_str());t1=atoi(Tempc);
strcpy(Tempc,M_CANX[1].c_str());
RunMessage(MS_RAT,t1,Tempc,0,0,0,0,NULL,0);
}
elif(stricmp(M_CMD.c_str(),"NewVar")==0)AddVar(M_CANX[0],M_CANX[1]);//创建变量
elif(stricmp(M_CMD.c_str(),"ClearVar")==0)ClearVar();//清除所有的变量
.....//后面的大同小异
再声明一次,这个脚本系统只是我学习C++之作,现在绝对不会这样写了,使用KGameV1.0这个源代
码也是迫不得已,因为目前我只对这个源代码熟(虽然圣剑得更好,但他的实现太麻烦)
可以看出这里基本都重复了,就是把这些参数给消息系统(当然现在我的实现方法不会说的:))
剩下的就是变量的处理了,看到这部分代码上面不明白的代码基本就明白了!
voidCScript::FenJVar()
{
if(M_()==0)
{
M_value=M_RIGHT;
replace_all_distinct(M_value,";","");
}
for(inti=0;i
if(VARA[i].Name==M_VAR)VARA[i].value=M_value;
M_();//返回清空
}
boolCScript::AddVar(stringName,stringvalue)
{
stVariableTVar;
=Name;
=value;
_back(TVar);//压这个变量
returntrue;
}
boolCScript::ClearVar()
{
//清空变量
();
returntrue;
}
再补充stVariable结构:
//变量结构
structstVariable{
stringName;//名称
stringvalue;//当前值
};
这段代码是后期写的,因为前期的不好构架,所以后期也不能大改了:(详细地看注释吧!
再一次草草完成一篇文章,其实这篇文章是给自己看的,免得以后忘了这段游戏开发的经历!
斜45度游戏开发(提高篇)
作者:Flysky2006
这篇文章我们主要讲的是遮挡的详细制作方法,我认为,如果你知道了上面几篇的原理,应该也能制作出
来,如果你制作出来了,就请不要往下看了,下面的很可能对你来说是浪费时间。
好,那我们开始吧。
方法一
(
对前面的那篇文章的改进
)
首先我们要学会union的使用,看下面的结构:
typedefstructst_mapunit{
BYTEb_magic;/*魔法数字*/
intx,y;/*这个图片右下角的坐标*/
intwidth,height;/*长度,宽度*/
LPDIRECTDRAWSURFACE7pic;
union{
st_npcnpc;/*NPC结构*/
st_thingsthing;/*景物结构*/
}u;
};
然后我们需要一个类,这个类包括NPC类、景物、显示类,还有一个可变大小的数组,我们可以使用vector
类组织这个(或用链表):
vector
声明两个宏:
#defineMAGIC_NPC0x0001
#defineMAGIC_THING0x0002
这样我们就可以对他们进行排序了,方法也很简单,在KGameV1.0的NPC里面出现过,我认为人物的插
入方法很好,但排序算法不是很好,你需要选择更好的方法。
注意:KGameV1.0当时没选用这个方法的原因是因为当时已经写了许多的代码了。
大家应该知道怎么做了吧,大类负责提供接口,小类当然是执行各个的工作了......
方法二
其实这个我也没有试过,但我认为是可行的,但速度可能比上面的差一些(但不多),但这样设计更符合
面向对象的方法。
1.分层类建立
这个我们需要链表,先看高级页面类:
classhsurface{
hlayer*hlayer_headle;/*层次列表的头*/
voidaddsurface(DWORDx,DWORDy,intlayer,LPDIRECTDRAWSURFACE7surface);/*添加一个
物件*/
LPDIRECTDRAWSURFACE7delsurface(intx,inty,intlayer);/*删除一个物件*/
voidclearhlayer(intlayer);/*清除一个层的所有物件*/
voidclearall();/*清除所有的东西*/
voiddraw(intlayer,LPDIRECTDRAWSURFACE7surface);/*将一个层绘到页面上*/
voiddraw(LPDIRECTDRAWSURFACE7surface);/*所有的层绘到页面上*/
};
现在应该有初步的认识了吧,如果你知道了原理,就不要往下看了(耽误时间)。
structhlayer{/*一个层结构*/
hlayer*next;/*下一个层的指针*/
inti_layer;/*这个层的索引值*/
hlayer_object*object_headle;/*这个层的物件的头*/
};
上面的这个是一个层的结构,层与层之间是成一个链的关系,我们又加上了i_layer这个值,这样我们就可
以让两个层之间的值不连续,最后一个便是物件指针的头,我们利用这个进行绘图。
最后一个结构:
structhlayer_object{
hlayer_object*next;
DWORDx,y;/*这个物件的X/Y值*/
inti_layer;/*这里大家可能有疑问,上面不时有了吗,这个是个验证值,验证这个物件的正确性
*/
LPDIRECTDRAWSURFACE7surface;/*这个页面*/
};
这样我们就说明完了,有几点注意的地方:
(1)X/Y值要按照90度的坐标,而不是针对的45度的坐标,这样更有普遍性.(如果你觉得这样就不能遮挡
了,那么你再看一遍上面的方法。
(2)我喜欢实现一个内存池,这样建立会更快。
2.将分层类运用到45度的游戏中
i_layer=0我们保留
每个东西:i_layer=x+y+1
云彩:i_layer=MAX_X+MAX_Y+1(注意,这个X+Y只是一个值,表明按一定的顺序绘图,具体的请看我的
45度教程。
什么东西最重要的还是看自己,我只是引了一个路,剩下的好办法还是你自己去挖掘(其实也不难)!!!!
关于游戏中的调度器
1.1调度游戏中的事件
一个调度其可以有效帮助以下游戏技术的实现,他们包括物理仿真,人物运动,碰撞检测,游戏中的
人工智能,渲染。在所有这些技术中有一个关键问题就是时间。在不同的时间里,当数百个不同的物体和
过程都需要更新时,这些仿真技术的很多种东西变得非常复杂。
调度器的重要能力在于它能够动态地增加和删除物体,这可以使新物体很平滑地加入到游戏里面去,
和其他游戏里面的物体一起参加仿真,然后在不需要的时候从调度里面把它删除。
1.1.1
调度器的组成
调度器的基本组件包括任务管理器,事件管理器和时钟。通过这些组件调度器就能生成基于时间或者
基于帧的事件,然后调用相应的事件处理器。
任务管理器处理任务的注册和组织。每个任务都有一个包含了一个管理器可以调用的回调函数的接
口。任务管理器维护了一个任务列表,其中包含了每一个任务的调度信息---例如开始时间,执行频率,持
续时间,优先级和其他的属性。他也可能包含一个用户数据的指针或者性能统计信息。
事件管理器是调度器的核心部分。任务管理器里面的每一个任务都定义了一个或多个其需要处理的事
件。一个事件指的是一个任务需要执行的时间。事件管理器的责任就是要产生必须的事件以执行相应的任
务。
真实时间与虚拟时间:一个真实时间的调度在概念上是很简单的—时间管理器不停地进行循环,察看
一个真实的时间时钟,每当目标到达的时候它就会触发一个事件。一个虚拟事件的调度器会把时间分成帧。
任务在帧之间以批处理的方式进行,在虚拟时间里运行,然后在每帧渲染出来的时候与真实的时间进行同
步。
时钟组件是用来跟踪真实时间,当前的仿真时间和帧数的。时间管理器负责事件的排序和产生。在某
些情况下,多个任务可能会设置在同一个时间运行。有较高优先级的先执行。如果优先级相等或者系统没
有优先级就轮流执行。我们经常需要动态地更改一个已经注册的任务属性,这可能会牵涉到更改他的优先
级,周期,持续时间或者要求在它找到还没有结束的时候就将它删除。为了能更新任务的属性,我们必须
使用一个外部的方法来找到他,可以使用一个唯一的注册ID来标志一个任务。
1.1.2
一个简单的调度器
调度器的设计主要集中在两个组件上面-----调度器引擎本身和ITask插件接口。要使调度器运行起来,
必须要有一个调用它的程序。在一个非图形里面的程序里面,这要求把它放在一个循环里面然后执行顺序
里面然后执行就可以。While(running)eFrame();有两种方法把调度器集成在一个消息驱动
的图形界面上。第一种方法是修改消息循环来处理消息和调用调度器。这是一个最容易想到的方法,但是
有个缺点,就是当窗口大小来来改变的时候调度器会停止工作。第二种方法是创建一个Windows时钟,利
用时钟消息来调用调度器。由于时钟消息并不会被窗口的拖动打断,调度器就可以在后台就接续运行了。
仿真:调度器可以用来驱动仿真系统。为了实现动画和碰撞检测功能,大多数仿真引擎都将时间分成独立
的小片。
我的理解:
调度器相当于一个总的控制器或者叫协调器,我称之为manager。游戏中的大部分事情可以看为一个
任务,例如渲染,碰撞检测,都可以看做一个任务,任务的执行是靠事件来驱动的,而事件在这里3种,
即:基于时间的事件,基于帧的事件,以及渲染事件。
基于时间的事件:该事件的发生依靠时间来判断,即当什么时间到了后,某个事件就会发生。
基于帧的事件:该事件的发生依靠帧来判断,即当游戏运行到指定帧后,某个事件就被触发。
渲染事件:该事件很特殊,它在每一帧里都会发生,因为每一帧游戏都会渲染。
在游戏里,我们把一切任务注册到调度器里,然后任务的执行就完全交给了调度器。调度器会负责什
么时候执行哪个任务,什么时候删除哪个任务,等等之类。
而为了实现让调度器能够处理一切注册进来的任务,调度器就加进了一个任务管理器。事实上,在这
里也就利用了语言的一个特性---定义了一个抽象类:
classITask
{
public:
virtualvoidExecute(intid,inttime,void*arg)=0;
};
要使用调度器,就需要从这个类继承下来处理所有任务。然后调度器通过ITask指针来操作所有任务。
看一下调度器的组成:
调度器为了知道什么时候基于时间的事件发生了,什么时候基于帧的时间发生了,于是就加进了一个
时钟组件(Clock)。因为事件有三类,当然与之对应的任务也有三类,于是调度器就维护了三类任务,分别
为渲染任务(其对应的事件只有一个),基于时间的任务,基于帧的任务。因此基于时间和帧的任务不止
一个,因此在这里又维护了两个链表。
游戏每一帧都会调用调度器的ExecuteFrame,该函数首先更新时间,然后执行所有会在本帧里执行的
基于时间的任务和基于帧的任务,以及渲染任务。
注意所有存储在链表中的任务实际上都是按照其执行的先后来排列的,这样程序每次都只需要从表头
元素进行处理。当程序执行完一次任务后,程序会检查该任务是否还需要进行执行,是的话就把它重新插
入链表,否则就标记为删除。
关于使用虚拟时间的好处,还不是很明白。
在DirectX中贴瓷砖:Part1
先决条件
对C/C++和DirectX知识都要有较好的基础,我希望读者应该能通过这个指南获得一些东西。我假定读
者能够使用DirectDraw并能够将图像文件加载到一个离屏表面。在本文中我不再描述怎样载入位图。
贯穿整篇文章,我将使用Windows/DirectX技术,但是如果你使用Dos或其他平台,你也不要担心,所
有演示的方法都是容易移植的:)
注意:我的所有代码不可能绝对没有Bug,而且我的技术可能也不是最有效的,但我将尽我的全力。一
起享受吧!
到底瓷砖是什么?
一块瓷砖就是一个小位图,是可以被重新置位的小块位图,有着相似或不同外观的小块一起拷贝到一个
表面上,就形成了你的游戏世界的外观了。相对于使用单一位图,“瓷砖”靠创建虚拟位图而节省了很多
内存。现在,普通的瓷砖都是32*32或16*16象素大小,而地图范围从几个瓷砖到几百个瓷砖不等。
在游戏的初始化或设置时,我们可以从ASCII文本文件中加载地图,其格式很象这样,“1”的位置就是一
个特殊的瓷砖,“2”的位置是的另一个瓷砖,以此类推。22222222222222222
211112
221122
211112
211112
22222222222222222
地图也可以被定义成字符数组,但这不是首选的。在这,我将使用这种格式,尽管我强烈建议从分离的
i/o文件中加载地图。
许多开发者喜欢用类或结构重新定义他的瓷砖,可以加入一些标记来指定不同的“运动”属性,扩展的项
目被用来放置特殊的瓷砖如可动的瓷砖和多重的瓷砖层,但是对于我们,仅仅把瓷砖贴到屏幕,使用瓷
砖类未免也有些过火了。
我们将定义我们的瓷砖组合就像如下所示,一个12*12个瓷砖的地图置入一个2维的字符数组里。char
map[12][12]={
{2,2,2,2,2,2,2,2,2,2,2,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,2},
{2,2,2,2,2,2,2,2,2,2,2,2}};
在这种格式中,我们能用map[x_tile][y_tyle]来访问每个瓷砖。在本例中,一个值为“2”的瓷砖就是一座
墙,或者一个实心的瓷砖,游戏中的角色不能穿越。ID#为“1”的瓷砖代表可以活动的地域。我们可以在
定义瓷砖的类型,只要设置map[x_tile][y_tile]的值就行了如:map[x_tile][y_tile]=new_id;
接下来我们要创建一个位图,它存储了每一个瓷砖的图像。你也能够创建你自己的瓷砖,或者从别人的
RPG中透取:)整理这些图像就好像这样。(图片略)
注意:
在画瓷砖的函数中,因为设置了RECT所以这种格式才能工作。如果你更喜欢用其他方法设置你位图中
的瓷砖,你必须相应的设置RECT
最后,我们能创建一个函数将字符数组中的“1”和“2”变成图像,并用DirectX显示之。你必须确信你已
经设置好DirectX并能运行之。我将用完我的每一段代码“NeHestyle”,因为他给了我指南的灵感,他
的指南总是那么容易理解。
#defineTILE_SIZE32
#defineWORLD_SIZEX12
#defineWORLD_SIZEY12
在这我定义了32*32象素的瓷砖,地图的大小为12*12个瓷砖。
voiddraw_tiles(void)
{
inttile;
intx;
inty;
RECTtile_src;
tile将被用在后面,用来决定map[y][x]的值,举个例子,当我们通过这个循环运行到地图数组时,
map[y][x]有一个ID#值为1或2将存入tile.x,y是两个变量用来遍历整个数组并且在适当的位置画出瓷
砖。RECTtile_src将用来指定每个瓷砖的位置,指向瓷砖图像将被画入离屏页面的位置。
for(y=0;y
{
for(x=0;x
{
tile=map[y][x];//tile存储了特殊瓷砖的ID
#这段代码用两个“for”循环来遍历地图数组并且捕获每一个瓷砖的ID#并存入tile变量中。
注意:我们的函数一个接一个遍历每个瓷砖并且一个接一个画到表面上,那是因为这样就好像把所有的
瓷砖被同时画到页面上。
tile_=(tile?1)*TILE_SIZE;
tile_=0;
tile_=tile*TILE_SIZE;
tiel_=TILE_SIZE;
tile_srcRECT的设置依赖于存储在tile中的瓷砖的ID值。
BltFast(x*TILE_SIZE,y*TILE_SIZE,lpddsoffscreen,&tile_src,NULL);
}
}
}
现在我们把瓷砖画到了屏幕的相应位置,这要使用x,y变量乘以32象素得到。我使用lpddsoffscreen作
为表面的名称,你可以用不同的名字。
结论
这是我的第一篇发表于的文章,我计划在将来写更多的文章。如果文章帮助了任何在
DireceX或贴瓷砖上有问题的人,我将感无比开心。如果文章中有什么错误,如果你喜欢我憎恨本人请
发邮件给我lpsoftware@.如果本文的帮助下,你创建了一个游戏,也请告诉我。
接下来的文章
接下来的文章将基于本文,并且尽可能包括滚动大地图的技术,剪切瓷砖的技术,碰撞检测和动画瓷砖
的技术。
在DirectX中贴瓷砖:Part2
绪论
朋友们,大家好,欢迎来到新的章节!在第二个部分我将在第一部分的基础上继续讨论贴瓷砖这个话题。由于
缺乏时间在本文中我仅仅讨论平滑滚动的问题。这样可能会使你们当中的某些人失望,但是请理解我,我是学
生,我有自己的要做的事,我的时间日益减少,对不起了。
平滑滚动
你是否记得第一部分的内容,我们设置了一个简单的瓷砖引擎,其中使用了一个
2
维的数组,并用
DirectDraw
的BltFast()将瓷砖贴到屏幕。我们不能仅此而已!我们即将一个大地图用滚动效果加入你的游戏。不要认为这
步是令人畏缩的。一旦你理解了这个原理,其实还是非常容易的。
因为我们中的有些人不懂“平滑滚动”,所以我要谈一谈一个基于瓷砖的背景在精灵背后的移动这一效果的实施。
尽管主角(或在你游戏中所有你想要控制的东西)正要从一个格子运动到另一个格子,但它能被画到屏幕的同
一位置。知道超级马丽吗?,它就是一个很好的平滑滚动的例子,想不想实现它?
原理
滚动的关键是保存大地图的当前轨迹这样就可以计算出哪些瓷砖要被画到屏幕。举个例子,假设按下键盘的右
方向键,游戏的精灵将向右移动两个瓷砖。在你的画瓷砖的函数中,你可以认可这个改变,并且画出被切换了
两个瓷砖的一系列瓷砖,或者说将游戏背景向右移动64像素。结果你的精灵看上去就好像改变过了一样。如果
这个解释很难理解的话,我很报慊,但是我希望你能清晰的理解,以便我们能继续。
接下来我们将创建两个变量来存储地图中的改变。这两个变量将用在最后版本的Draw_tiles()函数中以决定哪些
瓷砖要被画到屏幕。原理就这些了,如果你有一些主意关于你的游戏,赶快加入这些技术到你的代码中吧。
设置
在我们重写draw_tiles()这个函数前,我们必须定义一些常量。
#defineTILE_SIZE32
#defineWORLD_SIZEX20
#defineWORLD_SIZEY20
#defineSCREEN_SIZEX12
#defineSCREEN_SIZEY12
在这儿我们定义了瓷砖的尺寸是32像素,地图的大小为20*20个瓷砖,屏幕大小为12*12个瓷砖。这样我们就有
12*12(144)个瓷砖能够被同时显示在屏幕上,于此同时有20*20个瓷砖在我的我们的数组中。我们必须建立一
个比屏幕可视区域更大的地图这样我们就能鉴证滚动的行为了。注意:你设置
DirectX
应用的决定,将使得你可
能不能看到整个被瓷砖填充的屏幕。
intworld_camerax=0;
intworld_cameray=0;
我在原理那节中提到的两个变量world_camerax和world_cameray,是为了跟踪地图中当前的位置。它们将
决定哪些瓷砖要被画到屏幕上。现在,我们初始化这两个变量为
0
,因此我们的当前位置是(
0
,
0
),或者说在
地图/数组的最左上方。world_camerax将存储X位置,或者说与0点的X轴距离。world_cameray将存储Y的
位置,或者说与0点的Y轴距离。如果你有问题请回顾原理一节。如果你仍然有困难,那么那可能是我的过错了。:)
最后要做的是定义新的地图格子(20*20个瓷砖),这要比向导一中的地图大多了。
charmap[WORLD_SIZEY][WORLD_SIZEX]={
{2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,2,2,2,2,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,1,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,1,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1,2,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2},
{2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},};
漂亮的地图格子就在这,随同一个小消息。:)
平滑的画出这些瓷砖
现在我们重写draw_tiles()这个历程
voiddraw_tiles(void)
{
inttile;
intx,y;
intscroll_x,scroll_y;//(NEW)
intofft_x,offt_y;//(NEW)
RECTtile_src;
tile用来决定map[scroll_y][scroll_x]值,例如,当我们通过遍历地图数组时,map[scroll_y][scroll_x]将有一个
ID#=1或2。x,y这两个变量决定在确切的位置画出瓷砖。RECTtile_src用来指定每一个瓷砖对应的图像的位置,
指向屏幕的偏移。scroll_xandscroll_y变量将服务于函数就好比向导一中x,y变量服务于draw_tiles()函数一样。
你也许会问“为什么不再用x,y?”问得好。答案是对地图的一些计算必须用到x,y变量,就如下一步所示,计算
的结果将被保存在scroll_x和scroll_y中,这样我们就能够使用x,y的原始值来画出瓷砖。offt_x和offt_y
用来决定画出瓷砖的精确位置。这两个变量是平滑移动的核心变量。
for(y=0;y
{
for(x=0;x
{
//(NEW)
scroll_x=x+(world_camerax/TILE_SIZE);
scroll_y=y+(world_cameray/TILE_SIZE);
//(NEW)
offt_x=world_camerax&(TILE_SIZE-1);
offt_y=world_cameray&(TILE_SIZE-1);
tile=map[scroll_y][scroll_x];
ok,新代码在这。首先,用一个嵌套的双重循环来遍历我们的地图获得每一个瓷砖的ID#值。接下来,分别设
置scroll_x,scroll_y值为x,y加上目前为止已经滚过的瓷砖个数。然后设置绘图偏移量,我们要做的就是,将
瓷砖的最后位置减去偏移量。
tile_=(tile-1)*TILE_SIZE;
tile_=0;
tile_=tile*TILE_SIZE;
tile_=TILE_SIZE;
tile_srcRECT的设置依赖于存储在map[scroll_y][scroll_x]中瓷砖的ID。
//(MODIFIED)
BltFast((x*TILE_SIZE)-offt_x,(y*TILE_SIZE)-offt_y,
lpddsoffscreen,&tile_src,NULL);
}
}
}
现在我们将瓷砖画到屏幕确切的位置,用x,y的值乘以32再减去偏移量。我使用lpddsoffscreen作为DirectX表
面的名字,你的表面可以用不同的名字。
结束语
我们已经有了一个平滑滚动的引擎,那么我们怎么把他用在游戏中呢?那非常容易.你只要作如下的事情:
if(keypress_right)
{
world_camerax+=8;
}
if(keypress_up)
{
world_cameray+=8;
}
现在玩家可以用键盘控制地图的滚动。当然地图和玩家的运作还有许多工作要做,但我确信你一定能解决。:)注
意:在这的kepress_right等,你可以使用DirectInput或者Win32API输入函数来得到。如果你在文章中发现
一些错误或bugs,请告诉我。
本文由棋牌游戏()提供。
本文发布于:2022-11-26 23:36:39,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/27321.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |