Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/121.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何在精灵套件中创建进度条?_Ios_Sprite Kit_Progress Bar - Fatal编程技术网

Ios 如何在精灵套件中创建进度条?

Ios 如何在精灵套件中创建进度条?,ios,sprite-kit,progress-bar,Ios,Sprite Kit,Progress Bar,我想在精灵套件中创建我自己的进度条。 我想我需要一个完整的图片-一个完全空的进度条和一个填充的进度条。 我有这些图像,我把填充的图像放在空图像的上面,它们是常规的SKSPriteNodes现在我不知道如何在需要的地方剪切填充的图像 如何在某个点切割SKSpriteNode图像?可能是纹理?非常简单:您需要一个帧图像(可选)和一个“条形”图像。条形图显示为单色、单色、所需高度和1或2像素宽。像酒吧一样的酒吧也可以 只需制作条并设置动画,只需更改SKSpriteNode的size属性即可。例如,要使

我想在精灵套件中创建我自己的进度条。
我想我需要一个完整的图片-一个完全空的进度条和一个填充的进度条。
我有这些图像,我把填充的图像放在空图像的上面,它们是常规的
SKSPriteNodes
现在我不知道如何在需要的地方剪切填充的图像


如何在某个点切割SKSpriteNode图像?可能是纹理?

非常简单:您需要一个帧图像(可选)和一个“条形”图像。条形图显示为单色、单色、所需高度和1或2像素宽。像酒吧一样的酒吧也可以

只需制作条并设置动画,只需更改SKSpriteNode的size属性即可。例如,要使条形图表示0到100之间的进度,只需执行以下操作:

sprite.size = CGSizeMake(progressValue, sprite.size.height);
每当值更改时更新大小

您会注意到图像的左右宽度都将增加,以使其仅向右拉伸将锚点更改为左对齐图像:

sprite.anchorPoint = CGPointMake(0.0, 0.5);
仅此而已。在它周围画一个框架精灵,使它看起来更漂亮。

没有“切割”图像/纹理

Cocos提供的另一种选择是制作一些纹理,并根据运行状况将它们交换到节点中。我做了一个游戏,健康栏每10点改变一次纹理(范围是0-100)。但经过一些尝试和错误,我最终还是按照Cocos的建议做了


假设
healthbanode
SKSpriteNode
的一个子类,其公共属性
health
在0.0和1.0之间变化,其父属性
纹理
是从宽度
\u textureWidth
的整个彩色条图像(私有属性)生成的,你可以这样做:

- (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;
}

将“运行状况”设置为新值将导致运行状况栏(例如,作为主场景的子对象添加到主场景)被正确裁剪。

我构建了一个小型库来处理这种情况!这是SpriteBar:

我建议您调查一下。有关
SKCropNode
工作原理的直观帮助,请在中查找。我已经阅读了整个文件多次,这是一个特别好的阅读

SKCropNode
基本上是一个SKNode,可以添加到场景中,但其子节点可以通过遮罩进行裁剪。此掩码在SKCropNode的
maskNode
属性中设置。这样,您只需要一个纹理图像。我将子类SKCropNode来实现移动或调整遮罩大小的功能,这样您就可以轻松地更新其外观

@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
在您的场景中:

#import "CustomProgressBar.h"

// ...

CustomProgressBar * progressBar = [CustomProgressBar new];
[self addChild:progressBar];

// ...

[progressBar setProgress:0.3];

// ...

[progressBar setProgress:0.7];

注意:此代码不会移动遮罩(因此精灵将在两侧被裁剪),但我相信您会明白这一点。

我就是这样做的,它工作得非常完美

首先,我声明了一个SKSpriteNode:

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,没有填充颜色(ClearColor),还有一个笔划颜色:

//The following was so useful
SKShapeNode *edges = [SKShapeNode shapeNodeWithRect:baseBar.frame];
edges.fillColor = [UIColor clearColor];
edges.strokeColor = [UIColor blackColor];
[self addChild:edges];
当我想减少健康时,我做了以下几点:

    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");
    }

简单,干净,一点也不复杂

这是我在swift中的
ProgressBar

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")
}

并将此进度条添加到任何
SKNode

self.addChild(progressBar)
//就这些

Swift 4: (我的答案1->制作一个快速简单的进度条)

要根据颜色制作一个简单的进度条,您可以使用
SKCropNode
对简单的
SKNode
进行子类化,而不使用
SKCropNode

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)
        }
    }
}
输出

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)
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)

Swift 4: (我的答案2->使用纹理制作复杂的进度条)

要根据纹理和颜色制作复杂的进度条,可以将简单的
SKNode
子类化。关于这种情况,
SpriteKit
目前(swift v4.1.2)没有直接切割
SKTexture
的方法。我们需要使用另一种称为
纹理(from:crop:)

输出

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)
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)
颜色:

使用纹理:

Swift 4 (我的答案3->旧的项目完全翻译成swift)

要根据
SKTextureAtlas
制作进度条,可以使用Henry Everett制作的名为SpriteBar的Objective C项目

我已经对这个项目进行了详细的翻译,这是来源:

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()
    }
}

要了解更多详细信息,您可以找到myGitHUBrepo

一个使用两个sprite节点的简单类

class PBProgressBar: SKNode {
private var baseNode : SKSpriteNode!
private var progressNode : SKSpriteNode!
private var basePosition: CGPoint!
var progress: CGFloat!
init(progress: CGFloat = 0.45, position: CGPoint = CGPoint.zero) {
    super.init()
    self.progress = progress
    self.basePosition = position
    configureProgress()
}
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
func configureProgress() {
    baseNode = SKSpriteNode(color: .white, size: CGSize(width: 10, height: 100))
    baseNode.anchorPoint = CGPoint.zero
    let heightFraction = baseNode.size.height * progress
    baseNode.position = basePosition
    progressNode = SKSpriteNode(color: .blue, size: CGSize(width: 10, height: heightFraction))
    progressNode.anchorPoint = CGPoint.zero
    baseNode.addChild(progressNode)
    self.addChild(baseNode)
}

}

是的,如果我有纯色和方形条,这很好,但如果我有复杂条怎么办?就想剪掉它?是否有任何方法可以剪切或操作Sprite Kit中的纹理?您应该能够通过调用
+(SKTexture*)TextureDirect:inTexture:
来获取节点纹理的子集(假定为整个颜色条)。另一种方法是使用
SKCropNode
作为此选项的另一个选项,并将其发送。:)我用这种方法得到垃圾来代替纹理。我试着改变不同的值,但同样,原始纹理是黄色的,我得到了一些蓝色和灰色的垃圾。@Dvole我能告诉你什么,它对我有用。我猜你做错了什么。有一种方法可以剪切图像,请参考我的答案
SKCropNode
。SKCropNode没有mask属性,所以我假设这应该是maskNode?现场!我已经编辑了答案以反映您的改进。您好,很抱歉问这个问题,但此代码确实显示了带有
progressBarImage
图像的精灵,但是
spriteNodeWithColor:[SKColor whiteColor]
没有任何作用,为什么?我正在ios9中开发。如何使
maskNode
与图像节点
sprite
的大小相同?这样我们就不需要手动指定
(300,20)
。@FranklinYu Solid idea-在这个例子中
(300,20)
是幻数。您可以在此处阅读有关魔法数字的内容:)
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)
class PBProgressBar: SKNode {
private var baseNode : SKSpriteNode!
private var progressNode : SKSpriteNode!
private var basePosition: CGPoint!
var progress: CGFloat!
init(progress: CGFloat = 0.45, position: CGPoint = CGPoint.zero) {
    super.init()
    self.progress = progress
    self.basePosition = position
    configureProgress()
}
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
func configureProgress() {
    baseNode = SKSpriteNode(color: .white, size: CGSize(width: 10, height: 100))
    baseNode.anchorPoint = CGPoint.zero
    let heightFraction = baseNode.size.height * progress
    baseNode.position = basePosition
    progressNode = SKSpriteNode(color: .blue, size: CGSize(width: 10, height: heightFraction))
    progressNode.anchorPoint = CGPoint.zero
    baseNode.addChild(progressNode)
    self.addChild(baseNode)
}