Under the SKSpriteNode class, you can do the following to animate an object
private func animate(){
var playerTextures:[SKTexture] = []
for i in 1...3 {
playerTextures.append(SKTexture(imageNamed: "image00\(i)"))
}
let playerAnimation = SKAction.repeatActionForever(
SKAction.animateWithTextures(playerTextures, timePerFrame: 0.1))
self.runAction(playerAnimation)
}
The above code can animate an object using the sequence of images. Here the image files would be image001.png image002.png image003.png
Here come's my question, how can I animate images if the file names are
image001.png
image002.png
...
image009.png
image010.png
image011.png
image012.png
The key point here is the problem with the zero paddings. Any ideas?
Assuming you will only have three digits number you could add your pictures that way:
for i in 1...3 {
let imageNumber = String(format: "%03d", i)
playerTextures.append(SKTexture(imageNamed: "image\(imageNumber)"))
}
This will give you image001, image002 and image003
This requires that you import Foundation at the beginning of your swift file
Related
For example, let's say I have 2 sprite nodes (but could also be more than 2) like this:
with each having their own separate images
What I want is to combine them and create a new sprite node with a single image (in tool mode)
like this:
Perhaps it's possible by using Image? (involving calculations no doubt)
or maybe something using VisualServer?
I found a solution,
tool
extends Sprite
export (Vector2) var img_size setget set_img_size
export (String) var img_path = "res://MergeImg.png"
export (bool) var generate_img = false setget set_generate_img
var ep_filesystem=EditorPlugin.new().get_editor_interface().get_resource_filesystem()
func set_img_size(new_val):
img_size=new_val
update()
func _draw():
draw_rect(Rect2(0,0,img_size.x,img_size.y), Color(0,200,0), false, 1.0)
func set_generate_img(new_val):
if(not new_val):
return
var screenshot_viewport=Viewport.new()
screenshot_viewport.size=img_size
screenshot_viewport.hdr=false
screenshot_viewport.transparent_bg=true
screenshot_viewport.render_target_v_flip=true
for child in get_children():
remove_child(child)
screenshot_viewport.add_child(child)
add_child(screenshot_viewport)
screenshot_viewport.set_update_mode(Viewport.UPDATE_ONCE)
yield(VisualServer,"frame_post_draw")
var mergeImg=screenshot_viewport.get_texture().get_data()
mergeImg.save_png(img_path)
for child in screenshot_viewport.get_children():
screenshot_viewport.remove_child(child)
add_child(child)
child.set_owner(get_tree().get_edited_scene_root())
screenshot_viewport.queue_free()
ep_filesystem.scan()
yield(ep_filesystem,"filesystem_changed")
texture=load(img_path)
centered=false
region_enabled=true
region_rect=Rect2(0,0,img_size.x,img_size.y)
print("Merged Images!")
func _init():
self_modulate=Color("#74646464")
once you press generate_img it creates an image like this:
The reason I added self_modulate=Color("#74646464") is so that there is no confusion between the merged image and the child sprites (like a ghost image)
Though I still feel like there might be a better solution, if so please post one
Edit:
For Centered offset
func _draw():
var origin_x=0
var origin_y=0
if(centered):
origin_x=-img_size.x/2
origin_y=-img_size.y/2
print("Redrawing")
draw_rect(Rect2(origin_x,origin_y,img_size.x,img_size.y), Color(0,200,0), false, 1.0)
func set_generate_img(new_val=true):
if(not new_val):
return
var screenshot_viewport=Viewport.new()
screenshot_viewport.size=img_size
screenshot_viewport.hdr=false
screenshot_viewport.transparent_bg=true
screenshot_viewport.render_target_v_flip=true
if(centered):
screenshot_viewport.global_canvas_transform.origin=Vector2(img_size.x/2,img_size.y/2)
for child in get_children():
remove_child(child)
screenshot_viewport.add_child(child)
add_child(screenshot_viewport)
screenshot_viewport.set_update_mode(Viewport.UPDATE_ONCE)
yield(VisualServer,"frame_post_draw")
var mergeImg=screenshot_viewport.get_texture().get_data()
mergeImg.save_png(img_path)
for child in screenshot_viewport.get_children():
screenshot_viewport.remove_child(child)
add_child(child)
child.set_owner(get_tree().get_edited_scene_root())
screenshot_viewport.queue_free()
ep_filesystem.scan()
yield(ep_filesystem,"filesystem_changed")
texture=load(img_path)
region_enabled=true
region_rect=Rect2(0,0,img_size.x,img_size.y)
Edit 2:
Fix for darker transparent images, thanks a million to Theraot's answer (upvote it!)
tool
extends Sprite
export (Vector2) var img_size setget set_img_size
export (String) var img_path = "res://MergeImg.png"
export (bool) var generate_img = false setget set_generate_img
var ep_filesystem=EditorPlugin.new().get_editor_interface().get_resource_filesystem()
func set_img_size(new_val):
img_size=new_val
update()
func _draw():
var origin_x=0
var origin_y=0
if(centered):
origin_x=-img_size.x/2
origin_y=-img_size.y/2
print("Redrawing")
draw_rect(Rect2(origin_x,origin_y,img_size.x,img_size.y), Color(0,200,0), false, 1.0)
func set_generate_img(new_val=true):
if(not new_val):
return
var screenshot_viewport=Viewport.new()
screenshot_viewport.size=img_size
screenshot_viewport.hdr=false
screenshot_viewport.transparent_bg=true
screenshot_viewport.render_target_v_flip=true
if(centered):
screenshot_viewport.global_canvas_transform.origin=Vector2(img_size.x/2,img_size.y/2)
for child in get_children():
remove_child(child)
screenshot_viewport.add_child(child)
add_child(screenshot_viewport)
screenshot_viewport.set_update_mode(Viewport.UPDATE_ONCE)
yield(VisualServer,"frame_post_draw")
var mergeImg=screenshot_viewport.get_texture().get_data()
### unmultiplication ###
mergeImg.lock()
for y in mergeImg.get_size().y:
for x in mergeImg.get_size().x:
var color:Color = mergeImg.get_pixel(x, y)
if color.a != 0:
mergeImg.set_pixel(x, y, Color(color.r / color.a, color.g / color.a, color.b / color.a, color.a))
mergeImg.unlock()
###
mergeImg.save_png(img_path)
for child in screenshot_viewport.get_children():
screenshot_viewport.remove_child(child)
add_child(child)
child.set_owner(get_tree().get_edited_scene_root())
screenshot_viewport.queue_free()
ep_filesystem.scan()
yield(ep_filesystem,"filesystem_changed")
texture=load(img_path)
func _init():
self_modulate=Color("#74646464")
I haven't write code for this But I want some help that can it be possible to code so that it shows new photo everyday. for example I have 10 images and I want to display each image for like September 16,17,18, and so on ?
Here you go...
Call method getImageForToday() where ever you want to fetch image for today (This solution will work specific to your case, i.e. as you mentioned you have 10 images)
func getImageForToday() -> UIImage
{
let arrImages = ["image1.png", "image2.png" ... "image10.png"]
var imageName = arrImages[getLastCharacterFromTodayDate()]
return UIImage(named: imageName)
}
func getLastCharacterFromTodayDate() -> Int
{
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let component = calendar.components(NSCalendarUnit.CalendarUnitDay, fromDate: NSDate())
return component.day % 10
}
You can use the object NSDate to know what day is it and after that doing something.
First you have your dictionnary of image like that :
let images = [ UIImage(named: "image1.png")!, UIImage(named: "image2.png")!, UIImage(named: "image3.png")]
so now, you must know what day is it for the first app openning in exemple.
So you can do that :
let date = NSDate()
You put the element date in UserDefaults. (in order to save it)
You can now display a image. You make a random number and you take UIImage at this random position.
The day after, or the next app opening you recheck the NSDate with the NSDate saved and if it's different you take a other photo.
I am using SceneKit to load a Collada (.dae) file.
Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.
Here's the loading code where sceneView is my SCNView:
//Load the scene and play
let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
The scene loads correctly and the animation plays on loading as required, but in a continuous loop.
I have tried removing all animations as follows:
sceneView.scene?.rootNode.removeAllAnimations()
but this seems to have no effect.
I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0
let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
let sceneSource = SCNSceneSource(URL:url!, options: nil)
let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)
if let ids = animationIndentifiers
{
for id in ids {
let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
animation.repeatCount = 1
}
}
I have also tried this another way:
let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
animation?.repeatCount = 1
}
}
but again no luck.
I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.
Update:
I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:
//Get the child nodes
let children = scene?.rootNode.childNodes
//Initialise a childNode counter
var childCount = 0
//Iterate through the child nodes
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
//Iterate through the animations
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
println("Initial Repeat count: \(animation.repeatCount)")
animation.repeatCount = 1
//Remove existing animation
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
//Add amended animation
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)
The above shows Initial Repeat count: inf
and on checking afterwards it is indeed set to 1.
However, the animation still runs in an infinite loop :-(
Any help to resolve this would be much appreciated.
Further update
I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:
func sceneSetup() {
let scene = SCNScene(named: "Scenes.scnassets/test10.dae")
let children = scene?.rootNode.childNodes
var childCount = 0
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
animation.repeatCount = 1
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
}
if anybody can explain that would be great!
Ah, but there's another problem!
Here's the start frame of the animation:
and here's the end frame where we want to end up:
But at the end of the animation the scene jumps back to the start view.
I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.
For the animation jumping back to the first frame, the isAppliedOnCompletion value is what you are looking for.
animation.repeatCount = 1
animation.isAppliedOnCompletion = true
This will make sure that the animation pauses on the final frame.
I know this is an old question, but I came across this same problem and found a solution for the jumping back to the beginning issue. If you set the animation to not be removed on completion, the object should stay at the end location:
if let animation = child.animationForKey(child.animationKeys.first!) {
animation.repeatCount = 1
animation.removedOnCompletion = false
...
This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.
Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.
Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada).
func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
onlyAnimateThisNodeOnce(node)
for childNode in node.childNodes {
animateEntireNodeTreeOnce(mostRootNode: childNode)
}
}
func onlyAnimateThisNodeOnce(_ node: SCNNode) {
if node.animationKeys.count > 0 {
for key in node.animationKeys {
let animation = node.animation(forKey: key)!
animation.repeatCount = 1
animation.isRemovedOnCompletion = false
node.removeAllAnimations()
node.addAnimation(animation, forKey: key)
}
}
}
I Agree with #Phil Dudas and I like to add also this workaround to Prevent The Animation from Start By Default By Reducing Speed to Zero, When you set Speed to Zero it will remain at First state,
nodeAnimation.speed = 0
animation.repeatCount = 1
nodeAnimation.animation.isRemovedOnCompletion = false
I'm trying to make a random image appear on the screen, but I'm new to Swift and am not sure how to do this. I have three images which I want have randomly shown in the image view, when the app is opened.
How do I do this?
Generate a ramdom number from 0 to 2 and show the image by randomly generated number.
var random = arc4random_uniform(3) //returns 0 to 2 randomly
switch random {
case 0:
//show first image
case 1:
//show second image
default:
//show third image
}
If the images are named basically the same thing. For example, "Image1.png, Image2.png, and Image3.png, then you can use this code:
override func viewDidLoad() {
super.viewDidLoad()
ImageView.image = UIImage(named: "Image\(arc4random_uniform(3) + 1).png")
}
imageArr = ["1.jpeg","2.jpeg","3.jpeg","4.jpeg"]
let RandomNumber = Int(arc4random_uniform(UInt32(self.imageArr.count)))
//imageArr is array of images
let image = UIImage.init(named: "\(imageArr[RandomNumber])")
let imageView = UIImageView.init(image: image)
It works for me (Swift 4.2):
let images: [UIImage] = [ #imageLiteral(resourceName: "randomImage1"),
#imageLiteral(resourceName: "randomImage2"),
#imageLiteral(resourceName: "randomImage3")]
let randomImage = images.shuffled().randomElement()
I've got some code that runs a fairly complex algorithm. I want to put together a fairly simple UI that will allow me to monitor the values of the various variables in the algorithm in some graphical ways -- think of it like a dashboard of sorts.
So, for simplicity's sake, let's say I have an algorithm like what follows. It searches a vector of values for the two values that most closely sum to a target value:
import Foundation
class algorithm {
var numbers = [Double]()
let numberOfRandoms = 1000
dynamic var a: String
dynamic var b: String
init () {
// Load initial vector with some random numbers between 0 and 1
for _ in 1...numberOfRandoms {
numbers.append(Double(arc4random()) / Double(UINT32_MAX))
}
a = " "
b = " "
}
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double) {
//Initializing this to a very large value
var currentBestSum = 1000.0
//Begin brute force search for the optimal solution
for i in 0...numbers.count-2 {
for j in i+1...numbers.count-1 {
//Check to see if the current candidate exceeds the best solution
if abs(numbers[i] + numbers[j] - target) < currentBestSum {
//If it does, store the new champion
a = String(i)
b = String(j)
//And reset the current top score to match
currentBestSum = abs(numbers[i] + numbers[j]-target)
}
}
}
}
}
Now, this is just a simple (and silly) example, but it suits these purposes. I basically want to create a simple UI that displays the important values in the process as it runs (dynamically).
In this example, let's say that I just want to display two labels that contain the index values of the two leaders as the algorithm executes.
I created the labels in the storyboard.
Then, I created IBOutlets in the ViewController (Actually, storyboards did it for me when I Ctrl-dragged):
class ViewController: NSViewController {
#IBOutlet weak var a: NSTextField!
#IBOutlet weak var b: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Then, I ctrl-dragged the labels to the a and b in the algorithm class to create the bindings.
Finally, I create an class variable in the view controller and instantiate it in the viewDidLoad method. This doesn't seem like the right thing to do -- maybe it is. Seems like you would want to keep separate your interface and data...
The labels do, in fact, show up -- but they never show any values of a and b. They just show the default text.
Not sure what I'm doing wrong.
Help!?
P.S., in response to Anthony Kong, I do recognize that I could manually synchronize all the view elements in the code, but I thought the whole point of using bindings was to avoid having to do this manual synchronization. I just can't figure out how to set it up.
Without commenting on your specific code I think I have experienced (and solved) the problem you describe. I was able to write an app that had two targets, one NIB-based and one Storyboard-based. As much as I was able I duplicated the code in each and shared the common data instance that I was trying to display in a TableView. The NIB-based app worked using the stock Cocoa Bindings that I set in IB. But the Storyboard-based app did not, the array controller did not see the data.
My solution was simply to add the binding for contentArray programmatically in viewDidLoad. The one line that fixed it for me is:
ac.bind("contentArray", toObject: cd, withKeyPath: "people", options: nil)
ac is the IBOutlet for the ArrayController in the Storyboard. cd is the class instance that contains the people array.
This is using XCode 6.2 (6C107a) which is Beta 3 I think.
This was the only binding that I had to set myself, the TableView to ArrayController (arrangedObjects) and TableViewCell to TableView (objectValue) didn't need any tweaking.
There are several problems with your code
1) In your code,
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
var test = algorithm()
test.findTheTwoNumbersThatAddUpTheClosestToTarget(0.5)
}
The variable test goes out of scope when the function exits. Based on the wording of your question, you are expecting it to be a long running process. So this will not do what you want.
You should
a) Add test as class variable to the ViewController class
b) instantiate the variable in viewDidLoad method.
2) In your algorithm it does not actually provide any feedback to the labels. Maybe you think because the class has the ivar a and b so they are hooked to the IBOutlet by the same names. But of course it is not the case. And you do not need the keyword dynamic too.
What you should do is:
a) provide a method in the View Controller class to update the labels. It will serve as a callback function to be used by algorithm class to feedback the calculation result.
It may look like this:
func update_value_callback(vala: String, valb: String) {
a.text = vala; // updating the label here
b.text = valb;
}
b) make the algorithm class calls the callback function e.g.
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double, viewController: ViewController) {
// do your stuff
...
// execute the callback
viewController.update_value_callback(a, b)
}