2小时开发《点球射门游戏》,动画演示思路(下),代码已开源

news2024/11/21 0:25:50

前沿

首选感谢各位对我这边文章(2小时开发《点球射门游戏》,动画演示思路(上),代码已开源)的点赞、收藏与支持,今天在这里主要是接上一篇文章,讲一讲游戏界面中的一些动画与逻辑的实现,希望大家一如既往的点赞、收藏+关注,鼓励一下勇哥。对于游戏怎么怎么画,请看上那篇文章。

1、请各位朋友收藏、点赞鼓励一波!

2、源码,在评论区扣6,我私信发给你!如果你着急也可以私信我。

本篇内容有那些值得大家学习【重点】

  • 使用二次曲线实现球的瞄准轨迹线【见下】。这个在QQ桌球、王者荣耀、愤怒的小鸟等游戏中都用到了!

  • 使用多线程实现守门员移动、飞球、蓄力区、时间轴等动画。

 

有那些逻辑需要实现【必看】

如下图,整个游戏的实现逻辑,按照对象来分,则为以下:

球对象逻辑:

  • 拖动鼠标:调整球射出的轨迹逻辑,支持上下左右的拖动调整★★★

  • Ctrl+拖动鼠标:摆放球逻辑

  • 点击球:球按照轨迹飞出运动的逻辑★★★

星星对象逻辑:

  • 球在飞行轨迹与星星重合则消除星星的逻辑★★★

守门员对象逻辑:

  • 在球门区左右来回移动的逻辑

石头对象逻辑:

  • 禁止遮挡部分球门,球不能从此射进的逻辑

球门对象逻辑:

  • 进球逻辑★★★

积分区对象逻辑:

  • 记时逻辑

  • 进球积分逻辑

游戏逻辑实现思路&代码

★★★调整射门轨迹的逻辑实现

轨迹实现的思路看起来难,实际还是挺难的,思路如下:

  • 假设有两点,黄色点为足球的中心点,红色点是球门的中心点

  • 在黄点和红点之间就存在一条红色线段

  • 在红色线段上随机取N个点,用白色表示,这样就形成了一个直线的轨迹点

接着再说一下拖动鼠标,轨迹跟着鼠标移动的实现思路:

  • 鼠标向上拖动,黄点和红点同步向上平移,这样线段上的轨迹点也同步平移

  • 鼠标向下拖动,黄点和红点同步向下平移,这样线段上的轨迹点也同步平移

  • 鼠标向右拖动,黄点和红点同步向右平移,这样线段上的轨迹点也同步平移

  • 鼠标向左拖动,黄点和红点同步向左平移,这样线段上的轨迹点也同步平移

最后注意,黄点和红点之间如果是曲线,效果更贴近自然,所以最后还需要把黄点和红点之间使用二次曲线进行实现。

轨迹线,参考实现代码:

// 记录红黄点 
public void reDraw(Ball ball,BackgroundPanel backgroundPanel,int stepX,int stepY,boolean isControlDown){
     // 开始点——黄点
     startY = ball.getY()+ball.getHeight()/2;
     startX = ball.getX()+ball.getWidth()/2;
     // 结束点——红点
     endX = getWidth()/2+stepX/3;
     endY = 0;
 }
// 画出轨迹线
public void paintComponent(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.RED);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    // 二次曲线
    QuadCurve2D quadCurve2D = new QuadCurve2D.Double(startX,startY, startX+stepX,endY+stepY+50, endX, endY+stepY);
    // 通过二次曲线,随机生成线上的几个点
    PathIterator pi = quadCurve2D.getPathIterator(g2d.getTransform(),6);// 从二次曲线中取出特征点
    points = new ArrayList<>(25);
    while (!pi.isDone()) {
        double[] coords = new double[6];
        switch (pi.currentSegment(coords)) {
            case PathIterator.SEG_MOVETO:
            case PathIterator.SEG_LINETO:
                points.add(new Point2D.Double(coords[0], coords[1]));
                break;
        }
        pi.next();
    }
    // 在每个点 画上小白圆圈
    g2d.setColor(Color.WHITE);
    Point2D.Double point = null;
    for (Point2D.Double temp : points) {
        point = temp;
        g2d.fillOval((int) point.x, (int) point.y,10,10);//在每个特征点上画一个小圆圈
    }
}

鼠标拖动参考实现代码:

ball.addMouseMotionListener(new MouseAdapter() {
    public void mouseDragged(MouseEvent e){
        // 记录拖动最后的坐标点,用于记录拖动平移的差量
        int stepX = e.getX();
        int stepY = e.getY();
        line.reDraw(ball,BackgroundPanel.this,stepX,stepY,e.isControlDown());
        repaint();
    }
});

拖动球,摆放球的逻辑实现

拖动球,可以摆放球的位置,相关实现思路是:

  • 拖动开始时(按下鼠标时)设置一个其实点,黄点

  • 拖动过程中(按下鼠标,并同时移动位置)换点跟随鼠标点

  • 拖动结束时(松开鼠标)球平移到最后的位置

参考实现代码:

 public void reDraw(Ball ball,BackgroundPanel backgroundPanel,int stepX,int stepY,boolean isControlDown){
    // 移动最后的位置点
    this.stepX = stepX;
    this.stepY = stepY;
    // 按下Ctrl键拖动才是,摆放
    if(isControlDown) {
        this.setVisible(false);
        ball.setBounds(ball.getX() + stepX, ball.getY(), ball.getWidth(), ball.getHeight());
    }
}

★★★射门,球按照轨迹飞行实现

鼠标点击球,球就按照之前的轨迹点飞行,实现的思路如下:

  • 获取到从二次曲线上的随机轨迹点,如下图中的白色小圆

  • 点击球是,开启一个线程

  • 在线程中,把球平移到轨迹点的第一个点,并休眠100毫秒

  • 100毫秒后,又把球平移到轨迹点的下一个点,再次休眠100毫秒

  • ....重复上一步动作,知道球平移到最后一个轨迹点

参考实现代码:

// 监听点击球
ball.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        // 用一个线程让球按照瞄准轨迹飞行
        new Thread(new Runnable() {
            @Override
            public void run() {
                int star =0;
                // 获取到轨迹线上的轨迹点
                for (Point2D.Double point : line.getPoints()) {
                    // 把球移动到轨迹点上
                    ball.setBounds((int) point.x, (int) point.y,ball.getWidth(),ball.getHeight());
                    try {
                        // 休眠100毫秒
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }).start();
    }
});

★★★消除轨迹点重合的星星逻辑实现

消除星星,实际就是判断球的坐标点是否与星星重合,如果是则消除,具体实现思路如下:

  • 星星当作是一个正方形,有4个点

  • 球也当作是一个正方形,有4个点

  • 球在飞行过程中,每移动到一个轨迹点时,检查星星和球的四个点是否有重合的区域

  • 如果有重合的区域则,消除星星,从游戏界面中删除星星

参考实现代码:

ball.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int star =0;
                for (Point2D.Double point : line.getPoints()) {
                    ball.setBounds((int) point.x, (int) point.y,ball.getWidth(),ball.getHeight());
                    try {
                        // 球在飞行过程中,没移动一个点,需要检查一下是否有星星需要消除
                        star += obstacleStart();
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                check(star);
            }
        }).start();
        CLICK_CLIP.play();
    }
});
public int obstacleStart(){
    Rectangle ballBounds = ball.getBounds();
    int tempX[] = {ballBounds.x,ballBounds.x+ballBounds.width,ballBounds.x,ballBounds.x+ballBounds.width};
    int tempY[] = {ballBounds.y,ballBounds.y,ballBounds.y+ballBounds.height,ballBounds.y+ballBounds.height};
    int count = 0;
    // 获取所有的星星,进行循环检查
    for (Component component : this.getComponents()) {
        if(component instanceof Star){
            Star obstacle = (Star)component;
            Rectangle goalkeeperBounds = obstacle.getBounds();
            int minX = goalkeeperBounds.x;
            int maxX = goalkeeperBounds.x+goalkeeperBounds.width;
            int minY = goalkeeperBounds.y;
            int maxY = goalkeeperBounds.y+goalkeeperBounds.height;
            miukoo:for (int i = 0; i < tempY.length; i++) {
                // 如何球的4个点,在星星的区域内,则命中
                if(tempX[i]>minX&&tempX[i]<maxX&&tempY[i]>minY&&tempY[i]<maxY){
                    System.out.println("================命中星星");
                    count++;
                    STAR_CLIP.play();
                    this.remove(obstacle);// 消除星星,自己删除即可
                    break miukoo;
                }
            }
        }
    }
    return count;
}

守门员来回移动的逻辑实现

守门员在球门前,左右移动,干扰射球的飞行过程,实现思路:

  • 开启一个线程

  • 向右平移守门员位置+30像素,并判断是否超出了最右边球门边缘,如果是则设置向左移动,然后休眠100毫秒

  • 向左平移守门员位置-30像素,并判断是否超出了最左边球门边缘,如果是则设置向右移动,然后休眠100毫秒

public void move() {
    try {
        Rectangle bounds = this.getBounds();
        double width = bounds.getX();
        if (isAdd) {// 向右
            width += 30;
            if (width < backgroundPanel.getWidth() * 4 / 5) {
                backgroundPanel.repaint();
                this.setBounds((int) width, bounds.y, bounds.width, bounds.height);
                Thread.sleep(100);
            } else {
                isAdd = false;
            }
        } else {// 向左
            width -= 30;
            if (width > backgroundPanel.getWidth() * 1 / 5) {
                backgroundPanel.repaint();
                this.setBounds((int) width, bounds.y, bounds.width, bounds.height);
                Thread.sleep(100);
            } else {
                isAdd = true;
            }
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

@Override
public void run() {
    while (true&&!Thread.interrupted()){
        move();
    }
}
 

石头的逻辑实现

石头就是一张图片,把其摆放到对应位置即可。其遮挡射门的逻辑,主要在球门逻辑中去判断。

参考代码:

public class Shitou extends JLabel implements Obstacle {
    BackgroundPanel backgroundPanel;
    public Shitou(BackgroundPanel backgroundPanel){
        this.backgroundPanel = backgroundPanel;
        this.setBounds(backgroundPanel.getWidth()/2+50,100,316,100);//设置图片放置的位置
        this.setPreferredSize(new Dimension(316,100));
        this.setIcon(new ImageIcon(ResourcesUtil.getRootPath()+"\\ball\\st.png"));
    }

    @Override
    public String name() {
        return "石头";
    }

    @Override
    public JComponent getComponent() {
        return this;
    }

    public void start(){
    }

    public void stop(){
    }

}

★★★进球逻辑实现

进球逻辑看起难,实际还是一个对象边框重合检查的过程,其实现思路如下:

  • 球门、守门员、石头、球都有自己的边界,都是平行四边形

  • 当前射出的球移动到轨迹最后一个点时,开始判断以上元素的边界是否重合,依此来判断是否进球

  • 进球依据:球的四个点都在球门四个点内部

  • 守住依据:球与守门员、石头有任意一个点的重合即为守住

  • 出界依据:球任意一个点不在球门范围内,则为球出界

参考代码:

ball.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        infoPanel.addCount();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int star =0;
                for (Point2D.Double point : line.getPoints()) {
                    ball.setBounds((int) point.x, (int) point.y,ball.getWidth(),ball.getHeight());
                    try {
                        star += obstacleStart();
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                // 轨迹点移动完成后,开始检查球是否进球
                check(star);
            }
        }).start();
    }
});
public void check(int star){
    //停止所有的线程,以便获取守门员最后的位置,与球判重
    stop();
    Rectangle ballBounds = ball.getBounds();
    // 检查是否被石头还有守门员
    boolean isLan = obstacle();
    int x = ballBounds.x;
    int y = ballBounds.y;
    boolean isOut = false;
    if(!isLan) {
        // 判断是否出界
        if (x < getWidth() / 5 || x > getWidth() * 4 / 5 || y < 85) {
            isOut = true;
            FAIL_CLIP.play();
            repaint();
            JOptionPane.showMessageDialog(null, "球出界了...", "Tipe", JOptionPane.ERROR_MESSAGE);
        }
    }
    if(!isLan&&!isOut){
        // 没有被守住,也没有出界,则进球啦~~~
        if(result!=null){
            this.remove(result);
        }
        infoPanel.addScore(star);
        System.out.println("===========赢了,开始显示祝贺彩带和播放音乐");
        result = new Result();
        result.setPreferredSize(new Dimension(230,187));
        result.setBounds((getWidth()-230)/2,(getHeight()-187)/2,230,187);
        this.add(result);
        this.repaint();
        WIN_CLIP.play();
        // 进球后,休眠5秒,然后自动复位球
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        WIN_CLIP.stop();
        this.remove(result);
    }else{
        FAIL_CLIP.stop();
    }
    line.setVisible(false);
    ball.setBounds((getWidth()-64)/2,470,64,64);
    // 重新开启线程,让守门员再次动起来
    start();
}
public boolean obstacle(){
    Rectangle ballBounds = ball.getBounds();
    int tempX[] = {ballBounds.x,ballBounds.x+ballBounds.width,ballBounds.x,ballBounds.x+ballBounds.width};
    int tempY[] = {ballBounds.y,ballBounds.y,ballBounds.y+ballBounds.height,ballBounds.y+ballBounds.height};
    for (Component component : this.getComponents()) {
        if(component instanceof Obstacle){// 守门员和石头都抽象成障碍物,判断球是否与障碍物重合
            Obstacle obstacle = (Obstacle)component;
            Rectangle goalkeeperBounds = obstacle.getComponent().getBounds();
            int minX = goalkeeperBounds.x;
            int maxX = goalkeeperBounds.x+goalkeeperBounds.width;
            int minY = goalkeeperBounds.y;
            int maxY = goalkeeperBounds.y+goalkeeperBounds.height;
            boolean isLan = false;
            for (int i = 0; i < tempY.length; i++) {
                if(tempX[i]>minX&&tempX[i]<maxX&&tempY[i]>minY&&tempY[i]<maxY){
                    isLan = true;
                    FAIL_CLIP.play();
                    repaint();
                    JOptionPane.showMessageDialog(null,"球被"+obstacle.name()+"守住了...","Tipe",JOptionPane.ERROR_MESSAGE);
                    return true;
                }
            }
        }
    }
    return false;
}

记时逻辑实现

记时实现的思路如下:

  • 进入游戏时初始一个数字变量

  • 开启一个线程,把数字变量增加1,然后休眠1秒

  • 循环增加1、循环休眠1秒

参考代码:

long time = 0;

@Override
public void run() {
    while (true){
        try {
            Thread.sleep(1000);
            time++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        repaint();// 刷新线程时间
        // 显示时间时把time转成对应格式,g2d.drawString(String.format("%02d:%02d",time/60,time%60),345,47);
    }
}

进球积分逻辑实现

在本游戏中积分的规则有以下两点:

  • 进球得1分

  • 进球的同时,消除一颗星星得1分

举个例子,如下图,射门进球同时消除了一颗星星,则得2分。

相关实现逻辑思路如下:

  • 在球飞行过程中进来消除星星的数量

  • 在进球时,把星星的数量当作分数累计

参考代码:

ball.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        infoPanel.addCount();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 记录飞行过程中消除星星的数量
                int star =0;
                for (Point2D.Double point : line.getPoints()) {
                    ball.setBounds((int) point.x, (int) point.y,ball.getWidth(),ball.getHeight());
                    try {
                        // 计算星星是否被消除,如果是则累计
                        star += obstacleStart();
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                // 把消除星星作为分数,传递给计分区
                check(star);
            }
        }).start();
        CLICK_CLIP.play();
    }
});
// 检查是否进球
public void check(int star){
    ........
    if(!isLan&&!isOut){// 进球了
        if(result!=null){
            this.remove(result);
        }
        // 增加计分区的数字,有多少星星则记多少
        infoPanel.addScore(star);
        ........
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71738.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot + Elasticsearch 实现模糊查询,批量CRUD,排序,分页,高亮!

一、引入依赖 当前Elasticsearch服务端的版本为8.5.1,此处Spring Data Elasticsearch的版本为2.6.1 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version&…

Java中不能在foreach中进行元素的remove和add操作

参考文献&#xff1a;https://juejin.im/post/6844903794795347981 在阿里巴巴Java开发手册中&#xff0c;有这样一条规定&#xff1a; 但是手册中并没有给出具体原因&#xff0c;本文就来深入分析一下该规定背后的思考。 foreach循环 以下实例演示了 普通for循环 和 foreach…

安卓APP源码和设计报告——健身系统

一、设计背景 1.需求分析 对于很多人来说拥有一副好身材能让自己增添不少魅力;对于爱吃而又担心自己发胖的人来说适当的运动健身是最好的选择。移动互联网时代&#xff0c;市场上“约跑”“约健身”健身APP软件成为新时代闺蜜朋友的互动模式&#xff0c;健身热潮的来临&#…

客快物流大数据项目(九十三):ClickHouse的ReplacingMergeTree深入了解

文章目录 ClickHouse的ReplacingMergeTree深入了解 一、创建ReplacingMergeTree表的说明 二、创建ReplacingMergeTree引擎的表

主成分分析 (PCA) 和独立成分分析 (ICA)附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步进步&#xff0c;matlab项目目标合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信息&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算…

某乎x-zse-96

看到有读者咨询知乎x-zse-96,简单做一下分析和记录。 版本:“x-api-version”:“3.0.91”,“x-zse-93”:“101_3_3.0” 随便找了一个搜视频接口 /api/v4/search_v3 经测试发现,目前请求必带的参数有headers 中的x-zse-96、x-zse-93、x-api-version 和 cookie中的d_c0。 …

聊一聊责任链模式

将一堆“事情”串联在一起&#xff0c;有序执行&#xff0c;就叫责任链 一、概述 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是将链中每一个节点看作是一个对象&#xff0c;每个节点处理的请求均不同&#xff0c;且内部自动维护一个下一节点对象。当…

【数据结构】图算法和LRUCache

文章目录最小生成树1.最小生成树概念2.Kruskal算法3.Prim算法最短路径算法单源最短路径--Dijkstra算法单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法LRUCacheLRUCache实现源码地址最小生成树 1.最小生成树概念 连通图中的每一棵生成树&#xff0c;都是原图…

软硬协同:基于倚天的视频云编码性能升级

算力时代&#xff0c;靠吃「硬件红利」便能搞定新应用场景的「甜蜜期」已经过去。人类社会的每一次科技跃迁&#xff0c;其本质都是计算力的突破与演进。 算盘拨出农耕文明的繁荣&#xff0c;机械计算机催生出第一次工业革命的袅袅蒸汽&#xff0c;而云计算的发展让万物互联成…

Spark零基础入门实战(二)Scala基础之数据类型

在Scala中,所有的值都有一个类型,包括数值和函数。如图1-4所示,说明了Scala的类型层次结构。 Any是Scala类层次结构的根,也被称为超类或顶级类。Scala执行环境中的每个类都直接或间接地从该类继承。该类中定义了一些通用的方法,例如equals()、hashCode()和toString()。…

【畅购商城】微信支付之支付模块

目录 支付页面 接口 后端实现 ​​​​​​​前端实现​​​​​​​ ​​​​​​​支付页面 步骤一&#xff1a;创建 flow3.vue组件 步骤二&#xff1a;引入第三方资源&#xff08;js、css&#xff09; <script> import TopNav from ../components/TopNav impor…

[附源码]Python计算机毕业设计Django疫情物资管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

电动汽车 (BEV) 优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

Vue3中的pinia使用(收藏版)

1.pinia介绍 pinia 是 Vue 的存储库&#xff0c;它允许您跨组件/页面共享状态。就是和vuex一样的实现数据共享。 依据Pinia官方文档&#xff0c;Pinia是2019年由vue.js官方成员重新设计的新一代状态管理器&#xff0c;更替Vuex4成为Vuex5。 Pinia 目前也已经是 vue 官方正式的状…

原创 | 传统医药零售如何实现数字化转型

一、数字化转型是零售药店未来实现增长的必由之路“十四五”规划将清洁能源、军事、消费、医药以及高端制造等行业定为未来需要重点关注的领域。围绕“医保、医疗、医药”三条主线&#xff0c;医药行业推出了一系列重要的改革措施&#xff0c;即将进入有史以来最大变革的黄金时…

【Mysql 基础知识】

一、引言 #1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。 文件&#xff08;File&#xff09;存储数据&#xff0c;保存在硬盘上&#xff0c;属于持久…

Talk | 香港大学在读博士生马逴凡:重新思考分辨率对于高效视频理解的意义

本期为TechBeat人工智能社区第460期线上Talk&#xff01; 北京时间12月7日(周三)20:00&#xff0c;香港大电子电机在读博士生——马逴凡的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “重新思考分辨率对于高效视频理解的意义 ”&#xff0c;届时…

web课程设计:HTML非遗文化网页设计题材【京剧文化】HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

12.8 - 每日一题 - 408

每日一句&#xff1a;成功与不成功之间有时距离很短只要后者再向前几步。 数据结构 1 若用冒泡排序方法对序列&#xff08;12&#xff0c;24&#xff0c;36&#xff0c;48&#xff0c;60&#xff0c;72&#xff09;从大到小排序&#xff0c;需要的比较次数是_____ A. 5 B. 1…

Tableau制作三元相图/三角图(Ternary/Triangle Chart)

三元相图 三角图就是有三个轴的坐标图&#xff0c;可以分别表示一个点在X、Y、Z三个维度上的不同比例情况。 与传统的XY坐标轴不同。XY坐标轴是有两个维度&#xff0c;而且每个维度没有上下限的限制。三角图中每个维度都在0-1的范围内&#xff0c;按百分比划分&#xff0c;即…