How to create progress bar in sprite kit?
我想在Sprite Kit中创建自己的进度条。
我想我需要图像 - 一个完全空的进度条和填充的进度条。
我有那些图像,我把它们放在空的一个上面,它们是常规的
如何在特定点剪切SKSpriteNode图像? 也许质地?
我建议调查SKCropNode。有关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @interface CustomProgressBar : SKCropNode /// Set to a value between 0.0 and 1.0. - (void) setProgress:(CGFloat) progress; @end @implementation CustomProgressBar - (id)init { if (self = [super init]) { self.maskNode = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(300,20)]; SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"progressBarImage"]; [self addChild:sprite]; } return self; } - (void) setProgress:(CGFloat) progress { self.maskNode.xScale = progress; } @end |
在你的场景中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #import"CustomProgressBar.h" // ... CustomProgressBar * progressBar = [CustomProgressBar new]; [self addChild:progressBar]; // ... [progressBar setProgress:0.3]; // ... [progressBar setProgress:0.7]; |
注意:此代码不会移动蒙版(因此精灵将在任何一侧被裁剪)但我相信你明白了。
很简单:你需要一个帧图像(可选)和一个"条形"图像。条形图像是单一的纯色,高达您需要的宽度和1或2像素宽。作为栏的SKShapeNode也可以。
只需更改SKSpriteNode的大小属性即可制作条形图和动画。例如,要使条形表示介于0和100之间的进度,只需执行以下操作:
1 | sprite.size = CGSizeMake(progressValue, sprite.size.height); |
每当progressValue更改时更新大小。
您会注意到图像的宽度会向左和向右增加,使其仅向右拉伸,将anchorPoint更改为左对齐图像:
1 | sprite.anchorPoint = CGPointMake(0.0, 0.5); |
就这些。在它周围画一个框架精灵,使它看起来更好。
我建立了一个小型库来处理这个确切的场景!这是SpriteBar:https://github.com/henryeverett/SpriteBar
假设
1 2 3 4 5 6 7 8 9 | - (void)setHealth:(CGFloat)fraction { self.health = MIN(MAX(0.0, fraction), 1.0); // clamp health between 0.0 and 1.0 SKTexture *textureFrac = [SKTexture textureWithRect:CGRectMake(0, 0, fraction, 1.0) inTexture:self.texture]; // check docs to understand why you can pass in self.texture as the last parameter every time self.size = CGSizeMake(fraction * _textureWidth, self.size.height); self.texture = textureFrac; } |
将
这是我在swift中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | import Foundation import SpriteKit class IMProgressBar : SKNode{ var emptySprite : SKSpriteNode? = nil var progressBar : SKCropNode init(emptyImageName: String!,filledImageName : String) { progressBar = SKCropNode() super.init() let filledImage = SKSpriteNode(imageNamed: filledImageName) progressBar.addChild(filledImage) progressBar.maskNode = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: filledImage.size.width * 2, height: filledImage.size.height * 2)) progressBar.maskNode?.position = CGPoint(x: -filledImage.size.width / 2,y: -filledImage.size.height / 2) progressBar.zPosition = 0.1 self.addChild(progressBar) if emptyImageName != nil{ emptySprite = SKSpriteNode.init(imageNamed: emptyImageName) self.addChild(emptySprite!) } } func setXProgress(xProgress : CGFloat){ var value = xProgress if xProgress < 0{ value = 0 } if xProgress > 1 { value = 1 } progressBar.maskNode?.xScale = value } func setYProgress(yProgress : CGFloat){ var value = yProgress if yProgress < 0{ value = 0 } if yProgress > 1 { value = 1 } progressBar.maskNode?.yScale = value } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } |
}
//如何使用 :
1 | let progressBar = IMProgressBar(emptyImageName:"emptyImage",filledImageName:"filledImage") |
要么
1 | let progressBar = IMProgressBar(emptyImageName: nil,filledImageName:"filledImage") |
并将此progressBar添加到任何
1 | self.addChild(progressBar) |
//就这样。
斯威夫特4
(我的回答3 - >旧的SpriteBar项目完全转换为swift)
要创建基于
我已经分叉并完全翻译了这个项目,这是源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | class SpriteBar: SKSpriteNode { var textureReference ="" var atlas: SKTextureAtlas! var availableTextureAddresses = Array<Int>() var timer = Timer() var timerInterval = TimeInterval() var currentTime = TimeInterval() var timerTarget: AnyObject! var timerSelector: Selector! init() { let defaultAtlas = SKTextureAtlas(named:"sb_default") let firstTxt = defaultAtlas.textureNames[0].replacingOccurrences(of:"@2x", with:"") let texture = defaultAtlas.textureNamed(firstTxt) super.init(texture: texture, color: .clear, size: texture.size()) self.atlas = defaultAtlas commonInit() } convenience init(textureAtlas: SKTextureAtlas?) { self.init() self.atlas = textureAtlas commonInit() } func commonInit() { self.textureReference ="progress" resetProgress() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func closestAvailableToPercent(_ percent:Int)->Int { var closest = 0 for thisPerc in self.availableTextureAddresses { if labs(Int(thisPerc) - percent) < labs(closest - percent) { closest = Int(thisPerc) } } return closest } func percentFromTextureName(_ string:String) -> Int? { let clippedString = string.replacingOccurrences(of:"@2x", with:"") let pattern ="(?<=\(textureReference)_)([0-9]+)(?=.png)" let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) let matches = regex?.matches(in: clippedString, options: [], range: NSRange(location: 0, length: clippedString.count)) // If the matches don't equal 1, you have done something wrong. if matches?.count != 1 { NSException(name: NSExceptionName(rawValue: String("SpriteBar: Incorrect texture naming.")), reason:"Textures should follow naming convention: \(textureReference)_#.png. Failed texture name: \(string)", userInfo: nil).raise() } for match: NSTextCheckingResult? in matches ?? [NSTextCheckingResult?]() { let matchRange = match?.range(at: 1) let range = Range(matchRange!, in: clippedString)! return Int(clippedString[range.lowerBound..<range.upperBound]) } return nil } func resetProgress() { self.texture = self.atlas.textureNamed("\(self.textureReference)_\(closestAvailableToPercent(0)).png") self.availableTextureAddresses = [] for name in self.atlas.textureNames { self.availableTextureAddresses.append(self.percentFromTextureName(name)!) } self.invalidateTimer() self.currentTime = 0 } func setProgress(_ progress:CGFloat) { // Set texure let percent: CGFloat = CGFloat(lrint(Double(progress * 100))) let name ="\(textureReference)_\(self.closestAvailableToPercent(Int(percent))).png" self.texture = self.atlas.textureNamed(name) // If we have reached 100%, invalidate the timer and perform selector on passed in object. if fabsf(Float(progress)) >= fabsf(1.0) { if timerTarget != nil && timerTarget.responds(to: timerSelector) { typealias MyTimerFunc = @convention(c) (AnyObject, Selector) -> Void let imp: IMP = timerTarget.method(for: timerSelector) let newImplementation = unsafeBitCast(imp, to: MyTimerFunc.self) newImplementation(self.timerTarget, self.timerSelector) } timer.invalidate() } } func setProgressWithValue(_ progress:CGFloat, ofTotal maxValue:CGFloat) { self.setProgress(progress/maxValue) } func numberOfFrames(inAnimation animationName: String) -> Int { // Get the number of frames in the animation. let allAnimationNames = atlas.textureNames let nameFilter = NSPredicate(format:"SELF CONTAINS[cd] %@", animationName) return ((allAnimationNames as NSArray).filtered(using: nameFilter)).count } func startBarProgress(withTimer seconds: TimeInterval, target: Any?, selector: Selector) { resetProgress() timerTarget = target as AnyObject timerSelector = selector // Split the progress time between animation frames timerInterval = seconds / TimeInterval((numberOfFrames(inAnimation: textureReference) - 1)) timer = Timer.scheduledTimer(timeInterval: timerInterval, target: self, selector: #selector(self.timerTick(_:)), userInfo: seconds, repeats: true) } @objc func timerTick(_ timer: Timer) { // Increment timer interval counter currentTime += timerInterval // Make sure we don't exceed the total time if currentTime <= timer.userInfo as! Double { setProgressWithValue(CGFloat(currentTime), ofTotal: timer.userInfo as! CGFloat) } } func invalidateTimer() { timer.invalidate() } } |
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | let progressBarAtlas = SKTextureAtlas.init(named:"sb_default") self.energyProgressBar = SpriteBar(textureAtlas: progressBarAtlas) self.addChild(self.energyProgressBar) self.energyProgressBar.size = CGSize(width:350, height:150) self.energyProgressBar.position = CGPoint(x:self.frame.width/2, y:self.frame.height/2) let wait = SKAction.wait(forDuration: 2.0) let action1 = SKAction.run { self.energyProgressBar.setProgress(0.7) } let action2 = SKAction.run { self.energyProgressBar.setProgress(0.0) } let action3 = SKAction.run { self.energyProgressBar.setProgress(1.0) } let action4 = SKAction.run { self.energyProgressBar.setProgress(0.5) } let action5 = SKAction.run { self.energyProgressBar.setProgress(0.1) } let action6 = SKAction.run { self.energyProgressBar.startBarProgress(withTimer: 10, target: self, selector: #selector(self.timeOver)) } let sequence = SKAction.sequence([wait,action1,wait,action2,wait,action3,wait,action4,wait,action5,wait,action6]) self.run(sequence) |
要了解更多详细信息,请在此处找到我的GitHUB仓库
我这样做了,它完美无缺。
首先,我宣布了一个SKSpriteNode:
1 2 3 4 5 6 7 8 9 10 | baseBar = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(CGRectGetMidX(self.frame)-40, self.frame.size.height/10)]; //The following will make the health bar to reduce from right to left //Change it to (1,0.5) if you want to have it the other way //But you'd have to play with the positioning as well [baseBar setAnchorPoint:CGPointMake(0, 0.5)]; CGFloat goodWidth, goodHeight; goodHeight =self.frame.size.height-(baseBar.frame.size.height*2/3); goodWidth =self.frame.size.width-(10 +baseBar.frame.size.width); [baseBar setPosition:CGPointMake(goodWidth, goodHeight)]; [self addChild:baseBar]; |
然后我为条形图添加了一个"框架",其中包含SKShapeNode,没有填充颜色(clearcolour)和笔触颜色:
1 2 3 4 5 | //The following was so useful SKShapeNode *edges = [SKShapeNode shapeNodeWithRect:baseBar.frame]; edges.fillColor = [UIColor clearColor]; edges.strokeColor = [UIColor blackColor]; [self addChild:edges]; |
当我想减少健康时,我做了以下事情:
1 2 3 4 5 6 7 8 9 | if (playerHealthRatio>0) { playerHealthRatio -= 1; CGFloat ratio = playerHealthRatio / OriginalPlayerHealth; CGFloat newWidth =baseBar.frame.size.width*ratio; NSLog(@"Ratio: %f newwidth: %f",ratio,newWidth); [baseBar runAction:[SKAction resizeToWidth:newWidth duration:0.5]]; }else{ // NSLog(@"Game over"); } |
简单,干净,并不复杂。
没有"切割"图像/纹理。
Cocos提供的替代方案是制作一些纹理,并根据健康状况将它们交换到您的节点中。我做了一个游戏,健康栏每10点改变一次纹理(范围是0-100)。经过一些试验和错误,我刚刚做了Cocos已经提出的建议。
斯威夫特4:
(我的回答2 - >使用纹理制作复杂的进度条)
要根据纹理和颜色制作复杂的进度条,您可以将一个简单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | class SKProgressImageBar: SKNode { var baseSprite: SKSpriteNode! var coverSprite: SKSpriteNode! var originalCoverSprite: SKSpriteNode! override init() { super.init() } convenience init(baseImageName:String="", coverImageName:String="", baseColor: SKColor, coverColor: SKColor, size: CGSize ) { self.init() self.baseSprite = baseImageName.isEmpty ? SKSpriteNode(color: baseColor, size: size) : SKSpriteNode(texture: SKTexture(imageNamed:baseImageName), size: size) self.coverSprite = coverImageName.isEmpty ? SKSpriteNode(color: coverColor, size: size) : SKSpriteNode(texture: SKTexture(imageNamed:coverImageName), size: size) self.originalCoverSprite = self.coverSprite.copy() as! SKSpriteNode self.addChild(baseSprite) self.addChild(coverSprite) self.coverSprite.zPosition = 2.0 } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setProgress(_ value:CGFloat) { print("Set progress bar to: \(value)") guard 0.0 ... 1.0 ~= value else { return } self.coverSprite.texture = self.originalCoverSprite.texture let originalSize = self.baseSprite.size var calculateFraction:CGFloat = 0.0 self.coverSprite.position = self.baseSprite.position if value == 1.0 { calculateFraction = originalSize.width } else if 0.01..<1.0 ~= value { calculateFraction = originalSize.width * value } let coverRect = CGRect(origin: self.baseSprite.frame.origin, size: CGSize(width:calculateFraction,height:self.baseSprite.size.height)) if let parent = self.parent, parent is SKScene, let parentView = (parent as! SKScene).view { if let texture = parentView.texture(from: self.originalCoverSprite, crop: coverRect) { let sprite = SKSpriteNode(texture:texture) self.coverSprite.texture = sprite.texture self.coverSprite.size = sprite.size } if value == 0.0 { self.coverSprite.texture = SKTexture() self.coverSprite.size = CGSize.zero } if value>0.0 && value<1.0 { let calculateFractionForPosition = originalSize.width - (originalSize.width * value) self.coverSprite.position = CGPoint(x:(self.coverSprite.position.x-calculateFractionForPosition)/2,y:self.coverSprite.position.y) } } } } |
用法:
一些纹理只是为了举个例子:
baseTxt.jpeg:
coverTxt.png:
码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | self.energyProgressBar = SKProgressImageBar.init(baseImageName:"baseTxt.jpeg", coverImageName:"coverTxt.png", baseColor: .white, coverColor: .blue, size: CGSize(width:200,height:50)) //self.energyProgressBar = SKProgressImageBar.init(baseColor: .white, coverColor: .blue, size: CGSize(width:200,height:50)) self.addChild(self.energyProgressBar) self.energyProgressBar.position = CGPoint(x:self.frame.width/2, y:self.frame.height/2) let wait = SKAction.wait(forDuration: 2.0) let action1 = SKAction.run { self.energyProgressBar.setProgress(0.7) } let action2 = SKAction.run { self.energyProgressBar.setProgress(0.0) } let action3 = SKAction.run { self.energyProgressBar.setProgress(1.0) } let action4 = SKAction.run { self.energyProgressBar.setProgress(0.5) } let action5 = SKAction.run { self.energyProgressBar.setProgress(0.1) } let sequence = SKAction.sequence([wait,action1,wait,action2,wait,action3,wait,action4,wait,action5]) self.run(sequence) |
输出:
有颜色:
带纹理:
斯威夫特4:
(我的回答1 - >快速简单的进度条)
要根据颜色创建一个简单的进度条,您可以在不使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class SKProgressBar: SKNode { var baseSprite: SKSpriteNode! var coverSprite: SKSpriteNode! override init() { super.init() } convenience init(baseColor: SKColor, coverColor: SKColor, size: CGSize ) { self.init() self.baseSprite = SKSpriteNode(color: baseColor, size: size) self.coverSprite = SKSpriteNode(color: coverColor, size: size) self.addChild(baseSprite) self.addChild(coverSprite) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setProgress(_ value:CGFloat) { print("Set progress bar to: \(value)") guard 0.0 ... 1.0 ~= value else { return } let originalSize = self.baseSprite.size var calculateFraction:CGFloat = 0.0 self.coverSprite.position = self.baseSprite.position if value == 0.0 { calculateFraction = originalSize.width } else if 0.01..<1.0 ~= value { calculateFraction = originalSize.width - (originalSize.width * value) } self.coverSprite.size = CGSize(width: originalSize.width-calculateFraction, height: originalSize.height) if value>0.0 && value<1.0 { self.coverSprite.position = CGPoint(x:(self.coverSprite.position.x-calculateFraction)/2,y:self.coverSprite.position.y) } } } |
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | self.energyProgressBar = SKProgressBar.init(baseColor: .white, coverColor: .blue, size: CGSize(width:200,height:50)) addChild(self.energyProgressBar) // other code to see progress changing.. let wait = SKAction.wait(forDuration: 2.0) let action1 = SKAction.run { self.energyProgressBar.setProgress(0.7) } let action2 = SKAction.run { self.energyProgressBar.setProgress(0.0) } let action3 = SKAction.run { self.energyProgressBar.setProgress(1.0) } let action4 = SKAction.run { self.energyProgressBar.setProgress(0.5) } let action5 = SKAction.run { self.energyProgressBar.setProgress(0.1) } let sequence = SKAction.sequence([wait,action1,wait,action2,wait,action3,wait,action4,wait,action5]) self.run(sequence) |
输出: