admin管理员组

文章数量:1664792

项目报告

基于Java的愤怒的小鸟游戏的设计与实现

Java语言是一门面向对象的编程语言,它不但汲取了C++语言的各种精髓,而且还抛弃了C++语言里晦涩难懂的多继承指针等概念,所以Java语言具有的特征便是:功能超强和易用实用。

Jbox2D中不仅集成了大量物理运动学和机械运动学计算,而且也将物理仿真包集成到类对象中,同时对象也应用在开发人员与用户的交互界面中。所以我们只需要调用对象或者使用相应的功能,就可以模拟出现实生活中的速度、加速度、抛物线运动、重力、碰撞、反弹等各种各样真实的物理运动。

愤怒的小鸟游戏的视角是横向版本的水平视角。背景是为了报复偷鸡蛋的猪,鸟将自己的身体作为武器,就像炮弹一样从弹弓上发射而出,撞向绿猪搭建的堡垒,以达到摧毁绿猪的目的。游戏非常简单,小鸟跳上弹弓,发射角度和强度的改变导致小鸟的落点的不同,玩家需要良好的整体计算,适当的调节强度和角度,从而更准确地击中绿猪。每次击中绿猪,便可获得一定的分数,积累足够的分数便可以通关,游戏设计的体现了放松的情调,乐观的风格。

本文将介绍使用Java语言实现经典的益智类游戏作品“愤怒的小鸟”的开发。游戏将主要实现以下几个功能:游戏玩法功能、页面管理功能、难度和级别功能、碰撞功能、鸟类管理功能、玩家管理功能和备份功能等。此游戏不仅可以给玩家带来欢乐,也会使玩家对开发游戏产生极大地兴趣,让玩家切身体会到Java语言在生活方面的无处不在。

 Java;C++;Jbox2D;物理运动;愤怒的小鸟

目录

摘要.................................................................................................................................. i

Abstract............................................................................................................................ ii

1 绪论............................................................................................................................. 1

1.1 游戏开发的背景.................................................................................................. 1

1.2 典型的Java游戏介绍.......................................................................................... 1

1.2.1 Minecraft介绍............................................................................................ 1

1.2.2 Super Mario Bros介绍................................................................................. 2

1.2.3 The Sims介绍............................................................................................. 3

1.3 游戏开发的意义.................................................................................................. 3

2 开发环境...................................................................................................................... 4

2.1 开发语言............................................................................................................. 4

2.2 开发工具............................................................................................................. 5

2.3 JDK介绍............................................................................................................. 5

2.4 Java Awt介绍....................................................................................................... 6

2.5 Java Swing 介绍................................................................................................... 7

2.6 Java语言开发平台搭建........................................................................................ 9

3系统需求分析............................................................................................................... 11

3.1 可行性分析........................................................................................................ 11

3.1.1 技术可行性.............................................................................................. 11

3.1.2 经济可行性.............................................................................................. 11

3.1.3 操作可行性.............................................................................................. 11

3.1.4 发展可行性.............................................................................................. 12

3.2 性能需求分析.................................................................................................... 12

3.3 功能需求分析.................................................................................................... 12

3.4 系统UML分析................................................................................................. 13

3.5界面需求分析..................................................................................................... 14

4 系统设计.................................................................................................................... 16

4.1系统流程设计..................................................................................................... 16

4.2 系统架构设计.................................................................................................... 17

5 详细设计.................................................................................................................... 20

5.1 主界面实现....................................................................................................... 20

5.2 游戏玩法实现.................................................................................................... 22

5.3 面板管理功能实现............................................................................................. 26

5.4 等级、级别管理功能实现.................................................................................. 28

5.5 碰撞管理功能实现............................................................................................. 32

5.6 鸟类管理功能实现............................................................................................. 35

5.7 胜利管理功能实现............................................................................................. 38

5.8 玩家管理和备份管理功能实现........................................................................... 40

6 系统测试.................................................................................................................... 44

6.1系统测试简介..................................................................................................... 44

6.2 系统测试方法.................................................................................................... 45

6.3 本系统测试....................................................................................................... 45

6.3.1 测试用例设计.......................................................................................... 45

6.3.2 测试方法和结论....................................................................................... 46

结论............................................................................................................................... 47

参考文献........................................................................................................................ 48

致  谢........................................................................................................................ 49

附录............................................................................................................................... 50

外文原文................................................................................................................. 50

中文原文................................................................................................................. 55

1 绪论

1.1 游戏开发的背景

游戏如今已经成为人们生活中不可或缺的一部分,无论是处于童趣中的小孩、还是繁忙工作中的成人,游戏无疑在每一个人生活中起着非常重要的调剂作用。不管是实体玩具还是如今的电子游戏,它们都能给我们留下很多美好的回忆。

本人对Java语言有很大的兴趣爱好,大学期间便对其一直进行学习,而且游戏的制作同时也是我非常感兴趣的一个方面,在此之前也进行过一些小游戏的开发。经此次毕业设计机会,使得我想在专业导师的指导下通过这次毕业设计,来提高自己的Java语言水平,为以后的工作打下坚实的语言基础。

游戏名称是“愤怒的小鸟”,英文称为“AngryBird”。 “愤怒的小鸟”是著名游戏公司Rovio偶然间开发出来的益智游戏,从200912月上市到iOS。,讲述了鸟类和猪因为猪偷鸟蛋反生的一系列故事。游戏的类型版本是横向版本的水平视角,背景是为了报复偷鸡蛋的猪,鸟将自己的身体作为武器,就像炮弹一样从弹弓射出抛到绿猪搭建的堡垒,并将其摧毁。 游戏非常简单,小鸟跳上弹弓,发射角度和强度由玩家的自行控制,游戏中需要良好的整体计算,适当的调节强度和角度,从而更精确地击中猪。每次击中更多的绿猪,将获得大量的分数游戏风格充满乐趣和幽默,游戏设计的体现了放松的情调,乐观的风格。

愤怒的小鸟这款游戏内容包含着丰富的人机交互:不管游戏本身设计的难度如何,只要玩家学会基本的游戏使用方法,大多数人都可以经过不断训练来打到通关这一目的。这一款益智类游戏的开发,目的是为了使人们更多了解物理引擎游戏所带来的与众不同。在今天如此复杂多样的游戏环境中,这一款游戏所带来的新鲜感让人为之疯狂,极大展现了Java语言在开发物理运动游戏方面独有的魅力,也极大促进了Java语言在游戏方面的发展。

1.2 典型的Java游戏介绍

1.2.1 Minecraft介绍

Minecraft是近乎是沙盒游戏的鼻祖,是由瑞典Mojang AB公司和4J Studios联合开发,发行于2009年,最后在2014年冬季被microsoft25亿美金收购。

图1-1 minecraft

1.2.2 Super Mario Bros介绍

Super Mario Bros是任天堂游戏公司开发的,相信我们小时候都玩过这一款游戏,它几乎是所有红白机游戏中最畅销的一款,游戏风格是横版过关类型,这个游戏几乎移植到了所有已知的游戏平台,为游戏爱好者广泛而知。

图1-2 Super Mario Bros

1.2.3 The Sims介绍

这是一款由Electronic Arts,简称EA开发的以模拟一个人生活中各种事情为主的模拟养成类游戏,。在这个模拟的世界中,玩家可以操作游戏人物进行近乎于现实生活的行动,仿造真实的情境,控制生理和精神的需求。

图1-3 The Sims

1.3 游戏开发的意义

随着计算机科学的发展,游戏的发展已经取得非常显著的成果。图形计算、应用数学、数据结构、算法设计,甚至网络安全都已经应用在强大的游戏引擎中,而且由于快速做出原型是游戏开发的重要组成部分,所以应该学习如何高效率编写可重用的代码是一个很值得关注的问题。即使游戏没有为他人做出贡献,但至少它给了我很多的灵感,让我认识到作为一个程序员的兴趣所在,我可以继续为未来所需的知识量而努力。况且,游戏已经影响到人类生活的方方面面,成为生活不可或缺的一部分。甚至来说,游戏作为一种产业,已经跻身到互联网这个巨大的利益链的前列。目前,中国的游戏产业已然成为一个新的经济增长,极大地刺激了中国经济的发展,成为了中国六大支柱产业之一。

游戏的发展不仅带来了经济的利益,同时也极大地丰富了人类的生活,增强人们的幸福感,但游戏也存在不可避免的弊端,所以积极宣传中国传统文化,引导青少年健康游戏、快乐游戏应该是,每个游戏工作者的责任和义务。

2 开发环境

2.1 开发语言

Java语言是一种面向对象的编程语言,它不仅拥有着C++语言的长处,而且也将C++中的多重继承和指针等概念进行摒弃,Java语言不仅具有易用实用的特征,同时也是一种功能强大的编程语言。

Java语言的特征大致可以分为以下一些:简单性、面向对象、分布式开发、稳健性、安全性、平台独立性、可移植性和多线程以及动态特性。这些特性以及Java语言本身的强大,是Java语言得以风靡全球的根本原因。

Java语言从外部看起来确实很像C++语言,但是为了使Java语言不那么晦涩难懂和难于学习,开发设计人员将很多原本用于C++语言的功能进行选择性删除。比如:Java语言将重载和多个继承功能中的重载运算符排除在外,并且去除主文件,从而消除了预处理器。再由于Java语言准确的是没有结构的,并且字符串数组是对象,所以指针存在也是不必要的。去除C++语言的糟粕后,Java语言可以自动管理参考对象和间接引用、自动垃圾收集,这些功能都可由使用户不必担心内存管理问题,从在将更多的时间和精力投入到研发开发当中,无疑是大大提升了编程开发效率。

如图2-1为Java语言总体特点:

图2-1 Java语言特点

由于Java语言是面向对象的语言。所以对于程序员来说,这意味着数据必须在方法中声明和操纵,笼统来说,程序员只需要学习对方法的熟悉和使用,而不是严格的创造方法过程。Java语言的特点使得语句不单单依赖于实现的方面,这种特殊的特点使得Java环境本身对新的硬件平台和操作系统是可移植的,并且起重要作用的Java编译器也是用Java语言所编写的,而且还包括用ANSIC语言编写的Java运行时系统。Java语言旨在适应进化,这说明它是一种动态语言。

由于Java语言的本质,Java游戏不会受到太多来自软件运行平台的诸多限制。假如我们以Java语言进行开发时,哪怕要重新编译成千上万个类,也只不过是花费很少的时间便可以办到的事情,这是CPP编译速度所无法企及的。而Java语言语法在一定程度上要比C语言或者C++语言简洁方便很多,开发者只需要掌握一些基本语法,久而久之的学习,绝大多数人都可以使用Java语言轻易地进行开发程序。本文将以电脑为基础,采用Java语言来编写一款叫做“愤怒的小鸟”的游戏。

2.2 开发工具

Eclipse是一个将源代码的开放、基于Java语言的具有可扩展性的编程开发平台。就其本身而言,它仅仅只有一个框架与一组服务,将其通过插件组件来构建成一个开发环境。比较需要注意的是,Eclipse 附带着一个标准的插件集,插件集中包含了各种开发工具,比如用于Java语言开发的Java开发工具Java Development KitJDKEclipse是著名的跨平台开发环境和自由集成开发环境的组合。它最初主要是用于Java语言的开发,在Eclipse通过安装不同的插件,使得Eclipse具有可以支持不同的计算机语言的特性,比如C++语言Python语言等开发语言。Eclipse简单来说,它本身仅仅只是一个框架平台,但是各种各样插件的支持使得Eclipse具有其他功能死板IDE软件所难以拥有的方便灵动性,这一特性让许多软件开发公司都将以Eclipse为基本框架来开发属于自己的IDE

Eclipse起始于19994月,它最开始是由OTIIBM这两家巨头公司的IDE产品开发项目组合力创建,。Eclipse基础代码最初是由IBM提供的,其中包括JDT PDE PlatformEclipse项目经由IBM发起,经过这么多年的发展, Eclipse这一当初小小的项目已然发展成为了一个巨大的Eclipse联盟,目前为止,大概有140多家软件公司先后参与到Eclipse开发项目中,其中包括Red HatSybase等公司。Eclipse本身作为一个开源的项目,它的初衷作为是作为Visual Age for Java的替代品,所以Eclipse界面效果与早期版本的Visual Age for Java相差不大,后来随着开放源代码,任何开发人员不仅可以免费得到Eclipse,而且可以在它的基础上进行各自插件的开发,这一缘由导致Eclipse受到越来越多的人的欢迎。在此之后还有包括Oracle等在内的许多软件大公司也先后加入了该项目,使得Eclipse到目前为止已经成为任何语言开发的IDE集成者,也是Java语言开发使用最广泛的平台。

2.3 JDK介绍

JDKJava语言的软件开发工具包(Java Development Kit),它在java语言开发中占有核心的地位,它包括Java的运行环境,java的基本工具和java的基础库等。

JDK包含的基本组件包括:

javac 编译器:javac编译器自动读取由java语言编写而成的类和接口的定义等,并将它们编译成字节代码(class文件)

jar打包工具:将相关的类文件整合为一个文件

javadoc文档生成器:从源码中将注释提取为一个文档

jdb debugger:调试工具

jav:可将编译后的java程序(.class后缀的程序)运行

appletviewer可在脱机环境的情况下运行applet

Javah:创建一个可以被Java程序所调用的C过程的头文件。

Javap:一种进行反编译的工具。

Jconsole: Java中可以对系统进行监控和调试的工具。如下图2-2所示:

图2-2 JDK基本组成

2.4 Java Awt介绍

AWTAbstract Window Toolkit),中文翻译是:抽象窗口工具包,该包提供了一个GUI交互的接口,Java提供了用于创建和配置Java GUI的基本工具。AWT中图形操作函数与系统中图形功能之间提供了一对一的关系,称为peeres,当开发者使用AWT编写图形用户界面时,实际上是使用本地操作系统所提供的图形库。由于各种操作系统的风格和功能不同,所以提供的图形库不一样,在一个平台上可能存在的功能反而在另一个平台不存在。为了实现“写一次(编写一次,任意平台都可以运行”)Java语言的概念,AWT必须以牺牲独立平台功能为代价,由各种系统提供的AWT图形功能的交集来作为awt图形功能的可用功能。

如下图为awt的几种基本布局:

图2-3 awt的几种基本布局

AWT在基础组件(components)的GUI应用于提供JavaAppletJava  Application 由于Java是一种独立于平台的编程语言,而是通常将GUI链接到特定的平台,Java技术允许使用相应的AWT可以提供一个平台接口独立应用于某个机器,这保证了相同的程序操作GUI 在不同的机器上具有相似的界面效果(但是并不能保证一定相同)。

来自AWT Java1.0(旧AWT)的和AWT Java1.1(新AWT)之后的有着截然不同的情况,新的AWT相比于旧的AWT有这更显着的改进并且摒弃了很多缺点,从而新的AWT可以更加方便的使用,这篇论文主要是讨论新的AWT,但不可否认在Java1.1中更旧版本的AWT程序也可以运行。

Abstract Window ToolkitAWT使用Java语言来操纵位图显示窗口,为一个图形过程库。 最后,扩展设计人员将AWT扩充为AWT Window Alternative ToolkitApplet Widget Toolkit 最新的图形界面叫做Swing,扩展了AWTSwing应用程序开发人员可以使用它来生成独立的GUI平台对象。

2.5 Java Swing 介绍

Java Swing 作为一个GUI工具包,是专门为Java设计与使用的,也是Java基础类的一部分,其中包含的一些组件如文本框、分割窗格、表格、按钮等

Java Swing可以提供的屏幕显示元素,在某种程度上要比Java Awt提供的要好的多。由于Java Swing使用纯Java语言编写的,在这一点上不与AWT相同,所以Java Swing拥有与Java语言一样的可扩展性,可以在多种平台上使用,并且Java Swing也是JFC的一部分。Java Swing可以对面板和主题进行变更(操作系统中存在着默认的主题,不同操作系统的主题不尽相同),这并不是真正的去使用这种操作系统的设备,只是在一定程度上在表面上模仿。这些特殊的功能导致程序猿可以可以使用Java的任何面板,并且不需要操作系统的限制。如下图2-4Java swing 基本组成:

图2-4 Java swing 基本组成

JFrame  java中的GUI是以JFrame为基础的一种基本思路,它是屏幕上创建window的对象,通过调用一些特定的方法,可以实现界面的最大化、最小化、关闭。
    JPanel :作为Java GUI 下的swing中的面板容器类,它包含在javax.swing 包中,使用的时候可以对其进行嵌套,它的功能是在窗体的使用中对其具有相同逻辑功能的组件进行整合,同时也是是一种轻量级容器,使用中可以加入到JFrame窗体中。。

JLabel JLabel 对象的功能是可以将文本、图像进行显示或同时对二者进行显示。它有垂直和水平两种对齐方式,可以指定标签显示区中的标签内容在任意一处进行对齐。通常默认情况下,标签默认在显示区域内居中垂直对齐,而且,显示文本的标签是开始一种边对齐,显示图像的标签则是居中水平对齐。

JTextField :它可以进行编辑单行文本,是一个轻量级的组件。

JPasswordField :功能是让我们输入了一个文本框类似于输入框,但是在输入密码的时候讲密码进行隐藏,类似替换成*符号。

JButton JButton 类的实现类,功能是在面板中创建按钮,可以对齐进行点击之类的操作。比如我们经常使用的登录、注册按钮。

2.6 Java语言开发平台搭建

首先登陆到官方网站Oracle | Cloud Applications and Cloud Platform之后跳转到JDK下载页面下载Java JDK ,版本选择最新的jdk1.8.0_131进行下载,下载界面如下图2-5所示:

图2-5 下载JDK

下载完成后安装JDK,安装路径为F:\Java\jdk1.8.0_131,安装路径如下图2-6所示:

图2-6 安装路径

安装完成JDK之后登陆网址https://www.Eclipse,进行下载Eclipse,下载界面如下图2-7所示:

图2-7 下载Eclipse

下载后,安装完成打开Eclipse,点击菜单栏上的window-preference进行配置JDK,之后便可以使用Eclipse进行编程开发,配置方式如下图2-8所示:

图2-8 配置JDK

如下图2-9位Eclipse的基本使用界面,通过多次使用来进行熟悉即可。

图2-9 Eclipse基本使用界面

3系统需求分析

3.1 可行性分析

可行性分析是要求以经济方法和效益为核心进行全面的系统分析,围绕影响项目的各种因素、收集大量数据分析论证项目的可行性。对整个项目进行可行性研究是为了对项目进行分析和评估,突出项目的优缺点和讨论如何对项目进行改进。为了结论的可行性,经常还需要添加一些配件,如测试数据、演示材料、电子表格、图纸等,以提高可行性研究的严谨性,使其更具有说服力。

3.1.1 技术可行性

技术可行性是从重要技术项目执行实施的角度,合理设计技术解决方案,进行比较评估不同行业和不同深度的大型技术可行性研究项目。这一款软件开发,在硬件方面不存在特殊要求,只需要在普通的硬件配置中便可以轻松实现,但必须保证系统的正常运行,并提高效率。如果硬件特别弱、性能差从而使得系统效率低下,导致整个系统不顺畅。但对于如今的个人PC的一般配置,进行软件开发是很容易满足条件的。 因此,该系统的开发在硬件方面完全可行。操作系统选择WINDOWS操作系统,开发软件为Eclipse,该系统的设计实现在软件方面是可行的。因此可以看出,该系统的开发是没有问题的。

3.1.2 经济可行性

   经济可行性是在资源配置、区域经济发展目标、经济资源有效配置、增加供给、创造就业机会、改善环境、改善生活等方面,对项目的价值进行评估。本基于Java开发的愤怒的小鸟游戏,需要的软硬件环境,在市场上都是很方便可以购买得到,开发人员主要是进行软件开发和简单的维护。所以人力和财力资源开发方面没有问题,开发周期长,经济可行性很大。

3.1.3 操作可行性

操作可行性是设想项目验证是可行的,它提出了各种方案的实施,并解释了不同方案的优缺点。该系统使用基于Java语言编写,使用个人PC安装Eclipse来进行访问和操做,并且界面简单易用,只要用户稍加以学习和多试几次,完全可以熟悉访问和操作。这一款愤怒的小鸟游戏,有着界面上易于管理和良好的互动操作等各种优点。

3.1.4 发展可行性

软件生命周期的观点:随着中国游戏方面还处于成长期,这类游戏软件将会被广大用户提出需求。因此,这是游戏软件长寿的主要原因,开发一款游戏软件,合理的运营将会使它和谐的发展,而且这并不是一个太难的开发项目,它不仅可以在PC端进行操作使用,甚至可以跨平台传播,而且用户操作简单,使用起来非常方便,绝大多数用户都可以不用花费大量的时间便可以完全上手,这不仅是适应用户对游戏的追求,同时适应当前的游戏发展趋势。

3.2 性能需求分析

为了保证愤怒的小鸟程序的长期、稳定和有效的运作,必须保证系统的发展。 在开发基于Java语言的愤怒的小鸟程序的过程中,系统必须确保使用适当方法来保证程序的安全性和有效性。 我们必须充分考虑以下几点:

安全性:在信息时代,信息是确保信息安全的重要资产,特别是个人信息也需要强大的安全性,所以在游戏个人信息保存方面,应该确保安全性是第一位。

超前性:结合当今时尚潮流,开发满足用户需求,让用户总是可以第一时间获得超前的游戏体验,获得新鲜感,获得新的乐趣。

可扩展性:基于Java的愤怒的小鸟游戏不仅可以在PC端进行使用,由于Java语言的可扩展性,注定这个游戏也可以进行多平台使用,更加方便的获取以及更加方便的操作。

3.3 功能需求分析

需求分析是对用户需求和要求的分析。需要准确评估用户需求的结果从而反映实际用户需要,这将直接影响整个设计的流程,也会对系统的使用产生影响。关注需求评估来完成调查收集和研究,可能会受到数据管理和信息安全过程的影响。一般用户相对于开发者来说,绝大部分用户都是缺少计算机相关知识,并且无法确定计算机是否可以为自己做到或者做不到一些事情,准确表达他们的需求是需要的,必须通过跟用户的深入了解来获取用户的需求从而准确的确定计算机的功能。

搜索用户分析和细化特征等用户的描述的信息是必要的。它是软件开发过程的第一阶段主要部分,主要任务是了解您需要什么,需要做个什么样的系统,使完整的目标系统拥有清晰准确的功能,并且可以进行书面表达。

在愤怒的小鸟程序中,主要实现以下功能

游戏玩法功能:

  1. 能实现发射器创建、发射角度、发射力度等
  2. 可以实现小鸟飞行功能,包括小鸟飞行抛物线、小鸟飞行悬停
  3. 实现游戏中猪的移动功能,包括猪的根据级别不同改变移动速度、猪在哪里移动
  4. 碰撞功能:实现小鸟与障碍的碰撞、小鸟与猪的碰撞、鸡蛋与障碍物的碰撞、鸡蛋与猪的碰撞、猪与障碍物的碰撞

页面管理功能:实现各个页面功能,包括主界面、载入游戏界面、控制帮助界面、跳转界面功能等

难度、级别功能:实现游戏难度功能,难度不同猪的移动速度不同,实现级别功能,级别不同地图难易程度也不同

实体类管理功能:创建游戏中各种实体类,包括鸟的种类、猪、障碍物、草地等

玩家管理功能和备份功能:实现玩家信息管理以及游戏信息保存的功能

如下图3-1所示:

图3-1 总体功能需求图

3.4 系统UML分析

UML是在面向对象的方法BoochTMOOOSE等基础上开发的方法,以及根据许多其他方法和材料的基础从而衍变出来的。 UML符号是为各种图形符号表示,消除混淆、冗余符号或很少使用以及容易导致的图形符号,同时添加一些新的图形符号。

UML是统一建模语言的缩写,也称为统一建模语言。它是用于可视化建模的一种语言。 UML是开发人员用于建模事物的客观标记,同时也为开发人员了解什么样的系统工作以及整个过程做出了准备工作。 现在我们在这个基础上对愤怒的小鸟进行建模分析,UML用例图如下图3-2所示:

图3-2 UML界面用例图

图3-3 UML游戏操作用例图

3.5界面需求分析

目前,界面设计已成为软件质量评估的重要指标,良好的用户界面能提高用户操作系统的信心和增加兴趣、提高效率。 客户界面是指与用户与机器进行交互的软件系统界面,通常覆盖界面格式输出,输入和其他人机对话。

1.输出设计

输出是游戏程序解决基本信息的输入,产生高质量的正确信息,并使其具有一定的格式,供管理人员使用,这是发布设计和目标的主要责任和目标。

与系统开发过程的实现不同,不是从输入设计到输出,而是输处设计到输入到设计。 这是因为输出表与用户直接链接,应设计为确保用户可以方便地使用输出表格,及时将有用的信息可以反映在各个部门。

2.输入设计

收集和输入输出数据更加不方便,需要大量的人力物力,而且经常出错。 一旦不正确的数据输入系统,处理后的输出会扩大这些错误,所以输入数据的准确性对系统的整体性能起决定性的作用。输入设计有以下几点原则:

1)输入量必须最小程度的满足处理要求。 输入数据越少,错误率会越小,数据准备时间也较少。

2)如果可能的话,尝试尽可能方便的准备和输入过程,以减少更多的发生错误发生率。

3)尽早检查输入数据(尽可能靠近原始数据生成点),及时进行纠错更改。

4)可能会尽可能早地将输入数据记录所需的处理形式进行记录,以防止从一个介质传输到另一个介质而可能发生的转录数据错误。

4 系统设计

4.1系统流程设计

在设计开发基于Java语言的愤怒的小鸟的时候,必须先进行需求分析,这样才可以对系统总体进行概括性规划,从而进行系统功能模块的设计、测试、基于Java语言的愤怒的小鸟的总体设计流程图,

如下图4-1所示:

图4-1 总体流程设计图

该程序在游戏概念和属性中使用面向对象的模型。 运行程序后,用户可以选择运行选项菜单,屏幕刷新屏幕重新设计一定频率,实时反映游戏状态。

如下图3-2显示了游戏控制的流程图。 首先,当游戏运行时会成为测试的关键角色,同时响应方向。 这是对鸟类在屏幕的主要方向的响应,同时检测释放的鸟类的强度。 如果你摧毁对象超过一个固定的分数,导致游戏结束。 当物体被鸟类摧毁时,根据物体的破坏得到不同的分数。

同时,还有其他检测键,主要用于检测菜单键。 菜单按钮包括检测输出和返回参数。 当游戏检测到退出按钮时,游戏结束。 当检测到游戏的返回键时,游戏将返回到主屏幕。

图4-2 游戏控制流程图 

4.2 系统架构设计

系统架构设计模型采用MVC经典建模模型,使用面向对象设计思想来帮助实现。 MVC全名模型视图控制器(Model-View-Controller),M、V、C分别是指逻辑模型、指视图模型、控制器。MVC已经开发成了用于图形用户界面中常规映射输入,处理和输出逻辑功能的独特结构。

我们参考的模型是一个对应于多个逻辑模型的视图模型,我们使用这个模型将视图模型的代码和逻辑模型的代码分开,以便我们可以以不同的形式实现相同的程序,然后控制台的控制着两者同时工作,其中一个变化, 另一个应该同时更新。

用户对此模型的最大优势是根据自己的方式选择更加适合自己的方式来浏览数据。 对于那些开发人员来说,这种模式的最大优点就是分离界面和应用程序的逻辑层,这个界面和程序员的设计者可以在格子的领域工作,而不会互相干扰。

Model(模型):

GameModel():实现游戏主要功能,包括碰撞、发射、得分。

Level():实现游戏难度、地图功能。

LevelNumber():存储地图功能

ListChangedEvent()、ListListener():碰撞功能的监听

Player():玩家备份功能

Entity()、Bird()、Block()、Egg()、Enemy()、EntityThread()、Grass()、HummingBird()、Pig()、Pigeon()、Sparrow() :定义游戏中各种模型的实体类。

图4-3 游戏实体类图

View(视图):

GameView():实现游戏中发射皮筋功能

GameViewMenu():实现背景界面及标题功

MenuDifficultyView():实现难度选择界面

MenuHomeView():实现主界面

MenuLevelView():实现级别选择界面

MenuLoadView():实现确认及删除界面

MenuNewView():实现增加玩家信息界面

MenuOptionsView():实现控制帮助

Controller(控制器):

GameController():实现监听事件(键盘,鼠标,更改实体列表)

MenuController():实现菜单控制界面跳转等功能

图4-4 游戏总体类图

5 详细设计

5.1 主界面实现

 (见图5-1)当玩家开始游戏时,共有4个选择:开始一个新游戏,加载一个保存的游戏,了解游戏控制或离开游戏。 如果启动新游戏,则需要输入其名称,以创建备份。 如果决定加载保存的游戏,玩家从已经备份的名称中进行选择。如果点击控制帮助选项,玩家将会看到游戏控制的帮助内容,以便更好的了解游戏、更容易上手。如果选择退出游戏,则游戏会立刻关闭。

图5-1 主界面

部分代码以及相应的页面布局:

public class MenuHomeView extends GameViewMenu

{

       private JButton newButton,loadButton,optionsButton,exitButton;

       public MenuHomeView() { 

        newButton = new JButton("新游戏");

        newButton.setSize(250,40);        

        newButton.setLocation(frameWidth/2-115, 150);

        loadButton = new JButton("载入游戏");

        loadButton.setSize(250,40);

        loadButton.setLocation(frameWidth/2-115, 225);

        optionsButton = new JButton("控制帮助");

        optionsButton.setSize(250,40);

        optionsButton.setLocation(frameWidth/2-115, 300);

        exitButton = new JButton("退出游戏");

        exitButton.setSize(250,40);

        exitButton.setLocation(frameWidth/2-115, 375);

        backButton.setVisible(false);

         this.add(newButton,new Integer(1));

         this.add(loadButton,new Integer(1));

         this.add(optionsButton,new Integer(1));

         this.add(exitButton,new Integer(1));

       }

       public JButton getNewButton()

       {

              return newButton;

       }

       public JButton getLoadButton()

       {

              return loadButton;

       }

       public JButton getOptionsButton()

       {

              return optionsButton;

       }

       public JButton getExitButton()

       {

              return exitButton;

       }}

5.2 游戏玩法实现

(见图5-2及5-3)选择一个级别后游戏开始。 首先玩家必须选择弹出一只鸟(游戏中总共有三只鸟供玩家使用),根据鸟的特点,玩家可以决定在飞行期间是否停止鸟,同时鸟也可以扔一个鸟蛋(鸟蛋的数量取决于不同的鸟的种类)。 玩家可以使用鸟蛋碰撞或直接鸟本身碰撞消灭阻碍物。 最后,玩家还可以借助鸡蛋或鸟类本身杀死绿猪,有时可能需要摧毁阻碍物才能到达消灭某些猪的目的。

图5-2游戏玩法图

图5-3为刚进入游戏时的界面,右上角显示还剩下几种鸟可以使用以及剩余鸟的数量,金色的蛋表示鸟剩余蛋的数量

图5-3 游戏内容图1

图5-4 游戏内容图2

图5-4表示鸟在飞行过程中的状态,飞行中可以按S键将小鸟悬停在空中,然后可以按space键释放鸟蛋

部分代码(创建面板以及地图的代码)以及相应的页面布局:

       public GameView(ArrayList<Entity> entities) {

        setFocusable(true);

        setDoubleBuffered(true);

        ImageIcon img1 = new ImageIcon(slingShotName1);

           slingShotImg1 = img1.getImage();

           ImageIcon img2 = new ImageIcon(slingShotName2);

           slingShotImg2 = img2.getImage();

        this.entities = entities;

       }

       public void paint(Graphics g) {

        super.paint(g);

     Graphics2D g2d = (Graphics2D)g;

        g2d.drawImage(map.getImage(), 0, 0,frameWidth,frameHeight, this);

        g2d.drawImage(slingShotImg2, 100,412, this);

           int k = 0;

           for(int i = entities.size()-1; i >=0;i--) {

                 Entity e = entities.get(i);

                        if(e instanceof Bird && e != currentBird) {

                               g2d.drawImage(e.getImage(), frameWidth-100-k*15, 100,e.getImageWidth(),e.getImageHeight(), this);

                               k++;

                        }

                        if(e == currentBird)

                        {

                               Egg egg = new Egg(0,0,28,30);

                               for (int j = 0; j < currentBird.getEggLeft(); ++j) {

                                  g2d.drawImage(egg.getImage(), frameWidth-100+j*15, 20, this);

                               }

                 }

           }

        for (Entity e : entities) {

             if (!(e instanceof Bird))

             {

                    g2d.drawImage(e.getImage(), (int) e.getPosition().getX(), (int) e.getPosition().getY(), this);

             }

             if(e== currentBird )

             {

                    g2d.setStroke(new BasicStroke(7.0f));

                    g2d.setColor(new Color(54, 28, 13));

                    //drawLine()划线的方法

                    if(!currentBird.isFlying())

g2d.drawLine(135,442, (int)e.getPosition().getX()+e.getImageWidth()/2, (int)e.getPosition().getY()+e.getImageHeight()/2);

                    g2d.drawImage(e.getImage(), (int) e.getPosition().getX(), (int) e.getPosition().getY(),e.getImageWidth(),e.getImageHeight(), this);          

                    if(!currentBird.isFlying())

                           g2d.drawLine(120,442, (int)e.getPosition().getX()+e.getImageWidth()/2, (int)e.getPosition().getY()+e.getImageHeight()/2);    

             }

                    g2d.setStroke(new BasicStroke(7.0f));

                    g2d.setColor(new Color(54, 28, 13));

                    if(currentBird.isFlying())

                           g2d.drawLine(135,442,120,442);

                    g2d.setStroke(new BasicStroke(1.0f));

                    g2d.setColor(new Color(0, 0, 0));

        }      

        g2d.drawImage(slingShotImg1, 100, 412, this);

        g.drawString("Highest Score : " + currentHighestScore, 10, 15);

        g.drawString("Flying time left : " + currentBird.getFlyingTimeLeft(), 10, 30);

        Toolkit.getDefaultToolkit().sync();

        g.dispose();

    }

       public void setEntityList(ArrayList<Entity> entities) {

              this.entities = entities;

       }

       public Level getMap() {

              return map;

       }

       public void setMap(Level map) {

              this.map = map;

       }

       public void setCurrentHighestScore(int highestScore) {

              this.currentHighestScore = highestScore;

       }

       @Override

       public void listChanged(ListChangedEvent event) {

              entities = event.getEntityList();

              currentBird = event.getCurrentBird();

              repaint();

             

       }

}

5.3 面板管理功能实现

菜单管理通过6个不同的面板进行如图5-5所示:

MenuHomeView:游戏启动时的主页

MenuNewView:创建新部分的页面  

MenuLoadView:加载游戏页面

MenuControlsView:游戏控制信息页面

MenuDifficultyView:游戏选择页面

MenuLevelView:级别选择页面

图5-5 管理菜单图

每个面板的实现一个特定的功能,但是它们都继承自GameViewMenu(abstract)类,该类具有所有面板共同的功能,例如Menu背景。GameViewMenu继承自JLayeredPane类,它允许向放置在面板上的元素分配不同的深度,这使得我们将按钮按照自己的意愿放置在在已经创建好背景上。菜单中的导航按钮通过创建鼠标的各种监听来完成,对于返回按钮的设置,使用 “转义”键通过键盘设置了额外的控制(相当于使用按钮“返回”)。 然后,检测窗口和窗口中的先前显示的面板。 在选择面板的时候,可以从新的游戏或者加载游戏等进行面板的访问。接下来的所示一系列图为各个面板:

创建备份面板:在此处可以输入玩家的用户名,点击确认后可以保存玩家信息

图5-6创建备份面板

载入游戏面板:在此处玩家可以选择之前的存档,然后接着上次游戏内容继续进行游戏

图5-7 载入游戏面板

控制游戏面板:此处是整个游戏需要学会的操作方式

图5-8 控制游戏面板

难度选择面板:此处可以对游戏进行难度选择,选择不同的难度,小猪的移动各不相同,随着难度的提高,小猪的移动速度越来越快

图5-9 难度选择面板

级别选择面板:此处选择不同的级别,导致进入的关卡不同,随着关卡级别的提高,关卡的地形难度会越来越高

图5-10 级别选择面板

5.4 等级、级别管理功能实现

创建一个文本文件来管理等级,所有的游戏等级都依赖于这个文本,以下内容为等级管理功能的详细解释:

此功能可用于实现某个级别的鸟类、阻碍物以及猪在这个环境中的位置、鸟类列表列在文件的顶部。

鸟类的等级与其出现顺序相同。在行中的字符串和不同类型的鸟的名字对等,以创建相应的鸟,并将其添加到实体的ArrayList。当程序阅读文本时,遇到“地图(Map)”一词时,程序便可以从该文本中知道可用于该级别的鸟类列表是完整的,并且文件同时也包含表示级别的地图。这些是相同大小的文本行,包含将在二维数组中实现的不同字符(文本文件的每一行对应于2D数组中的一行)。每个元素都包含在具有26 * 26像素的相应图像中,并且在此图像对应的尺寸为1200 * 600像素的窗口中,该表的一行包含47个元素,一列包含22.每个字符对应于不同的元素装饰:

“0”表示地图上没有任何内容。

“1”表示地图上现在有一个草块。

“2”表示地图上现在有一块阻碍物。

“3”表示敌方猪的起始位置。

图5-11所示的为游戏的基本地图:

图5-11 游戏地图

相应的实体(猪,草块或阻碍物)是通过特定元素(“猪”,“草块”或“阻碍物”)创建的。 该实体通过构造函数创建,取其位置x(使用表的列索引和与实体对应的图像的大小计算)及其位置y(以l为单位计算)使用数组的行索引和 与实体对应的图像的大小)。

部分代码以及相应的页面布局:

public class Level { 

       private String backgroundImagePath = "res/images/background.png";      private Image image;

       private int tabMap[][];

       private int tabMapSizeX = 47;

       private int tabMapSizeY = 22;

       private int blockSize = 26;

       private String grassImagePath = "res/images/grass.png";

       private Image grass;

       private String blockImagePath = "res/images/block.png";

       private Image block;

       private boolean isLoaded;

       private ArrayList<Entity> entities;

       private int pigSpeed;

              public Level(String fileMapPath, String difficulty) {

              ImageIcon ii = new ImageIcon(backgroundImagePath);

              image = ii.getImage();

              ImageIcon gr = new ImageIcon(grassImagePath);

              grass = gr.getImage();

              ImageIcon bl = new ImageIcon(blockImagePath);

              block = bl.getImage();

                            if (difficulty == "easy")

                     pigSpeed = 0;

              if (difficulty == "medium")

                     pigSpeed = 1;

              if (difficulty == "hard")

                     pigSpeed = 2;

              if (difficulty == "extreme")

                     pigSpeed = 3;

              entities = new ArrayList<Entity>();

                            try {

                     FileInputStream ips = new FileInputStream(fileMapPath);

                     isLoaded = true;

                     InputStreamReader ipsr = new InputStreamReader(ips);

                     BufferedReader br = new BufferedReader(ipsr);

                     String line;

                     line = br.readLine();

                     while (!line.equals("Map")) {       

                            if (line.equals("Pigeon")){

                                   entities.add(new Pigeon());

                            }

                            if (line.equals("Humming Bird")){

                                   entities.add(new HummingBird());

                            }

                            if (line.equals("Sparrow")){                        

              entities.add(new Sparrow());

                            }

                            line = br.readLine();

                     }

                     tabMap = new int[tabMapSizeY][tabMapSizeX];

                     for (int i = 0; i < tabMapSizeY; i++) {

                            line = br.readLine();

                            for (int j = 0; j < tabMapSizeX; j++) {            

                                   char car = line.charAt(j);

                                   String st = String.valueOf(car);

                                   tabMap[i][j] = Integer.parseInt(st);

                                   if (tabMap[i][j] == 3)

                                         entities.add(new Pig(j * blockSize, i * blockSize, pigSpeed));

                                   if (tabMap[i][j] == 2)

                                          entities.add(new Block(j * blockSize, i * blockSize, blockSize, blockSize));

                                   if (tabMap[i][j] == 1)

                                          entities.add(new Grass(j * blockSize, i * blockSize, blockSize, blockSize));

                            }

                     }

              }

              catch (Exception e) {

                     isLoaded = false;

              }

       }

5.5 碰撞管理功能实现

碰撞管理和与其相关的事件在updateEntity()函数中执行。 创建的每个实体都具有hitBox,也就是说,每个图形对象都被一个碰撞矩形包围。 然后,按照实体的处理,通过测试其hitBox是否与其他人的hitBox相交的方式,来进行碰撞的各种处理。

如果一个元素与另一个元素的冲突导致删除两个对象之一,则程序会将一个或多个元素添加到实体列表(toRemove()调用)。 反过来,这在函数结束时遍历,以执行GameModel中实体的删除。 这使得可以在算法中删除相同级别的所有相关实体,并确保在删除它们之前已经执行了实体上的所有进程。 各种管理冲突如下:

鸡蛋碰撞:

•与绿猪:当检测到碰撞时,绿猪和当前的鸡蛋被添加到要删除的实体列表中。

•蛋与阻碍物:在要删除的实体列表中涉及到阻碍物和相关蛋的处理。

•与草块:碰撞导致当前的蛋被地添加到toRemove()列表。 草地是坚不可摧的。

猪碰撞:

•与阻碍物:

管理碰撞:碰撞的管理是不同的,在这里,因为在阻碍物与猪的碰撞矩形上没有完成。 实际上,猪可以在阻碍物上自由移动,但是当它们从侧面撞击一块阻碍物时,碰撞必须导致猪的方向移动。 必须能够测试块的hitBox的哪个边缘,猪的hitBox相交。

事件:当猪碰到一块阻碍物的左边缘或右边缘时,猪会改变运动方向(左侧或右侧); 当猪不与其中一块阻碍物(即猪的任何阻碍物上移动)的一个上边缘碰撞时,猪坠落,直到它碰到其中一个块阻碍物或草,则猪会进行反向移动,而块的下边缘猪不会进行改变方向。

猪:当两头猪相遇时,这两头猪就会改变方向。

鸟类碰撞:

阻碍物:当一只鸟与一块阻碍物碰撞时,这两个实体被添加到删除列表中。可以再次证明在功能结束时选择对缺失的处理。只有当鸟类被第一个块的碰撞检测直接攻击时,其他将会崩溃的块将不会被测试。

与猪一起:鸟和猪的碰撞导致在删除列表中添加这两个实体。

在theupdateEntity()函数中,可以看到位置所在的实体(左,右或底部)。 相关要素被添加到删除列表中。

部分代码以及相应的页面布局:

              if(entity == currentBird)

                     {

                            Bird bird = (Bird) entity;

                            if(!bird.isVisible())

                                   toRemove.add(bird);

                            Rectangle hitBoxBird = bird.getHitBox();

                           

                            for(Entity entity2 : entities) {

                                   if(entity2 instanceof Block)

                                   {

                                          Block block = (Block) entity2;

                                          Rectangle hitBoxBlock = entity2.getHitBox();

                                          if(hitBoxBird.intersects(hitBoxBlock))

                                          {

                                                 toRemove.add(block);

                                                 toRemove.add(bird);

                                          }

                                   }

                            }

                            for(Entity entity2 : entities) {

                                   if (entity2 instanceof Pig) {

                                          Pig pig = (Pig) entity2;

                                          Rectangle hitBoxPig =  pig.getHitBox();

                                          if(hitBoxBird.intersects(hitBoxPig)) {

                                                 toRemove.add(pig);

                                                 toRemove.add(bird);

                                                

                                     boolean win = true;

                                     for (Entity entity3 : entities) {

                                            if (entity3 instanceof Pig && !toRemove.contains(entity3)) {

                                                   win = false;

                                                   break;

                                            }

                                     }

                                     if (win) {

                                    

                                            for (Entity entity3 : entities) {

                                                   if (entity3 instanceof Bird)

                                                          ++score;

                                            }

                                            win();

                                     }

                                          }

                                   }

                            }

                            for (Entity entity2 : entities) {

                           if (entity2 instanceof Bird && !toRemove.contains(entity2)) {

                                  currentBird = (Bird) entity2;

                                  break;

                           }

                            }

                     }

              }

              for(Entity entity : toRemove)

                     entities.remove(entity);

              for(Entity entity4 : entities){

                     if(entity4 instanceof Bird)

                            birdTest++;

                     if(entity4 instanceof Pig)

                            pigTest++;

                     if(entity4 instanceof Egg)

                     {

                            eggTest++;

                     }

              }   

              if(birdTest==0 && eggTest==0 && pigTest!=0){

                     angryView.repaint();

                     lose();

              }                                

       }

5.6 鸟类管理功能实现

所有鸟类都继承了抽象类Bird。

创建当前的鸟:当前的鸟,位于弹弓上的鸟是通过浏览实体列表顺序找到的第一只鸟。 要将鸟类定义为当前目录,扫描实体列表,当检测到Bird的第一个实例时,将在该实例上放置currentBird指针。

鸟的状态:当鸟儿飞行时,他们有两个阶段:第一个准备飞行和一秒钟的飞行。 准备不良:鸟类位于抛石机上,玩家有可能试着对弹弓进行拉伸,以确定初始速度和进行鸟投掷的初始角度。 要做到这一点,玩家必须点击弹弓上的鸟,然后拖动它,同时按住鼠标按钮。 当释放鼠标按钮时,鸟类切换到飞行模式(isFlying布尔值设置为true)。

飞行阶段:飞行中的鸟类受到加速度公式,Y中的加速度为9.81(模拟重力)。鸟的位置被修改为时间,角度初始速度和施加的初始速度的函数。 在飞行中,玩家有可能释放鸡蛋(在分配的鸡蛋数量的限制内:eggLeft)。玩家也可以将鸟类允许(鸽子或蜂鸟)悬停。目前的鸟的死亡:当目前的鸟死亡(与块碰撞,与猪碰撞,在窗口中消失或飞行时间用完),鸟被添加到updateEntity函数的删除列表中删除。然后,部分代码以及相应的页面布局:

public abstract class Bird extends Entity {

       protected short flyingTime;   

       protected int eggLeft;                  

       protected ArrayList<Egg> eggs;

       protected boolean isFlying;    

       protected boolean isMoving;       

       private double time;           

       protected Dimension frameSize; 

       private double accelX;                 

       private double accelY;                 

       protected int startLocationX;  

       protected int startLocationY;

       private long lastTime;                  

       private long flyingTimeLeft;

       public Bird(int width, int height) {

              super(100,440,width,height); 

              isMoving = false;                   

              time = 0.1;                                

              accelX = 0;                               

              startLocationX = 100;        

              startLocationY = 440;             

              accelY = 9.81;                        

              flyingTimeLeft = 10000;     

       }

       public int getEggLeft() {

              return eggLeft;

       }

       public void setEggLeft(int i) {

              this.eggLeft = i;

       }

       public int getStartLocationX()

       {

              return startLocationX;

       }

       public int getStartLocationY()

       {

              return startLocationY;

       } 

       public abstract void hovering();

       public boolean isFlying() {

              return isFlying;

       }

public void move() {

         if(isFlying){

                 if(isMoving){    

                        hitBox.x = (int) Math.round(speed*Math.cos(angle)*time+0.5*accelX*time*time+startLocationX);

                        hitBox.y = (int) Math.round(0.5*accelY*time*time-Math.sin(angle)*speed*time+startLocationY);

                        time+=0.1;

                 }

                 long currentTime = System.currentTimeMillis();

                 flyingTimeLeft -= (currentTime - lastTime);

                 lastTime = currentTime;

          }

                 if (hitBox.y > (int) frameSize.getHeight() || hitBox.x > (int) frameSize.getWidth() ||flyingTimeLeft <= 0 )

                        visible = false;

                 //

    }

public void launch(){

         isMoving = true

          isFlying = true

          lastTime = System.currentTimeMillis();

    }

       public void moveRight() {

              accelX+=0.1;

    }

public void moveLeft() {

         accelX-=0.1;

    }

       public long getFlyingTimeLeft() {

              return flyingTimeLeft;

       }

}

5.7 胜利管理功能实现

每个级别都有一些敌人(猪)。 当所有级别的猪都被杀死(从实体列表中删除)时,游戏将获胜。 胜利测试在updateEntity()函数中执行。 当浏览实体列表时,我们更新猪的行为,或者如果实体列表不再包含任何猪实例,则游戏级别完成。 一旦达到这个级别,该程序将进入WIN()函数,这将允许执行胜利的所有条件。 与胜利管理相同的方式,失败事件在upledateEntity()函数中触发,但在这种情况下,事件不是在实体中的鸟的实例中完成的。 当列表中没有更多的鸟类时,将调用lost()函数。 在这种情况下,首先处理猪是重要的,所以当没有更多的鸟或猪时,不会调用lost()函数(对于win()函数的调用将优先进行)。

图5-12  胜利后的提示信息

图5-13 失败后的提示信息

部分代码以及相应的页面布局:

public void win() {

              javax.swing.JOptionPane.showMessageDialog(null, "祝贺你胜利 ,你的分数是: " + score+".");

              currentPlayer.finished(currentLevel, difficulty, score);

              Level lvl = new Level("res/maps/lvl0" + (currentLevel+1) + ".txt",difficulty);

              if (lvl.isLoaded()) {

                     angryView.setMap(lvl);

                     this.setMap(lvl);

                     this.setCurrentLevel(currentLevel+1);

              }

              else {

                     javax.swing.JOptionPane.showMessageDialog(null, "没有此难度的地图!");

              }

       }

       public void lose() {

              javax.swing.JOptionPane.showMessageDialog(null, "游戏结束,再试一次?");

             

              Level lvl = new Level("res/maps/lvl0" + (currentLevel) + ".txt",difficulty);

              if (lvl.isLoaded()) {

                     angryView.setMap(lvl);

                     this.setMap(lvl);

              }

       }

5.8 玩家管理和备份管理功能实现

这个功能目的是为了保存玩家的进度,让他恢复之前所成功进行的游戏。 因此,设置了一个“配置文件”系统:保存文件夹包含每个视图页面的备份文件,其中包含他的名字。 该文件包含对应于游戏的对象的序列化。

Player类专门为此而设计。 游戏可以选择从先前保存的配置文件中选择,或者创建一个新的配置文件。

玩家相关数据:对于每个玩家,希望存储他的名字,他成功的水平以及获得的分数。 因此,Player类包含一个名称的String字段和每个难度和级别的两个ArrayLists,一个包含相应文件的解锁级别,另一个包含后续级别的分数。

序列化:为了在双方之间保存数据,有必要序列化Player类。为此,最简单的方法是使用Java提供的ObjectOutputStream和ObjectInputStream。我们不需要担心对象的序列化实现的细节,或者从文件中读取的细节。这种技术防止备份文件被我们的其他程序读取,但这并不是这个项目的问题。另一个问题可能是游戏的名称:在游戏中,备份文件被命名为“nom.save”,但是在创建新配置文件时输入的名称没有进行验证。

得分:只有最高的分数保留给玩家和给定的水平。当玩家完成一个级别时,Player类的finished()方法被调用,其中级别和score作为参数。完成后检查,看看给定的分数是否是一个新的记录

图5-14 已经存在的玩家信息

部分代码以及相应的页面布局:

       public Player(String name) {

              this.name = name;

              easy = new ArrayList<Integer>();

              medium = new ArrayList<Integer>();

              hard = new ArrayList<Integer>();

              extreme = new ArrayList<Integer>();

              easyScores = new ArrayList<Integer>();

              mediumScores = new ArrayList<Integer>();

              hardScores = new ArrayList<Integer>();

              extremeScores = new ArrayList<Integer>(); 

              highestEasyScores = new ArrayList<Integer>();

              highestMediumScores = new ArrayList<Integer>();

              highestHardScores = new ArrayList<Integer>();

              highestExtremeScores = new ArrayList<Integer>();

              for (int i = 0; i < LevelNumber.getLevelNumber(); ++i) {

                     easyScores.add(0);

                     mediumScores.add(0);

                     hardScores.add(0);

                     extremeScores.add(0);

                    

                     highestEasyScores.add(0);

                     highestMediumScores.add(0);

                     highestHardScores.add(0);

                     highestExtremeScores.add(0);

                    

              }

             

      

              this.save();

       }

      

       public static Player loadFromFile(String name) {

              try {

                     FileInputStream fichier = new FileInputStream("save/" + name

                                   + ".save");

                     ObjectInputStream ois = new ObjectInputStream(fichier);

                     Player player = (Player) ois.readObject();

                     return player;

              } catch (java.io.IOException e) {

                     e.printStackTrace();

              } catch (ClassNotFoundException e) {

                     e.printStackTrace();

              }

              return null;

       }

      

       public void save() {

              try {

                     File file = new File("save/" + name + ".save");

                     file.delete();

                     file.createNewFile();

                    

                     FileOutputStream fos = new FileOutputStream(file);

                     ObjectOutputStream oos = new ObjectOutputStream(fos);

                     oos.writeObject(this);

                     oos.flush();

                     oos.close();

              } catch (java.io.IOException e) {

                     e.printStackTrace();

              }

       }

      

       public String toString() {

              return name;

       }

       public boolean isFinished(int level, String difficulty) {

              if (difficulty.equals("easy")) {

                     return easy.contains(level);

              } else if (difficulty.equals("normal")) {

                     return medium.contains(level);

              } else if (difficulty.equals("hard")){

                     return hard.contains(level);

              } else if (difficulty.equals("extreme")){

                     return extreme.contains(level);

              }

              return false;

       }

       public void finished(int level, String difficulty, int score) {

              if (difficulty.equals("easy")) {

                     if (!(easy.contains(level)))

                            easy.add(level);

                     easyScores.set(level-1, score);

                     if (score > highestEasyScores.get(level-1))

                            highestEasyScores.set(level-1, score);

                    

              } else if (difficulty.equals("medium")) {

                     if (!(medium.contains(level)))

                            medium.add(level);

                     mediumScores.set(level-1, score);

                     if (score > highestMediumScores.get(level-1))

                            highestMediumScores.set(level-1, score);

              } else if (difficulty.equals("hard")) {

                     if ( !(hard.contains(level)))

                            hard.add(level);

                     hardScores.set(level-1, score);

                     if (score > highestHardScores.get(level-1))

                            highestHardScores.set(level-1, score);

              } else if (difficulty.equals("extreme")) {

                     if (!(extreme.contains(level)))

                            extreme.add(level);

                     extremeScores.set(level-1, score);

                     if (score > highestExtremeScores.get(level-1))

                            highestExtremeScores.set(level-1, score);

              }

              save();

       }

       public String getName() {

              return name;

       }

6 系统测试

6.1系统测试简介

系统测试(System Testing)。它是软件、计算机硬件和已被定义为信息系统进行各种装配测试和验证测试的外围设备的组合。系统测试是对软件需求分析、设计和编码实现的回顾。通常系统测试描述的定义有两个,一个是找到程序执行错误的过程;另一个是基于软件开发的各个阶段的规范和程序的内部结构,并仔细设计了一些测试用例,并使用这些测试用例来运行程序并找到错误的过程。系统测试是对整个产品系统的测试,目的是验证任何系统满足规格要求的规范,找出符合要求的规格和不匹配或矛盾的地方,以便于公开全面的检查系统的弊端与不足。系统测试后发现问题,尝试找出错误的原因和位置,然后纠正它。根据黑匣子类测试的整体系统要求,应覆盖系统的所有组件。包括测试软件的需要,还有软件依赖于硬件、外设支持的软件及其界面。

必须尽快进行测试,防止在开发过程中形成了软件的漏洞,并且引入了缺陷。测试的目的是设计测试用例,通过这些测试用例找到软件缺陷,以最小的成本和时间来发现不同类型的错误并且纠正。

图6-1所示为基本系统测试方案:

图6-1 系统测试方案

6.2 系统测试方法

测试方法系统可以分为静态测试和动态测试两种,测试分为静态检测的计算机以及黑盒测试和白盒测试的动态测试。黑盒测试,也称为功能测试,则测试程序模块为黑盒,即不管内部配置和程序的处理,只对界面操作来测试程序。白盒测试,程序为透明白盒,即了解内部结构和详细的处理步骤,对程序的内部结构进行测试。即设计每个测试所需的逻辑路径,并检查每个循环和每个分支。另外在设计测试用例时,应该根据软件测试的原理,选择那些发现测试数据错误的可能性很大的输入数据。

6.3 本系统测试

6.3.1 测试用例设计

如下表1位测试用例设计表

表1 测试用例设计表

测试用例

系统测试

测试项目名称

基于Java的愤怒的小鸟游戏的设计与实现

测试用例编号

01

测试人员

李晨阳

测试时间

2017-5-20

测试项目标题

所有基本页面能正确显示连接并且游戏功能正常体现

测试内容

验证系统客户端首页是否能够正常显示

验证各功能是否能够正常实现

验证系统客户端内各模块内容信息是否正确

验证游戏功能是否正常实现

测试环境与系统配置

软件环境

Eclipse nexo

硬件环境

CPU:I5-3230M  内存:8G 显卡:GT-730M

网络环境

2人共享50MB/s 带宽

测试输入数据

测试次数

15

预期结果

可以正确显示系统客户端首页

可以实现各个功能

可以正确显示系统客户端内各模块内容

可以正确的进行各种游戏功能

测试过程

多次打开软件进入首页看首页是否正常显示

查看各功能的实现结果是否正确

查看每个页面的连接是否有误

进行不同角度、力度释放弹弓,查看小鸟飞行轨迹以及碰撞效果是否有误

测试结果

可以正确显示系统客户端首页

可以正确的实现各个功能

可以正确显示系统客户端内各模块内容信息

可以正确的系统内各种游戏功能

实现限制

6.3.2 测试方法和结论

主要测试系统是当前系统的测试方法,系统要求的总体规格是基于黑盒测试类别。 测试系统是基于计算机系统的要素、硬件项目、外围设备、一些支持软件,数据和人员的组合以及应用程序健康管理正确的功能,调试和其他测试的整体的软件系统。

测试系统的不同功能模块后,结果表明该软件工作良好,达到系统设计目标和功能要求达到预期目标

结论

经过近两个多月的忙碌之后,游戏开发之愤怒的小鸟可以根据用户的需求完成全部功能。我亲身经历了这一段程序从小到大,从无到有,伴随着整个设计过程,也正是我的学习过程。在整个设计过程中是不断学习、识别问题、分析问题和解决问题的过程,从这次毕业设计中我获得了很多很多感受、想法和经验。在完成的学习设计中吸取的经验教训,对我的未来工作都有很大的影响。另外,使用参考文献的过程中,有很多文献中精湛的细节值得认真学习和理解。我毕业设计的项目是我刚刚完成的最重要的程序。在此期间,Java学习在慢慢进步,使我感觉回到了初学者的时候,各种需要解决的问题我都是从互联网得知,由于Java是一个成熟的技术,网络上参考资料非常多,特别是陈锐的《Java游戏课程原理与实践》对我的帮助非常之大,而且我在网络上获得的资源,都是由作者、翻译者辛苦的工作成果,他们发布在在互联网上免费下载,是开源精神的完美体现。我相信在未来的学习中,我将把我的游戏设计的更加完美和成熟!这次毕业时机经历也是我大学学习中最重要的一段经历,更是我人生中不可或缺的一部分。

参考文献

  1. 基于Box2D物理引擎的刚体运动和碰撞模拟[J]. 奚焱.  电脑编程技    巧与         维护. 2011(24)
  2.  基于物理引擎三维物理仿真实验的实现方法[J]. 田超,张文俊,张小凤,刘东平.  微型电脑应用. 2010(02)
  3. 虚拟现实环境中的物理模拟及物理引擎应用的研究[J].陈定方.  湖北工业大学学报. 2008(02)
  4. 游戏音效在物理引擎中的运用[J]. 魏婷,郑豪.  福建电脑. 2008(01)
  5. 探析JAVA Swing工具包在图形用户界面设计中的应用[J]. 苏碧霞.  信息与电脑(理论版). 2013(05)
  6. Swing可视化组件多线程操作机制研究[J]. 胡家芬.  电脑知识与技术. 2012(31)
  7. 一种Swing组件的动态国际化解决方案[J]. 肖荣.  软件导刊. 2009(10)
  8. 使用Java Swing组件进行事件处理方法的分析与比较[J]. 张海越,范曦.  软件导刊. 2013(06)
  9.  Beginning Java ObjectsJacquie Barker 2005-
  10. A portable AWT/Swing architecture for Java game development Yi‐            HsienWang Softw: Pract. Exper. 2007(07)
  11. Objective viewpoint: Java AWT layout management 101 George     Crawford Crossroads 1998(01)

中文原文

一、box2d基础知识

1、关于

 Box2D 是一个用于游戏的 2D 刚体仿真库。从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你的物体。

2、核心概念 

刚体(rigid body)

一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。

形状(shape)

一块严格依附于物体(body) 2D 碰撞几何结构(collision geometry)。形状具有摩擦(friction)和恢

(restitution)的材料性质。

约束(constraint) 

一个约束(constraint)就是消除物体自由度的物理连接。在 2D 中,一个物体有 3 个自由度。如果我

们把一个物体钉在墙上(像摆锤那样),那我们就把它约束到了墙上。这样,此物体就只能绕着这个钉子旋

转,所以这个约束消除了它 2 个自由度。

接触约束(contact constraint)

一个防止刚体穿透,以及用于模拟摩擦(friction)和恢复(restitution)的特殊约束。你永远都不必创建

一个接触约束,它们会自动被 Box2D 创建。

关节(joint)

它是一种用于把两个或多个物体固定到一起的约束。Box2D 支持的关节类型有:旋转,棱柱,距离等

等。关节可以支持限制(limits)和马达(motors)

关节限制(joint limit)

一个关节限制(joint limit)限定了一个关节的运动范围。例如人类的胳膊肘只能做某一范围角度的运

动。

关节马达(joint motor)

一个关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用一个马达来驱动一个肘的

旋转。

世界(world)

一个物理世界就是物体,形状和约束相互作用的集合。Box2D 支持创建多个世界,但这通常是不必要

的。

 3 创建一个世界

 每个 Box2D 程序都将从一个世界对象(world object)的创建开始。这是一个管理内存,对象和模拟的中心。

要创建一个世界对象,我们首先需要定义一个世界的包围盒。Box2D 使用包围盒来加速碰撞检测。尺寸并不关键,但合适的尺寸有助于性能。这个包围盒过大总比过小好。

b2AABB worldAABB;
worldAABB.lowerBound.Set(-100.0f, -100.0f);
worldAABB.upperBound.Set(100.0f, 100.0f); 

接下来我们定义重力矢量。

b2Vec2 gravity(0.0f, -10.0f);
bool doSleep = true; //
当动态物体静止时使它休眠,减少性能开销

 现在我们创建世界对象。

b2World world(worldAABB, gravity, doSleep);//在栈上创建world

那么现在我们有了自己的物理世界,让我们再加些东西进去。

4、创建一个地面

 第一步,我们创建地面体。要创建它我们需要一个物体定义(body definition),通过物体定义我们来指定地面体的初始位置。

b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f, -10.0f);

 第二步,将物体定义传给世界对象来创建地面体。世界对象并不保存到物体定义的引用。地面体是作为静态物体(static body)创建的,静态物体之间并没有碰撞,它们是固定的。当一个物体具有零质量的时候 Box2D 就会确定它为静态物体,物体的默认质量是零,所以它们默认就是静态的。

b2Body* ground = world.CreateBody(&groundBodyDef);

第三步,我们创建一个地面的多边形定义。我们使用 SetAsBox 简捷地把地面多边形规定为一个盒子(矩形)形状,盒子的中点就位于父物体的原点上。

b2PolygonDef groundShapeDef;
groundShapeDef.SetAsBox(50.0f, 10.0f);

其中,SetAsBox 函数接收了半个宽度和半个高度,这样的话,地面盒就是 100 个单位宽(x )以及 20 个单位高(y )Box2D 已被调谐使用米,千克和秒来作单位,所以你可以用米来考虑长度。

 在第四步中,我们在地面体上创建地面多边形,以完成地面体。

groundBody->CreateShape(&groundShapeDef);//创建形状用于碰撞检测等

5 创建一个动态物体

首先我们用 CreateBody 创建物体。

b2BodyDef bodyDef;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);

接下来我们创建并添加一个多边形形状到物体上。注意我们把密度设置为 1,默认的密度是 0。并且,形状的摩擦设置到了 0.3。形状添加好以后,我们就使用 SetMassFromShapes 方法来命令物体通过形状去计算其自身的质量。这暗示了你可以给单个物体添加一个以上的形状。如果质量计算结果为 0,那么物体会变成真正的静态。

 b2PolygonDef shapeDef;

shapeDef.SetAsBox(1.0f, 1.0f);
shapeDef.density = 1.0f;
shapeDef.friction = 0.3f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();

 6、模拟(Box2D )世界

 我们已经初始化好了地面盒和一个动态盒。现在我们只有少数几个问题需要考虑。Box2D 中有一些数学代码构成的积分器(integrator),积分器在离散的时间点上模拟物理方程,它将与游戏动画循环一同运行。所以我们需要为 Box2D 选取一个时间步,通常来说游戏物理引擎需要至少 60Hz 的速度,也就是 1/60 的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调整定义。我们也不喜欢时间步变化得太大,所以不要把时间步关联到帧频(除非你真的必须这样做)。直截了当地,这个就是时间步:float32 timeStep = 1.0f / 60.0f; 

 除了积分器之外,Box2D 中还有约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另一个。要得到良好的解,我们需要迭代所有约束多次。建议的 Box2D 迭代次数是 10 次。你可以按自己的喜好去调整这个数,但要记得它是速度与质量之间的平衡。更少的迭代会增加性能并降低精度,同样地,更多的迭代会减少性能但提高模拟质量。这是我们选择的迭代次数:

 int32 iterations = 10;//一个时间步遍历10次约束

 现在我们可以开始模拟循环了,在游戏中模拟循环应该并入游戏循环。每次循环你都应该调用 b2World::Step,通常调用一次就够了,这取决于帧频以及物理时间步。

 这就是模拟 1 秒钟内 60 个时间步的循环

for (int32 i = 0; i < 60; ++i)
{
    world.Step(timeStep, iterations);
}

 7API 设计

单位

 Box2D 使用浮点数,所以必须使用一些公差来保证它正常工作。这些公差已经被调谐得适合米-千克-(MKS)单位。尤其是,Box2D 被调谐得能良好地处理 0.1 10 米之间的移动物体。这意味着从罐头盒到公共汽车大小的对象都能良好地工作。 

注意:Box2D 已被调谐至 MKS 单位。移动物体的尺寸大约应该保持在 0.1 10 米之间。你可能需要一些缩放系统来渲染你的场景和物体。Box2D 中的例子是使用 OpenGL 的视口来变换的。

用户数据

b2Shapeb2Body b2Joint 类都允许你通过一个 void 指针来附加用户数据。这在你测试 Box2D 数据结构,以及你想把它们联系到自己的引擎中的时候是较方便的。举个典型的例子,在角色上的刚体中附加到角色的指针,这就构成了一个循环引用。如果你有角色,你就能得到刚体。如果你有刚体,你就能得到角色。

GameActor* actor = GameCreateActor();
b2BodyDef bodyDef;
bodyDef.userData = actor;
actor->body = box2Dworld->CreateBody(&bodyDef);

这是一些需要用户数据的案例: 

使用碰撞结果给角色施加伤害

当玩家进入一个包围盒时播放一段脚本事件

Box2D 通知你一个关节即将摧毁时访问一个游戏结构

记得用户数据是可选的,并且能放入任何东西。然而,你需要保持一致性。例如,如果你想在一个物体中保存一个角色的指针,那你就应该在所有物体中都保存一个角色指针。不要在一个物体中保存角色指针,却在另一个物体中保存一个其它指针。这可能会导致程序崩溃。

8、世界 

 b2World 类包含着物体和关节。它管理着模拟的方方面面,并允许异步查询(就像 AABB 查询)。你与 Box2D 的大部分交互都将通过 b2World 对象来完成。

 要创建或摧毁一个世界你需要使用 new delete

b2World* myWorld = new b2World(aabb, gravity, doSleep);
// ... do stuff ...
delete myWorld;

 世界类用于驱动模拟。你需要指定一个时间步和一个迭代次数。例如:

float32 timeStep = 1.0f / 60.f;
int32 iterationCount = 10;
myWorld->Step(timeStep, iterationCount);

 在时间步完成之后,你可以调查物体和关节的信息。最可能的情况是你会获取物体的位置,这样你才能更新你的角色并渲染它们。你可以在游戏循环的任何地方执行时间步,但你应该意识到事情发生的顺序。例如,如果你想要在一帧中得到新物体的碰撞结果,你必须在时间步之前创建物体。推荐使用固定的时间步。使用大一些的时间步你可以在低帧率的情况下提升性能。1/60 的时间步通常会呈现一个高质量的模拟。

  扫描世界:

世界就是一个物体和关节的容器。你可以获取世界中所有物体和关节并遍历它们。例如,这段代码会唤醒世界中的所有物体:

for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    b->WakeUp();
}

 AABB 查询:

有时你需要求出一个区域内的所有形状。b2World 类为此使用了 broad-phase 数据结构,提供了一个 log(N) 的快速方法。你提供一个世界坐标的 AABB,而 b2World 会返回一个所有大概相交于此 AABB 的形状之数组。这不是精确的,因为函数实际上返回那些 AABB 与规定之 AABB 相交的形状。例如,下面的代码找到所有大概与指定 AABB 相交的形状并唤醒所有关联的物体。

b2AABB aabb;
aabb.minVertex.Set(-1.0f, -1.0f);
aabb.maxVertex.Set(1.0f, 1.0f);
const int32 k_bufferSize = 10;
b2Shape *buffer[k_bufferSize];
int32 count = myWorld->Query(aabb, buffer, k_bufferSize);
for (int32 i = 0; i < count; ++i)
{
    buffer[i]->GetBody()->WakeUp();
}

 9 物体

 物体具有位置和速度。你可以应用力,扭矩和冲量到物体。物体可以是静态的或动态的,静态物体永远不会移动,并且不会与其它静态物体发生碰撞。物体是形状的主干,物体携带形状在世界中运动。在 Box2D 中物体总是刚体,这意味着同一刚体上的两个形状永远不会相对移动。通常你会保存所有你所创建的物体的指针,这样你就能查询物体的位置,并在图形实体中更新它的位置。另外在不需要它们的时候你也需要通过它们的指针摧毁它们。

  质量性质:

1)在物体定义中显式地设置 

 bodyDef.massData.mass = 2.0f;//物体的质量是2kg

 2)显式地在物体上设置(在其创建之后)

3)基于物体上的形状来进行密度设置 

 b2PolygonDef shapeDef;

shapeDef.SetAsBox(1.0f, 1.0f);
shapeDef.density = 1.0f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();//
这个函数成本较高,所以你应该只在需要时使用它。

你可以在运行时调整一个物体的质量,这通常是在添加或移除物体上之形状时完成的。可能你会根据物体上的当前形状来调整其质量。

可能你也会直接设置质量。例如,你可能会改变形状,但你只想使用自己的质量公式。

void SetMass(const b2MassData* massData);

通过以下这些函数可以获得物体的质量数据:

float32 GetMass() const;
float32 GetInertia() const;
const b2Vec2& GetLocalCenter() const;

位置和角度:

bodyDef.position.Set(0.0f, 2.0f);   // the body's origin position.
bodyDef.angle = 0.25f * b2_pi;      // the body's angle in radians.

你可以访问一个物体的位置和角度,这在你渲染相关游戏角色时很常用。你也可以设置位置,尽管这不怎么常用。

bool SetXForm(const b2Vec2& position, float32 angle);
const b2XForm& GetXForm() const;
const b2Vec2& GetPosition() const;
float32 GetAngle() const;

 你可以访问线速度与角速度,线速度是对于质心所言的。

void SetLinearVelocity(const b2Vec2& v);
b2Vec2 GetLinearVelocity() const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity() const;

阻尼:

阻尼用于减小物体在世界中的速率。阻尼与摩擦是不同的,因为摩擦仅在物体有接触的时候才会发生,而阻尼的模拟要比摩擦便宜多了。然而,阻尼并不能取代摩擦,往往这两个效果需要同时使用。阻尼参数的范围可以在 0 到无穷之间,0 的就是没有阻尼,无穷就是满阻尼。通常来说,阻尼的值应在 0 0.1 之间,我通常不使用线性阻尼,因为它会使物体看起来发飘。

bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;

阻尼相似于稳定性与性能,阻尼值较小的时候阻尼效应几乎不依赖于时间步,而阻尼值较大的时候阻尼效应将随着时间步而变化。如果你使用固定的时间步(推荐)这就不是问题了。 

 休眠参数:

模拟物体的成本是高昂的,所以如果物体更少,那模拟的效果就能更好。当一个物体停止了运动时,我们要停止去模拟它。  Box2D 确定一个物体(或一组物体)已经停止移动时,物体就会进入休眠状态,消耗很小的 CPU 开销。如果一个醒着的物体接触到了一个休眠中的物体,那么休眠中的物体就会醒来。当物体上的关节或

触点被摧毁的时候,它们同样会醒来。你也可以手动地唤醒物体。通过物体定义,你可以指定一个物体是否可以休眠,或者创建一个休眠的物体。

bodyDef.allowSleep = true;
bodyDef.isSleeping = false;

 子弹:

高速移动的物体在 Box2D 被称为子弹(bullet),你需要按照游戏的设计来决定哪些物体是子弹。如果你决定一个物体应该按照子弹去处理,使用下面的设置。

bodyDef.isBullet = true;

子弹开关只影响动态物体。

有的时候,在一个时间步内可能会有大量的刚体同时运动。如果一个物理引擎没有处理好大幅度运动的问题,你就可能会看见一些物体错误地穿过了彼此。这种效果被称为隧道效应(tunneling)。默认情况下,Box2D 会通过连续碰撞检测(CCD)来防止动态物体穿越静态物体,这是通过从形状的旧位置到新位置的扫描来完成的。引擎会查找扫描中的新碰撞,并为这些碰撞计算碰撞时间(TOI)。物体会先被移动到它们的第一个 TOI,然后一直模拟到原时间步的结束。如果有必要这个步骤会重复执行。一般 CCD 不会应用于动态物体之间,这是为了保持性能。在一些游戏环境中你需要在动态物体上也使用 CCD,譬如,你可能想用一颗高速的子弹去射击薄壁。没有 CCD,子弹就可能会隧穿薄壁。 CCD 的成本是昂贵的,所以你可能不希望所有运动物体都成为子弹。所以 Box2D 默认只在动态物体和静态物体之间使用 CCD,这是防止物体逃脱游戏世界的一个有效方法。然而,可能你有一些高速移动的物体需要一直使用 CCD

 状态信息:

物体的状态含有多个方面,通过这些函数你可以访问这些状态数据:

bool IsBullet() const;
void SetBullet(bool flag);
bool IsStatic() const;
bool IsDynamic() const;
bool IsFrozen() const;
bool IsSleeping() const;
void AllowSleeping(bool flag);
void WakeUp();

 力和冲量:

你可以对一个物体应用力,扭矩,以及冲量。当应用一个力或冲量时,你需要提供一个世界位置。这常常会导致对质心的一个扭矩。

void ApplyForce(const b2Vec2& force, const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyImpulse(const b2Vec2& impulse, const b2Vec2& point);

应用力,扭矩或冲量会唤醒物体,有时这是不合需求的。例如,你可能想要应用一个稳定的力,并允许物体休眠来提升性能。这时,你可以使用这样的代码:

if (myBody->IsSleeping() == false)
{
    myBody->ApplyForce(myForce, myPoint);
}

  坐标转换:

物体类包含一些工具函数,它们可以帮助你在局部和世界坐标系之间转换点和向量。如果你不了解这些概念,请看 Jim Van Verth Lars Bishop “Essential Mathematics for Games and Interactive Applications”。这些函数都很高效,所以可放心使用。

b2Vec2 GetWorldPoint(const b2Vec2& localPoint);
b2Vec2 GetWorldVector(const b2Vec2& localVector);
b2Vec2 GetLocalPoint(const b2Vec2& worldPoint);
b2Vec2 GetLocalVector(const b2Vec2& worldVector);

 列表

你可以遍历一个物体的形状,其主要用途是帮助你访问形状的用户数据。

for (b2Shape* s = body->GetShapeList(); s; s = s->GetNext())
{
    MyShapeData* data = (MyShapeData*)s->GetUserData();
    ... do something with data ...
}

你也可以用类似的方法遍历物体的关节列表。

 10 形状

 形状就是物体上的碰撞几何结构。另外形状也用于定义物体的质量。也就是说,你来指定密度,Box2D 可以帮你计算出质量。形状具有摩擦和恢复的性质。形状还可以携带筛选信息,使你可以防止某些游戏对象之间的碰撞。形状永远属于某物体,单个物体可以拥有多个形状。形状是抽象类,所以在 Box2D 中可以实现许多

类型的形状。如果你有勇气,那便可以实现出自己的形状类型(和碰撞算法)

形状定义 :

形状定义用于创建形状。通用的形状数据会保存在 b2ShapeDef 中,特殊的形状数据会保存在其派生类中。 

1)摩擦和恢复 

 摩擦可以使对象逼真地沿其它对象滑动。Box2D 支持静摩擦和动摩擦,但使用相同的参数。摩擦参数经常会设置在 0 1 之间,0 意味着没有摩擦,1 会产生强摩擦。当计算两个形状之间的摩擦时,Box2D 必须联合两个形状的摩擦参数,这是通过以下公式完成的:

float32 friction;
friction = sqrtf(shape1->friction * shape2->friction);

 恢复可以使对象弹起,想象一下,在桌面上方丢下一个小球。恢复的值通常设置在 0 1 之间,0 的意思是小球不会弹起,这称为非弹性碰撞;1 的意思是小球的速度会得到精确的反射,这称为完全弹性碰撞。恢复是通过这样的公式计算的:

float32 restitution;
restitution = b2Max(shape1->restitution, shape2->restitution);

 当一个形状发生多碰撞时,恢复会被近似地模拟。这是因为 Box2D 使用了迭代求解器.

2) 密度

Box2D 可以根据附加形状的质量分配来计算物体的质量以及转动惯量。直接指定物体质量常常会导致不协调的模拟。因此,推荐的方法是使用b2Body::SetMassFromShape 来根据形状设置质量。 

 3) 筛选

碰撞筛选是一个防止某些形状发生碰撞的系统。 

Box2D 支持 16 个种群,对于任何一个形状你都可以指定它属于哪个种群。你还可以指定这个形状可以和其它哪些种群发生碰撞。例如,你可以在一个多人游戏中指定玩家之间不会碰撞,怪物之间也不会碰撞,但是玩家和怪物会发生碰撞。这是通过掩码来完成的,例如:

playerShapeDef.filter.categoryBits = 0x0002;
monsterShapeDef.filter.categoryBits = 0x0004;
playerShape.filter.maskBits = 0x0004;
monsterShapeDef.filter.maskBits = 0x0002;

 碰撞组可以让你指定一个整数的组索引。你可以让同一个组的所有形状总是相互碰撞(正索引)或永远不碰撞(负索引)。组索引通常用于一些以某种方式关联的事物,就像自行车的那些部件。在下面的例子中,shape1 shape2 总是碰撞,而 shape3 shape4 永远不会碰撞。

shape1Def.filter.groupIndex = 2;
shape2Def.filter.groupIndex = 2;
shape3Def.filter.groupIndex = -8;
shape4Def.filter.groupIndex = -8;

 不同组索引之间形状的碰撞会按照种群和掩码来筛选。换句话说,组筛选比种群筛选有更高的优选权。

注意在 Box2D 中的其它碰撞筛选,这里是一个列表: 

静态物体上的形状永远不会与另一个静态物体上的形状发生碰撞

同一个物体上的形状之间永远不会发生碰撞

你可以有选择地启用或禁止由关节连接的物体上的形状之间是否碰撞

有时你可能希望在形状创建之后去改变其碰撞筛选,你可以使用 b2Shape::GetFilterData 以及 b2Shape::SetFilterData 来存取已存在形状之 b2FilterData 结构。Box2D 会缓存筛选结果,所以你需要使用 b2World::Refilter 手动地进行重筛选。

 4)传感器

 有时候游戏逻辑需要判断两个形状是否相交,但却不应该有碰撞反应。这可以通过传感器(sensor)来完成。传感器会侦测碰撞而不产生碰撞反应。你可以将任一形状标记为传感器,传感器可以是静态或动态的。记得,每个物体上可以有多个形状,并且传感器和实体形状是可以混合的。

 myShapeDef.isSensor = true;

 5 圆形定义

b2CircleDef 扩充了 b2ShapeDef 并增加一个半径和一个局部位置。

b2CircleDef def;
def.radius = 1.5f;
def.localPosition.Set(1.0f, 0.0f); 

 6)多边形定义

b2PolyDef 用于定义凸多边形。要正确地使用需要一点点技巧,所以请仔细阅读。最大顶点数由 b2_maxPolyVertices 定义,当前是 8。如果你需要更多顶点,你必须修改 b2Settings.h 中的 b2_maxPolyVertices。当创建多边形定义时,你需要给出所用的顶点数目。这些顶点必须按照相对于右手坐标系之 z 轴逆时

(CCW)的顺序定义。在你的屏幕上可能是顺时针的,这取决于你的坐标系统规则。多边形必须是凸多边形,也就是,每个顶点都必须指向外面。最后,你也不应该重叠任何顶点。Box2D 会自动地封闭环路。 

 这里是一个三角形的多边形定义的例子:

b2PolygonDef triangleDef;
triangleDef.vertexCount = 3;
triangleDef.vertices[0].Set(-1.0f, 0.0f);
triangleDef.vertices[1].Set(1.0f, 0.0f);
triangleDef.vertices[2].Set(0.0f, 2.0f);

 7)形状工厂

 初始化一个形状定义,而后将其传递给父物体;形状就是这样创建的。

b2CircleDef circleDef;
circleDef.radius = 3.0f;
circleDef.density = 2.5f;
b2Shape* myShape = myBody->CreateShape(&circleDef);

 11、关节

 关节的作用是把物体约束到世界,或约束到其它物体上。在游戏中的典型例子是木偶,跷跷板和滑轮。关节可以用许多种不同的方法结合起来,创造出有趣的运动。

有些关节提供了限制(limit),以便你控制运动范围。有些关节还提供了马达(motor),它可以以指定的速度驱动关节,直到你指定了更大的力或扭矩。

1)关节定义

各种关节类型都派生自 b2JointDef。所有关节都连接两个不同的物体,可能其中一个是静态物体。如果你想浪费内存的话,那就创建一个连接两个静态物体的关节 

你可以为任何一种关节指定用户数据。你还可以提供一个标记,用于预防相连的物体发生碰撞。实际上,这是默认行为,你可以设置 collideConnected 布尔值来允许相连的物体碰撞。很多关节定义需要你提供一些几何数据。一个关节常常需要一个锚点(anchor point)来定义,这是固定于相接物体中的点。在 Box2D 中这点需要在局部坐标系中指定,这样,即便当前物体的变化违反了关节约束,关节还是可以被指定 —— 在游戏存取进度时这经常会发生。另外,有些关节定义需要默认的

物体之间的相对角度。这样才能通过关节限制或固定的相对角来正确地约束旋转。初始化几何数据可能有些乏味。所以很多关节提供了初始化函数,消除了大部分工作。然而,这些初始化函数通常只应用于原型,在产品代码中应该直接地定义几何数据。这能使关节行为更加稳固。其余的关节定义数据依赖于关节的类型。下面我们来介绍它们。 

 2)距离关节

 距离关节是最简单的关节之一,它描述了两个物体上的两个点之间的距离应该是常量。当你指定一个距离关节时,两个物体必须已在应有的位置上。随后,你指定两个世界坐标中的锚点。第一个锚点连接到物体 1,第二个锚点连接到物体 2。这些点隐含了距离约束的长度。

 这是一个距离关节定义的例子。在此我们允许了碰撞。

b2DistanceJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, worldAnchorOnBody1, 
worldAnchorOnBody2);
jointDef.collideConnected = true;

 3)旋转关节

 一个旋转关节会强制两个物体共享一个锚点,即所谓铰接点。旋转关节只有一个自由度:两个物体的相对旋转。这称之为关节角。

 要指定一个旋转关节,你需要提供两个物体以及一个世界坐标的锚点。初始化函数会假定物体已经在应有位置了。在此例中,两个物体被旋转关节连接于第一个物体的质心。

b2RevoluteJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter());

 这里是对上面旋转关节定义的修订;这次,关节拥有一个限制以及一个马达,后者用于模拟摩擦。

b2RevoluteJointDef jointDef;
jointDef.Initialize(body1, body2, myBody1->GetWorldCenter());//
使用 Initialize() 创建关节时,旋转关节角为 0,无论两个物体当前的角度怎样。
jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees最小角度
jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees最大角度
jointDef.enableLimit = true;
jointDef.maxMotorTorque = 10.0f;//
马达
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;

你可以访问旋转关节的角度,速度,以及扭矩。 

float32 GetJointAngle() const;  

float32 GetJointSpeed() const;
float32 GetMotorTorque() const;

你也可以在每步中更新马达参数。 

void SetMotorSpeed(float32 speed);

void SetMaxMotorTorque(float32 torque);  

 关节马达有一些有趣的能力。你可以在每个时间步中更新关节速度,这可以使关节像正弦波一样来回

移动,或者按其它什么函数运动。

// ... Game Loop Begin ...
myJoint->SetMotorSpeed(cosf(0.5f * time));
// ... Game Loop End ...

你还可以使用关节马达来追踪某个关节角度。例如:

// ... Game Loop Begin ...
float32 angleError = myJoint->GetJointAngle() - angleTarget;
float32 gain = 0.1f;
myJoint->SetMotorSpeed(-gain * angleError);
// ... Game Loop End ...

通常来讲你的增益参数不应过大,否则你的关节可能会变得不稳定。

4)移动关节

 移动关节(prismatic joint)允许两个物体沿指定轴相对移动,它会阻止相对旋转。因此,移动关节只有一个自由度。

移动关节的定义有些类似于旋转关节;只是转动角度换成了平移,扭矩换成了力。以这样的类比,我们来看一个带有关节限制以及马达摩擦的移动关节定义:

b2PrismaticJointDef jointDef;

b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter(), 
worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.motorForce = 1.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;

 旋转关节隐含着一个从屏幕射出的轴,而移动关节明确地需要一个平行于屏幕的轴。这个轴会固定于两个物体之上,沿着它们的运动方向。就像旋转关节一样,当使用 Initialize() 创建移动关节时,移动为 0。所以一定要确保移动限制范围内包含了 0。移动关节的用法类似于旋转关节,这是它的相关成员函数: 

float32 GetJointTranslation() const;
float32 GetJointSpeed() const;
float32 GetMotorForce() const;
void SetMotorSpeed(float32 speed);

void SetMotorForce(float32 force);

 5)滑轮关节

 滑轮关节用于创建理想的滑轮,它将两个物体接地(ground)并连接到彼此。这样,当一个物体升起时,另一个物体就会下降。滑轮的绳子长度取决于初始时的状态。

length1 + length2 == constant

 你还可以提供一个系数(ratio)来模拟滑轮组,这会使滑轮一侧的运动比另一侧要快。同时,一侧的约束力也比另一侧要小。你也可以用这个来模拟机械杠杆(mechanical leverage)length1 + ratio * length2 == constant 举个例子,如果系数是 2,那么 length1 的变化会是 length2 的两倍。另外连接 body1 的绳子的约束力将会是连接 body2 绳子的一半。当滑轮的一侧完全展开时,另一侧的绳子长度为零,这可能会出问题。此时,约束方程将变得奇异。因此,滑轮关节约束了每一侧的最大长度。另外出于游戏原因你可能也希望控制这个最大长度。最大长度能提高稳定性,以及提供更多的控制。

这是一个滑轮定义的例子:

b2Vec2 anchor1 = myBody1->GetWorldCenter();
b2Vec2 anchor2 = myBody2->GetWorldCenter();
b2Vec2 groundAnchor1(p1.x, p1.y + 10.0f);
 b2Vec2 groundAnchor2(p2.x, p2.y + 12.0f);
float32 ratio = 1.0f;
b2PulleyJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, 
anchor1, anchor2, ratio);
jointDef.maxLength1 = 18.0f;
jointDef.maxLength2 = 20.0f;

 滑轮关节提供了当前长度:

float32 GetLength1() const;

float32 GetLength2() const; 

 6 齿轮关节

 如果你想要创建复杂的机械装置,你可能需要齿轮。原则上,在 Box2D 中你可以用复杂的形状来模拟轮齿,但这并不十分高效,而且这样的工作可能有些乏味。另外,你还得小心地排列齿轮,保证轮齿能平稳地啮合。Box2D 提供了一个创建齿轮的更简单的方法:齿轮关节。

 齿轮关节需要两个被旋转关节或移动关节接地(ground)的物体,你可以任意组合这些关节类型。另外,创建旋转或移动关节时,Box2D 需要地(ground)作为 body1。类似于滑轮的系数,你可以指定一个齿轮系数(ratio),齿轮系数可以为负。另外值得注意的是,当一个是旋转关节(有角度的)而另一个是移动关节(平移)时,齿轮系数是长度或长度分之一。coordinate1 + ratio * coordinate2 == constant这是一个齿轮关节的例子:

b2GearJointDef jointDef;
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.joint1 = myRevoluteJoint;
jointDef.joint2 = myPrismaticJoint;
jointDef.ratio = 2.0f * b2_pi / myLength; 

注意:齿轮关节总应该先于旋转或移动关节被删除,否则你的代码将会由于齿轮关节中的无效关节

指针而导致崩溃。另外齿轮关节也应该在任何相关物体被删除之前删除。

7)关节工厂

 关节是通过世界的工厂方法来创建和摧毁的,这引出了一个旧问题: 

注意:不要试图在栈上创建物体或关节,也不要使用 new malloc 在堆上创建。物体以及关节必须要通过 b2World 类的方法来创建或摧毁。

这是一个关于旋转关节生命期的例子:

b2RevoluteJointDef jointDef;
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.anchorPoint = myBody1->GetCenterPosition();
b2RevoluteJoint* joint = myWorld->CreateJoint(&jointDef);
// ... do stuff ...
myWorld->DestroyJoint(joint);
joint = NULL;

 8)使用关节

 在许多模拟中,关节被创建之后便不再被访问了。然而,关节中包含着很多有用的数据,使你可以创建出丰富的模拟。首先,你可以在关节上得到物体,锚点,以及用户数据。

b2Body* GetBody1();
b2Body* GetBody2();
b2Vec2 GetAnchor1();
b2Vec2 GetAnchor2();
void* GetUserData();

 11、接触

 接触(contact)是由 Box2D 创建的用于管理形状间碰撞的对象。接触有不同的种类,它们都派生自 b2Contact,用于管理不同类型形状之间的接触。例如,有管理多边形之间碰撞的类,有管理圆形之间碰撞的类。

这里是 Box2D 中的一些与碰撞有关的术语:

 触点(contact point)

两个形状相互接触的点。实际上当物体的表面相接触时可能会有一定接触区域,在 Box2D 则近似地

以少数点来接触。

接触向量(contact normal)

shape1 指向 shape2 的单位向量。

接触分隔(contact separation)

分隔相反于穿透,当形状相重叠时,分隔为负。可能以后的 Box2D 版本中会以正隔离来创建触点,所以当有触点的报告时你可能会检查符号。

法向力(normal force)

Box2D 使用了一个迭代接触求解器,并会以触点保存结果。你可以安全地使用法向力来判断碰撞强度。例如,你可以使用这个力来引发破碎,或者播放碰撞的声音。

 切向力(tangent force)

它是接触求解器关于摩擦力的估计量。

接触标识(contact ids)

Box2D 会试图利用一个时间步中的触点压力(contact force)结果来推测下一个时间步中的情况。接触标识用于匹配跨越时间步的触点,它包含了几何特征索引以便区分触点。

 当两个形状的 AABB 重叠时,接触就被创建了。有时碰撞筛选会阻止接触的创建,有时尽管碰撞已筛选了 Box2D 还是须要创建一个接触,这种情况下它会使用 b2NullContact 来防止碰撞的发生。当 AABB 不再重叠之后接触会被摧毁。也许你会皱起眉头,为了没有发生实际碰撞的形状(只是它们的 AABB)却创建了接触。好吧,的确是这样的,这是一个鸡或蛋的问题。我们并不知道是否需要一个接触,除非我们创建一个接触去分析碰撞。如果形状之间没有发生碰撞,我们需要正确地删除接触,或者,我们可以一直等到 AABB 不再重叠。Box2D 选择了后面这个方法。

 1)接触监听器

 通过实现 b2ContactListener 你就可以接受接触数据。当一个触点被创建时,当它持续超过一个时间步时,以及当它被摧毁时,这个监听器(listener)就会发出报告。请留意两个形状之间可能会有多个触点。

 class MyContactListener : public b2ContactListener

{

public:

 void Add(const b2ContactPoint* point)

 {

 // handle add point

 }

 void Persist(const b2ContactPoint* point)

 {

 // handle persist point

 }

 void Remove(const b2ContactPoint* point)

 {

 // handle remove point

 }

 void Result(const b2ContactResult* point)

 {

 // handle results

 }

};

 2)接触筛选

 通常,你不希望游戏中的所有物体都发生碰撞。例如,你可能会创建一个只有某些角色才能通过的门。这称之为接触筛选,因为一些交互被筛选出了。

通过实现 b2ContactFilter 类,Box2D 允许定制接触筛选。这个类需要一个 ShouldCollide 函数,用于接收两个 b2Shape 的指针,如果应该碰撞那么就返回 true。默认的 ShouldCollide 实现使用了 6 形状 中的 b2FilterData

bool b2ContactFilter::ShouldCollide(b2Shape* shape1, b2Shape* shape2)
{
 const b2FilterData& filter1 = shape1->GetFilterData();
 const b2FilterData& filter2 = shape2->GetFilterData();
 if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex != 0)
 {
 return filter1.groupIndex > 0;
 }
 bool collide = (filter1.maskBits & filter2.categoryBits) != 0 && 
(filter1.categoryBits & filter2.maskBits) != 0;
 return collide;
}

 12、杂项

 1)

你可以实现一个 b2BoundaryListener,这样当有物体超出世界的 AABB b2World 就能通知你。当你得到回调时,你不应该试图删除物体;取而代之的是,你可以为角色做个删除或错误处理标记,在物理时间步之后再进行这个事件的处理。

class MyBoundaryListener : public b2BoundaryListener

{

 void Violation(b2Body* body)

 {

 MyActor* myActor = (MyActor*)body->GetUserData();

 myActor->MarkForErrorHandling();

 }

};

随后你可以在世界对象中注册你的边界监听器实例,这应该安排在世界初始化过程中。

myWorld->SetListener(myBoundaryListener);

 2)隐式摧毁

如果你摧毁一个 Box2D 实体,你应该保证所有到它的引用都删除了。如果你只有实体的单个引用的话,那就简单了。但如果你有很多个引用,你可能要考虑实现一个处理类来封装原始指针。通常使用 Box2D 时你需要创建并摧毁许多物体,形状还有关节。管理这些实体有些自动化,如果你摧毁一个物体,所有它的形状,关节,以及接触都会摧毁,这称为隐式摧毁。任何连接于这些关节或接触之一的物体将被唤醒,通常这是便利的。然而,你应该意识到了一个关键问题:

Box2D 提供了一个名为 b2WorldListener 的监听器类,你可以实现它并提供给世界对象,随后当关节将被隐式摧毁时世界对象就会提醒你。

你可以实现一个 b2DestructionListener,这样当一个形状或关节隐式摧毁时 b2World 就能通知你,这可以帮助你预防访问无效指针。

class MyDestructionListener : public b2DestructionListener

{

 void SayGoodbye(b2Joint* joint)

 {

 // remove all references to joint.

 }

};

随后你可以注册它,这应该在世界初始化过程中。

myWorld->SetListener(myDestructionListener);

参考资料:

基于java的愤怒的小鸟游戏系统毕业设计(项目报告+答辩PPT+源代码+数据库+截图+部署视频)https://download.csdn/download/dwf1354046363/87813609

Java毕业设计174例,包含部署视频_易小侠的博客-CSDN博客https://cv2022.blog.csdn/article/details/124463185?spm=1001.2014.3001.5502

本文标签: 源代码截图小鸟愤怒数据库