`
wangxiaohigh
  • 浏览: 1419155 次
文章分类
社区版块
存档分类
最新评论

Java版SLG游戏开发入门[2]--数据的读取及保存

 
阅读更多
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="ProgId" content="Word.Document"> <meta name="Generator" content="Microsoft Word 11"> <meta name="Originator" content="Microsoft Word 11"> <link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cchenpeng%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"> <!--[if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:PunctuationKerning/> <w:DrawingGridVerticalSpacing>7.8 磅</w:DrawingGridVerticalSpacing> <w:DisplayHorizontalDrawingGridEvery>0</w:DisplayHorizontalDrawingGridEvery> <w:DisplayVerticalDrawingGridEvery>2</w:DisplayVerticalDrawingGridEvery> <w:ValidateAgainstSchemas/> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:Compatibility> <w:SpaceForUL/> <w:BalanceSingleByteDoubleByteWidth/> <w:DoNotLeaveBackslashAlone/> <w:ULTrailSpace/> <w:DoNotExpandShiftReturn/> <w:AdjustLineHeightInTable/> <w:BreakWrappedTables/> <w:SnapToGridInCell/> <w:WrapTextWithPunct/> <w:UseAsianBreakRules/> <w:DontGrowAutofit/> <w:UseFELayout/> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> </w:WordDocument> </xml><![endif]--><!--[if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" LatentStyleCount="156"> </w:LatentStyles> </xml><![endif]--><style> <!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} --> </style> <!--[if gte mso 10]> <style> /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;} </style> <![endif]-->

说到SLG游戏开发,无论其如何运转,里面都离不开各种数据的处理,一般来说,游戏越专业,需要处理的数据量将相对越大,类别也分得越细。

游戏离不开美工,也离不开策划,各项参数的专业划分同样是评价一款SLG游戏是否优秀的必要指标之一。所谓的好游戏仅仅画面出彩,配乐一流是绝对不够的,做“靓”很容易,做“专”则很难。

比如日本的超级机器人大战系列,自90年代初开始出现以来,截止到今天为止其中涉及的动漫超过60部,出场知名人物多达600名以上,几乎涵盖了日本所有知名机器人动画的机体(当然也有遗憾,比如机动警察)。这些角色无分善恶是各有fans,各有各的崇拜者。请这些“天王”出场,如果还做不好,过强或者过弱某一方,或者该出现的“华丽”技能没有出现,日本这些“ACG宅”的愤怒可是很恐怖的,正所谓“秋叶原前一声吼,东京也要抖三抖”。

对动漫人物的把握程度,对角色参数的设定,起了尤为重要的作用。

在这里鄙人不由得想起某位大神,就是SRC (Simulation RPG Construction)的作者鬼子Kei。这家伙从90年代末就用VB5.0开始制作SRC这个机战的同人制作工具,而我这辈子读的第一段程序代码,也正是某杂志随盘附录的SRC0.6版及其源码,当时我连VB是什么都不知道,彻底的读天书,于是才买书钻研VB……一晃10年,VB6.0我都已放下很久,他居然还在更新SRC,而且还是使用VB5开发,我不由惊叹鬼子的勤奋还有专注。10年工夫,我是无论如何也不信Kei不会用更简便的工具来制作SRC的,但是他却没有,硬是把VB5这个现在很多人用都没用过的古董级工具(实际上我也没用过|||)做出一款亚洲知名的机战同人开发工具来,10年来此人网站流量累计超过1690万,而且我也真的见过很多同人爱好者的SLG游戏是采用SRC开发。日本人真是恐怖,居然有人能甘心钻研VB5如此之久,如果把这种劲头用在工作上,想想我都不寒而栗,有这样的恒心这样弃而不舍的精神,在亚洲中国最大的潜在对手始终非日本莫属……咳咳,扯远了。


SRC运行画面,运行需要VB5运行库,并日文Windows环境。(或者先用AppLocale转内码,再转日文脚本乱码后载入)







通常来讲,我们不太可能将各种游戏数据硬编码到程序中,这样既不利于测试,也不方便重用,总需要一个外部文件作为存储介质。这时的选择其实很多,在Java游戏开发中我们即可以使用xml这类现有的规范格式,也可以干脆如SRC般自己定义脚本,或者将少量数据利用properties存储。

就我个人认为,自己订制游戏脚本格式从长远看是最可取的,以后的同类项目方便重用,也不容易被他人盗取数据。而xml虽然有很多现成的组件可用,但是处理复杂业务时就没有自己的脚本用着方便,而且当数据很少时也有些杀鸡用牛刀的感觉。至于properties,存取单键值的数据固然很方便,但是对于表格类的数据,即使很简单也不适用,至少不直观了。

在本例中我所采用的,是一种更为偷懒的方式,并不归属于以上所说,而是另辟蹊径的利用csv格式,实现了一种较为另类的表格式数据存储。

CSVComma Separated value),也叫逗号分隔值文件,是一种用来存储数据的纯文本文件格式,通常用于电子表格或数据库软件。我们打开windows记事本,随便打几个字母用“,”分割,再用excel查看,这时excel就会自动以表格方式显示这些数据。



同样对于Java中的表格数据存储,也可以采用了这种方式保存,并且利用reflect机制映射到类,看上去即直观,也比xml省心,比自己写脚本省力。


核心代码如下:

  1. packageorg.loon.simple.slg.utils;
  2. importjava.io.IOException;
  3. importjava.lang.reflect.Array;
  4. importjava.util.ArrayList;
  5. importjava.util.HashMap;
  6. importjava.util.Iterator;
  7. importjava.util.List;
  8. importjava.util.Map;
  9. importjava.util.Set;
  10. importjava.util.Map.Entry;
  11. /**
  12. *Copyright2008
  13. *
  14. *LicensedundertheApacheLicense,Version2.0(the"License");youmaynot
  15. *usethisfileexceptincompliancewiththeLicense.Youmayobtainacopyof
  16. *theLicenseat
  17. *
  18. *http://www.apache.org/licenses/LICENSE-2.0
  19. *
  20. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  21. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,WITHOUT
  22. *WARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.Seethe
  23. *Licenseforthespecificlanguagegoverningpermissionsandlimitationsunder
  24. *theLicense.
  25. *
  26. *@projectloonframework
  27. *@authorchenpeng
  28. *@email:ceponline@yahoo.com.cn
  29. *@version0.1
  30. */
  31. publicclassCSVConfigure{
  32. /**
  33. *载入CSV数据后转化为指定类的Object[]
  34. *
  35. *@paramfileName
  36. *@paramclazz
  37. *@return
  38. */
  39. finalstaticpublicObject[]loadPropertys(finalStringfileName,
  40. finalClassclazz){
  41. Object[]results=null;
  42. try{
  43. Listproperts=CSVConfigure.loadPropertys(fileName);
  44. intsize=properts.size();
  45. results=(Object[])Array.newInstance(clazz,size);
  46. for(inti=0;i<size;i++){
  47. Mapproperty=(Map)properts.get(i);
  48. Setset=property.entrySet();
  49. results[i]=clazz.newInstance();
  50. for(Iteratorit=set.iterator();it.hasNext();){
  51. Entryentry=(Entry)it.next();
  52. ClassUtils.beanRegister(results[i],
  53. (String)entry.getKey(),(String)entry.getValue());
  54. }
  55. }
  56. }catch(Exceptionex){
  57. thrownewRuntimeException(ex+""+fileName);
  58. }
  59. returnresults;
  60. }
  61. /**
  62. *载入CSV数据到List
  63. *
  64. *@paramfileName
  65. *@return
  66. */
  67. finalstaticpublicListloadPropertys(finalStringfileName){
  68. Listresult=newArrayList();
  69. try{
  70. CSVReadercsv=newCSVReader(fileName);
  71. Listnames=csv.readLineAsList();
  72. intlength=names.size();
  73. for(;csv.ready();){
  74. Mappropertys=newHashMap(length);
  75. String[]csvItem=csv.readLineAsArray();
  76. for(inti=0;i<length;i++){
  77. propertys.put((String)names.get(i),csvItem[i]);
  78. }
  79. result.add(propertys);
  80. }
  81. }catch(IOExceptione){
  82. e.printStackTrace();
  83. }
  84. returnresult;
  85. }
  86. }


使用方法:

  1. friends=(Friend[])CSVConfigure.loadPropertys(FRIEND_FILE_NAME,
  2. Friend.class);
只是一个很简单的反射,就可以将CSV表格数据注射到类上。

再说一下游戏数据的存储,一般来讲就是指游戏记录,这在Java中是最好办的。因为只要你的关键数据对象实现serializable,大可以直接将当前状态序列化到本地文件中,不过是ObjectInputStreamObjectOutputStream的把戏罢了。

如果没有或者你不想的话,你只要将关键数据以某种格式保存(这个真是随便,能再读出来就成),然后再反馈给游戏即可。实际上我们都知道所谓读档/保存并不是时间真的重来了,而是数据被还原罢了。


用例代码:


  1. packageorg.loon.simple.slg.utils;

  2. importjava.io.BufferedInputStream;
  3. importjava.io.BufferedOutputStream;
  4. importjava.io.BufferedReader;
  5. importjava.io.ByteArrayInputStream;
  6. importjava.io.ByteArrayOutputStream;
  7. importjava.io.File;
  8. importjava.io.FileInputStream;
  9. importjava.io.FileOutputStream;
  10. importjava.io.IOException;
  11. importjava.io.InputStream;
  12. importjava.io.InputStreamReader;
  13. importjava.io.OutputStream;
  14. importjava.io.Reader;
  15. importjava.io.UnsupportedEncodingException;
  16. importjava.net.URLDecoder;
  17. importjava.net.URLEncoder;
  18. importjava.util.ArrayList;
  19. importjava.util.List;
  20. importjava.util.zip.GZIPInputStream;
  21. importjava.util.zip.GZIPOutputStream;

  22. /**
  23. *
  24. *Copyright2008
  25. *
  26. *LicensedundertheApacheLicense,Version2.0(the"License");
  27. *youmaynotusethisfileexceptincompliancewiththeLicense.
  28. *YoumayobtainacopyoftheLicenseat
  29. *
  30. *http://www.apache.org/licenses/LICENSE-2.0
  31. *
  32. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  33. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  34. *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,
  35. *eitherexpressorimplied.SeetheLicenseforthespecificlanguage
  36. *governingpermissionsandlimitationsundertheLicense.
  37. *
  38. *@projectloonframework
  39. *@authorchenpeng
  40. *@email:ceponline@yahoo.com.cn
  41. *@version0.1
  42. */
  43. publicclassKeep{

  44. privatestaticfinallongserialVersionUID=-1982090153295778606L;

  45. finalstaticpublicStringLS=System.getProperty("line.separator","/n");

  46. finalstaticpublicStringFS=System.getProperty("file.separator","//");
  47. /**
  48. *增位变更指定字符串(混淆记录用)
  49. *
  50. *@params
  51. *@paramx
  52. *@return
  53. */
  54. finalstaticpublicStringaddChange(finalStrings,finalintx){
  55. Stringresult=null;
  56. StringBuildersbr=newStringBuilder();
  57. for(inti=0;i<s.length();i++){
  58. charp=(char)(s.charAt(i)+x);
  59. sbr.append(p);
  60. }
  61. try{
  62. result=URLEncoder.encode(sbr.toString(),"UTF-8");
  63. }catch(UnsupportedEncodingExceptione){
  64. thrownewRuntimeException(e);
  65. }
  66. returnresult;
  67. }

  68. /**
  69. *减位变更指定字符串(混淆记录用)
  70. *
  71. *@params
  72. *@paramx
  73. *@return
  74. */
  75. finalstaticpublicStringbackChange(finalStrings,finalintx){
  76. Stringresult=null;
  77. try{
  78. result=URLDecoder.decode(s,"UTF-8");
  79. }catch(UnsupportedEncodingExceptione){
  80. thrownewRuntimeException(e);
  81. }
  82. StringBuildersbr=newStringBuilder();
  83. for(inti=0;i<result.length();i++){
  84. charp=(char)(result.charAt(i)-x);
  85. sbr.append(p);
  86. }
  87. returnsbr.toString();
  88. }

  89. /**
  90. *压缩byte[]
  91. *
  92. *@parambuffer
  93. *@return
  94. */
  95. publicstaticbyte[]compress(finalbyte[]buffer){
  96. try{
  97. ByteArrayOutputStreambaos=newByteArrayOutputStream();
  98. GZIPOutputStreamgzipos=newGZIPOutputStream(baos);
  99. gzipos.write(buffer);
  100. gzipos.flush();
  101. gzipos.close();
  102. returnbaos.toByteArray();
  103. }catch(IOExceptione){
  104. returnnull;
  105. }
  106. }

  107. /**
  108. *压缩byte[]
  109. *
  110. *@parambuffer
  111. *@return
  112. */
  113. publicstaticbyte[]uncompress(finalbyte[]buffer){
  114. try{
  115. GZIPInputStreamgzipis=newGZIPInputStream(
  116. newByteArrayInputStream(buffer));
  117. ByteArrayOutputStreambaos=newByteArrayOutputStream();
  118. byte[]tmp=newbyte[8192];
  119. intlen;
  120. while((len=gzipis.read(tmp))>0){
  121. baos.write(tmp,0,len);
  122. }
  123. baos.flush();
  124. returnbaos.toByteArray();
  125. }catch(IOExceptione){
  126. returnnull;
  127. }
  128. }

  129. /**
  130. *保存游戏记录
  131. *
  132. *@paramfile
  133. *@parambytes
  134. *@throwsIOException
  135. */
  136. publicstaticvoidsave(finalStringfileName,finalStringmessage)throwsIOException{
  137. save(newFile(fileName),newByteArrayInputStream(Keep.compress(message.getBytes())));
  138. }

  139. /**
  140. *保存记录到文件
  141. *
  142. *@paramfile
  143. *@paraminput
  144. *@throwsIOException
  145. */
  146. privatestaticvoidsave(finalFilefile,finalInputStreaminput)
  147. throwsIOException{
  148. mkdirs(file);
  149. BufferedOutputStreamoutput=null;
  150. try{
  151. intcontentLength=input.available();
  152. output=newBufferedOutputStream(
  153. newFileOutputStream(file,false));
  154. while(contentLength-->0){
  155. output.write(input.read());
  156. }
  157. }finally{
  158. close(input,file);
  159. close(output,file);
  160. }
  161. }
  162. finalprivatestaticbyte[]read(finalInputStreaminputStream){
  163. byte[]arrayByte=null;
  164. BufferedInputStreambuffer=newBufferedInputStream(inputStream);
  165. ByteArrayOutputStreambyteArrayOutputStream=newByteArrayOutputStream();
  166. byte[]bytes=newbyte[8192];
  167. try{
  168. intread;
  169. while((read=buffer.read(bytes))>=0){
  170. byteArrayOutputStream.write(bytes,0,read);
  171. }
  172. arrayByte=byteArrayOutputStream.toByteArray();
  173. }catch(IOExceptione){
  174. thrownewRuntimeException(e);
  175. }finally{
  176. try{
  177. if(buffer!=null){
  178. buffer.close();
  179. buffer=null;
  180. }
  181. }catch(IOExceptione){
  182. thrownewRuntimeException(e);
  183. }
  184. }
  185. returnKeep.uncompress(arrayByte);
  186. }

  187. /**
  188. *读取记录到list
  189. *
  190. *@paramfileName
  191. *@return
  192. *@throwsIOException
  193. */
  194. publicstaticListload(finalStringfileName)throwsIOException{
  195. Filefile=newFile(fileName);
  196. BufferedReaderreader=newBufferedReader(newInputStreamReader(newByteArrayInputStream(Keep.read(newFileInputStream(file)))));
  197. Listrecords=newArrayList();
  198. Stringrecord=null;
  199. try{
  200. while((record=reader.readLine())!=null){
  201. records.add(record);
  202. }
  203. }finally{
  204. close(reader,file);
  205. }
  206. returnrecords;
  207. }

  208. /**
  209. *创建文件夹
  210. *
  211. *@paramfile
  212. *@throwsIOException
  213. */
  214. privatestaticvoidmkdirs(finalFilefile)throwsIOException{
  215. checkFile(file);
  216. FileparentFile=file.getParentFile();
  217. if(parentFile!=null){
  218. if(!parentFile.exists()&&!parentFile.mkdirs()){
  219. thrownewIOException("Creatingdirectories"
  220. +parentFile.getPath()+"failed.");
  221. }
  222. }
  223. }

  224. privatestaticvoidcheckFile(finalFilefile)throwsIOException{
  225. if(file.exists()&&!file.isFile()){
  226. thrownewIOException("File"+file.getPath()
  227. +"isactuallynotafile.");
  228. }
  229. }

  230. privatestaticvoidclose(finalInputStreaminput,finalFilefile){
  231. if(input!=null){
  232. try{
  233. input.close();
  234. }catch(IOExceptione){
  235. closingFailed(file,e);
  236. }
  237. }
  238. }

  239. privatestaticvoidclose(finalOutputStreamoutput,finalFilefile){
  240. if(output!=null){
  241. try{
  242. output.close();
  243. }catch(IOExceptione){
  244. closingFailed(file,e);
  245. }
  246. }
  247. }

  248. privatestaticvoidclose(finalReaderreader,finalFilefile){
  249. if(reader!=null){
  250. try{
  251. reader.close();
  252. }catch(IOExceptione){
  253. closingFailed(file,e);
  254. }
  255. }
  256. }

  257. privatestaticvoidclosingFailed(finalFilefile,finalIOExceptione){
  258. Stringmessage="Closingfile"+file.getPath()+"failed.";
  259. thrownewRuntimeException(message+":"+e.getMessage());
  260. }

  261. }

导出的结果:


比如在这个示例游戏中,我将保存的数据先递增字符位数,再经过URL编码,最后gzip压缩,出来的一组鬼画符般记录文档,唯一的作用就是不让人轻易读出并修改罢了。当然这只是最简单的例子,我们完全加密的更复杂(比如玩玩DES),验证的更变态,让玩家绞尽脑汁也无法修改游戏分毫,毕竟让玩家快乐且痛苦的游戏,就是游戏制作者最大的乐趣及兴奋点啊,哈哈哈。(惊见板砖+臭鸡蛋,我闪~~~


主菜单界面:

游戏基本界面,背景设定主角在一座城镇中,有五项基本命令可供选择。



队友雇用界面:

即酒店界面,用于寻找战友加入



物品购入界面:

商店,用于补充游戏中物品



物品装备界面:

道具装备,用于装备购入的物品



关卡选择界面:

任务选择,本示例由于没有考虑做大,所以直接将关卡做成赏金模式供玩家选择



战斗画面:

最初有过在此例复刻梦幻模拟战2的打算,但由于没有找到整套的素材,所以作罢。谁有兴趣帮兄弟找到整套素材(关键是各兵种战斗图)的话,我可以再作一个复刻梦幻2的例子。


SLG游戏入门示例及源码下载地址:http://download.csdn.net/source/809105



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics