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的光標圖片。

 

  在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 image = Toolkit.getDefaultToolkit().createImage(
  4.                 new MemoryImageSource(1616, pixels, 016));
  5.         Cursor transparentCursor = Toolkit.getDefaultToolkit()
  6.                 .createCustomCursor(image, 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.image.BufferedImage;
  16. import java.awt.image.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.  * http://www.apache.org/licenses/LICENSE-2.0
  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 protected]
  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.         // 創建一個背景緩存用image
  62.         _background = new BufferedImage(currentWidth, currentHeight,
  63.                 BufferedImage.TYPE_INT_ARGB);
  64.         // 導入開始時背景圖像
  65.         _backgroundImage = Utility.loadImage("image/start.gif");
  66.         // 導入光標圖系列,以列寬46讀取到image數組
  67.         _mouses = Utility.getImageColumns(
  68.                 Utility.loadImage("image/cursor.png"), 46);
  69.         // 初始背景爲黑色
  70.         setBackground(new Color(000));
  71.         _arrow = Utility.loadImage("image/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 image = Toolkit.getDefaultToolkit().createImage(
  147.                 new MemoryImageSource(1616, pixels, 016));
  148.         // 製作一個透明的遊標
  149.         Cursor transparentCursor = Toolkit.getDefaultToolkit()
  150.                 .createCustomCursor(image, 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. }

  運行效果如下圖:

 


  當您看見此行文字時,我的CSDN上傳功能還無法使用……


發佈了35 篇原創文章 · 獲贊 0 · 訪問量 4301
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章