說到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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 protected]
* @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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 protected]
* @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(new ByteArrayInputStream(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的例子。
SLG游戲入門示例及源碼下載地址:http://download.csdn.net/source/809105
本文出自 “Java究竟怎麼玩” 博客,請務必保留此出處http://cping1982.blog.51cto.com/601635/116596