唐伯虎 发表于 2021-6-27 15:53:11

Java版SLG游戏开发入门[0]--让绘制的窗口响应鼠标事件

  什么是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的光标图片。

http://p.blog.csdn.net/p_w_picpaths/p_blog_csdn_net/cping1982/EntryImages/20081015/cursor.png

在Java桌面开发中,我们可以通过分解这组图片来得到小图,以此来自定义鼠标光标。

但是有一个问题,这时无论图片原始大小如何,至多也只能是32x32大小,如果超出这个范围,则无法作为游标在窗体中完整显示。

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

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

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

如下代码记录了键盘及鼠标状态下的图标移动:

[*]/
[*]    键盘事件设置
[*]   
[*]   */
[*]    public void setKeyset() {
[*]      addKeyListener(new KeyAdapter() {
[*]            public void keyTyped(KeyEvent e) {
[*]            }
[*]
[*]            public void keyReleased(KeyEvent e) {
[*]            }
[*]
[*]            public void keyPressed(KeyEvent e) {
[*]                if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
[*]                  currentX = currentX + move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_LEFT) {
[*]                  currentX = currentX - move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_UP) {
[*]                  currentY = currentY - move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
[*]                  currentY = currentY + move;
[*]                }
[*]                repaint();
[*]            }
[*]      });
[*]    }
[*]
[*]    /
[*]    鼠标事件设置
[*]   
[*]   */
[*]    public void setMouse() {
[*]      addMouseListener(new MouseAdapter() {
[*]            public void mousePressed(MouseEvent e) {
[*]            }
[*]
[*]            public void mouseReleased(MouseEvent e) {
[*]            }
[*]
[*]      });
[*]      addMouseMotionListener(new MouseMotionAdapter() {
[*]            public void mouseMoved(MouseEvent ex) {
[*]                currentX = ex.getX();
[*]                currentY = ex.getY();
[*]                repaint();
[*]            }
[*]
[*]            public void mouseDragged(MouseEvent ex) {
[*]                currentX = ex.getX();
[*]                currentY = ex.getY();
[*]                repaint();
[*]            }
[*]      });
[*]    }

  此时,我们只需将光标在指定位置drawImage一下,自然就会得到想要的结果。但是有一点需要注意,那就是系统的Cursor此时还存在,如果我们不
进行处理,画面上会出现两个游标的尴尬局面。但是Java并没有提供给我们直接取消光标的方法,这时该怎么办呢?很简单,我们将其“隐形”即可。

比如这样:

[*]      CursorFrame f = new CursorFrame();
[*]      int[] pixels = new int;
[*]      Image p_w_picpath = Toolkit.getDefaultToolkit().createImage(
[*]                new MemoryImageSource(16, 16, pixels, 0, 16));
[*]      Cursor transparentCursor = Toolkit.getDefaultToolkit()
[*]                .createCustomCursor(p_w_picpath, new Point(0, 0), "hidden");
[*]      f.setCursor(transparentCursor);

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

现在我们据此制作一个假单的仿Langrisser2开始界面,代码如下:
[*]package org.slg.simple;
[*]
[*]import java.awt.Color;
[*]import java.awt.Cursor;
[*]import java.awt.Graphics;
[*]import java.awt.Graphics2D;
[*]import java.awt.Image;
[*]import java.awt.Panel;
[*]import java.awt.Point;
[*]import java.awt.Toolkit;
[*]import java.awt.event.KeyAdapter;
[*]import java.awt.event.KeyEvent;
[*]import java.awt.event.MouseAdapter;
[*]import java.awt.event.MouseEvent;
[*]import java.awt.event.MouseMotionAdapter;
[*]import java.awt.p_w_picpath.BufferedImage;
[*]import java.awt.p_w_picpath.MemoryImageSource;
[*]
[*]/
[*] 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:ceponline@yahoo.com.cn
[*] * @version 0.1
[*] */
[*]public class ExemplePanel extends Panel {
[*]
[*]    /**
[*]   
[*]   /
[*]    private static final long serialVersionUID = 1L;
[*]
[*]    final static int currentWidth = 480;
[*]
[*]    final static int currentHeight = 360;
[*]
[*]    //背景缓冲图
[*]    final Image _background;
[*]    //鼠标指针图形组
[*]    final Image[] _mouses;
[*]    //背景图
[*]    final Image _backgroundImage;
[*]
[*]    Graphics _backgroundGraphics;
[*]
[*]    Image _mouse;
[*]
[*]    Image _arrow;
[*]
[*]    // 选中项,默认指向第一条
[*]    int _select = 1;
[*]
[*]    // 游标x轴
[*]    int _currentX = 0;
[*]
[*]    // 游标y轴
[*]    int _currentY = 0;
[*]
[*]    int _move = 5;
[*]
[*]    public ExemplePanel() {
[*]      // 创建一个背景缓存用p_w_picpath
[*]      _background = new BufferedImage(currentWidth, currentHeight,
[*]                BufferedImage.TYPE_INT_ARGB);
[*]      // 导入开始时背景图像
[*]      _backgroundImage = Utility.loadImage("p_w_picpath/start.gif");
[*]      // 导入光标图系列,以列宽46读取到p_w_picpath数组
[*]      _mouses = Utility.getImageColumns(
[*]                Utility.loadImage("p_w_picpath/cursor.png"), 46);
[*]      // 初始背景为黑色
[*]      setBackground(new Color(0, 0, 0));
[*]      _arrow = Utility.loadImage("p_w_picpath/arrow.png");
[*]      _backgroundGraphics = _background.getGraphics();
[*]      // 设定键盘监听
[*]      setKeyset();
[*]      // 设定鼠标监听
[*]      setMouse();
[*]      // 设置鼠标动画(本例只是一个简单示例,实际应根据相应事件变更游标造型)
[*]      Thread mouseAnimation = new Thread() {
[*]            public void run() {
[*]                int cursorMax = _mouses.length;
[*]                int cursorIndex = 0;
[*]                do {
[*]                  if (cursorIndex < cursorMax) {
[*]                        _mouse = _mouses;
[*]                        try {
[*]                            Thread.sleep(500);
[*]                        } catch (InterruptedException e) {
[*]                            e.printStackTrace();
[*]                        }
[*]                        repaint();
[*]                  } else {
[*]                        cursorIndex = 0;
[*]                  }
[*]                  cursorIndex++;
[*]                } while (true);
[*]            }
[*]      };
[*]      // 开启鼠标动画
[*]      mouseAnimation.start();
[*]
[*]    }
[*]
[*]    public void paint(Graphics g) {
[*]      // 绘制背景
[*]      _backgroundGraphics.drawImage(_backgroundImage, 0, 0, this);
[*]      // 绘制光标
[*]      _backgroundGraphics.drawImage(_mouse, _currentX, _currentY, this);
[*]      drawTitle((Graphics2D) _backgroundGraphics);
[*]      // 加载缓存图
[*]      g.drawImage(_background, 0, 0, this);
[*]    }
[*]
[*]    public void update(Graphics g) {
[*]      paint(g);
[*]    }
[*]
[*]    /**
[*]    键盘事件设置
[*]   
[*]   */
[*]    public void setKeyset() {
[*]      addKeyListener(new KeyAdapter() {
[*]            public void keyTyped(KeyEvent e) {
[*]            }
[*]
[*]            public void keyReleased(KeyEvent e) {
[*]            }
[*]
[*]            public void keyPressed(KeyEvent e) {
[*]                if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
[*]                  _currentX = _currentX + _move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_LEFT) {
[*]                  _currentX = _currentX - _move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_UP) {
[*]                  _currentY = _currentY - _move;
[*]                }
[*]                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
[*]                  _currentY = _currentY + _move;
[*]                }
[*]                repaint();
[*]            }
[*]      });
[*]    }
[*]
[*]    /**
[*]    鼠标事件设置
[*]   
[*]   */
[*]    public void setMouse() {
[*]      int[] pixels = new int;
[*]      Image p_w_picpath = Toolkit.getDefaultToolkit().createImage(
[*]                new MemoryImageSource(16, 16, pixels, 0, 16));
[*]      // 制作一个透明的游标
[*]      Cursor transparentCursor = Toolkit.getDefaultToolkit()
[*]                .createCustomCursor(p_w_picpath, new Point(0, 0), "hidden");
[*]      // 插入透明游标,以此模拟无游标状态
[*]      setCursor(transparentCursor);
[*]      addMouseListener(new MouseAdapter() {
[*]            public void mousePressed(MouseEvent e) {
[*]                if (e.getButton() == 1) {
[*]                  State.l_clk = true;
[*]                }
[*]                if (e.getButton() == 3) {
[*]                  State.r_clk = true;
[*]                }
[*]            }
[*]
[*]            public void mouseReleased(MouseEvent e) {
[*]                if (e.getButton() == 1) {
[*]                  State.l_clk = false;
[*]                }
[*]                if (e.getButton() == 3) {
[*]                  State.r_clk = false;
[*]                }
[*]            }
[*]
[*]      });
[*]      addMouseMotionListener(new MouseMotionAdapter() {
[*]            public void mouseMoved(MouseEvent ex) {
[*]                _currentX = ex.getX();
[*]                _currentY = ex.getY();
[*]                repaint();
[*]            }
[*]
[*]            public void mouseDragged(MouseEvent ex) {
[*]                _currentX = ex.getX();
[*]                _currentY = ex.getY();
[*]                repaint();
[*]            }
[*]      });
[*]    }
[*]
[*]    /
[*]    绘制标题选项
[*]   
[*]   * @param g
[*]   /
[*]    void drawTitle(Graphics2D g) {
[*]      Utility.font(g, 15, 1);
[*]      Utility.color(g, 0, 0, 0);
[*]      if (_select != 0) {
[*]            g.drawImage(_arrow, 168, 227 + _select 20, null);
[*]      }
[*]      //PS:如果不想在程序中绘制,也可以直接在准备好的背景图上写文字,pc版的Langrisser就是那样......
[*]      g.drawString("开始新游戏", 195, 260);
[*]      g.drawString("载入记录", 203, 280);
[*]      g.drawString("退出游戏", 203, 300);
[*]
[*]      for (int i = 0; i < 3; i++) {
[*]            if (_currentX > 195 && _currentX < 270 && _currentY > i * 20 + 235
[*]                  && _currentY < i * 20 + 275) {
[*]                _select = i + 1;
[*]            }
[*]      }
[*]
[*]      repaint();
[*]      Utility.wait(20);
[*]      if (State.l_clk && !State.lock_lck) {
[*]            State.lock_lck = true;
[*]            if (_select == 1) {
[*]                System.out.println("您选择了:开始");
[*]            }
[*]            if (_select == 2) {
[*]                System.out.println("您选择了:继续");
[*]            }
[*]            if (_select == 3) {
[*]                System.out.println("您选择了:结束");
[*]                System.exit(0);
[*]            }
[*]      }
[*]      if (!State.l_clk && State.lock_lck) {
[*]            State.lock_lck = false;
[*]      }
[*]
[*]    }
[*]}
[*]

运行效果如下图:

http://p.blog.csdn.net/p_w_picpaths/p_blog_csdn_net/cping1982/EntryImages/20081015/20081014_slg_02.gif

页: [1]
查看完整版本: Java版SLG游戏开发入门[0]--让绘制的窗口响应鼠标事件