一起用Go做一个小游戏(中)

news2024/11/25 12:37:42

限制飞船的活动范围

上一篇文章还留了个尾巴,细心的同学应该发现了:飞船可以移动出屏幕!!!现在我们就来限制一下飞船的移动范围。我们规定飞船可以左右超过半个身位,如下图所示:

b123297befac40c6f9efa84b2cff7d2c.png

很容易计算得出,左边位置的x坐标为:

x = -W2/2

右边位置的坐标为:

x = W1 - W2/2

修改input.go的代码如下:

func (i *Input) Update(ship *Ship, cfg *Config) {
  if ebiten.IsKeyPressed(ebiten.KeyLeft) {
    ship.x -= cfg.ShipSpeedFactor
    if ship.x < -float64(ship.width)/2 {
      ship.x = -float64(ship.width) / 2
    }
  } else if ebiten.IsKeyPressed(ebiten.KeyRight) {
    ship.x += cfg.ShipSpeedFactor
    if ship.x > float64(cfg.ScreenWidth)-float64(ship.width)/2 {
      ship.x = float64(cfg.ScreenWidth) - float64(ship.width)/2
    }
  }
}

运行结果如下:

24643b354fe73ad77fc5aaeec43341f0.gif

发射子弹

我们不用另外准备子弹的图片,直接画一个矩形就ok。为了可以灵活控制,我们将子弹的宽、高、颜色以及速率都用配置文件来控制:

{
  "bulletWidth": 3,
  "bulletHeight": 15,
  "bulletSpeedFactor": 2,
  "bulletColor": {
    "r": 80,
    "g": 80,
    "b": 80,
    "a": 255
  }
}

新增一个文件bullet.go,定义子弹的结构类型和New方法:

type Bullet struct {
  image       *ebiten.Image
  width       int
  height      int
  x           float64
  y           float64
  speedFactor float64
}

func NewBullet(cfg *Config, ship *Ship) *Bullet {
  rect := image.Rect(0, 0, cfg.BulletWidth, cfg.BulletHeight)
  img := ebiten.NewImageWithOptions(rect, nil)
  img.Fill(cfg.BulletColor)

  return &Bullet{
    image:       img,
    width:       cfg.BulletWidth,
    height:      cfg.BulletHeight,
    x:           ship.x + float64(ship.width-cfg.BulletWidth)/2,
    y:           float64(cfg.ScreenHeight - ship.height - cfg.BulletHeight),
    speedFactor: cfg.BulletSpeedFactor,
  }
}

首先根据配置的宽高创建一个rect对象,然后调用ebiten.NewImageWithOptions创建一个*ebiten.Image对象。

子弹都是从飞船头部发出的,所以它的横坐标等于飞船中心的横坐标,左上角的纵坐标=屏幕高度-飞船高-子弹高。

随便增加子弹的绘制方法:

func (bullet *Bullet) Draw(screen *ebiten.Image) {
  op := &ebiten.DrawImageOptions{}
  op.GeoM.Translate(bullet.x, bullet.y)
  screen.DrawImage(bullet.image, op)
}

我们在Game对象中增加一个map来管理子弹:

type Game struct {
  // -------省略-------
  bullets map[*Bullet]struct{}
}

func NewGame() *Game {
  return &Game{
    // -------省略-------
    bullets: make(map[*Bullet]struct{}),
  }
}

然后在Draw方法中,我们需要将子弹也绘制出来:

func (g *Game) Draw(screen *ebiten.Image) {
  screen.Fill(g.cfg.BgColor)
  g.ship.Draw(screen)
  for bullet := range g.bullets {
    bullet.Draw(screen)
  }
}

子弹位置如何更新呢?在Game.Update中更新,与飞船类似,只是飞船只能水平移动,而子弹只能垂直移动。

func (g *Game) Update() error {
  for bullet := range g.bullets {
    bullet.y -= bullet.speedFactor
  }
  // -------省略-------
}

子弹的更新、绘制逻辑都完成了,可是我们还没有子弹!现在我们就来实现按空格发射子弹的功能。我们需要在Input.Update方法中判断空格键是否按下,由于该方法需要访问Game对象的多个字段,干脆传入Game对象:

func (i *Input) Update(g *Game) {
  if ebiten.IsKeyPressed(ebiten.KeyLeft) {
    // -------省略-------
  } else if ebiten.IsKeyPressed(ebiten.KeyRight) {
    // -------省略-------
  } else if ebiten.IsKeyPressed(ebiten.KeySpace) {
    bullet := NewBullet(g.cfg, g.ship)
    g.addBullet(bullet)
  }
}

给Game对象增加一个addBullet方法:

func (g *Game) addBullet(bullet *Bullet) {
  g.bullets[bullet] = struct{}{}
}

目前有两个问题:

  • 无法一边移动一边发射,仔细看看Input.Update方法中的代码,你能发现什么问题吗?

  • 子弹太多了,我们想要限制子弹的数量。

下面来逐一解决这些问题。

第一个问题很好解决,因为在KeyLeft/KeyRight/KeySpace这三个判断中我们用了if-else。这样会优先处理移动的操作。将KeySpace移到一个单独的if中即可:

func (i *Input) Update(g *Game) {
  // -------省略-------
  if ebiten.IsKeyPressed(ebiten.KeySpace) {
    bullet := NewBullet(g.cfg, g.ship)
    g.addBullet(bullet)
  }
}

第二个问题,在配置中,增加同时最多存在的子弹数:

{
  "maxBulletNum": 10
}
type Config struct {
  MaxBulletNum      int        `json:"maxBulletNum"`
}

然后我们在Input.Update方法中判断,如果目前存在的子弹数小于MaxBulletNum才能创建新的子弹:

if ebiten.IsKeyPressed(ebiten.KeySpace) {
  if len(g.bullets) < cfg.MaxBulletNum {
    bullet := NewBullet(g.cfg, g.ship)
    g.addBullet(bullet)
  }
}

再次运行:

9b2cfdcbcf905ee88c41d80241da430d.gif

数量好像被限制了,但是不是我们配置的10。原来Input.Update()的调用间隔太短了,导致我们一次space按键会发射多个子弹。我们可以控制两个子弹之间的时间间隔。同样用配置文件来控制(单位毫秒):

{
  "bulletInterval": 50
}
type Config struct {
  BulletInterval    int64      `json:"bulletInterval"`
}

Input结构中增加一个lastBulletTime字段记录上次发射子弹的时间:

type Input struct {
  lastBulletTime time.Time
}

距离上次发射子弹的时间大于BulletInterval毫秒,才能再次发射,发射成功之后更新时间:

func (i *Input) Update(g *Game) {
  // -------省略-------
  if ebiten.IsKeyPressed(ebiten.KeySpace) {
    if len(g.bullets) < g.cfg.MaxBulletNum &&
      time.Now().Sub(i.lastBulletTime).Milliseconds() > g.cfg.BulletInterval {
      bullet := NewBullet(g.cfg, g.ship)
      g.addBullet(bullet)
      i.lastBulletTime = time.Now()
    }
  }
}

运行:

f22207d40599be5365db5a5c29a97236.gif

又出现了一个问题,10个子弹飞出屏幕外之后还是不能发射子弹。我们需要把离开屏幕的子弹删除。这适合在Game.Update函数中做:

func (g *Game) Update() error {
  g.input.Update(g)
  for bullet := range g.bullets {
    if bullet.outOfScreen() {
      delete(g.bullets, bullet)
    }
  }
  return nil
}

为了Bullet添加判断是否处于屏幕外的方法:

func (bullet *Bullet) outOfScreen() bool {
  return bullet.y < -float64(bullet.height)
}

再次运行:

da0c8153e05ea70c555c0ab7d892eb63.gif

外星人来了

外星人图片如下:

9a37e0c7da67151a1632a50b6c26da65.png

同飞船一样,编写Alien类,添加绘制自己的方法:

type Alien struct {
  image       *ebiten.Image
  width       int
  height      int
  x           float64
  y           float64
  speedFactor float64
}

func NewAlien(cfg *Config) *Alien {
  img, _, err := ebitenutil.NewImageFromFile("../images/alien.png")
  if err != nil {
    log.Fatal(err)
  }

  width, height := img.Size()
  return &Alien{
    image:       img,
    width:       width,
    height:      height,
    x:           0,
    y:           0,
    speedFactor: cfg.AlienSpeedFactor,
  }
}

func (alien *Alien) Draw(screen *ebiten.Image) {
  op := &ebiten.DrawImageOptions{}
  op.GeoM.Translate(alien.x, alien.y)
  screen.DrawImage(alien.image, op)
}

游戏开始时需要创建一组外星人,计算一行可以容纳多少个外星人,考虑到左右各留一定的空间,两个外星人之间留一点空间。在游戏一开始就创建一组外星人:

type Game struct {
  // Game结构中的map用来存储外星人对象
  aliens  map[*Alien]struct{}
}

func NewGame() *Game {
  g := &Game{
    // 创建map
    aliens:  make(map[*Alien]struct{}),
  }
  // 调用 CreateAliens 创建一组外星人
  g.CreateAliens()
  return g
}

func (g *Game) CreateAliens() {
  alien := NewAlien(g.cfg)

  availableSpaceX := g.cfg.ScreenWidth - 2*alien.width
  numAliens := availableSpaceX / (2 * alien.width)

  for i := 0; i < numAliens; i++ {
    alien = NewAlien(g.cfg)
    alien.x = float64(alien.width + 2*alien.width*i)
    g.addAlien(alien)
  }
}

左右各留一个外星人宽度的空间:

availableSpaceX := g.cfg.ScreenWidth - 2*alien.width

然后,两个外星人之间留一个外星人宽度的空间。所以一行可以创建的外星人的数量为:

numAliens := availableSpaceX / (2 * alien.width)

创建一组外星人,依次排列。

同样地,我们需要在Game.Draw方法中添加绘制外星人的代码:

func (g *Game) Draw(screen *ebiten.Image) {
  // -------省略-------
  for alien := range g.aliens {
    alien.Draw(screen)
  }
}

运行:

41d51a64c1198db2bf9a471dc76231d2.png

再创建两行:

func (g *Game) CreateAliens() {
  // -------省略-------
  for row := 0; row < 2; row++ {
    for i := 0; i < numAliens; i++ {
      alien = NewAlien(g.cfg)
      alien.x = float64(alien.width + 2*alien.width*i)
      alien.y = float64(alien.height*row) * 1.5
      g.addAlien(alien)
    }
  }
}

让外星人都起来,同样地还是在Game.Update方法中更新位置:

func (g *Game) Update() error {
  // -------省略-------
  for alien := range g.aliens {
    alien.y += alien.speedFactor
  }
  // -------省略-------
}

1ee0e7f5647412001b2ec755a93c9f3d.gif

射击!!

当前子弹碰到外星人直接穿过去了,我们希望能击杀外星人。这需要检查子弹和外星人之间的碰撞。我们新增一个文件collision.go,并编写检查子弹与外星人是否碰撞的函数。这里我采用最直观的碰撞检测方法,即子弹的4个顶点只要有一个位于外星人矩形中,就认为它们碰撞。

// CheckCollision 检查子弹和外星人之间是否有碰撞
func CheckCollision(bullet *Bullet, alien *Alien) bool {
  alienTop, alienLeft := alien.y, alien.x
  alienBottom, alienRight := alien.y+float64(alien.height), alien.x+float64(alien.width)
  // 左上角
  x, y := bullet.x, bullet.y
  if y > alienTop && y < alienBottom && x > alienLeft && x < alienRight {
    return true
  }

  // 右上角
  x, y = bullet.x+float64(bullet.width), bullet.y
  if y > alienTop && y < alienBottom && x > alienLeft && x < alienRight {
    return true
  }

  // 左下角
  x, y = bullet.x, bullet.y+float64(bullet.height)
  if y > alienTop && y < alienBottom && x > alienLeft && x < alienRight {
    return true
  }

  // 右下角
  x, y = bullet.x+float64(bullet.width), bullet.y+float64(bullet.height)
  if y > alienTop && y < alienBottom && x > alienLeft && x < alienRight {
    return true
  }

  return false
}

接着我们在Game.Update方法中调用这个方法,并且将碰撞的子弹和外星人删除。

func (g *Game) CheckCollision() {
  for alien := range g.aliens {
    for bullet := range g.bullets {
      if CheckCollision(bullet, alien) {
        delete(g.aliens, alien)
        delete(g.bullets, bullet)
      }
    }
  }
}

func (g *Game) Update() error {
  // -------省略-------

  g.CheckCollision()

  // -------省略-------
  return nil
}

注意将碰撞检测放在位置更新之后。运行:

00a562ee07aad5d8fc1405b3e56f3e41.gif

增加主界面和结束界面

现在一旦运行程序,外星人们就开始运动了。我们想要增加一个按下空格键才开始的功能,并且游戏结束之后,我们也希望能显示一个Game Over的界面。首先,我们定义几个常量,表示游戏当前所处的状态:

const (
  ModeTitle Mode = iota
  ModeGame
  ModeOver
)

Game结构中需要增加mode字段表示当前游戏所处的状态:

type Game struct {
  mode    Mode
  // ...
}

为了显示开始界面,涉及到文字的显示,文字显示和字体处理起来都比较麻烦。ebitengine内置了一些字体,我们可以据此创建几个字体对象:

var (
  titleArcadeFont font.Face
  arcadeFont      font.Face
  smallArcadeFont font.Face
)

func (g *Game) CreateFonts() {
  tt, err := opentype.Parse(fonts.PressStart2P_ttf)
  if err != nil {
    log.Fatal(err)
  }
  const dpi = 72
  titleArcadeFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
    Size:    float64(g.cfg.TitleFontSize),
    DPI:     dpi,
    Hinting: font.HintingFull,
  })
  if err != nil {
    log.Fatal(err)
  }
  arcadeFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
    Size:    float64(g.cfg.FontSize),
    DPI:     dpi,
    Hinting: font.HintingFull,
  })
  if err != nil {
    log.Fatal(err)
  }
  smallArcadeFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
    Size:    float64(g.cfg.SmallFontSize),
    DPI:     dpi,
    Hinting: font.HintingFull,
  })
  if err != nil {
    log.Fatal(err)
  }
}

fonts.PressStart2P_ttf就是ebitengine提供的字体。创建字体的方法一般在需要的时候微调即可。将创建外星人和字体封装在Game的init方法中:

func (g *Game) init() {
  g.CreateAliens()
  g.CreateFonts()
}

func NewGame() *Game {
  // ...
  g.init()
  return g
}

启动时游戏处于ModeTitle状态,处于ModeTitle和ModeOver时只需要在屏幕上显示一些文字即可。只有在ModeGame状态才需要显示飞船和外星人:

func (g *Game) Draw(screen *ebiten.Image) {
  screen.Fill(g.cfg.BgColor)

  var titleTexts []string
  var texts []string
  switch g.mode {
  case ModeTitle:
    titleTexts = []string{"ALIEN INVASION"}
    texts = []string{"", "", "", "", "", "", "", "PRESS SPACE KEY", "", "OR LEFT MOUSE"}
  case ModeGame:
    g.ship.Draw(screen)
    for bullet := range g.bullets {
      bullet.Draw(screen)
    }
    for alien := range g.aliens {
      alien.Draw(screen)
    }
  case ModeOver:
    texts = []string{"", "GAME OVER!"}
  }

  for i, l := range titleTexts {
    x := (g.cfg.ScreenWidth - len(l)*g.cfg.TitleFontSize) / 2
    text.Draw(screen, l, titleArcadeFont, x, (i+4)*g.cfg.TitleFontSize, color.White)
  }
  for i, l := range texts {
    x := (g.cfg.ScreenWidth - len(l)*g.cfg.FontSize) / 2
    text.Draw(screen, l, arcadeFont, x, (i+4)*g.cfg.FontSize, color.White)
  }
}

Game.Update方法中,我们判断在ModeTitle状态下按下空格,鼠标左键游戏开始,切换为ModeGame状态。游戏结束时切换为GameOver状态,在GameOver状态后按下空格或鼠标左键即重新开始游戏。

func (g *Game) Update() error {
  switch g.mode {
  case ModeTitle:
    if g.input.IsKeyPressed() {
      g.mode = ModeGame
    }
  case ModeGame:
    for bullet := range g.bullets {
      bullet.y -= bullet.speedFactor
    }

    for alien := range g.aliens {
      alien.y += alien.speedFactor
    }

    g.input.Update(g)

    g.CheckCollision()

    for bullet := range g.bullets {
      if bullet.outOfScreen() {
        delete(g.bullets, bullet)
      }
    }
  case ModeOver:
    if g.input.IsKeyPressed() {
      g.init()
      g.mode = ModeTitle
    }
  }

  return nil
}

input.go中增加IsKeyPressed方法判断是否有空格或鼠标左键按下。

判断游戏胜负

我们规定如果击杀所有外星人则游戏胜利,有3个外星人移出屏幕外或者碰撞到飞船则游戏失败。

首先增加一个字段failCount用于记录移出屏幕外的外星人数量和与飞船碰撞的外星人数量之和:

type Game struct {
  // -------省略-------
  failCount int // 被外星人碰撞和移出屏幕的外星人数量之和
}

然后我们在Game.Update方法中检测外星人是否移出屏幕,以及是否与飞船碰撞:

for alien := range g.aliens {
  if alien.outOfScreen(g.cfg) {
    g.failCount++
    delete(g.aliens, alien)
    continue
  }

  if CheckCollision(alien, g.ship) {
    g.failCount++
    delete(g.aliens, alien)
    continue
  }
}

这里有一个问题,还记得吗?我们前面编写的CheckCollision函数接受的参数类型是*Alien*Bullet,这里我们需要重复编写接受参数为*Ship*Alien的函数吗?不用!

我们将表示游戏中的实体对象抽象成一个GameObject结构:

type GameObject struct {
  width  int
  height int
  x      float64
  y      float64
}

func (gameObj *GameObject) Width() int {
  return gameObj.width
}

func (gameObj *GameObject) Height() int {
  return gameObj.height
}

func (gameObj *GameObject) X() float64 {
  return gameObj.x
}

func (gameObj *GameObject) Y() float64 {
  return gameObj.y
}

然后定义一个接口Entity

type Entity interface {
  Width() int
  Height() int
  X() float64
  Y() float64
}

最后让我们游戏中的实体内嵌这个GameObject对象,即可自动实现Entity接口:

type Alien struct {
  GameObject
  image       *ebiten.Image
  speedFactor float64
}

这样CheckCollision即可改为接受两个Entity接口类型的参数:

// CheckCollision 两个物体之间是否碰撞
func CheckCollision(entityA, entityB Entity) bool {
  top, left := entityA.Y(), entityA.X()
  bottom, right := entityA.Y()+float64(entityA.Height()), entityA.X()+float64(entityA.Width())
  // 左上角
  x, y := entityB.X(), entityB.Y()
  if y > top && y < bottom && x > left && x < right {
    return true
  }

  // 右上角
  x, y = entityB.X()+float64(entityB.Width()), entityB.Y()
  if y > top && y < bottom && x > left && x < right {
    return true
  }

  // 左下角
  x, y = entityB.X(), entityB.Y()+float64(entityB.Height())
  if y > top && y < bottom && x > left && x < right {
    return true
  }

  // 右下角
  x, y = entityB.X()+float64(entityB.Width()), entityB.Y()+float64(entityB.Height())
  if y > top && y < bottom && x > left && x < right {
    return true
  }

  return false
}

如果游戏失败则切换为ModeOver模式,屏幕上显示"Game Over!"。如果游戏胜利,则显示"You Win!":

if g.failCount >= 3 {
  g.overMsg = "Game Over!"
} else if len(g.aliens) == 0 {
  g.overMsg = "You Win!"
}

if len(g.overMsg) > 0 {
  g.mode = ModeOver
  g.aliens = make(map[*Alien]struct{})
  g.bullets = make(map[*Bullet]struct{})
}

注意,为了下次游戏能顺序进行,这里需要清楚所有的子弹和外星人。运行:

b431066dc7b0c1b95883ad5e3d79113b.gif

总结

本文接着上篇文章,介绍了发射子弹,检测碰撞,增加主界面和游戏结束界面等内容。至此一个简答的游戏就做出来了。可以看出使用ebitengine做一个游戏还是很简单的,非常推荐尝试呢!上手之后,建议看看官方仓库examples目录中的示例,会非常有帮助。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄

参考

  1. Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib

  2. ebitengine 官网:https://ebitengine.org/

  3. Python 编程(从入门到实践):https://book.douban.com/subject/35196328/

我的博客:https://darjun.github.io

欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~

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

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

相关文章

vue3+ts打包报错处理

打包报错 但是npm run dev 是运行正常的 经过一番搜索之后&#xff0c;这个错误是比较难搜索到的 注意看package.json 中的vue-tsc --noEmit 删掉就可以了 { “name”: “vuevitec”, “version”: “0.0.0”, “scripts”: { “dev”: “vite”, “build”: “vue-tsc --noEm…

NextJs 学习笔记

NextJs 学习笔记 简述 之前使用过 Nuxt3 基于前端框架 Vue3 来开发网站&#xff0c;因为 Nuxt3 很多地方借鉴了基于 React 的 SSR 框架 Next&#xff0c;因此最近抽时间开始学习一下 Next 这个框架。 创建项目 npx create-next-applatest # or yarn create next-app # or p…

大学生川菜网页制作教程 学生HTML静态美食菜品网页设计作业成品 简单网页制作代码 学生美食网页作品免费设计

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

适老化产品开发:用科技和智能解决老龄化难题

随着人口的深度老龄化&#xff0c;很多老年人不会使用智能手机、不会上网&#xff0c;在消费、出行等日常生活中遇到很多不便&#xff0c;为老人提供更多的养老、适老化产品&#xff0c;完善的养老服务&#xff0c;打造舒适安全的养老环境是我们急需解决的问题。实现适老化产品…

给自己的hexo博客个性化Volantis主题

文章目录准备工作一、搭建一个大致框架二、在_config.yml的一些基础配置三、在_config.volantis.yml的进阶配置1.首先我们要创建页面2.自定义导航栏3.自定义友链页面设置4.自定义页脚5.自定义封面6.自定义侧边栏7.利用不蒜子添加访问人数8.添加阅读时长和字数9.添加音乐播放器1…

【从零开始学习深度学习】12. 什么是模型的训练误差?基于三阶多项式的欠拟合与过拟合训练过程演示

目录前言1.1 训练误差和泛化误差1.2 模型选择1.2.1 验证数据集1.2.3 KKK折交叉验证1.3 欠拟合和过拟合1.3.1 模型复杂度1.3.2 训练数据集大小1.4 多项式函数拟合示例1.4.1 生成数据集1.4.2 定义、训练和测试模型1.4.3 三阶多项式函数拟合&#xff08;正常&#xff09;1.4.4 线性…

【Docker教程系列】Docker学习5-Docker镜像理解

通过前面几篇文章的学习&#xff0c;我们已经安装好了Docker&#xff0c;也学会使用一些常用的命令。比如启动命令、镜像命令、容器命令。常用命令分类后的第二个就是镜像命令。那么镜像是什么&#xff1f;拉取镜像的时候为什么是一层一层的&#xff1f;镜像加载的原理是什么&a…

LeetCode #2.两数相加

力扣 | 2.两数相加 题目截图 官方方法 总共有三个链表&#xff0c;l1,l2和最后的结果链表。l1和l2各自滑动对应链表各个位数支出相应的值&#xff0c;最后相加&#xff0c;将结果添加到新链表上。 唯一需要考虑的是数值相加后的进位。 先设置进位carry为0 结果相加的时候利…

[附源码]计算机毕业设计考试系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

如何系统全面的成为一个网络工程师?看完这个你就懂了

如何系统全面的成为一个网络工程师&#xff1f;总有粉丝这样问我&#xff0c;今天就来给大家推荐一个方法。 想要真正系统全面地成为一个网络工程师&#xff0c;掌握系统全面的网络技术的话&#xff0c;我这边建议的话还是完整的学一下华为认证HCIA、HCIP、HCIE&#xff0c;这三…

JavaScript -- Date对象及常用方法介绍

文章目录Date1 Date介绍2 常用方法介绍3 创建指定日期的Date对象4 日期的格式化Date Date - JavaScript 1 Date介绍 在JS中所有的和时间相关的数据都由Date对象来表示 创建Date对象 let d new Date() // 直接通过new Date()创建时间对象时&#xff0c;它创建的是当前的时…

双12哪些数码好物实用不吃灰?2022最值得入手的数码好物推荐

年末的双十二来了&#xff0c;相信不少人像我一样已经将自己想买的一些数码产品加入购物车啦。实用性强的数码产品才是最好的&#xff0c;才能最大程度地体现它的价值。那么&#xff0c;有哪些数码好物实用性强且不吃灰呢&#xff1f;下面&#xff0c;我来给大家推荐几款2022最…

电力系统的延时功率流 (CPF)的计算【 IEEE-14节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

计算机网络-排队时延和分组丢失

有志者&#xff0c;事竟成 文章目录一、描述1、排队时延和分组丢失二、总结一、描述 1、排队时延和分组丢失 每台分组交换机有多条链路与之相连。对于每条相连的链路&#xff0c;该分组交换机具有一个输出缓存也称为输出队列&#xff0c;它用于存储路由器准备发往那条链路的分…

数字图像处理实验(四)|图像压缩与编码实验{JPGE编码、离散余弦变换DCT、图像分块dctmtx|blkproc}(附matlab实验代码和截图)

文章目录一、 实验目的二、 实验原理1.图像压缩基本概念及原理&#xff08;1&#xff09;无损压缩编码种类&#xff08;2&#xff09;有损压缩编码种类&#xff08;3&#xff09;混合编码2. JPEG压缩编码原理(1)使用正向离散余弦变换(forward discrete cosine transform&#x…

压缩与打包

压缩文件 准备工作 命令&#xff1a;gzip -v anaconda-ks.cfg 压缩之后&#xff0c;原文件删除&#xff0c;只有压缩文件 解压文件 解压缩之后&#xff0c;压缩文件删除&#xff0c;生成原文件 tar命令 生成打包文件 清空/tmp/part1目录 将/etc目录拷贝到/tmp/part1…

展望未来 | Google Play 与时俱进,奔赴下一个十年

作者 / Google Play 产品总监 Alex Musil对于 Google Play 来说&#xff0c;今年是具有里程碑意义的重要年份。不久前&#xff0c;我们迎来了十周年纪念&#xff0c;而现在&#xff0c;我们将展望未来十年&#xff0c;思考我们的平台需要怎样发展完善&#xff0c;与时俱进。为此…

浏览器格式化文件代码以及浏览器调试小技巧

前端开发之浏览器格式化文件代码前言效果图1、在谷歌浏览器中查看源文件2、在谷歌浏览器中的源文件点击下面的{}就会生成相应的格式话文件3、中断调试&#xff1a;添加断点后&#xff0c;当JS代码运行到断点时会中断&#xff08;对于添加了中断条件的断点在符合条件时中断&…

CSS媒体查询简介及案例

文章目录一、简介二、使用方式2.1 目标2.2 import2.3 link、source等2.4 media2.5 其他三、媒体类型(Media Types)3.1 简介3.2 常见的媒体类型值3.3 已废弃媒体类型四、媒体特性(Media Features)4.1 简介4.2 常见媒体特性简介五、媒体查询 - 逻辑操作符(Logical Operators)5.1 …

金色传说:SAP-QM-周期性检验:MSC1N/MSC2N/MSC3N下一次检验日期逻辑问题

文章目录前言一、实现方案二、收货测试1.建立生产订单2.触发周期检验前言 物料在存储过程中&#xff0c;通常会有定期检验&#xff0c;也叫周期性检验的需求&#xff0c;这里给大家分享下周期性检验的实现过程。 一、实现方案 1.首先要在TCode&#xff1a;MM02-质量管理视图中…