JAVA游戏项目之构建Map对象

举报
tea_year 发表于 2025/05/28 16:19:35 2025/05/28
【摘要】  构建Map对象一、本章目标完成Map地图对象的构建实现多图层展示、图层之间的偏移再论Swing的布局方式执行效果图二、涉及知识点面向对象的分析与设计Collections工具类Swing布局方式详解三、知识点讲解1. 面向对象的分析与设计参考上一章内容List: 队列, 排队做核酸就是一个List ,通过下标访问 Set: 一堆对象, 街上下象棋,都围着棋盘,打麻将围成一圈等, Map: ...

 构建Map对象

一、本章目标

  1. 完成Map地图对象的构建

  2. 实现多图层展示、图层之间的偏移

  3. 再论Swing的布局方式

  4. 执行效果图

    image-20221214220733264.png

二、涉及知识点

  1. 面向对象的分析与设计

  2. Collections工具类

  3. Swing布局方式详解

三、知识点讲解

1. 面向对象的分析与设计

参考上一章内容

List: 队列, 排队做核酸就是一个List ,通过下标访问 Set: 一堆对象, 街上下象棋,都围着棋盘,打麻将围成一圈等, Map: key—value映射关系,一个老公对应一个老婆,一个男朋友对应一个女朋友

List 单列集合:特点是有序可以重复 Set 单列集合:特点是无序不可以重复 Map 双列集合: 特点无序key不允许重复,Value可以重复 USA – >美国鬼子, JPN – >日本鬼子 ,CHN – >中国, 豫 – >河南省

image-20221215101733111.png


回到羊了个羊游戏: Map(游戏中的地图类)包括多个Layer ,从list、set、map三大金刚选择一个存储多个Layer图层,选择哪一个?

Map地图有一个属性: List<Layer> list = new ArrayList<>(); 另一个属性:多少图层int变量。

2. Collections工具类

  • 一个操作Set、List和Map等集合的工具类

  • 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象不可变、对集合对象实现同步控制等方法

1. 排序方法(List接口

Set接口及Map接口中的元素是无序的,所以不涉及排序

image-20230703103714737.jpeg


代码示例:

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. 替代方法

image-20230707103714737.jpeg

代码示例:

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);
    }
}

执行效果:

image-20221214233735822.png

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);
    }
}

运行效果:


image-20230103103714737.png

2.3 网格布局

网格布局管理器GridLayout以矩形网格形式对组件进行布置,把容器按行列分成大小相等的矩形网格,一个网格中放置一个组件,组件宽高自动撑满网格。容器大小改变,网格大小也改变。

a.GridLayout的构造方法

image-20221214234130305.png

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);
    }
}

执行效果:

image-20221214234356125.png

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();
    }
}

执行效果:

image-20221214210606600.png

问题:那个图层在最上面?在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;
    }
}

image-20221214213610144.png

以上图层是完全被遮住的,游戏当中会有部分覆盖的效果,可以看到下一层牌的一部分,从而可以猜测是什么牌。这样增加了游戏的可玩性,下面我们来增加偏移量的设置。

  1. 在Layer图层类增加 offsetx偏移量属性

    public class Layer {
    
        private int offsetx;//偏移量 x轴
        private int offsety;//偏移量 y轴
        //getter/setter方法
  2. 在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;
        }
    }
  3. 在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);
            }
        }
    }
  4. 执行效果,偏移量设置的不是太明显,可以增加偏移量;

    image-20221214214734395.png


    layer1.setOffsetx(60);
    layer2.setOffsetx(40);
    layer3.setOffsetx(20);
  5. 执行效果如下图所示:

    image-20221214215109882.png


    再加入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);
        }
       //省略剩下的代码
    }
  6. MapUtil类中的代码可以删除

    layer1.setOffsetx(60);
    layer2.setOffsetx(40);
    layer3.setOffsetx(20);
  7. 修改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);
            }
        }
    }
  8. 运行效果如下图所示:

    image-20221214220733264.png

  9. 至此Map对象构建完成。

五、本章总结

  1. 面向对象的分析与设计,需要积累一定的项目经验的,至少要从头开始参与3~5个项目。

  2. Swing布局方式详解,绝对布局,通过指定x轴和y轴坐标位置,实现定位和偏移。

  3. Collections工具类

六、本章作业

  1. 完成Collections工具类的相关代码

  2. 完成四种布局方式的代码

  3. 完成Map图层的构建,并添加随机偏移量

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。