安卓小游戏:小板弹球

news2025/6/9 8:57:23

安卓小游戏:小板弹球

前言

这个是通过自定义View实现小游戏的第三篇,是小时候玩的那种五块钱的游戏机上的,和俄罗斯方块很像,小时候觉得很有意思,就模仿了一下。

需求

这里的逻辑就是板能把球弹起来,球在碰撞的时候能把顶部的目标打掉,当板没有挡住球,掉到了屏幕下面,游戏就结束了。核心思想如下:

  • 1,载入配置,读取游戏信息、配置及掩图
  • 2,启动游戏控制逻辑,球体碰到东西有反弹效果
  • 3,手势控制板的左右移动

效果图

效果图已经把游戏的逻辑玩出来了,大致就是这么个玩法,就是我感觉这不像一个游戏,因为小球的初始方向就决定了游戏结果,也许我应该把板的速度和球的方向结合起来,创造不一样。

ball

代码

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.silencefly96.module_views.R
import java.lang.ref.WeakReference
import kotlin.math.*

/**
 * 弹球游戏view
 *
 * 1,载入配置,读取游戏信息、配置及掩图
 * 2,启动游戏控制逻辑,球体碰到东西有反弹效果
 * 3,手势控制板的左右移动
 *
 * @author silence
 * @date 2023-02-08
 */
class BombBallGameView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {
    companion object {
        // 游戏更新间隔,一秒20次
        const val GAME_FLUSH_TIME = 50L
        // 目标移动距离
        const val TARGET_MOVE_DISTANCE = 20

        // 距离计算公式
        fun getDistance(x1: Int, y1: Int, x2: Int, y2: Int): Float {
            return sqrt(((x1 - x2).toDouble().pow(2.0)
                    + (y1 - y2).toDouble().pow(2.0)).toFloat())
        }

        // 两点连线角度计算, (x1, y1) 为起点
        fun getDegree(x1: Float, y1: Float, x2: Float, y2: Float): Double {
            // 弧度
            val radians = atan2(y1 - y2, x1 - x2).toDouble()
            // 从弧度转换成角度
            return Math.toDegrees(radians)
        }
    }

    // 板的长度
    private val mLength: Int

    // 行的数量、间距
    private val rowNumb: Int
    private var rowDelta = 0

    // 列的数量、间距
    private val colNumb: Int
    private var colDelta = 0

    // 球的掩图
    private val mBallMask: Bitmap?

    // 目标的掩图
    private val mTargetMask: Bitmap?

    // 目标的原始配置
    private val mTargetConfigList = ArrayList<Sprite>()

    // 目标的集合
    private val mTargetList = ArrayList<Sprite>()

    // 球
    private val mBall = Sprite(0, 0, 0f)

    // 板
    private val mBoard = Sprite(0, 0, 0f)

    // 游戏控制器
    private val mGameController = GameController(this)

    // 上一个触摸点X的坐标
    private var mLastX = 0f

    // 画笔
    private val mPaint = Paint().apply {
        color = Color.WHITE
        strokeWidth = 10f
        style = Paint.Style.STROKE
        flags = Paint.ANTI_ALIAS_FLAG
        textAlign = Paint.Align.CENTER
        textSize = 30f
    }

    init {
        // 读取配置
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BombBallGameView)
        mLength = typedArray.getInteger(R.styleable.BombBallGameView_length, 300)
        rowNumb = typedArray.getInteger(R.styleable.BombBallGameView_row, 30)
        colNumb = typedArray.getInteger(R.styleable.BombBallGameView_col, 20)

        // 球的掩图
        var drawable = typedArray.getDrawable(R.styleable.BombBallGameView_ballMask)
        mBallMask = if (drawable != null) drawableToBitmap(drawable) else null
        // 目标的掩图
        drawable = typedArray.getDrawable(R.styleable.BombBallGameView_targetMask)
        mTargetMask = if (drawable != null) drawableToBitmap(drawable) else null

        // 读取目标的布局配置
        val configId = typedArray.getResourceId(R.styleable.BombBallGameView_targetConfig, -1)
        if (configId != -1) {
            getTargetConfig(configId)
        }
        typedArray.recycle()
    }

    private fun drawableToBitmap(drawable: Drawable): Bitmap? {
        val w = drawable.intrinsicWidth
        val h = drawable.intrinsicHeight
        val config = Bitmap.Config.ARGB_8888
        val bitmap = Bitmap.createBitmap(w, h, config)
        //注意,下面三行代码要用到,否则在View或者SurfaceView里的canvas.drawBitmap会看不到图
        val canvas = Canvas(bitmap)
        drawable.setBounds(0, 0, w, h)
        drawable.draw(canvas)
        return bitmap
    }

    private fun getTargetConfig(configId: Int) {
        val array = resources.getStringArray(configId)
        try {
            for (str in array) {
                // 取出坐标
                val pos = str.substring(1, str.length - 1).split(",")
                val x = pos[0].trim().toInt()
                val y = pos[1].trim().toInt()
                mTargetConfigList.add(Sprite(x, y, 0f))
            }
        }catch (e : Exception) {
            e.printStackTrace()
        }
        // 填入游戏的list
        mTargetList.clear()
        mTargetList.addAll(mTargetConfigList)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 开始游戏
        load()
    }

    // 加载
    private fun load() {
        mGameController.removeMessages(0)
        // 设置网格
        rowDelta = height / rowNumb
        colDelta = width / colNumb
        // 设置球,随机朝下的方向
        mBall.posX = width / 2
        mBall.posY = height / 2
        mBall.degree = (Math.random() * 180 + 180).toFloat()
        // 设置板
        mBoard.posX = width / 2
        mBoard.posY = height - 50
        // 将目标集合中的坐标改为实际坐标
        for (target in mTargetList) {
            val exactX = target.posY * colDelta + colDelta / 2
            val exactY = target.posX * rowDelta + rowDelta / 2
            target.posX = exactX
            target.posY = exactY
        }
        mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
    }

    // 重新加载
    private fun reload() {
        mGameController.removeMessages(0)
        // 重置
        mTargetList.clear()
        mTargetList.addAll(mTargetConfigList)
        mGameController.isGameOver = false
        // 设置球,随机朝下的方向,注意:因为Y轴朝下应该是180度以内
        mBall.posX = width / 2
        mBall.posY = height / 2
        mBall.degree = (Math.random() * 180 + 180).toFloat()
        // 设置板
        mBoard.posX = width / 2
        mBoard.posY = height - 50
        // 由于mTargetConfigList内对象被load修改了,清空并不影响对象,不需要再转换了
        mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 绘制网格
        mPaint.strokeWidth = 1f
        for (i in 0..rowNumb) {
            canvas.drawLine(0f, rowDelta * i.toFloat(),
                width.toFloat(), rowDelta * i.toFloat(), mPaint)
        }
        for (i in 0..colNumb) {
            canvas.drawLine(colDelta * i.toFloat(), 0f,
                colDelta * i.toFloat(), height.toFloat(), mPaint)
        }
        mPaint.strokeWidth = 10f

        // 绘制板
        canvas.drawLine(mBoard.posX - mLength / 2f, mBoard.posY.toFloat(),
            mBoard.posX + mLength / 2f, mBoard.posY.toFloat(), mPaint)

        // 绘制球
        canvas.drawBitmap(mBallMask!!, mBall.posX - mBallMask.width / 2f,
            mBall.posY - mBallMask.height / 2f, mPaint)

        // 绘制目标物
        for (target in mTargetList) {
            canvas.drawBitmap(mTargetMask!!, target.posX - mTargetMask.width / 2f,
                target.posY - mTargetMask.height / 2f, mPaint)
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.action) {
            MotionEvent.ACTION_DOWN -> {
                mLastX = event.x
            }
            MotionEvent.ACTION_MOVE -> {
                val len = event.x - mLastX
                val preX = mBoard.posX + len
                if (preX > mLength / 2 && preX < (width - mLength / 2)) {
                    mBoard.posX += len.toInt()
                    invalidate()
                }
                mLastX = event.x
            }
            MotionEvent.ACTION_UP -> {}
        }
        return true
    }

    private fun gameOver() {
        AlertDialog.Builder(context)
            .setTitle("继续游戏")
            .setMessage("请点击确认继续游戏")
            .setPositiveButton("确认") { _, _ -> reload() }
            .setNegativeButton("取消", null)
            .create()
            .show()
    }

    // kotlin自动编译为Java静态类,控件引用使用弱引用
    class GameController(view: BombBallGameView): Handler(Looper.getMainLooper()){
        // 控件引用
        private val mRef: WeakReference<BombBallGameView> = WeakReference(view)
        // 游戏结束标志
        internal var isGameOver = false

        override fun handleMessage(msg: Message) {
            mRef.get()?.let { gameView ->
                // 移动球
                val radian = Math.toRadians(gameView.mBall.degree.toDouble())
                val deltaX = (TARGET_MOVE_DISTANCE * cos(radian)).toInt()
                val deltaY = (TARGET_MOVE_DISTANCE * sin(radian)).toInt()
                gameView.mBall.posX += deltaX
                gameView.mBall.posY += deltaY
                // 检查反弹碰撞
                checkRebound(gameView)

                // 球和目标的碰撞
                val iterator = gameView.mTargetList.iterator()
                while (iterator.hasNext()) {
                    val target = iterator.next()
                    if (checkCollision(gameView.mBall, target,
                            gameView.mBallMask!!, gameView.mTargetMask!!)) {
                        // 与目标碰撞,移除该目标并修改球的方向
                        iterator.remove()
                        collide(gameView.mBall, target)
                        break
                    }
                }

                // 循环发送消息,刷新页面
                gameView.invalidate()
                if (!isGameOver) {
                    gameView.mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
                }else {
                    gameView.gameOver()
                }
            }
        }

        // 检测碰撞
        private fun checkCollision(s1: Sprite, s2: Sprite, mask1: Bitmap, mask2: Bitmap): Boolean {
            // 选较长边的一半作为碰撞半径
            val len1 = if(mask1.width > mask1.height) mask1.width / 2f else mask1.height / 2f
            val len2 = if(mask2.width > mask2.height) mask2.width / 2f else mask2.height / 2f
            return getDistance(s1.posX, s1.posY, s2.posX, s2.posY) <= (len1 + len2)
        }

        // 击中目标时获取反弹角度,角度以两球圆心连线对称并加180度
        private fun collide(ball: Sprite, target: Sprite) {
            // 圆心连线角度,注意向量方向,球的方向向上,连线以球为起点
            val lineDegree = getDegree(ball.posX.toFloat(), ball.posY.toFloat(),
                target.posX.toFloat(), target.posY.toFloat())
            val deltaDegree = abs(lineDegree - ball.degree)
            ball.degree += if(lineDegree > ball.degree) {
                2 * deltaDegree.toFloat() + 180
            }else {
                -2 * deltaDegree.toFloat() + 180
            }
        }

        // 击中边缘或者板时反弹角度,反射角度和法线对称,方向相反
        private fun checkRebound(gameView: BombBallGameView) {
            val ball = gameView.mBall
            val board = gameView.mBoard
            // 左边边缘,法线取同向的180度
            if (ball.posX <= 0) {
                val deltaDegree = abs(180 - ball.degree)
                ball.degree += if (ball.degree < 180)  {
                    2 * deltaDegree - 180
                }else {
                    -2 * deltaDegree - 180
                }
            // 右边边缘
            }else if (ball.posX >= gameView.width) {
                val deltaDegree: Float
                ball.degree += if (ball.degree < 180)  {
                    deltaDegree = ball.degree - 0
                    -2 * deltaDegree + 180
                }else {
                    deltaDegree = 360 - ball.degree
                    2 * deltaDegree - 180
                }
            // 上边边缘
            }else if(ball.posY <= 0) {
                val deltaDegree = abs(90 - ball.degree)
                ball.degree += if (ball.degree < 90)  {
                    2 * deltaDegree + 180
                }else {
                    -2 * deltaDegree + 180
                }
            // 和板碰撞,因为移动距离的关系y不能完全相等
            }else if (ball.posY + gameView.mBallMask!!.height / 2 >= board.posY) {
                // 板内
                if (abs(ball.posX - board.posX) <= gameView.mLength / 2){
                    val deltaDegree = abs(270 - ball.degree)
                    ball.degree += if (ball.degree < 270)  {
                        2 * deltaDegree - 180
                    }else {
                        -2 * deltaDegree - 180
                    }
                }else {
                    isGameOver = true
                }
            }
        }
    }

    // 圆心坐标,角度方向(degree,对应弧度radian)
    data class Sprite(var posX: Int, var posY: Int, var degree: Float)

    /**
     * 供外部回收资源
     */
    fun recycle()  {
        mBallMask?.recycle()
        mTargetMask?.recycle()
        mGameController.removeMessages(0)
    }
}

对应style配置,这里rowNunb不能用了,和上个贪吃蛇游戏冲突了,不能用一样的名称。游戏数据的数组我也写在这里了,实际应该分开写的,但是小游戏而已,就这样吧!

res -> values -> bomb_ball_game_view_style.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BombBallGameView">
        <attr name="length" format="integer"/>
        <attr name="row" format="integer"/>
        <attr name="col" format="integer"/>
        <attr name="ballMask" format="reference"/>
        <attr name="targetMask" format="reference"/>
        <attr name="targetConfig" format="reference"/>
    </declare-styleable>
    <string-array name="BombBallGameConfig">
        <item>(0,5)</item>
        <item>(0,6)</item>
        <item>(0,7)</item>
        <item>(0,8)</item>
        <item>(0,9)</item>

        <item>(0,10)</item>
        <item>(0,11)</item>
        <item>(0,12)</item>
        <item>(0,13)</item>
        <item>(0,14)</item>

        <item>(1,3)</item>
        <item>(1,5)</item>
        <item>(1,7)</item>
        <item>(1,9)</item>
        <item>(1,11)</item>
        <item>(1,13)</item>
        <item>(1,15)</item>
    </string-array>
</resources>

掩图也还是从Android Studio里面的vector image来的,我觉得还阔以。

res -> drawable -> ic_circle.xml

<vector android:height="24dp" android:tint="#6F6A6A"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

res -> drawable -> ic_target.xml

<vector android:height="24dp" android:tint="#6F6A6A"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

layout也说一下,前面都没写layout,这里用到了字符串数组,说下吧

    <com.silencefly96.module_views.game.BombBallGameView
        android:id="@+id/gamaView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        app:ballMask="@drawable/ic_circle"
        app:targetMask="@drawable/ic_target"
        app:targetConfig="@array/BombBallGameConfig"
        />

主要问题

下面简单讲讲吧,主要结构和前面游戏没什么变化,就是游戏逻辑变得复杂了很多。

资源加载

和前面一样,资源加载就是从styleable配置里面读取设置,这里需要额外说明的就是目标的配置文件了。

这里顶部目标是通过外部的配置文件来设置的,接受的是一个字符串数组的资源id,我这保存在下面:

res -> values -> bomb_ball_game_view_style.xml -> BombBallGameConfig

结构是一个坐标,需要注意的是要配合row和col使用(行数和列数),第一个数字表示第几行,第二个数字表示第几列。

<item>(0,5)</item>

读取的时候是把行标和列标读到了Sprite的posX和posY里面,这里是错误的,当时在init读取的时候无法获得控件的宽高,所以暂时先存放下,在onMeasuer -> onSizeChanged得到宽高之后,在load中对数据进行处理,mTargetList(游戏操作的列表)和mTargetConfigList(原始数据列表)都保存的是读取到的配置对象,即使mTargetList清空了,配置对象不变,依然保存在mTargetConfigList,这里要分清,不然reload的时候再处理就大错特错了。

板的移动

这里叫板,实际是通过paint画出来的线,只是设置的strokeWidth比较粗而已。移动的时候在onTouchEvent的ACTION_MOVE事件中更新板的坐标,在onDraw会以它的坐标和长度绘制成“板”。

球对四周的反弹

球的数据保存在Sprite对象里面,里面保存了三个变量,坐标以及方向。球在四个边的反弹(板实际就是下边),类似光的反射,找到反射面以及反射的法线,再以法线对称就得到反射路线了。实际操作上,先获取入射方向与法线夹角的绝对值,对称到法线另一边,再旋转180度掉头,就能得到出射方向了。

当然计算的时候要根据实际情况计算,尤其是0度和360度作为法线时。

球和目标的碰撞时的反射

球和目标的碰撞就不说了,很简单,计算下两个中心的距离就行了。这里说下碰撞后的反射问题,和上面在四周的反射类似,这里也是要通过反射面和法线来决定,实际上法线就是两个圆心的连线,而且小球和目标碰撞时,方向只会向上,所以取小球中心为起点,目标中心为中点,得到法线向量,再去计算角度就很简单了。

球的初始随机方向问题

球的初始随机方向我是想让它向上的,那应该生成哪个范围的角度呢?我们上学的时候X轴向右,Y轴向上,上半部分角度时[0, 180],那这时候U轴向下了,角度范围呢?答案很简单了,就是[180, 360],上面碰撞的代码实际是我以默认上半区为[0, 180]的时候写的,实际也无需修改,因为只是坐标轴对称了,逻辑并没对称。

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

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

相关文章

股票交易开放接口是什么意思?

在股票量化市场上&#xff0c;大家可能对股票交易开放接口的意思不太理解&#xff0c;其实换个角度来看&#xff0c;就是关于由开发团队进行开发的股票交易开放接口&#xff0c;那么对于接口的开发原理跟代码是怎么样的呢&#xff1f;一、股票交易开放接口函数的调用&#xff1…

面试的同学看这里!这套Java面试八股文,已经帮助200+人进入大厂

在看这篇文章之前&#xff0c;我想我们需要先搞明白八股文是什么&#xff1f;&#xff1f;&#xff1f; 明清科举考试的一种文体&#xff0c;也称制义、制艺、时文、八比文。八股文章就四书五经取题&#xff0c;内容必须用古人的语气&#xff0c;绝对不允许自由发挥&#xff0c…

Git 常用命令

一、基本的git命令 1、查看现在在哪个分支 git branch 2、切换到某分支 git checkout 某分支 3、添加修改后的代码到缓存区 git add . 4、添加提交代码的备注 git commit -m "注释" 5、提交代码到指定的分支 git push origin 某分支 6、从远程仓库克隆git仓库…

四 、QML常用控件的使用详解

在Qt Quick的世界里&#xff0c;window对象用于创建一个与操作系统相关的顶层窗口&#xff0c;而其他的元素&#xff0c;如Text Rectangle,Image等&#xff0c;都睡Windows提功能场景里面的显示对象&#xff0c;Window还有一个派生类&#xff0c;即是大名鼎鼎的Application Win…

基于DSP+FPGA高速运动控制器设计

基于“PC运动控制器”结构的开放式机器人运动控制系统能够充分利用PC开放程 度高、通用性好、处理能力强等特点以及运动控制器运算速度快、实时性能好、控制能 力强等特点&#xff0c;因此得到较快发展&#xff0c;成为目前的研究热点。但目前采用此种结构的开放式 机器人运动控…

3D模型深度生成网络【ShapeAssembly】

推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 我们提出了一个深度生成模型&#xff0c;该模型学习在ShapeAssembly中编写新颖的程序&#xff0c;ShapeAssembly是一种用于建模3D形状结构的特定领域语言。 执行 ShapeAssembly 程序会生成一个由部件代理长方体的分层连…

HashMap put() 方法源码分析

文章目录一、前置知识红黑树定义二、构造方法HashMap()HashMap(int initialCapacity, float loadFactor)tableSizeFor(int cap)&#xff1a;计算hashmap初始容量三、put 方法源码1. put()hash(Object key)&#xff1a;计算key的hash值2. putVal()通过 hash 计算数组下标3. resi…

jdk版本切换工具jenv使用指南

1.下载jenv包 下载链接&#xff1a;GitHub - FelixSelter/JEnv-for-Windows: Change your current Java version with one line 下载包的文件&#xff1a;JEnv.zip 然后解压缩&#xff0c;放到一个目录下&#xff0c;我这里放到了目录&#xff1a;D:\tools\JEnv 2.将JENV添…

chatGPT学习

最近看到一个火爆的AI智能聊天工具&#xff1a;ChatGPT。它的功能&#xff1a;文能写文章&#xff0c;武能改BUG&#xff0c;马斯克对它的评价是“Scary Good!”。我非常感兴趣&#xff0c;就试用了一下&#xff0c;感觉还不错&#xff0c;希望大家能喜欢。 ChatGPT&#xff0…

炼石完成近亿元A+轮融资,冲刺1500亿数据安全赛道

近日&#xff0c;炼石宣布完成近亿元A轮融资&#xff0c;本轮融资由重庆科技成果转化基金独家投资&#xff0c;由清科资本担任独家财务顾问&#xff0c;这是继安天科技、安云资本、国科嘉和、腾讯等多轮之后的新一轮投资。随着本轮资金的引入&#xff0c;炼石将更深入研发迭代以…

MASA Stack 1.0 发布会讲稿 —— 产品篇

架构 基于MASA的云原生技术架构 我们基于MASA去做了一个云原生技术架构&#xff0c;左下角的MASA Blazor主要是为我们去集成一个多端的UI能力&#xff0c;具体的使用场景将在实践篇为大家介绍——MASA Blazor如何去做多端的 接着就是集成非业务能力的MASA Framework&#xff…

1.Docker 简介

Docker 简介 什么是 Docker&#xff1f; Docker的英文翻译是“搬运工”的意思&#xff0c;他搬运的东西就是我们常说的集装箱Container&#xff0c;Container 里面装的是任意类型的 App&#xff0c;我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的…

C语言位运算

所谓位运算&#xff0c;就是对一个比特&#xff08;Bit&#xff09;位进行操作。比特&#xff08;Bit&#xff09;是一个电子元器件&#xff0c;8个比特构成一个字节&#xff08;Byte&#xff09;&#xff0c;它已经是粒度最小的可操作单元了。C语言提供了六种位运算符&#xf…

linux系统下centos7 或 openwrt安装 使用 SpeedTest 测速

博主博客 https://blog.uso6.com https://blog.csdn.net/dxk539687357 一、测试本地是否安装 python(已安装可以跳过) 1.在命令行中输入rpm -qa | grep python36 判断是否已经安装 python3。 2.如果没有安装输入 yum install python36 进行安装。 二、测试 CentOS 到 Speed Te…

响应式布局以及提交网站

1.慕客协作平台1. /摹客官网地址&#xff1a; https://www.mockplus.cn/ 注册一个账号2. 下载moke ps插件 3. PS 安装/摹客/蓝湖插件3. 打开PS/摹客/蓝湖插件4. 上传&#xff08;需要切图&#xff0c;需要先标注切图&#xff09;5. 查看项目6. 邀请成员进入&#xff08;分享按钮…

【Java 面试合集】简述下自定义异常的应用场景

简述下自定义异常的应用场景 1. 概述 如上图所示&#xff0c;我们想回答这个问题就要了解异常的基本结构。哪些是我们可以控制的&#xff0c;哪些是我们不能控制的。 也许有人会问了&#xff0c;其实在逻辑中可以多加判断&#xff0c;为什么要需要自定义呢。 其实判断的内容无…

rigol 普源MSO5104数字示波器技术参数

MSO5000系列数字示波器是基于RIGOL UltraVision II代技术的高性能中低端数字示波器&#xff0c;采用9英寸多点电容触摸屏&#xff0c;集7种仪器于一身。具有超高的采样带宽比和存储深度等优异的性能指标&#xff0c;以及精巧便携的外观设计。高集成度的ASIC芯片组和创新性的前端…

大数据技术架构(组件)27——Spark:CacheCheckpoint BroadcastAccumulate

2.1.7、Cache&Checkpoint&Broadcast&Accumulate2.1.7.1、Cache2.1.7.1.1、Cache原理RDD是通过iterator进行计算的。当然该方法是内部调用的&#xff0c;不会暴露给用户使用&#xff1b;1、CacheManager通过BlockManager从Local或者Remote获取数据&#xff0c;然后通…

网站虚拟主机的流量为什么会受限制?

虚拟主机流量限制是指网站所在的虚拟主机提供商对网站流量的限制&#xff0c;网站的流量超出限制的话将会受到一定影响&#xff0c;从而影响网站的正常运行。那么网站虚拟主机流量为什么会受限制?本文将详细介绍。 一、虚拟主机流量限制的主要原因 1、虚拟主机提供商设置的流量…

SpringBoot/SpringCloudAlibaba(ruoyi)中cron表达式(配置每天指定整点执行)读取配置文件

场景 若依微服务版手把手教你本地搭建环境并运行前后端项目&#xff1a; 若依微服务版手把手教你本地搭建环境并运行前后端项目_霸道流氓气质的博客-CSDN博客 在上面的基础上某业务需要配置cron表达式&#xff0c;该表达式需要指定每天的指定的整点执行&#xff0c; 比如每…