JAVA游戏项目之构建Map对象
一、本章目标
-
完成Map地图对象的构建
-
实现多图层展示、图层之间的偏移
-
再论Swing的布局方式
-
二、涉及知识点
-
面向对象的分析与设计
-
Collections工具类
-
Swing布局方式详解
三、知识点讲解
1. 面向对象的分析与设计
参考上一章内容
List: 队列, 排队做核酸就是一个List ,通过下标访问 Set: 一堆对象, 街上下象棋,都围着棋盘,打麻将围成一圈等, Map: key—value映射关系,一个老公对应一个老婆,一个男朋友对应一个女朋友
List 单列集合:特点是有序可以重复 Set 单列集合:特点是无序不可以重复 Map 双列集合: 特点无序key不允许重复,Value可以重复 USA – >美国鬼子, JPN – >日本鬼子 ,CHN – >中国, 豫 – >河南省
回到羊了个羊游戏: Map(游戏中的地图类)包括多个Layer ,从list、set、map三大金刚选择一个存储多个Layer图层,选择哪一个?
Map地图有一个属性: List<Layer> list = new ArrayList<>(); 另一个属性:多少图层int变量。
2. Collections工具类
-
一个操作Set、List和Map等集合的工具类
-
提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象不可变、对集合对象实现同步控制等方法
1. 排序方法()
Set接口及Map接口中的元素是无序的,所以不涉及排序
代码示例:
public static void main(String [] args) {
ArrayList<String> list = new ArrayList<>();
list.add("123");
list.add("AA");
list.add("DD");
list.add("CC");
list.add("XX");
list.add("FF");
// list集合中元素的原本排序:[123, AA, DD, CC, XX, FF]
System.out.println(list);
// 反转排序
Collections.reverse(list);
// 反转后:[FF, XX, CC, DD, AA, 123]
System.out.println(list);
// 随机排序
Collections.shuffle(list);
// 输出结果:[123, FF, DD, AA, CC, XX]
System.out.println(list);
// 升序
Collections.sort(list);
// 升序后:[123, AA, CC, DD, FF, XX]
System.out.println(list);
// 指定位置调换
Collections.swap(list,1,3);
// [123, DD, CC, AA, FF, XX]
System.out.println(list);
}
2. 替代方法
代码示例:
public static void main(String [] args) {
ArrayList<Integer> list1 = new ArrayList<>();
Integer i;
list1.add(123);
list1.add(456);
list1.add(789);
list1.add(32);
list1.add(43);
list1.add(43);
list1.add(43);
list1.add(3456);
// list1集合的原本排序 [123, 456, 789, 32, 43, 43, 43, 3456]
System.out.println(list1);
// 获取最大值
i = Collections.max(list1);
// 3456
System.out.println(i);
// 获取最小值
i = Collections.min(list1);
// 32
System.out.println(i);
// 获取指定数据的出现次数
i = Collections.frequency(list1,43);
// 3
System.out.println(i);
// 替换数据
Collections.replaceAll(list1, 43, 99);
// 替换后:[123, 456, 789, 32, 99, 99, 99, 3456]
System.out.println(list1);
// 新造集合,并复制原有集合(新造集合的长度必须和原有集合一致)
List list2 = Arrays.asList(new Object[list1.size()]);
Collections.copy(list2,list1);
// 拷贝结果与原集合一样 [123, 456, 789, 32, 99, 99, 99, 3456]
System.out.println(list2);
}
3.同步控制
Collections类中提供了多个synchronizedXxx( ) 方法,该方法可将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
代码示例:
public static void main(String [] args) {
// Arraylist1是线程不安全的,同样对象list也是线程不安全的
ArrayList<String> list = new ArrayList<>();
// 把list放入synchronizedList参数中,list就变成线程安全的了
List list1 = Collections.synchronizedList (list);
}
// synchronizedList的源码
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
3. Collections打散数组
package cn.yunhe.demo;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
public class TestArray {
private static Random random = new Random();
public static String[] brandNames = {
"刷子","刷子","刷子",
"手套","手套","手套",
"玉米","玉米","玉米",
"白菜","白菜","白菜",
"胡萝卜","胡萝卜","胡萝卜",
"铃铛","铃铛","铃铛"
};
public static void main(String[] args) {
Collections.shuffle(Arrays.asList(brandNames));
System.out.println(Arrays.toString(brandNames));
}
}
查看源码:
private static void swap(Object[] arr, int i, int j) {
Object tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
3. Swing布局方式详解
为了将Java Swing的各种组件添加到面板容器中(JPanel),需要给面板容器指定布局管理器(LayoutManager),明确各个组件之间的排列布局方式。着重介绍以下三种常用的布局管理器,想要了解其他布局管理器可以参考官方API文档。
布局管理器 | 相关介绍 |
---|---|
FlowLayout | 流式布局,按组件加入的顺序水平方向排列,排满一行换下一行继续排列 |
BorderLayout | 边界布局,把窗口按方位分为5个区域,每个区域放置一个组件 |
GridLayout | 网格布局,以矩形网格形式对组件进行布置,把容器按行列分成大小相等的矩形网格 |
2.1 流式布局
按组件加入的顺序水平方向排列,排满一行换下一行继续排列。
a.FlowLayout的构造方法
构造方法 | 语法 | 功能 |
---|---|---|
无参构造方法 | FlowLayout() | 默认居中对齐 |
含参构造方法 | FlowLayout(int align) | 指定对齐方式 |
含参构造方法 | FlowLayout(int align, int hgap, int vgap) | 指定对齐方式,水平、竖直的间隔 |
tip:对齐方式的参考值:
-
FlowLayout.LEFT : 左对齐
-
FlowLayout.CENTER : 居中对齐(默认)
-
FlowLayout.RIGHT : 右对齐
-
FlowLayout.LEADING : 与容器方向的开始边对齐
-
FlowLayout.TRAILING : 与容器方向的结束边对齐
b.实例展示
创建一个示例:
package cn.yunhe.demo;
import java.awt.*;
import javax.swing.*;
public class FlowLayoutTest {
public static void main(String[] args) {
JFrame jf = new JFrame("这是一个JFrame");
jf.setLayout(new FlowLayout(FlowLayout.LEFT));
jf.setSize(250, 250);
jf.setLocation(400, 300);
//面板组件,容器
JPanel jp = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton btn01 = new JButton("按钮01");
JButton btn02 = new JButton("按钮02");
JButton btn03 = new JButton("按钮03");
JButton btn04 = new JButton("按钮04");
JButton btn05 = new JButton("按钮05");
jp.add(btn01);
jp.add(btn02);
jp.add(btn03);
jp.add(btn04);
jp.add(btn05);
jf.setContentPane(jp);
jf.setVisible(true);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
执行效果:
2.2 边界布局
默认的布局,它把窗口按方位分为东、西、南、北、中五个区域,每个区域放置一个组件。
a.BorderLayout的构造方法
构造方法 | 语法 | 功能 |
---|---|---|
无参构造方法 | BorderLayout() | 构造一个组件之间没有间距的边界布局管理器 |
含参构造方法 | BorderLayout(int hgap, int vgap) | 构造一个边界布局管理器,指定组件之间水平、竖直间距 |
b.BorderLayout的重要常量
-
BorderLayout.NORTH:表示容器的北部
-
BorderLayout.SOUTH:表示容器的南部
-
BorderLayout.WEST:表示容器的西部
-
BorderLayout.EAST:表示容器的东部
-
BorderLayout.CENTER:表示容器的中部
c.实例展示
创建一个示例:
package cn.yunhe.demo;
import javax.swing.*;
import java.awt.*;
public class BorderLayoutTest {
public static void main(String[] args) {
JFrame frame = new JFrame("学籍管理系统");
frame.setSize(800,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout());
JButton button1 = new JButton("北部战区");
JButton button2 = new JButton("南部战区");
JButton button3 = new JButton("西部战区");
JButton button4 = new JButton("东部战区");
JButton button5 = new JButton("中部战区");
//添加到JFrame窗体中
frame.add(button1,BorderLayout.NORTH);
frame.add(button2,BorderLayout.SOUTH);
frame.add(button3,BorderLayout.WEST);
frame.add(button4,BorderLayout.EAST);
frame.add(button5,BorderLayout.CENTER);
frame.setVisible(true);
}
}
运行效果:
2.3 网格布局
网格布局管理器GridLayout以矩形网格形式对组件进行布置,把容器按行列分成大小相等的矩形网格,一个网格中放置一个组件,组件宽高自动撑满网格。容器大小改变,网格大小也改变。
a.GridLayout的构造方法
b.实例展示
创建一个示例:
package cn.yunhe.demo;
import java.awt.*;
public class GridLayoutDemo{
public static void main(String[] args) {
//1.创建Frame对象,并且标题设置为计算器
Frame frame = new Frame("计算器");
//2.创建一个Panel对象,并且往Panel中放置一个TextField组件
Panel p1 = new Panel();
p1.add(new TextField(30));
//3.把上述的Panel放入到Frame的北侧区域
frame.add(p1,BorderLayout.NORTH);
//4.创建一个Panel对象,并且设置其布局管理器为GridLayout
Panel p2 = new Panel();
p2.setLayout(new GridLayout(3,5,4,4));
//5.往上述Panel中,放置15个按钮,内容依次是:0,1,2,3,4,5,6,7,8,9,+,-,*,/,.
for (int i = 0; i < 10; i++) {
p2.add(new Button(i+""));
}
p2.add(new Button("+"));
p2.add(new Button("-"));
p2.add(new Button("*"));
p2.add(new Button("/"));
p2.add(new Button("."));
//6.把上述Panel添加到Frame的中间区域中国
frame.add(p2);
//7.设置Frame为最佳大小
frame.pack();
//8.设置Frame可见
frame.setVisible(true);
}
}
执行效果:
2.4 绝对布局(null)
绝对布局没有特定一个布局管理器类来表示,给容器的布局管理器设置为 null,就表示使用绝对布局,即通过设置组件的坐标和宽高来布置组件。给组件设置坐标和宽高相的关方法,以Button为例
JPanel panel = new JPanel(null);
JButton button= new JButton("Button");
//设置按钮的坐标
button.setLocation(int x, int y);
//设置按钮的宽高
button.setSize(int width, int height);
//设置按钮的界限(一次性设置组件的 坐标 和 宽高)
button.setBounds(int x, int y, int width, int height);
完整的案例:
package cn.yunhe.demo;
import javax.swing.*;
import java.awt.*;
public class AbsoluteLayoutTest {
public static void main(String[] args) {
JFrame frame = new JFrame("学生管理系统");
frame.setSize(300,250);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout(FlowLayout.LEFT));
//绝对布局
JPanel jPanel = new JPanel(null);
JButton addBtn = new JButton("添加");
addBtn.setLocation(10,10);
addBtn.setSize(50,25);
//添加修改按钮
JButton updateBtn = new JButton("修改");
//updateBtn.setLocation(60,10);
//updateBtn.setSize(50,25);
updateBtn.setBounds(80,10,50,25);
//把按钮加到jPanel
jPanel.add(addBtn);
jPanel.add(updateBtn);
//把面板放到窗体Frame
frame.setContentPane(jPanel);
frame.setVisible(true);
}
}
四、代码实现
在model包创建Map类,代码如下:
package cn.yunhe.model;
import java.util.ArrayList;
import java.util.List;
/**
* 一个地图有多个图层,层层之间遮盖
*/
public class Map {
//层高,有几张图层,游戏难度相关
private int floorHeight;
//为什么不用set,用list
private List<Layer> list = new ArrayList<>();
public int getFloorHeight() {
return floorHeight;
}
public void setFloorHeight(int floorHeight) {
this.floorHeight = floorHeight;
}
public List<Layer> getList() {
return list;
}
public void setList(List<Layer> list) {
this.list = list;
}
}
在util包创建MapUtil类创建Map对象。
package cn.yunhe.util;
import cn.yunhe.model.Layer;
import cn.yunhe.model.Map;
public class MapUtil {
public static Map buildMap(int floorHeight){
Map map = new Map();
map.setFloorHeight(floorHeight);
Layer layer1 = LayerUtil.buildLayer(3,3);
Layer layer2 = LayerUtil.buildLayer(6,6);
Layer layer3 = LayerUtil.buildLayer(9,9);
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
return map;
}
}
在test包下创建TestRenderMap类,复制一个TestRenderLayer类。
package cn.yunhe.test;
import cn.yunhe.model.Brand;
import cn.yunhe.model.Cell;
import cn.yunhe.model.Layer;
import cn.yunhe.model.Map;
import cn.yunhe.util.LayerUtil;
import cn.yunhe.util.MapUtil;
import javax.swing.*;
import java.util.List;
public class TestRenderMap extends JFrame {
private final Map map = MapUtil.buildMap(3);
public TestRenderMap() {
//1.初始化基本信息
init();
//2.渲染图层:添加组件的方法,可以添加自己定义的组件都当前的窗口中
List<Layer> list = map.getList();
for (Layer layer : list) {
renderLayer(layer);
}
//3.自动刷新
autoRefresh();
}
private void renderLayer(Layer layer) {
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);
}
}
}
private void autoRefresh() {
TestRenderMap start = this;
new Thread(() -> {
while (true) {
start.repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private void init(){
this.setTitle("云和数据版-羊了个羊");
this.setSize(450,800);
this.setLayout(null);
this.setLocationRelativeTo(null);//居中
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
new TestRenderMap();
}
}
执行效果:
问题:那个图层在最上面?在renderLayer加一行代码,显示一下图层的内容:
private void renderLayer(Layer layer) {
Cell[][] cells = layer.getCells();
layer.showCells();//显示图层的内容
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);
}
}
}
==注意:layer.showCells(); 加在Layer类中;==
public void showCells(){
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Cell cell = cells[row][col];
Brand brand = cell.getBrand();
System.out.print(brand.getName()+"-");
}
System.out.println();
}
}
查看控制台输出和图层对应的数据,得出结论:最先加入Map的图层显示在最上面, 3 * 3在最上面。
==结论:在绝对布局中,同样位置,先加入的组件,展示最上层== 后加入的在下面,和常识中的后加入的盖住前面的不一致。
改成3x3的图层大小,验证一下。
package cn.yunhe.util;
import cn.yunhe.model.Layer;
import cn.yunhe.model.Map;
public class MapUtil {
public static Map buildMap(int floorHeight){
Map map = new Map();
map.setFloorHeight(floorHeight);
//结论:在绝对布局中,同样位置,先加入的组件,展示最上层
//后加入的在下面,和常识中的后加入的盖住前面的不一致
Layer layer1 = LayerUtil.buildLayer(3,3);
Layer layer2 = LayerUtil.buildLayer(3,3);
Layer layer3 = LayerUtil.buildLayer(3,3);
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
return map;
}
}
以上图层是完全被遮住的,游戏当中会有部分覆盖的效果,可以看到下一层牌的一部分,从而可以猜测是什么牌。这样增加了游戏的可玩性,下面我们来增加偏移量的设置。
-
在Layer图层类增加 offsetx偏移量属性
public class Layer { private int offsetx;//偏移量 x轴 private int offsety;//偏移量 y轴 //getter/setter方法
-
在MapUtil类设置图层的offsetx偏移量
package cn.yunhe.util; import cn.yunhe.model.Layer; import cn.yunhe.model.Map; public class MapUtil { public static Map buildMap(int floorHeight){ Map map = new Map(); map.setFloorHeight(floorHeight); //结论:在绝对布局中,同样位置,先加入的组件,展示最上层 //后加入的在下面,和常识中的后加入的盖住前面的不一致 Layer layer1 = LayerUtil.buildLayer(3,3); Layer layer2 = LayerUtil.buildLayer(3,3); Layer layer3 = LayerUtil.buildLayer(3,3); //指定图层的偏移量 layer1.setOffsetx(30); layer2.setOffsetx(20); layer3.setOffsetx(10); map.getList().add(layer1); map.getList().add(layer2); map.getList().add(layer3); return map; } }
-
在TestRenderMap的renderLayer方法中加入偏移量
private void renderLayer(Layer layer) { Cell[][] cells = layer.getCells(); layer.showCells(); 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 + layer.getOffsetx(); int y = row * 50; brand.setBounds(x,y,50,50); this.getContentPane().add(brand); } } }
-
执行效果,偏移量设置的不是太明显,可以增加偏移量;
layer1.setOffsetx(60); layer2.setOffsetx(40); layer3.setOffsetx(20);
-
执行效果如下图所示:
再加入offsety属性,Y轴的偏移量,从Layer类开始修改代码。
public class Layer { private int offsetx;//偏移量 x轴 private int offsety;//偏移量 y轴 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]; //创建图层的时候通过随机数设置偏移量 this.offsetx = new Random().nextInt(100); this.offsety = new Random().nextInt(100); } //省略剩下的代码 }
-
MapUtil类中的代码可以删除
layer1.setOffsetx(60); layer2.setOffsetx(40); layer3.setOffsetx(20);
-
修改TestRenderMap类的renderLayer方法,加入Y轴的偏移量
private void renderLayer(Layer layer) { Cell[][] cells = layer.getCells(); layer.showCells(); 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 + layer.getOffsetx(); int y = row * 50 + layer.getOffsety();//加入y轴的偏移量 brand.setBounds(x,y,50,50); this.getContentPane().add(brand); } } }
-
运行效果如下图所示:
-
至此Map对象构建完成。
五、本章总结
-
面向对象的分析与设计,需要积累一定的项目经验的,至少要从头开始参与3~5个项目。
-
Swing布局方式详解,绝对布局,通过指定x轴和y轴坐标位置,实现定位和偏移。
-
Collections工具类
六、本章作业
-
完成Collections工具类的相关代码
-
完成四种布局方式的代码
-
- 点赞
- 收藏
- 关注作者
评论(0)