JAVA游戏项目之显示图层数据
(一)图层数据模型分析
(二)图层数据内容显示
二、涉及知识点
(一)面向对象的分析与设计
(二)一维数组、二维数组、一维转换成二维、对象数组、数组的遍历和赋值
(三)数组打散:交互位置、Collections工具类
三、知识点讲解
(一)面向对象的分析与设计
1、什么是分析和设计
分析其强调的是对问题和需求的调查研究,而不是解决方案,如需求分析、线上问题分析等。案例:1.如果需要做一个在线交易系统?那么他应该有哪些功能?如何使用它 2.现在一个系统有问题,需要梳理整理相关问题。分析可以理解为定义问题阶段。Book( 属性和方法 ) Category分类 Publisher 设计(design)强调的是满足需求的==概念上==的解决方案,而不是具体实现,如面向对象设计、数据库设计。案例:设计一个数据库的表,设计E-R图等。设计可以理解对问题定义清楚好,开始寻找解决方案阶段。
2、什么是面向对象分析和设计
面向对象分析强调的是在问题领域内发现和描述对象的概念。例如在航班信息系统里面包含飞机(Plane)、航班(Flight)和飞行员等概念。面向对象设计过程中,强调的是定义软件对象以及问它们如何协作以实现需求。
3、一个简单案例
骰子游戏:模拟游戏者掷两个骰子,如果总数是7则赢得游戏,否则为输。下图给从需求分析到最终软件设计整个流程。
-
定义用例
需求分析可能包括人们如何使用的系统的情节或者场景,这些情节或者场景可以被编写成用例。对于非软件开发者他们感知软件系统更多是通过功能、交互、流程、规则等 骰子游戏用例:游戏者请求掷骰子。系统结果展示:如果骰子的总数点数是7,则游戏者赢;否则游戏者输。
-
定义模型
面向对象分析的结果可以表示为领域模型,在领域模型中展示重要的领域概念对象。值得说明是:这里的领域模型不是对软件对象的描述(在后续软件设计设计中会受到领域模型启发),它是真实世界中领域概念和想象可视化。领域模型是一种概念模型,也叫问题域模型,他是为了准确定义需要解决问题而构造的额抽象模型。 方法:从主语、宾语、定语、状语中提炼名词和形容词,对相应名词进行抽象成为领域模型,相关形容词抽象为其属性。从谓语、状语、定语中提炼动词和形容词,这些词决定了领域模型之间的关联关系。 按照这个方法分析上面用例:名词:游戏者、骰子、骰子游戏,动词:请求、掷。如下图所示可以得到如下概念对象模型,Die是骰子意思。
-
定义对象职责并绘制时序图
面向对象设计关注软件对象的定义-他的职责和协作。
-
定义设计类图
如下图所示给出类图关系,玩家依赖iceGame play方法去触发游戏,这里面DiceGame和Die是聚合关系,DiceGame有个times属性表示要掷几次骰子。带减号的表示私有的属性,带+加号表示共有方法,属性冒号后面表示属性的数据类型。
4、基于羊了个羊的面向对象分析与设计
-
Brand类
分析项目的数据结构,最基础、最底层的数据结构是Brand,代表一张牌,被盖住的牌是灰色的,不能点击的。所以牌Brand也有两种状态,就是灰色和彩色。前面的代码已经实现,主要的属性和方法如下:
m(method)方法 f—Field(字段数据库/属性);类图:类名称、类的构造方法、类的属性、类的方法
表格—6*6行列—–单元格—数据
-
ell类
比Brand牌大的是什么,牌放在哪里?单元格,Cell类表示,一个牌要放到一个单元格中,所有单元格都有图案,有的单元格是空的。所以单元Cell类有两种状态:有牌和无牌。查看在线表格插入单元格图片,加深理解。
Cell类的属性和方法如下图所示:
-
Layer类
Cell又放到哪里呢?一个更大的表格了,这个表格有行和列组成,单元格的数量,单元格的数据等。在面向对象分析与设计中,使用Layer表示,图层的意思,游戏中有多个图层,层层遮盖,被盖住的牌就是灰色的不能点击的。这是游戏的关键点。图层就是一个二维表格,每个单元格当中是一个Cell类的对象。属性和方法如下图所示:
-
Map类
图层又放到哪里? 因为游戏有多个图层,所以可以设计一个Map类,包含了所有的元素,比如多个图层,下面的消除框,道具之类的内容。
-
总结
自顶向下Map、Layer、Cell、Brand组成了整个羊了个羊游戏的数据结构。一个地图有多个图层,一个图层又多个单元格,一个单元格有一个或零个牌,一个牌包含两张图片(1张正常,1张灰色)。
(二)二维数组、对象数组
1. 基本概念
-
从定义形式上看: int [][]; int nums [] = { 100,200,300,500 };
-
可以这样理解,二维数组的每个元素都是一维数组, 这样就构成了二维数组。
-
举例
int[][] arr = { {0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 0},
{0, 2, 0, 3, 0, 0},
{0, 0, 0, 0, 0, 0}
};
-
关于二维数组的关键概念
二维数组的元素个数 = 二维数组中一维数组的个数 = arr.length; 二维数组的每个元素是一维数组, 所以如果需要得到每个一维数组中的元素,还需要再次遍历; arr[i][j]=》 二维数组的第 i +1 个一维数组的第 j+1 个元素; eg: arr[0][0] = arr 中的第1个一维数组中的第1个元素。
例子:输出二维数组中的每一个元素
public class TwoDimensionalArray01 { public static void main(String[] args) { int[][] arr = { {0, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0}, {0,2, 0, 3, 0, 0}, {0, 0, 0, 0, 0, 0} }; for(int i = 0; i < arr.length; i++) { //遍历二维数组的每个元素(数组) //1. arr[i] 表示 二维数组的第 i+1 个元素 比如 arr[0]:二维数组的第一个元素 //2. arr[i].length 得到 对应的 每个一维数组的长度 for(int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); //输出了一维数组 } System.out.println();//换行 } } }
2. 数组初始化
2.1 静态初始化 基本语法: 类型 数组名 = {{值 1,值 2…},{值 1,值 2…},{值 1,值 2…}}; 举例:int arr = { {1,1,1}, {8,8,9}, {100} }; 注意:二维数组的每个元素必须是一维数组,不允许是其他数据类型;每个一维数组中的元素个数可以相等/不相等。上例中,第一个一维数组有 3 个元素 , 第二个一维数组有 3 个元素, 第三个一维数组只有 1 个元素。 2.2 动态初始化 2.2.1 第一种方式(列数确定) 基本语法: 类型 数组名 = new 类型[大小][大小]; 举例: int arr[][] = new int[2][3]; 这种初始化方式在声明二维数组的同时开辟了其所需要的堆内存空间; 2.2.2 第二种方式(列数确定) 基本语法: 先声明:类型[][] 数组名; 再定义(开辟空间):数组名 = new 类型[大小][大小]; 然后给一维数组中的元素赋值,若不赋值,则为默认值。 2.2.3 第三种方式(列数不确定) 基本语法: 先声明:类型 数组名; 再定义(开辟空间):数组名 = new 类型[大小][]; 注意:此时一维数组未开辟堆内存空间; 遍历二维数组,给一维数组开辟堆内存空间,此时开辟的空间可以不相等;也就是列数不确定。 最后给一维数组中的元素赋值。 代码说明:
int[][] arr = new int[3][];// 此时一维数组还没有分配内存空间;arr[i]的地址为null;
for (int i = 0; i < arr.length; i++) {
arr[i] = new int[i + 1];// 给每个一维数组开辟空间,若没有这一步,一维数组的内存空间就是null;
}
3. 二维数组的内存存在形式
-
以动态初始化的第一种方式为例,int arr[][] = new int[2][3];
-
其内存示意图如下:
说明:
-
首先在栈内存中声明了一个二维数组类型的变量 arr,在堆内存中为其开辟了一个大小为 2 的地址空间,里面分别存储着一维数组 arr[0]、arr[1] 的地址; Date date = new Date();
-
同时在堆内存中另外开辟了两个新的大小为 3 的内存空间,空间的地址就是 arr[0]、arr[1] 的内存地址,内存里面存储着一维数组中的元素。
-
注意:如果是以动态初始化(列数不确定)的方式创建二维数组,例如: int arr = new int[3][]; 则此时所有的一维数组都没有开辟内存空间,它们的地址都为null;
4. 经典案例:杨辉三角
使用二维数组打印一个 10 行杨辉三角,如下图所示:
-
规律:
-
第一行有 1 个元素, 第 n 行有 n 个元素 ;
-
每一行的第一个元素和最后一个元素都是 1 ;
-
从第三行开始, 对于非第一个元素和最后一个元素的元素的值 arr[i][j] ,arr[i][j] = arr[i-1][j] + arr[i-1][j-1]。
-
代码实现:
public class TwoDimentionalArray01 { public static void main(String[] args) { int[][] a = new int[10][]; for (int i = 0; i < a.length; i++) { // 给每个一维数组开辟堆内存空间,第n行有n个元素 a[i] = new int[i + 1]; // 遍历每一个一维数组,赋值 for (int j = 0; j < a[i].length; j++) { // 每一行的第一个元素和最后一个元素都是1 if (j == 0 || j == a[i].length - 1) { a[i][j] = 1; } else { // 每一行非第一个元素和最后一个元素的值 = 上一行的同一列 + 上一行的上一列 a[i][j] = a[i - 1][j] + a[i - 1][j - 1]; } } } // 输出杨辉三角 for (int i = 0; i < a.length; i++) { for (int k = 0; k < a[i].length; k++) { System.out.print(a[i][k] + "\t"); } System.out.println(); } } }
5. 二维数组使用细节和注意事项
(1)一维数组的声明方式有: int[] x 或者 int x[] ; (2)二维数组的声明方式有: int y 或者 int[] y[] 或者 int y ; (3)二维数组实际上是由多个一维数组组成的,它的各个一维数组的长度可以相同,也可以不相同。
比如: map 是 一个二维数组, int map = {{1,2},{3,4,5}} ;其中,map[0] 是一个含有两个元素的一维数组 ,map[1] 是一个含有三个元素的一维数组,因此, map 也称为 “列数不等的二维数组”。
(三)数组打散:交互位置
1. 数组交换位置
package cn.yunhe.demo;
import java.util.Arrays;
import java.util.Random;
public class TestArray {
private static Random random = new Random();
public static String[] brandNames = {
"刷子","刷子","刷子",
"手套","手套","手套",
"玉米","玉米","玉米",
"白菜","白菜","白菜",
"胡萝卜","胡萝卜","胡萝卜",
"铃铛","铃铛","铃铛"
};
public static void main(String[] args) {
for (int i = 0; i < brandNames.length; i ++) {
String brandA = brandNames[i];
//交换位置B的索引
int randomIndex = random.nextInt(brandNames.length);
String brandB = brandNames[randomIndex];
//画图演示
brandNames[i] = brandB;
brandNames[randomIndex] = brandA;
}
System.out.println(Arrays.toString(brandNames));
}
}
2. 对象数组
对象数组:就是指包含了一组相关的对象。
对象数组的声明:类 对象数组名称[]=new 类[数组长度];
需要注意的是,对象数组初始化之前,每一个数字对象都是默认值,并没有实例化,所以需要分别实例化!
1)动态初始化:类 对象数组名称[]=new 类[数组长度];
对象数组名称[i]=new 类(参数,参数,参数,);
2)静态初始化:类 对象数组名称[]={new 类(参数,参数),new 类(参数,参数),new 类(参数,参数),};
动态初始化例子:
class Person{
private String name ; // 姓名属性
public Person(String name){ // 通过构造方法设置内容
this.name = name ; // 为姓名赋值
}
public String getName(){
return this.name ; // 取得姓名
}
};
public class ObjectArrayDemo01{
public static void main(String args[]){
// 类名称 数组名称[] = new 类名称[长度]
Person per[] = new Person[3] ; // 开辟了三个空间大小的数组
System.out.println("============== 数组声明 =================") ;
// 对象数组初始化之前,每一个元素都是默认值
for(int x=0;x<per.length;x++){ // 循环输出
System.out.print(per[x] + "、") ; // 因为只是开辟好了空间,所以都是默认值
}
// 分别为数组中的每个元素初始化,每一个都是对象,都需要单独实例化
per[0] = new Person("张三") ; // 实例化第一个元素
per[1] = new Person("李四") ; // 实例化第二个元素
per[2] = new Person("王五") ; // 实例化第三个元素
System.out.println("\n============== 对象实例化 =================") ;
for(int x=0;x<per.length;x++){ // 循环输出
System.out.print(per[x].getName() + "、");//此时,已经实例化完成了,所以会直接打印出姓名
}
}
};
静态初始化:
class Person{
private String name ; // 姓名属性
public Person(String name){ // 通过构造方法设置内容
this.name = name ; // 为姓名赋值
}
public String getName(){
return this.name ; // 取得姓名
}
}
public class ObjectArrayDemo02{
public static void main(String args[]){
// 声明一个对象数组,里面有三个对象,使用静态初始化方式完成
Person per[] = {new Person("张三"),new Person("李四"),new Person("王五")} ;
System.out.println("============== 数组输出 =================");
// 循环输出
for(int x=0;x<per.length;x++){
// 此时,已经实例化完成了,所以会直接打印出姓名
System.out.print(per[x].getName() + "、") ;
}
}
}
四、代码实现
(一)Cell类的实现
package cn.yunhe.model;
public class Cell {
private int state ;//0无牌,1有牌
private Brand brand;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
}
(二)Layer类的实现
一个Map当中有多个图层Layer,层层遮盖,被盖住的牌就是灰色的,不能点击。这是游戏的关键点。图层是二维表格,每个单元格当中是一个Cell类的对象。
一个图层当中不是所有单元格都有图案,有的单元格是空的。所以单元格Cell类有两种状态:有牌和无牌。
-
图层类的其他重要属性:rowNum,colNum两个变量行和列的数量;
-
capacity int类型,代表当前图层能够容纳的牌的总数
-
size int类型,代表当前图层目前有多少牌
-
通过有参构造器初始化图层对象
图层数据的构建:
-
基于图层类,创建一个对象,内部有一个二维数组用来存储我们的牌,但是刚刚创建我们的二维数组内部是空白的,没有任何牌;
-
我们把牌放入到二维数组中,业务流程如下:
第一步:创建一个数组,存放所有的牌的名称,每次随机抽取一个牌的名字,因为每次进入地图,牌都是随机生成的。不是整整齐齐摆好的,那样就没有游戏乐趣了。
package cn.yunhe.model;
/**
* 图层类:二维表格
*/
public class Layer {
private int rowNum;//行
private int colNum;//列
private int capacity;//最大容量
private int size;//目前有多少牌,当牌添加和消失的时候改变
private Cell[][] cells ;
public Layer(int rowNum, int colNum) {
this.rowNum = rowNum;
this.colNum = colNum;
this.capacity = this.rowNum*this.colNum;
if (this.capacity % 3 != 0) {
throw new RuntimeException("容量不是3的倍数..");
}
this.cells = new Cell[rowNum][colNum];
}
public Cell[][] getCells() {
return cells;
}
public void setCells(Cell[][] cells) {
this.cells = cells;
}
public int getRowNum() {
return rowNum;
}
public void setRowNum(int rowNum) {
this.rowNum = rowNum;
}
public int getColNum() {
return colNum;
}
public void setColNum(int colNum) {
this.colNum = colNum;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
(三)Layer测试类
package cn.yunhe.test;
import cn.yunhe.model.Brand;
import cn.yunhe.model.Cell;
import cn.yunhe.model.Layer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
//把图层的数据准备完善
public class TestBuildLayer {
public static Random random = new Random();
public static String[] brandNames = {
"刷子","剪刀","叉子",
"手套","水桶","火",
"玉米","球","瓶子",
"白菜","稻草","肉腿",
"胡萝卜","苹果","苹果",
"铃铛","青草"
};
/**
* 每次调用获取一个牌的名称
* @return
*/
public static String getBrandName (){
int randomIndex = random.nextInt(brandNames.length);
return brandNames[randomIndex];
}
public static void main(String[] args) {
Layer layer = new Layer(6,6);//36张牌
//System.out.println(getBrandName());
Brand[] brands = new Brand[layer.getCapacity()];//数组的容量和图层的容量相等
System.out.println("===========version1===============");
//问题:某一类牌的数量不一定是3的倍数,三张相同的牌消除
/*
for (int i = 0; i < brands.length; i++) {
String randomBrandName = getBrandName();//每次获取一个随机的牌名称
Brand brand = new Brand(randomBrandName);
brands[i] = brand;
}
*/
System.out.println("===========version2===============");
//解决方案:每次一个牌连续创建3张,这样就可以了。
//问题:如果图层的总数不是3的倍数就出异常:索引越界,譬如:2,5,
//解决办法:在Layer类中加以判断,
for (int i = 0; i < brands.length; i+=3) {
String randomBrandName = getBrandName();
Brand brand1 = new Brand(randomBrandName);
Brand brand2 = new Brand(randomBrandName);
Brand brand3 = new Brand(randomBrandName);
brands[i] = brand1;
brands[i+1] = brand2;
brands[i+2] = brand3;
}
for (int i = 0; i < brands.length; i++) {
System.out.print(brands[i].getName()+"-");
}
System.out.println("\n===========version3===============");
//出现的问题:三张牌连在一起,需要随机打散,增加难度
for (int i = 0; i < brands.length; i ++) {
Brand brandA = brands[i];//当前位置A
//交换位置B的索引
int randomIndex = random.nextInt(brands.length);
Brand brandB = brands[randomIndex];
//画图演示
brands[i] = brandB;
brands[randomIndex] = brandA;
}
//简单的方法
//Collections.shuffle(Arrays.asList(brands));
System.out.println("===========打乱顺序后===============");
for (int i = 0; i < brands.length; i++) {
System.out.print(brands[i].getName()+"-");
}
System.out.println();
/*
* * * * *
* * * * *
* * * * *
*/
Cell[][] cells = layer.getCells();
int flag = 0;
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
System.out.print(row +"-" + col +"\t");
Cell cell = new Cell();
cell.setState(1);
cell.setBrand(brands[flag++]);
cells[row][col] = cell;
}
System.out.println();
}
System.out.println("-----------输出layer图层的值--------------------");
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Brand brand = cells[row][col].getBrand();
System.out.print(brand.getName()+"-");
}
System.out.println();
}
}
}
(四)代码重构
上面的测试类当中实现了图层数据的构建,代码都放在一起,后期很难阅读和维护。
-
首先我们创建一个BrandUtil工具类,用来封装创建随机牌的功能代码
-
其次我们创建LayerUtil工具类,用来封装创建图层数据的功能代码
-
第三对外提供统一的访问入口
注意:很多优秀的产品都是不断的迭代升级而来的,不是第一次就是完美的,大家学习编程的过程也是一样。
1. BrandUtil工具类
package cn.yunhe.util;
import cn.yunhe.model.Brand;
import java.util.Random;
public class BrandUtil {
public static Random random = new Random();
public static String[] brandNames = {
"刷子","剪刀","叉子",
"手套","水桶","火",
"玉米","球","瓶子",
"白菜","稻草","肉腿",
"胡萝卜","苹果","苹果",
"铃铛","青草"
};
/**
* 每次调用获取一个牌的名称
* @return
*/
public static String getBrandName (){
int randomIndex = random.nextInt(brandNames.length);
return brandNames[randomIndex];
}
/**
* 创建随机牌的数组
* @param capacity
* @return
*/
public static Brand[] buildBrands(int capacity){
Brand[] brands = new Brand[capacity];//数组的容量和图层的容量相等
for (int i = 0; i < brands.length; i+=3) {
String randomBrandName = getBrandName();
Brand brand1 = new Brand(randomBrandName);
Brand brand2 = new Brand(randomBrandName);
Brand brand3 = new Brand(randomBrandName);
brands[i] = brand1;
brands[i+1] = brand2;
brands[i+2] = brand3;
}
for (int i = 0; i < brands.length; i ++) {
Brand brandA = brands[i];//当前位置A
//交换位置B的索引
int randomIndex = random.nextInt(brands.length);
Brand brandB = brands[randomIndex];
brands[i] = brandB;
brands[randomIndex] = brandA;
}
//简单的方法
//Collections.shuffle(Arrays.asList(brands));
return brands;
}
}
2. LayerUtil工具类
package cn.yunhe.util;
import cn.yunhe.model.Brand;
import cn.yunhe.model.Cell;
import cn.yunhe.model.Layer;
public class LayerUtil {
public static Layer buildLayer(int rowNum,int colNum){
Layer layer = new Layer(rowNum,colNum);
//创建36张牌,放到一维数组中brands
Brand[] brands = BrandUtil.buildBrands(layer.getCapacity());
Cell[][] cells = layer.getCells();
int flag = 0;
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Cell cell = new Cell();
cell.setState(1);
cell.setBrand(brands[flag++]);
cells[row][col] = cell;
}
}
return layer;
}
}
3. 测试类
package cn.yunhe.test;
import cn.yunhe.model.*;
import cn.yunhe.util.LayerUtil;
import javax.swing.*;
public class TestRenderLayer extends JFrame {
private final Layer layer = LayerUtil.buildLayer(6,6);
public TestRenderLayer() {
//1.初始化基本信息
init();
//2.渲染图层:添加组件的方法,可以添加自己定义的组件都当前的窗口中
renderLayer();
//3.自动刷新
autoRefresh();
}
private void renderLayer() {
Cell[][] cells = layer.getCells();
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Brand brand = cells[row][col].getBrand();
int x = col * 50;
int y = row * 50;
brand.setBounds(x,y,50,50);
this.getContentPane().add(brand);
System.out.print(brand.getName()+"-");
}
System.out.println();
}
}
private void autoRefresh() {
TestRenderLayer start = this;
new Thread(() -> {
while (true) {
start.repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
public void init(){
this.setTitle("云和数据版-羊了个羊");
this.setSize(450,800);
this.setLocationRelativeTo(null);
this.setLayout(null);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
new TestRenderLayer();
}
}
==注意:Brand类的属性:x,y,width,height 删除getter/setter方法,==运行效果:
五、本章总结
(一)面向对象的分析与设计
(二)一维数组、二维数组、一维转换成二维、对象数组、数组的遍历和赋值
(三)数组打散:交互位置
(四)Swing的相关知识
六、布置作业
-
二维数组的练习,杨辉三角,对象数组,Person对象数组,数组的交换位置
-
本章的项目案例完成,渲染出图层
-
每个人都在processon上画组件关系图,我分享的有链接,钉钉群里。
- 点赞
- 收藏
- 关注作者
评论(0)