JAVA游戏项目之图层遮盖算法
0、课程回顾
final关键字的用法
模板方法设计模式
适配器设计模式
一、本章目标
实现图层遮盖算法
问题1:假定我们有9个图层,最顶层为第1层,最下层为第9层,那么此时第6层当中有一张牌,它应该显示正常还是显示灰色的状态呢?
问题2:第一层的牌呢?最后一层第9层的牌呢?
判定第6层的某一张牌和他的上层5层的所有牌进行一个比较,是否有交集。如果有,则说明盖住了,显示灰色。
不用再判断6层的这张牌和上层的其他牌的遮盖问题。
如果没有,则说明第5层的牌并没有盖住它(6层的这张票),则继续和第四层的比较,如果还没有,继续比较更高层的牌,直到最高层1层,全部比较完毕。都没有交集,那么显示未正常颜色。
特殊情况:
-
第一层的牌没有遮盖的问题,都是显示正常
-
消除的游戏,所以牌在减少,某一层的牌为0了,或者某个单元格为空,说明什么:底层的牌不用再给改单元格进行比较。所以牌在消除的时候,要修改Cell单元格的状态为:0 无牌,还要修改brand属性为null。
-
图层建立时,同时也建立链式的结构,更方便的找到上一层,Layer类当中增加parentLayer属性,也就是当前图层的上层。
-
实现compare函数,比较一张牌和一个图层的遮盖问题。
-
递归实现:如果和当前图层没有遮盖,继续比较上一层,直到顶层。
-
把map当中所有牌都调用一遍compare函数,从而判断是否置灰, 默认加载彩色图片。
二、涉及知识点
static 关键字: 静态的,对象无关的,和某一个类型有关。
递归方法调用: 渲染map的时候调用,让所有的牌和上一层的牌进行比较,如果上一层没有遮盖继续上一层。
矩阵交集方法: 如何判断遮盖。
三、知识点讲解
static 关键字
生活中的案例:
package cn.yunhe.demo;
class ICBC{ //这是什么银行? CCB 存存吧 ABC 农业 俺不存
private int total;//总存款数,公共厕所公共医院
private String name;//银行的名称
public ICBC(String name) {
this.name = name;
}
public int saveMoney(int money){
this.total += money;
return this.total;
}
public int getTotal() {
return total;
}
public String getName() {
return name;
}
}
public class TestStatic {
public static void main(String[] args) {
ICBC icbc1 = new ICBC("高新区支行");
icbc1.saveMoney(1000);
System.out.println(icbc1.getName()+"总存款"+icbc1.getTotal());
ICBC icbc2 = new ICBC("中原区支行");
icbc2.saveMoney(2000);
System.out.println(icbc2.getName()+"总存款"+icbc2.getTotal());
ICBC icbc3 = new ICBC("金水区支行");
icbc3.saveMoney(3000);
System.out.println(icbc3.getName()+"总存款"+icbc3.getTotal());
}
}
static中文意思:静态的,static是和对象无关的。 PI 圆周率与某个对象又关吗?
public final class Math {
/**
* Don't let anyone instantiate this class.
*/
private Math() {}
/**
* The {@code double} value that is closer than any other to
* <i>e</i>, the base of the natural logarithms.
*/
public static final double E = 2.7182818284590452354;
/**
* The {@code double} value that is closer than any other to
* <i>pi</i>, the ratio of the circumference of a circle to its
* diameter.
*/
public static final double PI = 3.14159265358979323846;
public 说明该属性是公有的,可以在别的类直接访问,不属于某个对象,所以不用private修饰
static 说明该属性和具体对象没有关系,可以直接通过类名.属性名进行访问
final 说明定义的是常量,圆周率不会随意改变
double 说明定义这个变量的类型是双精度浮点型
PI 说明定义的常量名称,常量一般大写字母,MAX_AGE多个单词之间用下划线隔开
3.14 说明常量的值
1.1 生活的角度来理解静态的资源
公共的资源的都属于静态的东西,对象可以使用静态的资源,但是和对象无关;
公共厕所就是静态资源,公立医院就是静态资源,不属于某一个对象, 共享单车。
1.2 Java中的静态
1.修饰成员变量:静态属性
2.修饰成员方法:静态方法
3.修饰代码块: 静态代码块,不属于某一个方法的,但是执行时机先于构造方法
class Man {
static String name;//静态的属性 和对象无关的
//静态方法
public static void eat () {
System.out.println("吃饭喝酒");
}
//静态代码块
static {
System.out.println("嘻嘻");
}
}
public class Demo1 {
public static void main(String[] args) {
//咋使用 类.静态的属性
Man.name = "狗蛋";
System.out.println(Man.name);
//使用 类.静态方法名字()
Man.eat();
}
}
1.2.1 static修饰成员变量
:
static String name;
语法格式:
static 数据类型 变量名;
注意事项:
1.使用static修饰的变量叫
2.代码中对象还没有创建的时候,如果加载了类,static修饰的属性已经存在了,和对象没有关系。
class Person {
String name;
int age;
static String country;
}
public class Demo2 {
public static void main(String[] args) {
Person sb = new Person();
sb.name = "张三";
sb.age = 23;
Person.country = "中国";
//The static field Person.country
//should be accessed in a static way
System.out.println(sb.country);//中国
System.out.println(Person.country);//in a static way
sb.country = "PRC";
System.out.println(Person.country);//PRC
}
}
1.2.2 static修饰成员方法
静态方法,语法格式:
public static 返回值 方法的名字 (参数列表) {}
调用静态方法:类.方法名字();
class Dog {
public void eat () {
System.out.println("普通的成员方法");
}
public static void sleep () {
System.out.println("睡吧不用看家了");
}
}
public class Demo3 {
public static void main(String[] args) {
Dog.sleep();
//Dog.eat();只能拿对象来调用这个方法eat
Demo3.test();
}
public static void test () {
System.out.println("嘻嘻");
}
}
1.2.3 static修饰代码块
语法格式: 静态代码块
static {
语句体
}
只要这个类加载,那么静态代码块一定会执行,
执行顺序: 静态代码块-》构造代码块-》构造方法
class Cat {
public Cat () {
System.out.println("无参构造方法");
}
{
System.out.println("构造代码块");
}
static {
System.out.println("静态的代码块");
}
}
public class Demo4 {
public static void main(String[] args) {
Cat cat = new Cat();
}
}
1.3 什么时候变量声明为实例的,什么时候声明为静态的?
如果这个类型的所有对象的某个属性值都是一样的,不建议定义为实例变量,浪费内存空间。 建议定义为类级别特征,定义为静态变量,在方法区中只保留一份,节省内存开销。
-
一个对象一份的是实例变量。
-
所有对象一份的是静态变量。
class Chinese{
// 身份证号
// 每一个人的身份证号不同,所以身份证号应该是实例变量,一个对象一份。
String idCard;
// 姓名
// 姓名也是一个人一个姓名,姓名也应该是实例变量。
String name;
String girlFriend;
String sex;
int age;
// 国籍
// 重点重点五颗星:加static的变量叫做静态变量
static String country = "中国";
static String minzu = "汉";
// 无参数
public Chinese(){
}
// 有参数
public Chinese(String s1,String s2){
idCard = s1;
name = s2;
}
}
注意:
-
静态变量在
类加载时初始化
,不需要new对象,静态变量的空间就开出来了。 -
静态变量存储在
方法区
。 -
静态代码块的案例:注意该代码不用练习,仅仅演示静态代码块的用法。
package cn.yunhe.util; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; public class MyBatisUtil { //获得SqlSession工厂 private static SqlSessionFactory factory; //创建ThreadLocal绑定当前线程中的SqlSession对象 private static final ThreadLocal<SqlSession> THREAD_LOCAL = new ThreadLocal<SqlSession>(); //静态代码块 static { try { //读取配置文件,数据库的配置信息 url driverClass username password InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); factory = new SqlSessionFactoryBuilder().build(is); } catch (Exception e) { e.printStackTrace(); } } //获得连接(从THREAD_LOCAL中获得当前线程SqlSession) private static SqlSession openSession(){ SqlSession session = THREAD_LOCAL.get(); if(session == null){ session = factory.openSession(); THREAD_LOCAL.set(session); } return session; } /** * 释放连接(释放当前线程中的SqlSession) */ public static void closeSession(){ SqlSession session = THREAD_LOCAL.get(); session.close(); THREAD_LOCAL.remove(); } /** * 提交事务(提交当前线程中的SqlSession所管理的事务) */ public static void commit(){ SqlSession session = openSession(); session.commit(); closeSession(); } /** * 回滚事务(回滚当前线程中的SqlSession所管理的事务) */ public static void rollback(){ SqlSession session = openSession(); session.rollback(); closeSession(); } /** * 获得接口实现类对象 * @param clazz 泛型对象 * @param <T> 泛型 * @return */ public static <T> T getMapper(Class<T> clazz){ SqlSession session = openSession(); return session.getMapper(clazz); } }
递归方法调用
1、基本说明
递归调用就是在当前的函数中调用当前的函数并传给相应的参数,这是一个动作,这一动作是层层进行的,直到满足一般情况的的时候,才停止递归调用,开始从最后一个递归调用返回。
简单的说:递归就是方法自己调用自己,每次调用是传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得整洁。
递归本质:程序调用自身的编程技巧叫做递归。AlphaGo 李世石, 自己和自己下棋
老顽童周伯通被困在桃花岛洞中的日子,相当无聊,对武功痴迷的他,竟然在无聊中悟出了一套绝世武功——左右互搏术。简单说,就是自己和自己打架玩,复杂点说,这是一套上等的绝妙武学,相当于将人一分为二,分心二用,左手和右手可以同时出不同的招数,相当于,一个人的战斗力可以成倍增加。
2、递归需要满足三个条件:
-
边界条件
-
递归前进段: 递推
-
递归返回段: 回归
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
3、递归能解决什么问题?
-
各种数学问题:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google编程大赛)
-
各种算法中也会使用到递归,比如快排,并归排序,二分查找,分治算法等。
-
调用栈解决的问题--->递归代码比较简洁
4、递归举例
4.1打印问题
package cn.yunhe.demo;
public class TestRecursion {
public static void main(String[] args) {
a a = new a();
a.test(4);
}
}
class a {
public void test(int n) {
if (n >2) {
test(n - 1);
}
System.out.println("n=" + n);
}
}
打印问题在JVM内存中的执行过程,栈的特点是先进后出,队列的特点:先进先出。 栈帧
4.2阶乘问题
public class TestRecursion {
public static void main(String[] args) {
int res = a.factorial(5);
System.out.println("res=" + res);
}
}
// 5! = 4!*5 4! = 4*3! 3! = 3 *2! 2 = 1!*2; 1! = 1*1
class a {
//阶乘问题
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n;
}
}
}
阶乘问题分析:
此题中,按照递归的三个条件来分析: (1)边界条件:阶乘,乘到最后一个数,即1的时候,返回1,程序执行到底; (2)递归前进段:当前的参数不等于1的时候,继续调用自身; (3)递归返回段:从最大的数开始乘,如果当前参数是5,那么就是54,即5(5-1),即n*(n-1)
5、递归方法进阶练习(求)
请使用递归的方式求出斐波那契数1,1,2,3,5,8,13....
给你一个整数n,求出他的值是多少
思路分析
-
当n = 1 斐波那契数是 1
-
当n = 2斐波那契数是 1
-
当n >= 3 斐波那契数是前两个数的和
-
这里就是一个递归的思路
代码实现
public class TestRecursion {
public static void main(String[] args) {
T t1 = new T();
System.out.println("当n=7 对应的斐波那契数=" + t1.fibonacci(7));
}
}
class T {
public int fibonacci(int n) {
if (n >= 1) {
if (n == 1 || n == 2) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
} else {
System.out.println("要求输入的n>=1的整数");
return -1;
}
}
}
控制台输出结果:当n=7时,对应的斐波那契数= 13;
6、递归重要规则
-
执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
-
方法的局部变量是独立的,不会相互影响,比如n变量
-
如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据。
-
递归必须向退出递归的条件逼近,否则就是无限递归(龟死了)
-
当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者放回时,该方法也就执行完毕。
矩阵交集方法
判断两个组件是否覆盖的方法。
package cn.yunhe.demo;
import javax.swing.*;
import java.awt.*;
public class AbsoluteLayoutTest {
public static void main(String[] args) {
JFrame jf = new JFrame("学籍管理系统");
jf.setSize(300,250);
jf.setLayout(null);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//放两个按钮
JButton addBtn = new JButton("添加");
JButton updBtn = new JButton("更新");
addBtn.setBounds(10,10,60,25);
updBtn.setBounds(50,10,60,25);//改成80再测试一下
jf.getContentPane().add(addBtn);
jf.getContentPane().add(updBtn);
//判断两个矩形是否有交集 intersects 交集
Rectangle addRect = addBtn.getBounds();
Rectangle updRect = updBtn.getBounds();
boolean b = addRect.intersects(updRect);
System.out.println("是否有交集:" + b);
jf.setVisible(true);
}
}
四、代码实现
-
Layer类增加自身类型的 parent 属性,存储上一层对象的引用
public class Layer {
private int offsetx;//偏移量 x轴
private int offsety;//偏移量 y轴
private int rowNum;//行
private int colNum;//列
private int capacity;//最大容量
private int size;//目前有多少牌,当牌添加和消失的时候改变
//中间大部分代码都省略......
private Layer parent;//上一层图层对象
//生成getter、setter方法
public Layer getParent() {
return parent;
}
public void setParent(Layer parent) {
this.parent = parent;
}
}
-
在MapUtil工具类增加建立链式结构的代码和比较的方法
package cn.yunhe.util;
import cn.yunhe.model.Brand;
import cn.yunhe.model.Cell;
import cn.yunhe.model.Layer;
import cn.yunhe.model.Map;
import java.awt.*;
public class MapUtil {
public static Map buildMap(int floorHeight){
Map map = new Map();
map.setFloorHeight(floorHeight);
Layer layer1 = LayerUtil.buildLayer(floorHeight,floorHeight);
Layer layer2 = LayerUtil.buildLayer(floorHeight,floorHeight);
Layer layer3 = LayerUtil.buildLayer(floorHeight,floorHeight);
//构建图层的链式关系
layer3.setParent(layer2);
layer2.setParent(layer1);
//parent==null说明当前这一层已经是顶层,
//这是循环或者递归结束的重要条件
layer1.setParent(null);
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
return map;
}
/**
* 函数的作用:判断当前牌和某一图层内所有牌是否有矩阵交集。
* true 有交集,当前牌显示灰色牌
* false 无交集,当前牌显示正常牌
*/
public static boolean compare(Brand brand,Layer layer){
Cell[][] cells = layer.getCells();
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
//如果当前的单元格是空的,不用比较
Cell cell = cells[row][col];
if (cell.getState() == 1) {
//单元格有牌,可以比较
Rectangle temp = cell.getBrand().getBounds();
Rectangle rect = brand.getBounds();
boolean result = rect.intersects(temp);
if (result) {
//有交集,说明上层的牌被盖住了,判定结束,所以使用return
return result;
}
}
}
}
//此时我们需要和更上层的牌进行比较判定,
//假如当前有9层,正在比较6层当中的某个牌,比较5层
if (layer.getParent() != null){
return compare(brand,layer.getParent());
} else {
//如果parent == null 说明已经到最顶层啦
return false;//显示正常牌
}
}
}
以上方法只是判定是否有交集,还需要在渲染Map的时候调用。
-
在Map类中增加compareAll方法,判断Map中的所有牌是否为灰色
package cn.yunhe.model;
import cn.yunhe.util.MapUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 一个地图有多个图层,层层之间遮盖
*/
public class Map {
//层高,有几张图层,游戏难度相关
private int floorHeight;
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;
}
/**
* 判断当前map中所有(每一张)牌,是否置灰
*/
public void compareAll(){
//i=0,最顶层Layer不需要判断
for (int i = 1; i < list.size(); i++) {
Layer layer = list.get(i);
Cell[][] cells = layer.getCells();
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Cell cell = cells[row][col];
if (cell.getState() == 1){ //有牌
Brand brand = cell.getBrand();
//注意是给上一层,i=1,layer.getParent就是上一层 i=0
boolean result = MapUtil.compare(brand,layer.getParent());
brand.setGray(result);//设置为灰色
}
}
}
}
}
}
-
修改Brand类事件处理的代码,如果是灰色单击事件不用响应
public Brand(String name){
this.name = name;
this.image = Toolkit.getDefaultToolkit().getImage("imgs//"+name + ".png");
this.grayImage = Toolkit.getDefaultToolkit().getImage("imgs//"+name+"_gray.png");
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Brand. mouseClicked 响应鼠标单击事件");
Brand brand = (Brand) e.getSource();//获取事件源
if (brand.getGray()) {//灰色 true
return;// return num; 只有return表示结束当前方法的执行
}else {
//通过父容器删除自己,一般树形结构使用这种方式
brand.getParent().remove(brand);
}
}
});
}
-
在TestRenderMap类中调用compareAll方法
public TestRenderMap() {
//1.初始化基本信息
init();
//2.渲染图层:添加组件的方法,可以添加自己定义的组件都当前的窗口中
List<Layer> list = map.getList();
for (Layer layer : list) {
renderLayer(layer);
}
map.compareAll();//游戏开始时调用
//3.自动刷新
autoRefresh();
}
-
测试执行效果
被遮盖的牌显示未灰色,点击也没有响应。
但是点击完正常颜色的牌后,灰色的牌并没有变成彩色的。
-
说明:在删除一张牌的时候,需要调用Map的比较方法,重新计算是否遮盖显示灰色牌,问题是Brand类中没有Map对象,如何解决?也就是在删除牌的时候我们需要调用一次map的comapareAll方法,这时需要把map对象声明为共有全局对象。代码如下:
public class TestRenderMap extends JFrame {
public static final Map map = MapUtil.buildMap(3);//把private改为public
//以下代码略
-
在Brand类中调用map的comapareAll方法,修改Brand类的事件监听方法。
public Brand(String name){
//代码略...
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Brand. mouseClicked 响应鼠标单击事件");
Brand brand = (Brand) e.getSource();//获取事件源
if (brand.getGray()) {//灰色
return;
}else {
//通过父容器删除自己,一般树形结构使用这种方式
brand.getParent().remove(brand);
//调用比较的方法
//TestRenderMap类,map是公有静态属性,compareAll方法
TestRenderMap.map.compareAll();
//System.out.println()
}
}
});
}
运行测试,发现漏出的灰色图片还是不能点击,还是灰色的,如下图所示:
-
如何解决以上问题?通过
brand.getParent().remove(brand);
只是在UI树中删除了brand对象,但是在Cell当中的状态state和brand并没有删除。界面删除了,但是在我们构建的数据结构中没有删除,内存中的引用没有删除。如何修改这个Bug? -
既要删除UI树中的组件,还要删除数据模型中的数据和对应的状态,这是两套体系。如何删除呢?我们需要把cell对象的内容做个修改:
cell.setState(0);//置为无牌状态
cell.setBrand(null);//引用对象为null
但是Brand类中没有单元格对象,我们需要在Brand类中增加单元格对象的属性,也就是Brand对象要知道自己属于哪个单元格。单元格包括那张牌,他们两个应该互通的。
-
修改Brand类,增加Cell属性,并生成getter、setter方法
public class Brand extends JComponent{
//代码略....
//宽高
private int width = 50;
private int height = 50;
private Cell cell;
//代码略....
public Cell getCell() {
return cell;
}
public void setCell(Cell cell) {
this.cell = cell;
}
}
问题:Brand对象如何获取Cell对象? 先思考Cell对象什么时候绑定的Brand对象?
-
修改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);
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++) {
Brand brand = brands[flag++];//不是创建新对象,
Cell cell = new Cell();
cell.setState(1);
cell.setBrand(brand);//把牌放到单元格
cells[row][col] = cell;
brand.setCell(cell);//把单元格赋值给牌
}
}
return layer;
}
}
-
再修改Brand类对象,在删除牌的时候,把单元格的数据修改一下
public Brand(String name){
this.name = name;
this.image = Toolkit.getDefaultToolkit().getImage("imgs//"+name + ".png");
this.grayImage = Toolkit.getDefaultToolkit().getImage("imgs//"+name+"_gray.png");
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Brand. mouseClicked 响应鼠标单击事件");
Brand brand = (Brand) e.getSource();//获取事件源
if (brand.getGray()) {//灰色
return;
}else {
//通过父容器删除自己,一般树形结构使用这种方式
brand.getParent().remove(brand);
cell.setState(0);//无牌状态
cell.setBrand(null);//无牌引用
//调用比较的方法
TestRenderMap.map.compareAll();
}
}
});
}
再次运行测试类,可以看到第2层、第3层可以正常显示了。
-
至此完成遮盖算法的全部实现。
五、本章总结
static 关键字
递归方法调用
矩阵交集方法
六、本章作业
-
static 关键字的案例
-
递归方法调用的案例
-
矩阵交集的案例
-
- 点赞
- 收藏
- 关注作者
评论(0)