首页 > 作文

Java实现年兽大作战游戏详解

更新时间:2023-04-04 14:16:26 阅读: 评论:0

目录
前言一、玩法介绍二、代码介绍2.1 程序入口【frame】2800米加油稿.2 构造器【gamepanel】2.3 游戏逻辑实现【gamepanel】2.4 游戏的血液【initprocessor】2.5 实体类【fireworksdo】【flowersdo】2.6 图片素材三、总结

前言

春节要到了,看惯了前端各种小游戏,确实做得很好,很精致。但是我也要为后端程序员稍微做一点贡献,做一款java版本的【年兽大作战】。

这个游戏加上编写文章,上班摸鱼时间加上回家的空闲时间,大概花了三天多。

java写这玩应真的很痛苦,各种状态位,各种图片和逻辑判断,脑袋都快炸了。而且肯定没有前端的精致,效果一般,偶尔会有卡顿,各位就图一乐,随便捧捧场啊。过程大于结果。

源码gitee地址

一、玩法介绍

进入初始界面,会看到一只大年兽位于正中间,然后是一直小老虎,也就是我们的玩家,点击【空格】即可开始游戏:

敲击空格,将进入游戏。从上至下分别是:

年兽的血量【nian’s hp】移动的年兽最下方的小老虎【玩家】

玩家通过【←】【→】键移动小老虎方向,使用【s】键发射炮弹:

当击中年兽后,会有烟花出现在背景:

每击中年兽三次,年兽会扔下炸弹:

如果玩家被击中,则直接【game over】 ,通过【空格】键重新开始:

当每击中年兽10次,其血量就会减少一个,年兽会随机扔下不同种类的爆竹,当前是11种,玩家可以移动方向键获取:

当玩家成功接到炮弹后,再次击中年兽,会更换背景烟花的种类。原本我想把子弹也换了,后来是实在整不动了。我玩了半天,想截个图,半天没成功,给自己心态玩崩了,就是下面的烟火:

当把年兽击败后,会出现新年快乐的字样:

上述就是全部玩法了,其实可以有更多扩展的,java写这东西实在写的太痛苦了。

二、代码介绍

效果不太好,但是学学代码实现总是好的吧,下面我简单说说怎么实现的。

2.1 程序入口【frame】

使用frame作为界面的基础和入口,可以设置大小,标题,展示位置等等,最主要的再次基础上添加一个面板,是我们游戏的实现:

public static void main(string[] args) {    //1.创建窗口对象    frame frame = new frame("年兽大作战");    // 设置窗体大小为900x800    frame.tsize(900, 800);    // 设置窗体为居中格式    frame.tlocationrelativeto(null);    // 设置窗体不可改变    frame.tresizable(fal);    // 在窗体中添加一个面板    frame.add(new gamepanel());    // 设置窗体可见    frame.tvisible(true);    // 窗口点击关闭    frame.addwindowlistener(new windowadapter() {        @override        public void windowclosing(windowevent arg0) {            system.exit(0);        }    });}

2.2 构造器【gamepanel】

第一步,定义一个空参构造,需要添加焦点事件,和键盘事件监听,定时器启动页面刷新,后面后有定时器的创建:

public gamepanel() {    // 获取焦点事件    this.tfocusable(true);    // 添加键盘监听事件    this.addkeylistener(this);    // 启动定时器    timer.start();}

2.3 游戏逻辑实现【gamepanel】

在启动类当中,我们在frame当中添加了一个gamepanel,作用是后面游戏的所有内容展现都在其中,包括页面,游戏逻辑等。

代码较为复杂,我只说关键点,全部代码在全篇开头的gitee链接,感兴趣自己获取。

class gamepanel extends jpanel implements keylistener, actionlistener

如上所示,gamepannel继承了jpanel,同时实现了keylistener和actionlistener。

jpanel

这是jdk提供的,使用java进行绘图的基础容器。面板不会向除其自身背景以外的任何内容添加颜色。但是,您可以轻松地为它们添加边框,并以其他方式自定义它们的绘画。

keylistener

这个接口是用来监听键盘事件的接口,提供一下几个方法:

public interface keylistener extends eventlistener {    /**     * 当按键被键入时调用     */    public void keytyped(keyevent e);    /**     * 当按键下压时调用     */    public void keypresd(keyevent e);    /**     * 当按键释放时调用    public void keyrelead(keyevent e);}

本文中我使用了keypresd和keyrelead。

keypresd主要用来完成键盘操作的移动,和射击功能。每当我们有按键操作,都会被它监听到,产生相应的事件。

此处有坑: 如果将按键一个一个的在此处判断,比如 if(左键) el if(设计) 这样,那么当你同时按下这两个按键,将会导致它们都失效。

解决办法如下:

定义一个全局t,用来存放每次按键的事件。

static t<integer> keys = new hasht<>();

当按键下压时添加:

/** * description: 键盘按下未释放 * * @param e * @return: void * @author: weirx * @time: 2022/1/10 14:02 */@sneakythrows@overridepublic void keypresd(keyevent e) {    // 添加按钮下压事件到t    initprocessor.keys.add(e.getkeycode());    // 遍历执行按钮事件    multikeys();}

在执行一个遍历方法,不断地去执行业务逻辑判断:

public void multikeys() {    for (integer key : initprocessor.keys) {        int keycode = key;        //空格键        if (keycode == keyevent.vk_space) {                    }        // 方向左键        el if (keycode == keyevent.vk_left) {                    }         // 射击        el if (keycode == keyevent.vk_s) {                        }    }}

然后在我们释放按键的时候,使用如下的方式将这个t的key释放掉:

/** * description: 释放按键 * @param e * @return: void * @author: weirx * @time: 2022/1/11 15:39 */@overridepublic void keyrelead(keyevent e) {    //按钮释放,则将该事件移除    initprocessor.keys.remove(e.getkeycode());}

动作监听器【actionlistener】

这个是整个画面能够动态呈现的引擎,我们使用定时器的方式,每到定时时间则会监听到动作事件,进行数据逻辑判断。

其接口如下:

public interface actionlistener extends eventlistener {    /**     * 当事件发生时     */    public void actionperformed(actionevent e);}

定时器定义:

/** * 定时器 */private timer timer = n优美开头结尾ew timer(15, this);

接口actionperformed部分代码展示:

    /**     * description: 定时器回调位置     * @param e     * @return: void     * @author: weirx     * @time: 2022/1/11 15:38     */    @override    public void actionperformed(actionevent e) {        // 当前年兽向右移动的情况        if (initprocessor.left.equals(initprocessor.movedirection)) {            // 被击中,换方向            if (initprocessor.hit) {                initprocessor.movedirection = initprocessor.right;            }            // 判断移动到边界            if (initprocessor.nian_x > 30) {                initprocessor.nian_x -= initprocessor.movespeed * 2;            } el {                initprocessor.movedirection = initprocessor.right;                initprocessor.nian_x += initprocessor.movespeed * 2;            }        } el {            // 被击中,换方向            if (initprocessor.hit) {                initprocessor.movedirection = initprocessor.left;            }            // 当前年兽向左移动的情况            // 判断移动到边界            if (initprocessor.nian_x < 640) {                initprocessor.nian_x += initprocessor.movespeed * 2;            } el {                initprocessor.movedirect形容力气大的成语ion = initprocessor.left;                initprocessor.nian_x -= initprocessor.movespeed * 2;            }        }                        //设置烟火的展示时间,定时器刷新50次,不准确,但是至少能明显感受到烟花存在        if (initprocessor.hitshow == 50) {            initprocessor.hit = fal;            initprocessor.hitshow = 0;        }        // 自增展示次数        initprocessor.hitshow++;        // 刷新页面        repaint();        timer.start();//启动计时器    }

到以上为止,按键事件,和定时器事件都完成了,可以说全部的逻辑判断都在上面去实现。下面我们关注在图像是如何出现的。

图像展示基础【jcomponent】

前面我们似乎没有看到这个组件的身影,那么它是在哪里呢?看下面的类图:

如上所示,gamepanel继承jpanel,而jpanel又继承了jcomponent。jcomponent有一个方法我们需要重写,这也就是我们实现图像展示的方法,其提供了绘制ui的能力,我们重写即可,部分代码如下:

/** * description: 画页面 * * @param g * @return: void * @author: weirx * @time: 2022/1/10 13:40 */@overrideprotected void paintcomponent(graphics g) {    // 清屏效果    super.paintcomponent(g);    // 游戏未开始    if (!initprocessor.isstared) {        background.painticon(this, g,0,0);        initprocessor.nian.painticon(this, g, 250, 130);        initprocessor.tiger.painticon(this, g, 220, 470);        // 绘制首页        // 设置游戏文字        g.tcolor(color.orange);        g.tfont(new font("幼圆", font.bold, 50));        g.drawstring("年兽大作战", 325, 550);        // 设置开始提示        g.tcolor(color.green);        g.tfont(new font("幼圆", font.bold, 30));        g.drawstring("按【空格】键开始游戏", 300, 620);        g.drawstring("按【←】【→】键移动", 300, 660);        g.drawstring("按【s】键发射炮弹", 300, 700);    } el if (isgameover) {        //输出gameover        initprocessor.gameover.painticon(this, g, 10, 10);        // 设置开始提示        g.tcolor(color.green);        g.tfont(new font("幼圆", font.bold, 20));        g.drawstring("按【空格】再次开始游戏", 340, 600);    }}

关键点是使用graphics绘制文字,背景,颜色等等内容。

图片需要使用imageicon类来进行绘画,我将imageicon初始化部分封装了,所以上面没显示,常规使用如下:

imageicon nian = new imageicon(path_prefix + "nian.png");nian.painticon(this, g, 250, 130);

2.4 游戏的血液【initprocessor】

为什么这么说是血液呢?因为这个类是我自己实现的一个初始化类,其中的内容是串联整个游戏的关键点,像身体的血液一样。

通过写这个游戏,我发现最关键的点在于【状态】,可以说全部的页面动画展示都在于一个状态,无论是子弹的运动,年兽的运动,包括礼花图片的切换,以及各种图片的坐标等等。

所以我专门抽象了这个类,用于各种状态的初始化,部分代码如下:

/** * @description: 初始化处理器 * @author:weirx * @date:2022/1/11 10:15 * @version:3.0 */public class initprocessor {    /**     * 游戏是否开始,默认是fal     */    public static boolean isstared = fal;    /**     * 游戏是否暂停,默认是fal     */    public static boolean isstopped = fal;    /**     * 礼花横坐标     */    public static int youwillbekill_x = 0;    /**     * 礼花纵坐标     */    public static int youwillbekill_y = nian_y + 200;    /**     * 展示炮弹     */    public static boolean showyouwillbekill = fal;    public static boolean isgameover = fal;    /**     * 图片路径     */    public final static string path_prefix = "src/main/java/com/wjbgn/nianfight/pic/";    public static imageicon nian = new imageicon(path_prefix + "nian.png");    public static imageicon tiger = new imageicon(path_prefix + "tiger\tiger2.png");    public static imageicon heart = new imageicon(path_prefix + "blood\heart.png");    /**     * 礼花容器     */    public static list<fireworksdo> fireworksdos = initfireworks();    /**     * 花容器     */    public static list<flowersdo> flowersdos = initflowers();    /**     * 初始化爆竹     */    private static list<flowersdo> initflowers() {        list<flowersdo> list = new arraylist<>();        list.add(new flowersdo(1, path_prefix + "flowers\flower1.png"));        list.add(new flowersdo(2, path_prefix + "fireworks\flower2.png"));        list.add(new flowersdo(3, path_prefix + "fireworks\flower3.png"));        list.add(new flowersdo(4, path_prefix + "fireworks\flower4.png"));        list.add(new flowersdo(5, path_prefix + "fireworks\flower5.png"));        list.add(new flowersdo(6, path_prefix + "fireworks\flower6.png"));        list.add(new flowersdo(7, path_prefix + "fireworks\flower7.png"));        list.add(new flowersdo(8, path_prefix + "fireworks\flower8.png"));        list.add(new flowersdo(9, path_prefix + "fireworks\flower9.png"));        list.add(new flowersdo(10, path_prefix + "fireworks\flower10.png"));        list.add(new flowersdo(11, path_prefix + "fireworks\flower11.png"));        return list;    }    /**     * description: 初始化礼花种类     *     * @return: void     * @author: weirx     * @time: 2022/1/11 10:58     */    public static list<fireworksdo> initfireworks() {        list<fireworksdo> list = new arraylist<>();        list.add(new fireworksdo(1, path_prefix + "fireworks\fireworks1.png"));        list.add(new fireworksdo(2, path_prefix + "fireworks\fireworks2.png"));        list.add(new fireworksdo(3, path_prefix + "fireworks\fireworks3.png"));        list.add(new fireworksdo(4, path_prefix + "fireworks\fireworks4.png"));        list.add(new fireworksdo(5, path_prefix + "fireworks\fireworks5.png"));        list.add(new fireworksdo(6, path_prefix + "fireworks\fireworks6.png"));        list.add(new fireworksdo(7, path_prefix + "fireworks\fireworks7.png"));        list.add(new fireworksdo(8, path_prefix + "fireworks\fireworks8.png"));        list.add(new fireworksdo(9, path_prefix + "fireworks\fireworks9.png"));        list.add(new fireworksdo(10, path_prefix + "fireworks\fireworks10.png"));        list.add(new fireworksdo(11, path_prefix + "fireworks\fireworks11.png"));        return list;    }    /**     * description: 初始化方法,用于重新开始游戏     *     * @return: void     * @author: weirx     * @time: 2022/1/11 10:39     */    public static void init() {        isstared = fal;        isstopped = fal;        attack = fal;        nian_x = 325;        nian_y = 50;        tiger_x = 325;        tiger_y = 660;        bullet_x = tiger_x + 20;        bullet_y = tiger_y - 20;        movespeed = 1;        movedirection = left;        hit = fal;        hitcount = 0;        hitshow = 0;        nianblood = 10;        success = fal;        keys = new hasht<>();        fireworks_x = 0;        fireworks_y = nian_y + 200;        showfireworks = fal;        currentfireworks = null;        takefireworks = fal;        currentflowers = null;        youwillbekill = 0;        youwillbekill_x = 0;        youwillbekill_y = nian_y + 200;        showyouwillbekill = fal;        isgameover = fal;    }}

2.5 实体类【fireworksdo】【flowersdo】

这是两个实体类,分别定义的爆竹和礼花样式,用于初始化,代码如下:

import javax.swing.*;import static com.wjbgn.nianfight.nianshou.initprocessor.path_prefix;/** * @description: 花容器 * @author:weirx * @date:2022/1/11 11:05 * @version:3.0 */public class flowersdo {    private integer id;    private string path;    private imageicon flower;    public imageicon getflower() {        return flower;    }    public void tflower(imageicon flower) {        this.flower = flower;    }    public integer getid() {        return id;    }    public void tid(integer id) {        this.id = id;    }    public string getpath() {        return path;    }    public void tpath(string path) {        this.path = path;    }    public flowersdo(integer id, string path) {        this.id = id;        this.path = path;        this.flower = new imageicon(path_prefix + "flowers\flower" + id + ".png");    }}
/** * @description: 礼花实体类 * @author:weirx * @date:2022/1/11 11:01 * @version:3.0 */public class fireworksdo {    /**     * id     */    private integer id;    /**     * 图片路径     */    private string path;    public imageicon getfirework() {        return firework;    }    public void tfirework(imageicon firework) {        this.firework = firework;    }    private imageicon firework;    public integer getid() {        return id;    }    public void tid(integer id) {        this.id = id;    }    public string getpath() {        return path;    }    public void tpath(string path) {        this.path = path;    }    public fire土建预算员worksdo(integer id, string path) {        this.id = id;        this.path = path;        this.firework = new imageicon(path_prefix + "fireworks\firew模字组词orks" + id + ".png");    }}

2.6 图片素材

游戏中使用了大量的图片素材,我在网上找了两个网站不错,一个是png图片网站,是免费的,还有一个是免费切图的,挺好用,都分享给大家

免费png图片网址(英文):www.cleanpng.com/

免费切图网址(简单版直接微信关注):www.uupoop.com/

我使用的素材都在项目的pic目录下:

三、总结

写java好几年了,其实从没有使用 javax.swing 和 java.awt 包下面的内容开发过代码,对于现在用户体验为前提的大环境下,综合编码体验,和游戏运行体验来看,确实是不太友好,不太符合环境背景。但是也是一次不错的学习过程。

问题总结

目前整个游戏还是存在一些bug的,后面有时间再翻出来调试吧,此处先记录一下:

子弹有时并不是从老虎正前方射出的,与实际的坐标存在偏差:此问题出现的原因我推测来自线程间共享变量的同步问题。子弹的初始横坐标取决于小老虎当前所在的横坐标,这个坐标同步没做好。关于按键切换、同时两个按键等情况造成卡顿的问题:前面解决两个按键同时按下的方案可能不是最优解,后面还需要优化。怪兽扔炸弹、爆竹随着年兽移动存在偏移:炸弹和爆竹的初始横坐标,是年兽当前的横坐标,需要一个变量记录当前年兽的位置,作为炸弹和爆竹的初始横坐标。

关于游戏就说这么多了,感兴趣的去文章开篇的gitee下载源码就行了。

以上就是java实现年兽大作战游戏详解的详细内容,更多关于java年兽大作战游戏的资料请关注www.887551.com其它相关文章!

本文发布于:2023-04-04 14:16:24,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/88caa3dacad74690d16db27515c33c0d.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:Java实现年兽大作战游戏详解.doc

本文 PDF 下载地址:Java实现年兽大作战游戏详解.pdf

标签:游戏   按键   定时器   事件
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图