SpriteKit框架详细解析(四) —— 创建一个简单的2D游戏(二)

2023-11-19

转自:https://www.jianshu.com/p/33f28911db17

版本记录

版本号 时间
V1.0 2017.08.12

前言

SpriteKit框架使用优化的动画系统,物理模拟和事件处理支持创建基于2D精灵的游戏。接下来这几篇我们就详细的解析一下这个框架。相关代码已经传至GitHub - 刀客传奇,感兴趣的可以阅读另外几篇文章。
1. SpriteKit框架详细解析(一) —— 基本概览(一)
2. SpriteKit框架详细解析(二) —— 一个简单的动画实例(一)
3. SpriteKit框架详细解析(三) —— 创建一个简单的2D游戏(一)

Collision Detection and Physics: Overview - 碰撞检测和物理:概述

你的忍者真正想要做的就是打倒怪物。因此,是时候添加一些代码来检测射弹何时与目标相交。

关于SpriteKit的一个好处是它内置了一个物理引擎!物理引擎不仅非常适合模拟真实的运动,而且它们也非常适合碰撞检测。

您将设置游戏以使用SpriteKit的物理引擎来确定怪物和射弹何时发生碰撞。从高层次来看,这就是你要做的事情:

  • Set up the physics world - 建立物理世界。物理世界是运行物理计算的模拟空间。默认情况下,在场景中设置一个,您可能希望在其上配置一些属性,如重力。
  • Create physics bodies for each sprite - 为每个精灵创建物理实体。在SpriteKit中,您可以将形状与每个sprite相关联以进行碰撞检测,并在其上设置某些属性。这被称为物理体physics body。请注意,物理主体形状不必与精灵完全相同。通常它是一个更简单,近似的形状,而不是像素完美,因为这对大多数游戏和性能已经可以满足了。
  • Set a category for each type of sprite - 为每种类型的精灵设置一个类别。您可以在物理主体上设置的属性之一是类别category,该类别是指示其所属的组或组的位掩码。在这个游戏中,你将有两个类别:一个用于射弹,一个用于怪物。然后当两个物理实体碰撞时,你可以通过查看它的类别轻松地告诉你正在处理什么样的精灵。
  • Set a contact delegate - 设置联系代理。还记得早期的物理世界吗?那么,您可以在其上设置联系人委托contact delegate,以便在两个物理机构发生碰撞时得到通知。在那里你会写一些代码来检查对象的类别,如果它们是怪物和抛射物,你会让它们爆炸!

现在您已经了解了战斗计划,现在是时候将其付诸行动了!


Collision Detection and Physics: Implementation - 碰撞检测与物理:实施

GameScene.swift的顶部添加下面这个结构体

 

struct PhysicsCategory {
  static let none      : UInt32 = 0
  static let all       : UInt32 = UInt32.max
  static let monster   : UInt32 = 0b1       // 1
  static let projectile: UInt32 = 0b10      // 2
}

这段代码设置了你需要的物理类别的常量。

注意:您可能想知道这里有什么花哨的语法。 SpriteKit上的类别只是一个32位整数,充当位掩码。 这是一种奇特的说法,即整数中的每个32位代表一个类别(因此最多可以有32个类别)。 在这里你设置第一个位来指示一个怪物,下一个位来表示一个射弹,依此类推。

接下来,在实现SKPhysicsContactDelegate协议的GameScene.swift末尾创建一个扩展:

 

extension GameScene: SKPhysicsContactDelegate {

}

然后在didMove(to :)里面添加玩家到场景后添加这些行:

 

physicsWorld.gravity =.zero
physicsWorld.contactDelegate = self

这将物理世界设置为没有重力,并将场景设置为当两个物理体碰撞时要通知的代理。

addMonster()里面,在创建怪物精灵后立即添加这些行:

 

monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 1
monster.physicsBody?.isDynamic = true // 2
monster.physicsBody?.categoryBitMask = PhysicsCategory.monster // 3
monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile // 4
monster.physicsBody?.collisionBitMask = PhysicsCategory.none // 5

这是这样做的:

  • 1)为精灵创建一个物理主体。在这种情况下,身体被定义为与精灵相同大小的矩形,因为这对于怪物来说是一个不错的近似值。
  • 2)将精灵设置为动态dynamic。这意味着物理引擎无法控制怪物的移动。您将使用您已编写的代码来进行移动。
  • 3)将类别位掩码设置为您之前定义的monsterCategory
  • 4)contactTestBitMask指示此对象在相交时应通知联系人侦听器的对象类别。你在这里选择射弹。
  • 5)collisionBitMask指示物理引擎处理的对象的哪些类别的对象接触响应(即反弹)。你不希望怪物和抛射物互相反弹 - 在这个游戏中他们可以直接穿过对方 - 所以你把它设置为.none

接下来在touchesEnded(_:with :)中添加一些类似的代码,在设置射弹位置的线后面:

 

projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.none
projectile.physicsBody?.usesPreciseCollisionDetection = true

作为测试,看看你是否能够理解这里的每一行以及它的作用。 如果没有,请参阅上面解释的要点!

接下来,添加一个方法,在GameScene的闭合大括号之前射弹与怪物碰撞时将被调用。 没有什么能自动调用它,你稍后会调用。

 

func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
  print("Hit")
  projectile.removeFromParent()
  monster.removeFromParent()
}

你在这里所做的就是在碰撞时从场景中移除射弹和怪物。 很简单吧?

现在是时候实现联系委托方法了。 将以下新方法添加到您之前创建的扩展中:

 

func didBegin(_ contact: SKPhysicsContact) {
  // 1
  var firstBody: SKPhysicsBody
  var secondBody: SKPhysicsBody
  if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    firstBody = contact.bodyA
    secondBody = contact.bodyB
  } else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
  }
 
  // 2
  if ((firstBody.categoryBitMask & PhysicsCategory.monster != 0) &&
      (secondBody.categoryBitMask & PhysicsCategory.projectile != 0)) {
    if let monster = firstBody.node as? SKSpriteNode,
      let projectile = secondBody.node as? SKSpriteNode {
      projectileDidCollideWithMonster(projectile: projectile, monster: monster)
    }
  }
}

由于您之前将场景设置为物理世界的contactDelegate,因此只要两个物理实体发生碰撞并且相应地设置了contactTestBitMasks,就会调用此方法。

这个方法有两个部分:

  • 1)此方法将两个碰撞的实体传递给您,但不保证它们以任何特定顺序传递。 所以这段代码只是安排它们,所以它们按类别位掩码进行排序,这样你就可以稍后做出一些假设。
  • 2)这里是检查碰撞的两个物体是否是射弹和怪物,如果是这样,你之前写的方法就被调用。

Build并运行,现在当你的射弹与目标相交时,它们应该消失!


Finishing Touches - 结束点击

你现在非常接近拥有一个非常简单但可行的游戏。 你只需要添加一些音效和音乐 - 什么样的游戏没有声音? - 和一些简单的游戏逻辑。

本教程的项目资源已经有一些很酷的背景音乐和一个很棒的pew-pew声音效果。 你只需要玩它们!

为此,将这些行添加到didMove(to :)的末尾:

 

let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
backgroundMusic.autoplayLooped = true
addChild(backgroundMusic)

这使用SKAudioNode播放和循环播放您游戏的背景音乐。

至于声音效果,请在touchesEnded(_:withEvent :)中的guard语句后添加此行:

 

run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))

Build并运行,就会发现一切OK了。

注意:如果您没有听到背景音乐,请尝试在设备上运行而不是在模拟器上运行。


Game Over, Man!

现在,创建一个新场景,作为You WinYou Lose指示器。 使用iOS \ Source \ Swift File模板创建一个新文件,将文件命名为GameOverScene,然后单击Create

将以下内容添加到GameOverScene.swift

 

import SpriteKit

class GameOverScene: SKScene {
  init(size: CGSize, won:Bool) {
    super.init(size: size)
    
    // 1
    backgroundColor = SKColor.white
    
    // 2
    let message = won ? "You Won!" : "You Lose :["
    
    // 3
    let label = SKLabelNode(fontNamed: "Chalkduster")
    label.text = message
    label.fontSize = 40
    label.fontColor = SKColor.black
    label.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(label)
    
    // 4
    run(SKAction.sequence([
      SKAction.wait(forDuration: 3.0),
      SKAction.run() { [weak self] in
        // 5
        guard let `self` = self else { return }
        let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
        let scene = GameScene(size: size)
        self.view?.presentScene(scene, transition:reveal)
      }
      ]))
   }
  
  // 6
  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

这里有六个部分要指出:

  • 1)将背景颜色设置为白色,与主场景相同。
  • 2)根据won参数,消息设置为You WonYou Lose
  • 3)这是使用SpriteKit在屏幕上显示文本标签的方法。如您所见,它非常简单。您只需选择字体并设置一些参数即可。
  • 4)最后,这将设置并运行两个动作的序列。首先它等待3秒,然后它使用run()动作来运行一些任意代码。
  • 5)这是您在SpriteKit中转换到新场景的方法。您可以从各种不同的动画过渡中选择您想要的场景显示方式。在这里,您选择了需要0.5秒的翻转过渡。然后创建要显示的场景,并在self.view上使用presentScene(_:transition :)
  • 6)如果在场景上重写了初始值器,则还必须实现所需的init(coder :)初始化器。但是,永远不会调用此初始化程序,因此您现在只需添加一个带有fatalError(_ :)的虚拟实现。

到现在为止还挺好!现在,您只需设置主场景,在适当的时候在场景中加载游戏结束页面。

切换回GameScene.swift,在addMonster()里面,用以下内容替换monster.run(SKAction.sequence([actionMove,actionMoveDone])):

 

let loseAction = SKAction.run() { [weak self] in
  guard let `self` = self else { return }
  let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
  let gameOverScene = GameOverScene(size: self.size, won: false)
  self.view?.presentScene(gameOverScene, transition: reveal)
}
monster.run(SKAction.sequence([actionMove, loseAction, actionMoveDone]))

这会创建一个新的lose action,当怪物离开屏幕时会在场景中显示游戏结束场景。 看看你是否理解这里的每一行,如果没有参考前面代码块的解释。

现在你也应该处理胜利的情况,不要对你的玩家残忍!在player声明之后立即将新属性添加到GameScene的顶部:

 

var monstersDestroyed = 0

并在projectileDidCollideWithMonster(projectile:monster:):的底部添加下面代码:

 

monstersDestroyed += 1
if monstersDestroyed > 30 {
  let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
  let gameOverScene = GameOverScene(size: self.size, won: true)
  view?.presentScene(gameOverScene, transition: reveal)
}

在这里你可以追踪玩家摧毁的怪物数量。 如果玩家成功摧毁了超过30个怪物,则游戏结束并且玩家赢得游戏!

Build并运行。 你现在应该有胜利和失败的条件,并在适当的时候看到场景中的游戏结束场景!


源码

下面给一个具体的源码。

 

1. GameScene.swift

 

import SpriteKit

func +(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}

func -(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x - right.x, y: left.y - right.y)
}

func *(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x * scalar, y: point.y * scalar)
}

func /(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x / scalar, y: point.y / scalar)
}

#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
  return CGFloat(sqrtf(Float(a)))
}
#endif

extension CGPoint {
  func length() -> CGFloat {
    return sqrt(x*x + y*y)
  }
  
  func normalized() -> CGPoint {
    return self / length()
  }
}

class GameScene: SKScene {
  
  struct PhysicsCategory {
    static let none      : UInt32 = 0
    static let all       : UInt32 = UInt32.max
    static let monster   : UInt32 = 0b1       // 1
    static let projectile: UInt32 = 0b10      // 2
  }
  
  // 1
  let player = SKSpriteNode(imageNamed: "player")
  var monstersDestroyed = 0
  
  override func didMove(to view: SKView) {
    // 2
    backgroundColor = SKColor.white
    // 3
    player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
    // 4
    addChild(player)
    
    physicsWorld.gravity = .zero
    physicsWorld.contactDelegate = self
    
    run(SKAction.repeatForever(
      SKAction.sequence([
        SKAction.run(addMonster),
        SKAction.wait(forDuration: 1.0)
        ])
    ))
    
    let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
    backgroundMusic.autoplayLooped = true
    addChild(backgroundMusic)
  }
  
  func random() -> CGFloat {
    return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
  }
  
  func random(min: CGFloat, max: CGFloat) -> CGFloat {
    return random() * (max - min) + min
  }
  
  func addMonster() {
    // Create sprite
    let monster = SKSpriteNode(imageNamed: "monster")
    
    monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 1
    monster.physicsBody?.isDynamic = true // 2
    monster.physicsBody?.categoryBitMask = PhysicsCategory.monster // 3
    monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile // 4
    monster.physicsBody?.collisionBitMask = PhysicsCategory.none // 5
    
    // Determine where to spawn the monster along the Y axis
    let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
    
    // Position the monster slightly off-screen along the right edge,
    // and along a random position along the Y axis as calculated above
    monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
    
    // Add the monster to the scene
    addChild(monster)
    
    // Determine speed of the monster
    let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
    
    // Create the actions
    let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
    let actionMoveDone = SKAction.removeFromParent()
    let loseAction = SKAction.run() { [weak self] in
      guard let `self` = self else { return }
      let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
      let gameOverScene = GameOverScene(size: self.size, won: false)
      self.view?.presentScene(gameOverScene, transition: reveal)
    }
    monster.run(SKAction.sequence([actionMove, loseAction, actionMoveDone]))
  }
  
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    // 1 - Choose one of the touches to work with
    guard let touch = touches.first else {
      return
    }
    run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))
    
    let touchLocation = touch.location(in: self)
    
    // 2 - Set up initial location of projectile
    let projectile = SKSpriteNode(imageNamed: "projectile")
    projectile.position = player.position
    
    projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
    projectile.physicsBody?.isDynamic = true
    projectile.physicsBody?.categoryBitMask = PhysicsCategory.projectile
    projectile.physicsBody?.contactTestBitMask = PhysicsCategory.monster
    projectile.physicsBody?.collisionBitMask = PhysicsCategory.none
    projectile.physicsBody?.usesPreciseCollisionDetection = true
    
    // 3 - Determine offset of location to projectile
    let offset = touchLocation - projectile.position
    
    // 4 - Bail out if you are shooting down or backwards
    if offset.x < 0 { return }
    
    // 5 - OK to add now - you've double checked position
    addChild(projectile)
    
    // 6 - Get the direction of where to shoot
    let direction = offset.normalized()
    
    // 7 - Make it shoot far enough to be guaranteed off screen
    let shootAmount = direction * 1000
    
    // 8 - Add the shoot amount to the current position
    let realDest = shootAmount + projectile.position
    
    // 9 - Create the actions
    let actionMove = SKAction.move(to: realDest, duration: 2.0)
    let actionMoveDone = SKAction.removeFromParent()
    projectile.run(SKAction.sequence([actionMove, actionMoveDone]))
  }
  
  func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
    print("Hit")
    projectile.removeFromParent()
    monster.removeFromParent()
    
    monstersDestroyed += 1
    if monstersDestroyed > 30 {
      let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
      let gameOverScene = GameOverScene(size: self.size, won: true)
      view?.presentScene(gameOverScene, transition: reveal)
    }
  }
}

extension GameScene: SKPhysicsContactDelegate {
  func didBegin(_ contact: SKPhysicsContact) {
    // 1
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
      firstBody = contact.bodyA
      secondBody = contact.bodyB
    } else {
      firstBody = contact.bodyB
      secondBody = contact.bodyA
    }
    
    // 2
    if ((firstBody.categoryBitMask & PhysicsCategory.monster != 0) &&
      (secondBody.categoryBitMask & PhysicsCategory.projectile != 0)) {
      if let monster = firstBody.node as? SKSpriteNode,
        let projectile = secondBody.node as? SKSpriteNode {
        projectileDidCollideWithMonster(projectile: projectile, monster: monster)
      }
    }
  }
}

 

2. GameViewController.swift

 

import UIKit
import SpriteKit

class GameViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let scene = GameScene(size: view.bounds.size)
    let skView = view as! SKView
    skView.showsFPS = true
    skView.showsNodeCount = true
    skView.ignoresSiblingOrder = true
    scene.scaleMode = .resizeFill
    skView.presentScene(scene)
  }
  
  override var prefersStatusBarHidden: Bool {
    return true
  }
}

 

3. GameOverScene.swift

 

import Foundation
import SpriteKit

class GameOverScene: SKScene {
  init(size: CGSize, won:Bool) {
    super.init(size: size)
    
    // 1
    backgroundColor = SKColor.white
    
    // 2
    let message = won ? "You Won!" : "You Lose :["
    
    // 3
    let label = SKLabelNode(fontNamed: "Chalkduster")
    label.text = message
    label.fontSize = 40
    label.fontColor = SKColor.black
    label.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(label)
    
    // 4
    run(SKAction.sequence([
      SKAction.wait(forDuration: 3.0),
      SKAction.run() { [weak self] in
        // 5
        guard let `self` = self else { return }
        let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
        let scene = GameScene(size: size)
        self.view?.presentScene(scene, transition:reveal)
      }
      ]))
  }
  
  // 6
  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

实现效果

下面看一下最终效果,我自己玩了一局!

后记

本篇主要讲述了创建一个简单的2D游戏,感兴趣的给个赞或者关注~~~



作者:刀客传奇
链接:https://www.jianshu.com/p/33f28911db17
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpriteKit框架详细解析(四) —— 创建一个简单的2D游戏(二) 的相关文章

随机推荐

  • 【计算机视觉】最后显示的CIFAR-100数据集照片很模糊怎么解决?

    文章目录 一 前言 二 如何解决 2 1 使用图像增强技术 2 2 使用插值方法 2 3 使用更高分辨率的图像数据集 2 4 手动调整图像尺寸 三 总结 一 前言 如果从CIFAR 100数据集加载的图像显示模糊 可能有几个可能的原因 分辨
  • 小程序中里的bindinput_微信小程序中input标签的使用方法(附代码)

    本篇文章给大家带来的内容是关于微信小程序中input标签的使用方法 附代码 有一定的参考价值 有需要的朋友可以参考一下 希望对你有所帮助 在开发过程中经常遇到这样的需求 用户只能输入数字并且只保留小数点两位 虽然我们可以在提交表单的时候进行
  • [ASM C/C++] C函数调用分析

    在执行程序时 操作系统为进程分配一块栈空间来保存函数栈帧 esp寄存器总是指向栈顶 x86平台上这个栈是从高地址向低地址增长的 每次调用一个函数都要分配一个栈帧来保存参数和局部变量 C函数参数是按从右到左的顺序入栈的 各个堆栈桢之间是通过把
  • 多线程(1):互斥锁

    leetcode 1114题 按序打印 给你一个类 public class Foo public void first print first public void second print second public void thi
  • 【Unity Shader】Shadow Caster、RenderType和_CameraDepthTexture

    当我们制作某些屏幕特效时 需要取到屏幕的深度图或法线图 比如ssao 景深等 另外像是制作软粒子shader 体积雾等也需要取到深度图 以计算深度差等 unity提供了两个内置的纹理 CameraDepthTexture和 CameraDe
  • fabric 环境快速搭建--Ubuntu20.04系统下使用fabric官方脚本搭建

    由于是初识hyper ledger fabric在安装的时候遇到了很多的问题 最后在师兄的帮助下终于删了从头到尾安装了一遍 因此想记录一下 并且给和我遇到相同问题的小伙伴提供一些帮助 如果你是萌新 找我就对啦 一 下载虚拟机VMware 直
  • 纯css画三角形及气泡样式的简单画法

    在做项目的过程中 遇到了要写一个气泡的样式 先布局了矩形部分 但小三角形的旗气泡遇到一点困难 后来梳理了一下 以此记录 首先是三角形的画法 三角形的原理就是矩形 然后分成四个三角形 一般使用border来画一个三角形 如下图所示 我们给一个
  • 云服务器木马文件该如何应对,云服务器木马入侵怎么办?服务器中木马怎么排查?...

    由于云服务器的扩展方便 并且能够减少硬件方面的维护成本 因此使用云服务器的用户数量越来越多 但是云服务器也同样存在安全方面的隐患 被入侵的案列也是时有发生 那么云服务器木马入侵怎么办 服务器中木马怎么排查 我们来了解下吧 云服务器木马入侵怎
  • java中抽象工厂模式_Java设计模式之抽象工厂模式

    转自http blog csdn net jason0539 article details 44976775 本文继续介绍23种设计模式系列之抽象工厂模式 前面已经介绍过简单工厂模式和工厂方法模式 这里继续介绍第三种工厂模式 抽象工厂模式
  • js倒计时

    html部分代码 div class time span 60 span span s span div js部分代码 var second document getElementById second var m 60 var time
  • 计算机产业能否迅速发展,工控机产业迅速发展:未来体系结构将更具交互性与可操作性...

    工控机 Industrial Personal Computer IPC 即工业控制计算机 是一种采用总线结构 对生产过程及机电设备 工艺装备进行检测与控制的工具总称 工控机具有重要的计算机属性和特征 如具有计算机CPU 硬盘 内存 外设及
  • 50行Python代码实现代理服务器的详细教程

    代理服务器是一种位于客户端与目标服务器之间的中间服务器 它可以代表客户端发送请求 并将响应返回给客户端 通过搭建自己的代理服务器 我们可以实现请求的拦截 修改和转发等功能 本文将为大家介绍如何使用50行Python代码实现代理服务器 一 准
  • [QT编程系列-37]:数据存储 - 日志文件、日志等级的支持:qDebug、Log4Qt

    目录 1 概述 2 qDebug 2 1 概述 2 2 qDebug对调试等级的支持 通过不同的宏来实现 2 3 qt日志等级的设置 1 概述 在 Qt 中 日志文件的支持通常是通过日志库 日志框架或自定义代码实现的 Qt 本身没有提供内置
  • vue3中实现音频播放器APlayer

    前言 vue2的时候 分享了一个很好用的插件是vue aplayer 但是他是不支持vue3的 这里分享vue3使用APlayer来实现一个播放器的方法 实现效果 官方 git地址 点我 api地址 点我 实现步骤 1 安装 npm npm
  • Spring Boot 过滤器、监听器和拦截器使用

    1 过滤器和监听器 Spring Boot中对于过滤器和监听器的使用跟一般web工程中使用方式没什么不同 使用注解方式就可以快速创建 只是要使用注解方式需要在Application类加上 ServletComponentScan 注解表明开
  • LIO-SAM运行自己数据包遇到的问题解决--SLAM不学无数术小问题

    LIO SAM 成功适配自己数据集 注意本文测试环境 Ubuntu18 04 ROS melodic版本 笔者用到的硬件以简单参数 激光雷达 速腾聚创16线激光雷达 RS Lidar 16 IMU 超核电子CH110型 9轴惯导 使用频率1
  • Ionic3开发教程 - 开发(2)

    Ionic3开发系列教程Ionic3开发教程 环境准备 1 Ionic3开发教程 开发 2 Ionic3开发教程 发布Android版本 3 Ionic3开发教程 发布IOS版本 4 Ionic3开发教程 更新 5 本文中介绍的Ionic3
  • mybatis在xml文件中处理大于号小于号的方法

    第一种方法 用了转义字符把 gt 和 lt 替换掉 然后就没有问题了 SELECT FROM test WHERE 1 1 AND start date lt CURRENT DATE AND end date gt CURRENT DAT
  • JAVA依赖冲突解决

    一 问题 启动时报错 二 原因 导入的包中存在依赖冲突 应该是打印日志的 三 解决办法 1 mvn dependency tree 打印项目的依赖树 2 安装MAVEN HELPER 2 1 查看依赖图 2 2 直接查看 四 解决 1 比如
  • SpriteKit框架详细解析(四) —— 创建一个简单的2D游戏(二)

    转自 https www jianshu com p 33f28911db17 版本记录 版本号 时间 V1 0 2017 08 12 前言 SpriteKit框架使用优化的动画系统 物理模拟和事件处理支持创建基于2D精灵的游戏 接下来这几