江南才子 发表于 2021-6-27 15:55:35

Java版AVG游戏开发入门[0]——游戏模式转换中的事件交互

AVG,即Adventure Game,可以直译为[冒险游戏]。但是通常情况下我们说AVG是指[文字冒险游戏],也有人更直白的解释成自己选择路线和结局的电子小说,与硬砍硬杀的RPG或者揉破键盘的ACT不同,AVG多以解谜或文字游戏等脑力攻关推动剧情发展。现在日本流行的ADV,可以看作是AVG英文全称的不同缩写方式,大体上讲,AVG == ADV。

由于商业化需要,现代主流的AVG往往是GalGame,也就是少女游戏,或称少女恋爱游戏,但GalGame != AVG,只是下属分支中的一环罢了,AVG包含GalGame,但GalGame并不能完全代表AVG/ADV。另外关于GalGame的详细介绍,在若木民喜《只有神才知道的世界》中演绎的相当生动,有兴趣的可以自己去看看~   https://blog.51cto.com/cping1982/../attachment/200902/200902091234158503139.jpg   就技术角度而言,AVG开发可以算得所有游戏类型中最容易的。一款简单AVG游戏的制作难度甚至在贪食蛇、俄罗斯方块之下。由于实现的简易性,导致AVG的开发重心往往着重于策划及美工,程序员的作用则微乎其微。同时也正因AVG开发的门坎约等于0,所以此类型的同人游戏之多即可堪称世界之冠。另外,AVG开发工具普及的也促进了AVG的量产化。利用工具,即始是小说作者、漫画家等非软件专业出身的人士,往往也能轻易制作出顶级的AVG大作。(顺便一提,目前我所见过最好的AVG制作工具是鬼子的livemaker,采用类似思维导图的方式构造整个游戏,很多轻小说作者乃至网络漫画家用它制作自己作品的宣传游戏。但就技术角度上说,livemaker的开发依旧没什么难度......)由于AVG的大泛滥,通常仅有文字、图片及语音的AVG往往无法满足用户需求(H除外-_-)。我们每每可在AVG游戏类型后发现个+号,比如《樱花大战》是AVG+SLG,《生化危机》是AVG+ACT。所以客观上说,AVG开发仅仅能进行字图的交互是不够的,还要解决多模块组件的协调问题。在Java桌面应用开发中,我们都知道绘图是极为简单的,有Image、Graphics两个对象就可以Paint一个图形,即使图形对象再多,最后它们也必须统一在一个Paint中,所以Java中不存在图像的交互问题。 但问题在于,图像的显示可以统一,但是触发图像变化的事件却是很难统一的。比如现在有需求如下,在AVG模式中,触发键盘事件上、下、左、右时为控制画面的前进、后退,切换模式到SLG模  式后,设定上、下、左、右是光标移动,那么如果我要在程序中实现,就必须记录当前模式,而后根据不同模式调用事件,再反馈到图形上。如果只有几个事件的区
别,我们当然可以很容易用分支来实现;问题是,随着游戏规模的加大,这些分支将成几何倍数增多,单纯的分支判定到最后只能忙于应付,落个费力不讨好。 其实在这时,我们大可以使用一些技巧来轻松解决问题。  示例如下:
首先,我们构造一个接口,命名为IControl,继承鼠标及键盘监听,并在其中设定两个抽象方法:
  view plaincopy to clipboardprint?
[*]package org.loon.simple.avg;
[*]import java.awt.Graphics;
[*]import java.awt.event.KeyListener;
[*]import java.awt.event.MouseListener;
[*]import java.awt.event.MouseMotionListener;
[*]/**
[*] Copyright 2008 - 2009
[*]
[*] * 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 interface IControl extends MouseListener, MouseMotionListener,
[*]      KeyListener {
[*]    public abstract void draw(final Graphics g);
[*]    public abstract IControl invoke();
[*]}
package org.loon.simple.avg;
import java.awt.Graphics;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
/**
Copyright 2008 - 2009

* 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 . 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 interface IControl extends MouseListener, MouseMotionListener,

      KeyListener {
    public abstract void draw(final Graphics g);
    public abstract IControl invoke();

}
而后,再构造一个接口,命名为IAVG,同样继承鼠标及键盘监听,并在其中设定三个抽象方法,用以操作IControl接口:
    view plaincopy to clipboardprint?
[*]package org.loon.simple.avg;
[*]import java.awt.Graphics;
[*]import java.awt.event.KeyListener;
[*]import java.awt.event.MouseListener;
[*]import java.awt.event.MouseMotionListener;
[*]/**
[*] Copyright 2008 - 2009
[*]
[*] * 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 interface IAVG extends MouseListener, MouseMotionListener,
[*]      KeyListener {
[*]    public abstract void draw(final Graphics g);
[*]    public abstract IControl getControl();
[*]    public abstract void setControl(final IControl control);
[*]}   
package org.loon.simple.avg;
import java.awt.Graphics;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
/**
Copyright 2008 - 2009

* 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 . 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 interface IAVG extends MouseListener, MouseMotionListener,

      KeyListener {
    public abstract void draw(final Graphics g);
    public abstract IControl getControl();
    public abstract void setControl(final IControl control);

}

  再后,制作一个显示图像用组件,命名为AVGCanva,继承自Canvas。

  view plaincopy to clipboardprint?
[*]package org.loon.simple.avg;
[*]import java.awt.Canvas;
[*]import java.awt.Graphics;
[*]/
[*] Copyright 2008 - 2009
[*]
[*] * 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 AVGCanvas extends Canvas {
[*]    /
[*]   
[*]   /
[*]    private static final long serialVersionUID = 1982278682597393958L;
[*]    private boolean start;
[*]    private IAVG avg;
[*]    public AVGCanvas(IAVG handler) {
[*]      this.avg = handler;
[*]      this.start = false;
[*]      this.addKeyListener(handler);
[*]      this.addMouseListener(handler);
[*]      this.addMouseMotionListener(handler);
[*]    }
[*]      
[*]    public void update(Graphics g) {
[*]      paint(g);
[*]    }
[*]    public void paint(Graphics g) {
[*]      if (this.start) {
[*]            this.avg.draw(g);
[*]      }
[*]    }
[*]    public void startPaint() {
[*]      this.start = true;
[*]    }
[*]    public void endPaint() {
[*]      this.start = false;
[*]    }
[*]}
package org.loon.simple.avg;
import java.awt.Canvas;
import java.awt.Graphics;
/**
Copyright 2008 - 2009

* 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 . 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 AVGCanvas extends Canvas {
/**

/
private static final long serialVersionUID = 1982278682597393958L;
private boolean start;
private IAVG avg;
public AVGCanvas(IAVG handler) {

      this.avg = handler;
      this.start = false;
      this.addKeyListener(handler);
      this.addMouseListener(handler);
      this.addMouseMotionListener(handler);
    }
   
    public void update(Graphics g) {
      paint(g);
    }
    public void paint(Graphics g) {
      if (this.start) {
            this.avg.draw(g);
      }
    }
    public void startPaint() {
      this.start = true;
    }
    public void endPaint() {
      this.start = false;
    }

}
  这段代码中的paint方法中并没有现成的方法,而是调用了IAVG接口的draw。紧接着,我们再设定一个AVGFrame用以加载AVGCanvas。

  view plaincopy to clipboardprint?
[*]package org.loon.simple.avg;
[*]import java.awt.Color;
[*]import java.awt.Dimension;
[*]import java.awt.Frame;
[*]import java.awt.event.WindowAdapter;
[*]import java.awt.event.WindowEvent;
[*]
[*]/
[*] Copyright 2008 - 2009
[*]
[*] * 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 AVGFrame extends Frame implements Runnable {
[*]    /**
[*]   
[*]   /
[*]    private static final long serialVersionUID = 198284399945549558L;
[*]    private IAVG avg;
[*]    private AVGCanvas canvas;
[*]    private boolean fps;
[*]    private String titleName;
[*]    private Thread mainLoop;
[*]    public AVGFrame(String titleName, int width, int height) {
[*]      this(new AVG(), titleName, width, height);
[*]    }
[*]    public AVGFrame(IAVG avg, String titleName, int width, int height) {
[*]      super(titleName);
[*]      Lib.WIDTH = width;
[*]      Lib.HEIGHT = height;
[*]      this.avg = avg;
[*]      this.titleName = titleName;
[*]      this.addKeyListener(avg);
[*]      this.setPreferredSize(new Dimension(width + 5, height + 25));
[*]      this.initCanvas(Lib.WIDTH, Lib.HEIGHT);
[*]      this.pack();
[*]      this.addWindowListener(new WindowAdapter() {
[*]            public void windowClosing(WindowEvent e) {
[*]                System.exit(0);
[*]            }
[*]      });
[*]      this.setResizable(false);
[*]      this.setLocationRelativeTo(null);
[*]      this.setVisible(true);
[*]    }
[*]    public void run() {
[*]      gameLoop();
[*]    }
[*]    /**
[*]    开始循环窗体图像
[*]   
[*]   */
[*]    private synchronized void gameLoop() {
[*]      canvas.startPaint();
[*]      long second = 0L;
[*]      int moveCount = 0;
[*]      // 循环绘制
[*]      for (;;) {
[*]            long start = System.currentTimeMillis();
[*]            this.paintScreen();
[*]            long end = System.currentTimeMillis();
[*]            long time = end - start;
[*]            long sleepTime = 20L - time;
[*]            if (sleepTime < 0L)
[*]                sleepTime = 0L;
[*]            try {
[*]                Thread.sleep(sleepTime);
[*]            } catch (InterruptedException e) {
[*]                e.printStackTrace();
[*]            }
[*]            if (this.fps) {
[*]                moveCount++;
[*]                second += System.currentTimeMillis() - start;
[*]                if (second >= 1000L) {
[*]                  this.setTitle(new StringBuilder(titleName).append(" FPS:")
[*]                            .append(moveCount).toString());
[*]                  moveCount = 0;
[*]                  second = 0L;
[*]                }
[*]            }
[*]      }
[*]    }
[*]    /**
[*]    启动游戏循环
[*]   
[*]   */
[*]    public void mainLoop() {
[*]      this.mainLoop = new Thread(this);
[*]      this.mainLoop.start();
[*]    }
[*]    /
[*]    初始化背景帆布
[*]   
[*]   * @param width
[*]    @param height
[*]   /
[*]    private void initCanvas(final int width, final int height) {
[*]      canvas = new AVGCanvas(avg);
[*]      canvas.setBackground(Color.black);
[*]      canvas.setPreferredSize(new Dimension(width, height));
[*]      this.add(canvas);
[*]    }
[*]    public IAVG getAVG() {
[*]      return this.avg;
[*]    }
[*]    protected void processWindowEvent(WindowEvent e) {
[*]      super.processWindowEvent(e);
[*]    }
[*]    public synchronized void paintScreen() {
[*]      canvas.repaint();
[*]    }
[*]    public boolean isShowFPS() {
[*]      return fps;
[*]    }
[*]    public void setShowFPS(boolean fps) {
[*]      this.fps = fps;
[*]    }
[*]    public Thread getMainLoop() {
[*]      return mainLoop;
[*]    }
[*]    public String getTitleName() {
[*]      return titleName;
[*]    }
[*]}
package org.loon.simple.avg;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
Copyright 2008 - 2009

* 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 . 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 AVGFrame extends Frame implements Runnable {
/**

/
private static final long serialVersionUID = 198284399945549558L;
private IAVG avg;
private AVGCanvas canvas;
private boolean fps;
private String titleName;
private Thread mainLoop;
public AVGFrame(String titleName, int width, int height) {

      this(new AVG(), titleName, width, height);
    }
    public AVGFrame(IAVG avg, String titleName, int width, int height) {
      super(titleName);
      Lib.WIDTH = width;
      Lib.HEIGHT = height;
      this.avg = avg;
      this.titleName = titleName;
      this.addKeyListener(avg);
      this.setPreferredSize(new Dimension(width + 5, height + 25));
      this.initCanvas(Lib.WIDTH, Lib.HEIGHT);
      this.pack();
      this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
      });
      this.setResizable(false);
      this.setLocationRelativeTo(null);
      this.setVisible(true);
    }
    public void run() {
      gameLoop();
    }
    /**
   * 开始循环窗体图像
   *
   */
    private synchronized void gameLoop() {
      canvas.startPaint();
      long second = 0L;
      int moveCount = 0;
      // 循环绘制
      for (;;) {
            long start = System.currentTimeMillis();
            this.paintScreen();
            long end = System.currentTimeMillis();
            long time = end - start;
            long sleepTime = 20L - time;
            if (sleepTime &lt; 0L)
                sleepTime = 0L;
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (this.fps) {
                moveCount++;
                second += System.currentTimeMillis() - start;
                if (second &gt;= 1000L) {
                  this.setTitle(new StringBuilder(titleName).append(" FPS:")
                            .append(moveCount).toString());
                  moveCount = 0;
                  second = 0L;
                }
            }
      }
    }
    /**
   * 启动游戏循环
   *
   */
    public void mainLoop() {
      this.mainLoop = new Thread(this);
      this.mainLoop.start();
    }
    /**
   * 初始化背景帆布
   *
   * @param width
   * @param height
   */
    private void initCanvas(final int width, final int height) {
      canvas = new AVGCanvas(avg);
      canvas.setBackground(Color.black);
      canvas.setPreferredSize(new Dimension(width, height));
      this.add(canvas);
    }
    public IAVG getAVG() {
      return this.avg;
    }
    protected void processWindowEvent(WindowEvent e) {
      super.processWindowEvent(e);
    }
    public synchronized void paintScreen() {
      canvas.repaint();
    }
    public boolean isShowFPS() {
      return fps;
    }
    public void setShowFPS(boolean fps) {
      this.fps = fps;
    }
    public Thread getMainLoop() {
      return mainLoop;
    }
    public String getTitleName() {
      return titleName;
    }

}
  我们可以看到,在本例鼠标键盘事件及图像绘制完全通过接口方式实现。此时,只要让不同组件统一实现IControl接口,便可以轻松转换事件及图像的绘制。也正是我们都再熟悉不过的MVC模式中,通过Event导致Controller改变Model及View的基本原理。
  下一回,我们将具体讲解一个AVG游戏实现的基本流程。
示例代码界面如下图:
https://blog.51cto.com/cping1982/../attachment/200902/200902091234158652221.jpg
https://blog.51cto.com/cping1982/../attachment/200902/200902091234158662438.jpg
https://blog.51cto.com/cping1982/../attachment/200902/200902091234158675579.jpg
https://blog.51cto.com/cping1982/../attachment/200902/200902091234158687103.jpg
https://blog.51cto.com/cping1982/../attachment/200902/200902091234158698953.jpg、

示例程序下载地址:http://download.csdn.net/source/×××73(源码在jar内)
页: [1]
查看完整版本: Java版AVG游戏开发入门[0]——游戏模式转换中的事件交互