什么是SLG呢?也就是Simulation Game的缩写,即模拟策略游戏。

  以我这种准骨灰级玩家的视点来看(鄙人88年开始玩FC,时年6岁),早期的SLG游戏,大体只是《三国志》(I由1985年开始发售)这类发布指令扩充 军备并战斗的“命令下达式游戏”,并没有什么分类上的难度。但自从《火焰纹章》(1990年开始发售)出现伊始,即策略游戏与传统RPG的分野变得模糊起 来,这种具有故事情节的战棋策略游戏,同时兼具了SLG及RPG的双特性,以后的岁月中人们习惯分类其为——SRPG,火焰系列也据此被后人视作SRPG 的鼻祖游戏之一。但事实上讲,此类游戏仍旧具备着传统SLG那样如同下棋般战斗并采用回合制的特点,RPG的情节部分仅仅是作为游戏内容的补充罢了,始终 摆脱不掉传统策略游戏地图-〉指令-〉战斗的大框架,故此客观上依然应被划入了SLG范围。再后来,随着电脑的普及,如大众软件这些媒体杂志又把文明和模 拟城市乃至美少女梦工厂这样的游戏也划分进SLG里,但按照现在的说法,足球经理、模拟人生应该是“SIM”,即单纯的Simulation,而美少女梦 工厂则是TCG——不过在日式游戏划分中,这些依然都属于SLG。

  就鄙人看来,强分策略类游戏类型是没有什么意义的,作为最初源泉的SLG是能够包含SRPG、RTS种种分支的。就好比有的人是博士、有的人是硕士,但我 们依旧可以将其统称为“知识分子”,划到一个大圈子里面去。又比如我们平时可能常说“上海人怎样”、“北京人如何”,但当我说“中国人”时,自然能够将这 些都包罗其中,无论好坏,谁都脱身不得。 而在此类游戏中,包含策略因素的这个大圈子的统一称谓,便是SLG无疑。

  实际上,绝大多数英文站点也是将此类游戏统一丢到Simulation Game下的(包括模拟城市之类的纯SIM),并没有进行SRPG(Strategies Role Play Games)或RTS(Real-Time Strategy Game)乃至其余种种的细分。归根究底,因为这些游戏近似的因素太多,在大多数时候已经难以区分其本来面貌,“名无实,实无名”,只能一概而论了。而今 有不少新生代玩家喜欢硬分游戏种类,窃以为愚了。

——————————————————————————————————————————————————————————

  闲话说了不少,现在开始进入正题。在本系列中,我将结合实例尝试以Java实现典型战棋类SLG的主要功能,本文为第0节,也就是准备章节。

  看过我以前写的RPG及ACT系列的朋友们,应该已对Java中2D图形绘制功能有了初步的认识,在本文中,我将阐述如何令绘制的窗体而非组件响应鼠标事件,及如何在窗体中自定义非标准大小的鼠标指针,作为本系列的预备知识。

  首先,我们都知道,在Java中可以通过Cursor组件自定义游标样式,比如下图有一组取材自Langrisser2的光标图片。

 

  在Java桌面开发中,我们可以通过分解这组图片来得到小图,以此来自定义鼠标光标。
 
  但是有一个问题,这时无论图片原始大小如何,至多也只能是32x32大小,如果超出这个范围,则无法作为游标在窗体中完整显示。

  也就是说,如上图这样46x46的大图,要么缩小显示,要么局部显示,总之46x46的状态下是无法完整的显示在窗体中的。

  可我们明明见到很多游戏中的光标是不规则不成比例的,究竟如何做到呢?其实很简单,自己绘制就好了。

  绝大多数不合规矩的东西,我们都可以自己把它“画出来”,只要能确定它的坐标。

  如下代码记录了键盘及鼠标状态下的图标移动:
  1. /**
  2.      * 键盘事件设置
  3.      * 
  4.      */
  5.     public void setKeyset() {
  6.         addKeyListener(new KeyAdapter() {
  7.             public void keyTyped(KeyEvent e) {
  8.             }

  9.             public void keyReleased(KeyEvent e) {
  10.             }

  11.             public void keyPressed(KeyEvent e) {
  12.                 if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
  13.                     currentX = currentX + move;
  14.                 }
  15.                 if (e.getKeyCode() == KeyEvent.VK_LEFT) {
  16.                     currentX = currentX - move;
  17.                 }
  18.                 if (e.getKeyCode() == KeyEvent.VK_UP) {
  19.                     currentY = currentY - move;
  20.                 }
  21.                 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
  22.                     currentY = currentY + move;
  23.                 }
  24.                 repaint();
  25.             }
  26.         });
  27.     }

  28.     /**
  29.      * 鼠标事件设置
  30.      * 
  31.      */
  32.     public void setMouse() {
  33.         addMouseListener(new MouseAdapter() {
  34.             public void mousePressed(MouseEvent e) {
  35.             }

  36.             public void mouseReleased(MouseEvent e) {
  37.             }

  38.         });
  39.         addMouseMotionListener(new MouseMotionAdapter() {
  40.             public void mouseMoved(MouseEvent ex) {
  41.                 currentX = ex.getX();
  42.                 currentY = ex.getY();
  43.                 repaint();
  44.             }

  45.             public void mouseDragged(MouseEvent ex) {
  46.                 currentX = ex.getX();
  47.                 currentY = ex.getY();
  48.                 repaint();
  49.             }
  50.         });
  51.     }
  此时,我们只需将光标在指定位置drawImage一下,自然就会得到想要的结果。但是有一点需要注意,那就是系统的Cursor此时还存在,如果我们不 进行处理,画面上会出现两个游标的尴尬局面。但是Java并没有提供给我们直接取消光标的方法,这时该怎么办呢?很简单,我们将其“隐形”即可。
 
  比如这样:
  1.         CursorFrame f = new CursorFrame();
  2.         int[] pixels = new int[256];
  3.         Image p_w_picpath = Toolkit.getDefaultToolkit().createImage(
  4.                 new MemoryImageSource(1616, pixels, 016));
  5.         Cursor transparentCursor = Toolkit.getDefaultToolkit()
  6.                 .createCustomCursor(p_w_picpath, new Point(00), "hidden");
  7.         f.setCursor(transparentCursor);

  我们绘制一张16x16的透明图作为游标,在使用者看来,就只能见到我们drawImage出的“伪游标”罢了。

  现在我们据此制作一个假单的仿Langrisser2开始界面,代码如下:
  1. package org.slg.simple;

  2. import java.awt.Color;
  3. import java.awt.Cursor;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.Image;
  7. import java.awt.Panel;
  8. import java.awt.Point;
  9. import java.awt.Toolkit;
  10. import java.awt.event.KeyAdapter;
  11. import java.awt.event.KeyEvent;
  12. import java.awt.event.MouseAdapter;
  13. import java.awt.event.MouseEvent;
  14. import java.awt.event.MouseMotionAdapter;
  15. import java.awt.p_w_picpath.BufferedImage;
  16. import java.awt.p_w_picpath.MemoryImageSource;

  17. /**
  18.  * Copyright 2008
  19.  * 
  20.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  21.  * use this file except in compliance with the License. You may obtain a copy of
  22.  * the License at
  23.  * 
  24.  * [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
  25.  * 
  26.  * Unless required by applicable law or agreed to in writing, software
  27.  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  28.  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  29.  * License for the specific language governing permissions and limitations under
  30.  * the License.
  31.  * 
  32.  * @project loonframework
  33.  * @author chenpeng
  34.  * @email:[email][email protected][/email]
  35.  * @version 0.1
  36.  */
  37. public class ExemplePanel extends Panel {

  38.     /**
  39.      * 
  40.      */
  41.     private static final long serialVersionUID = 1L;

  42.     final static int currentWidth = 480;

  43.     final static int currentHeight = 360;

  44.     //背景缓冲图
  45.     final Image _background;
  46.     //鼠标指针图形组
  47.     final Image[] _mouses;
  48.     //背景图
  49.     final Image _backgroundImage;

  50.     Graphics _backgroundGraphics;

  51.     Image _mouse;

  52.     Image _arrow;

  53.     // 选中项,默认指向第一条
  54.     int _select = 1;

  55.     // 游标x轴
  56.     int _currentX = 0;

  57.     // 游标y轴
  58.     int _currentY = 0;

  59.     int _move = 5;

  60.     public ExemplePanel() {
  61.         // 创建一个背景缓存用p_w_picpath
  62.         _background = new BufferedImage(currentWidth, currentHeight,
  63.                 BufferedImage.TYPE_INT_ARGB);
  64.         // 导入开始时背景图像
  65.         _backgroundImage = Utility.loadImage("p_w_picpath/start.gif");
  66.         // 导入光标图系列,以列宽46读取到p_w_picpath数组
  67.         _mouses = Utility.getImageColumns(
  68.                 Utility.loadImage("p_w_picpath/cursor.png"), 46);
  69.         // 初始背景为黑色
  70.         setBackground(new Color(000));
  71.         _arrow = Utility.loadImage("p_w_picpath/arrow.png");
  72.         _backgroundGraphics = _background.getGraphics();
  73.         // 设定键盘监听
  74.         setKeyset();
  75.         // 设定鼠标监听
  76.         setMouse();
  77.         // 设置鼠标动画(本例只是一个简单示例,实际应根据相应事件变更游标造型)
  78.         Thread mouseAnimation = new Thread() {
  79.             public void run() {
  80.                 int cursorMax = _mouses.length;
  81.                 int cursorIndex = 0;
  82.                 do {
  83.                     if (cursorIndex < cursorMax) {
  84.                         _mouse = _mouses[cursorIndex];
  85.                         try {
  86.                             Thread.sleep(500);
  87.                         } catch (InterruptedException e) {
  88.                             e.printStackTrace();
  89.                         }
  90.                         repaint();
  91.                     } else {
  92.                         cursorIndex = 0;
  93.                     }
  94.                     cursorIndex++;
  95.                 } while (true);
  96.             }
  97.         };
  98.         // 开启鼠标动画
  99.         mouseAnimation.start();

  100.     }

  101.     public void paint(Graphics g) {
  102.         // 绘制背景
  103.         _backgroundGraphics.drawImage(_backgroundImage, 00this);
  104.         // 绘制光标
  105.         _backgroundGraphics.drawImage(_mouse, _currentX, _currentY, this);
  106.         drawTitle((Graphics2D) _backgroundGraphics);
  107.         // 加载缓存图
  108.         g.drawImage(_background, 00this);
  109.     }

  110.     public void update(Graphics g) {
  111.         paint(g);
  112.     }

  113.     /**
  114.      * 键盘事件设置
  115.      * 
  116.      */
  117.     public void setKeyset() {
  118.         addKeyListener(new KeyAdapter() {
  119.             public void keyTyped(KeyEvent e) {
  120.             }

  121.             public void keyReleased(KeyEvent e) {
  122.             }

  123.             public void keyPressed(KeyEvent e) {
  124.                 if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
  125.                     _currentX = _currentX + _move;
  126.                 }
  127.                 if (e.getKeyCode() == KeyEvent.VK_LEFT) {
  128.                     _currentX = _currentX - _move;
  129.                 }
  130.                 if (e.getKeyCode() == KeyEvent.VK_UP) {
  131.                     _currentY = _currentY - _move;
  132.                 }
  133.                 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
  134.                     _currentY = _currentY + _move;
  135.                 }
  136.                 repaint();
  137.             }
  138.         });
  139.     }

  140.     /**
  141.      * 鼠标事件设置
  142.      * 
  143.      */
  144.     public void setMouse() {
  145.         int[] pixels = new int[256];
  146.         Image p_w_picpath = Toolkit.getDefaultToolkit().createImage(
  147.                 new MemoryImageSource(1616, pixels, 016));
  148.         // 制作一个透明的游标
  149.         Cursor transparentCursor = Toolkit.getDefaultToolkit()
  150.                 .createCustomCursor(p_w_picpath, new Point(00), "hidden");
  151.         // 插入透明游标,以此模拟无游标状态
  152.         setCursor(transparentCursor);
  153.         addMouseListener(new MouseAdapter() {
  154.             public void mousePressed(MouseEvent e) {
  155.                 if (e.getButton() == 1) {
  156.                     State.l_clk = true;
  157.                 }
  158.                 if (e.getButton() == 3) {
  159.                     State.r_clk = true;
  160.                 }
  161.             }

  162.             public void mouseReleased(MouseEvent e) {
  163.                 if (e.getButton() == 1) {
  164.                     State.l_clk = false;
  165.                 }
  166.                 if (e.getButton() == 3) {
  167.                     State.r_clk = false;
  168.                 }
  169.             }

  170.         });
  171.         addMouseMotionListener(new MouseMotionAdapter() {
  172.             public void mouseMoved(MouseEvent ex) {
  173.                 _currentX = ex.getX();
  174.                 _currentY = ex.getY();
  175.                 repaint();
  176.             }

  177.             public void mouseDragged(MouseEvent ex) {
  178.                 _currentX = ex.getX();
  179.                 _currentY = ex.getY();
  180.                 repaint();
  181.             }
  182.         });
  183.     }

  184.     /**
  185.      * 绘制标题选项
  186.      * 
  187.      * @param g
  188.      */
  189.     void drawTitle(Graphics2D g) {
  190.         Utility.font(g, 151);
  191.         Utility.color(g, 000);
  192.         if (_select != 0) {
  193.             g.drawImage(_arrow, 168227 + _select * 20null);
  194.         }
  195.         //PS:如果不想在程序中绘制,也可以直接在准备好的背景图上写文字,pc版的Langrisser就是那样......
  196.         g.drawString("开始新游戏"195260);
  197.         g.drawString("载入记录"203280);
  198.         g.drawString("退出游戏"203300);

  199.         for (int i = 0; i < 3; i++) {
  200.             if (_currentX > 195 && _currentX < 270 && _currentY > i * 20 + 235
  201.                     && _currentY < i * 20 + 275) {
  202.                 _select = i + 1;
  203.             }
  204.         }

  205.         repaint();
  206.         Utility.wait(20);
  207.         if (State.l_clk && !State.lock_lck) {
  208.             State.lock_lck = true;
  209.             if (_select == 1) {
  210.                 System.out.println("您选择了:开始");
  211.             }
  212.             if (_select == 2) {
  213.                 System.out.println("您选择了:继续");
  214.             }
  215.             if (_select == 3) {
  216.                 System.out.println("您选择了:结束");
  217.                 System.exit(0);
  218.             }
  219.         }
  220.         if (!State.l_clk && State.lock_lck) {
  221.             State.lock_lck = false;
  222.         }

  223.     }
  224. }


  运行效果如下图: