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

简介:
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 格式,实现了一种较为另类的表格式数据存储。
 
   CSV Comma Separated value ),也叫逗号分隔值文件,是一种用来存储数据的纯文本文件格式,通常用于电子表格或数据库软件。我们打开 windows 记事本,随便打几个字母用“ , ”分割,再用 excel 查看,这时 excel 就会自动以表格方式显示这些数据。

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

  核心代码如下:
package org.loon.simple.slg.utils;


import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
    * Copyright 2008
    * 
    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
    * use this file except in compliance with the License. You may obtain a copy of
    * the License at
    * 
    * [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
    * 
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    * License for the specific language governing permissions and limitations under
    * the License.
    * 
    * @project loonframework
    * @author chenpeng
    * @email:[email]ceponline@yahoo.com.cn[/email]
    * @version 0.1
    */

public  class CSVConfigure {

          /**
            * 载入CSV数据后转化为指定类的Object[]
            * 
            * @param fileName
            * @param clazz
            * @return
            */

          final  static  public Object[] loadPropertys( final String fileName,
                          final Class clazz) {
                 Object[] results =  null;
                  try {
                         List properts = CSVConfigure.loadPropertys(fileName);
                          int size = properts.size();
                         results = (Object[]) Array.newInstance(clazz, size);
                          for ( int i = 0; i < size; i++) {
                                 Map property = (Map) properts.get(i);
                                 Set set = property.entrySet();
                                 results[i] = clazz.newInstance();
                                  for (Iterator it = set.iterator(); it.hasNext();) {
                                         Entry entry = (Entry) it.next();
                                         ClassUtils.beanRegister(results[i],
                                                         (String) entry.getKey(), (String) entry.getValue());
                                 }
                         }
                 }  catch (Exception ex) {
                          throw  new RuntimeException(ex+ " "+fileName);
                 }
                  return results;
         }

          /**
            * 载入CSV数据到List
            * 
            * @param fileName
            * @return
            */

          final  static  public List loadPropertys( final String fileName) {
                 List result =  new ArrayList();
                  try {
                         CSVReader csv =  new CSVReader(fileName);
                         List names = csv.readLineAsList();
                          int length = names.size();
                          for (; csv.ready();) {
                                 Map propertys =  new HashMap(length);
                                 String[] csvItem = csv.readLineAsArray();
                                  for ( int i = 0; i < length; i++) {
                                         propertys.put((String) names.get(i), csvItem[i]);
                                 }
                                 result.add(propertys);
                         }
                 }  catch (IOException e) {
                         e.printStackTrace();
                 }
                  return result;
         }

}


  使用方法:

  
     1. friends = (Friend[]) CSVConfigure.loadPropertys(FRIEND_FILE_NAME,
     2.                                 Friend. class);


   只是一个很简单的反射,就可以将CSV表格数据注射到类上。
 
   再说一下游戏数据的存储,一般来讲就是指游戏记录,这在 Java 中是最好办的。因为只要你的关键数据对象实现 serializable ,大可以直接将当前状态序列化到本地文件中,不过是 ObjectInputStream ObjectOutputStream 的把戏罢了。
 
  如果没有或者你不想的话,你只要将关键数据以某种格式保存(这个真是随便,能再读出来就成),然后再反馈给游戏即可。实际上我们都知道所谓读档 / 保存并不是时间真的重来了,而是数据被还原罢了。

  用例代码:

  
package org.loon.simple.slg.utils;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;


/**
    * 
    * Copyright 2008 
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    * either express or implied. See the License for the specific language
    * governing permissions and limitations under the License.
    *
    * @project loonframework
    * @author chenpeng    
    * @email:[email]ceponline@yahoo.com.cn[/email] 
    * @version 0.1
    */

public  class Keep {


          private  static  final  long serialVersionUID = -1982090153295778606L;


          final  static  public String LS = System.getProperty( "line.separator""\n");


          final  static  public String FS = System.getProperty( "file.separator", "\\");
         
          /**
            * 增位变更指定字符串(混淆记录用)
            * 
            * @param s
            * @param x
            * @return
            */

          final  static  public String addChange( final String s,  final  int x) {
                 String result =  null;
                 StringBuilder sbr =  new StringBuilder();
                  for ( int i = 0; i < s.length(); i++) {
                          char p = ( char) (s.charAt(i) + x);
                         sbr.append(p);
                 }
                  try {
                         result = URLEncoder.encode(sbr.toString(), "UTF-8");
                 }  catch (UnsupportedEncodingException e) {
                          throw  new RuntimeException(e);
                 }
                  return result;
         }


          /**
            * 减位变更指定字符串(混淆记录用)
            * 
            * @param s
            * @param x
            * @return
            */

          final  static  public String backChange( final String s,  final  int x) {
                 String result =  null;
                  try {
                         result = URLDecoder.decode(s, "UTF-8");
                 }  catch (UnsupportedEncodingException e) {
                          throw  new RuntimeException(e);
                 }
                 StringBuilder sbr =  new StringBuilder();
                  for ( int i = 0; i < result.length(); i++) {
                          char p = ( char) (result.charAt(i) - x);
                         sbr.append(p);
                 }
                  return sbr.toString();
         }
         


          /**
            * 压缩byte[]
            * 
            * @param buffer
            * @return
            */

          public  static  byte[] compress( final  byte[] buffer) {
                  try {
                         ByteArrayOutputStream baos =  new ByteArrayOutputStream();
                         GZIPOutputStream gzipos =  new GZIPOutputStream(baos);
                         gzipos.write(buffer);
                         gzipos.flush();
                         gzipos.close();
                          return baos.toByteArray();
                 }  catch (IOException e) {
                          return  null;
                 }
         }


          /**
            * 压缩byte[]
            * 
            * @param buffer
            * @return
            */

          public  static  byte[] uncompress( final  byte[] buffer) {
                  try {
                         GZIPInputStream gzipis =  new GZIPInputStream(
                                          new ByteArrayInputStream(buffer));
                         ByteArrayOutputStream baos =  new ByteArrayOutputStream();
                          byte[] tmp =  new  byte[8192];
                          int len;
                          while ((len = gzipis.read(tmp)) > 0) {
                                 baos.write(tmp, 0, len);
                         }
                         baos.flush();
                          return baos.toByteArray();
                 }  catch (IOException e) {
                          return  null;
                 }
         }


          /**
            * 保存游戏记录
            * 
            * @param file
            * @param bytes
            * @throws IOException
            */

          public  static  void save( final String fileName, final String message)  throws IOException {
                 save( new File(fileName),  new ByteArrayInputStream(Keep.compress(message.getBytes())));
         }


          /**
            * 保存记录到文件
            * 
            * @param file
            * @param input
            * @throws IOException
            */

          private  static  void save( final File file, final InputStream input)
                          throws IOException {
                 mkdirs(file);
                 BufferedOutputStream output =  null;
                  try {
                          int contentLength = input.available();
                         output =  new BufferedOutputStream(
                                          new FileOutputStream(file,  false));
                          while (contentLength-- > 0) {
                                 output.write(input.read());
                         }
                 }  finally {
                         close(input, file);
                         close(output, file);
                 }
         }
         
          final  private  static  byte[] read( final InputStream inputStream) {
                  byte[] arrayByte =  null;
                 BufferedInputStream buffer =  new BufferedInputStream(inputStream);
                 ByteArrayOutputStream byteArrayOutputStream =  new ByteArrayOutputStream();
                  byte[] bytes =  new  byte[8192];
                  try {
                          int read;
                          while ((read = buffer.read(bytes)) >= 0) {
                                 byteArrayOutputStream.write(bytes, 0, read);
                         }
                         arrayByte = byteArrayOutputStream.toByteArray();
                 }  catch (IOException e) {
                          throw  new RuntimeException(e);
                 }  finally {
                          try {
                                  if (buffer !=  null) {
                                         buffer.close();
                                         buffer =  null;
                                 }
                         }  catch (IOException e) {
                                  throw  new RuntimeException(e);
                         }
                 }
                  return Keep.uncompress(arrayByte);
         }


          /**
            * 读取记录到list
            * 
            * @param fileName
            * @return
            * @throws IOException
            */

          public  static List load( final String fileName)  throws IOException {
                 File file =  new File(fileName);
                 BufferedReader reader =  new BufferedReader( new InputStreamReader( newByteArrayInputStream(Keep.read( new FileInputStream(file)))));
                 List records =  new ArrayList();
                 String record =  null;
                  try {
                          while ((record = reader.readLine()) !=  null) {
                                 records.add(record);
                         }
                 }  finally {
                         close(reader, file);
                 }
                  return records;
         }


          /**
            * 创建文件夹
            * 
            * @param file
            * @throws IOException
            */

          private  static  void mkdirs( final File file)  throws IOException {
                 checkFile(file);
                 File parentFile = file.getParentFile();
                  if (parentFile !=  null) {
                          if (!parentFile.exists() && !parentFile.mkdirs()) {
                                  throw  new IOException( "Creating directories "
                                                 + parentFile.getPath() +  " failed.");
                         }
                 }
         }


          private  static  void checkFile( final File file)  throws IOException {
                  if (file.exists() && !file.isFile()) {
                          throw  new IOException( "File " + file.getPath()
                                         +  " is actually not a file.");
                 }
         }


          private  static  void close( final InputStream input, final File file) {
                  if (input !=  null) {
                          try {
                                 input.close();
                         }  catch (IOException e) {
                                 closingFailed(file, e);
                         }
                 }
         }


          private  static  void close( final OutputStream output, final File file) {
                  if (output !=  null) {
                          try {
                                 output.close();
                         }  catch (IOException e) {
                                 closingFailed(file, e);
                         }
                 }
         }


          private  static  void close( final Reader reader, final File file) {
                  if (reader !=  null) {
                          try {
                                 reader.close();
                         }  catch (IOException e) {
                                 closingFailed(file, e);
                         }
                 }
         }


          private  static  void closingFailed( final File file, final IOException e) {
                 String message =  "Closing file " + file.getPath() +  " failed.";
                  throw  new RuntimeException(message +  ":" + e.getMessage());
         }


}


导出的结果:
  


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

主菜单界面:

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

  

  队友雇用界面:

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

  

   物品购入界面:  

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

  物品装备界面:

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

  

   关卡选择界面:   

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

   

    战斗画面:

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



相关文章
|
10天前
|
JSON NoSQL Java
Redis入门到通关之Java客户端SpringDataRedis(RedisTemplate)
Redis入门到通关之Java客户端SpringDataRedis(RedisTemplate)
24 0
|
10天前
|
Java
Java编程语言入门指南
Java编程语言入门指南
13 1
|
10天前
|
安全 Java 大数据
|
3天前
|
存储 监控 Java
如何在Java中实现等待文件修改后再读取数据的功能?
如何在Java中实现等待文件修改后再读取数据的功能?
9 0
|
5天前
|
设计模式 算法 安全
Java多线程编程实战:从入门到精通
【4月更文挑战第30天】本文介绍了Java多线程编程的基础,包括线程概念、创建线程(继承`Thread`或实现`Runnable`)、线程生命周期。还讨论了线程同步与锁(同步代码块、`ReentrantLock`)、线程间通信(等待/通知、并发集合)以及实战技巧,如使用线程池、线程安全设计模式和避免死锁。性能优化方面,建议减少锁粒度和使用非阻塞算法。理解这些概念和技术对于编写高效、可靠的多线程程序至关重要。
|
6天前
|
算法 Java 大数据
Java从入门到精通学习报告
Java从入门到精通学习报告
13 1
|
6天前
|
安全 Java 网络安全
深入理解Java异常处理网络安全与信息安全:保护数据的关键策略
【4月更文挑战第29天】本文将深入探讨Java中的异常处理机制,包括异常的概念、分类、捕获和处理等方面。通过详细的解析和实例演示,帮助读者更好地理解Java异常处理的原理和应用。 【4月更文挑战第29天】在数字化时代,数据成为最宝贵的资产之一。随着网络攻击的日益猖獗,了解并实施有效的网络安全和信息保护措施变得至关重要。本文将深入探讨网络安全漏洞的概念、加密技术的重要性以及提升个人和企业的安全意识的必要性。我们将分析当前的安全挑战,并提供一系列实用的防护策略,旨在帮助读者构建一个更加安全的数字环境。
|
6天前
|
Java 关系型数据库 MySQL
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
|
6天前
|
数据采集 存储 前端开发
Nutch库入门指南:利用Java编写采集程序,快速抓取北京车展重点车型
2024年北京车展凸显电动车全球热度,中国引领市场,展出117台全球首发车,包括30台跨国公司电动车。借助Nutch库抓取汽车网站数据,分析电动车市场趋势。通过配置代理和多线程爬虫,高效收集新车信息,助力理解市场动态,推动可持续交通发展。
Nutch库入门指南:利用Java编写采集程序,快速抓取北京车展重点车型
|
8天前
|
Java Apache
java读取excel数据案例
Java代码示例使用Apache POI库读取Excel(example.xlsx)数据。创建FileInputStream和XSSFWorkbook对象,获取Sheet,遍历行和列,根据单元格类型(STRING, NUMERIC, BOOLEAN)打印值。需引入Apache POI库并确保替换文件路径。
7 1