I went through the docs and couldn't find anything.
Is it possible to create a custom transition?
I have to simulate a throw with custom easing etc.
In cocos2d I was able to just create a custom action, with corona I'm puzzled.
The docs are quite silent on the topic. However, if you do some trying out, you will find out that a custom easing function takes four parameters (only nil from the fifth parameter and up). Some playing around reveals that a custom easing function looks like this:
function easer(currentTime, duration, startValue, targetDelta)
...
end
Explanation
currentTime / duration: duration is self-explanatory, and currentTime is simply [0..duration] during the course of the transition.
startValue: Snapshot of the value at the beginning of the transition.
targetDelta: This is the value of the target-value, minus the startValue, i.e. the distance the easing-function "has to walk"
Annotated Example
Say you have the following code:
foo.frob = 1
transition.to(foo, {time=1001, frob=0.25})
I.e., you want a transition of foo.frob of [1..0.25]. Then:
function easer(currentTime, duration, startValue, targetDelta)
-- currentTime: [0..1001]
-- duration: 1001
-- startValue: 1
-- targetDelta: to-from = 0.25-1 = -0.75
return startValue + (currentTime/duration) * targetDelta -- a linear interpolator
end
Return Value
The return value should be startValue in the beginning, and startValue+targetDelta at the end. It is perfectly allowed to leave that range; however, once the transition stops, it automatically becomes startValue+targetDelta, so make it startValue and startValue+targetDelta at the beginning and end.
But you can be creative between the start and end, and let the function bounce back and forth, for example, like some of the included easing functions already do.
Well the answer is in the docs.
One of the params to transition.to is easing, which can be a custom function.
Related
I have a game which is running well but as soon as I introduce a while loop my entire UI goes blank.
I have some code which generates a sprite from an array and moves it down the screen
func addALetter() {
let randomX = CGFloat.random(in: 50...size.width - 50)
let shrink = SKAction.scale(to: 0.1, duration: 0.01)
let grow = SKAction.scale(to: 1, duration: 0.5)
let wait = SKAction.wait(forDuration: 0.7)
let spawn = SKAction.move(to: CGPoint(x: randomX, y: size.height - tileSize), duration: 0.001)
let move = SKAction.moveTo(y: -500, duration: 7)
let sequence = SKAction.sequence([shrink, spawn, grow, move, wait])
// scroll through the lettersArray
if activeLetter < lettersArray.count - 1 {
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
activeLetter += 1
} else {
// go back to first in letter array
activeLetter = 0
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
}
}
It is working fine triggered using an SKAction in my didMove to view run(SKAction.repeatForever(SKAction.sequence([SKAction.run(addALetter), SKAction.wait(forDuration: spawnTime)])))
but I have problems with that as I get to the end of the array because the action repeats too frequently making sprites disappear before I want them too.
So I tried using a while loop instead...
while gameOn == true {
addALetter()
}
to repeat the action. But then I get a completely blank UI - I assume because it's then stuck in the loop there's no time to update the UI?
Looking for a solid way to repeat the function that I can vary the frequency as the array gets to low numbers
It seems likely that the problem is that spawnTime is small enough that you're wrapping around through the letters too quickly and start doing bubbles[activeLetter].removeAllActions() before the bubble's previous action sequence has finished.
I think the most best way to deal with this sort of situation is to coordinate through completion blocks. I.e., use the run method with a closure to be called after the action finishes, https://developer.apple.com/documentation/spritekit/sknode/1483103-run. That way you don't wind up trying to adjust delays explicitly in an effort to keep actions from being prematurely cancelled. The completion blocks will update state that you can use to coordinate, or they can run other actions directly.
It's not clear from your question what behavior you want. But, e.g., you might have addALetter set a flag for the bubble when it starts the action sequence and include a completion block for the sequence to clear the flag. Then before addALetter restarts the sequence for a bubble it can make sure that the flag is clear; if it's not (the bubble is still running the previous sequence), just return without doing anything.
I am using tilemaps and animated tiles from the 2dExtras in unity.
My tiles have 6 frames, at speed=2f, and my tilemap frame rate is 2.
New tiles placed always start on frame 1 and then immediately jump to the current frame of the other tiles already placed, the tilemap is keeping every tile at the same pace, which is working as I want.
However I would like the newly placed tiles to start at the frame the others are currently on,(instead of placing a tile that jumps from frame 1 to frame 4) I would like the new tile to start on frame 4
I've found how to pick the frame I want to start on, however I am having trouble retrieving which frame the animation is currently on, so I was wondering how exactly can I access the current frame of animation of a given tilemap ( Or a given tile, I can create a dummy tile and just read the info out of it, how can I get the current frame of an animated tile? )
The animated tilemaps feature seems to lack the feature to retrieve this information, also when I try tilemap.getsprite it always returns the first frame of the sequence(does not return the sprite currently displayed), and there doesn't seem to be any method to poll info from tilemap.animationFrameRate.
I thought another method would be to set a clock and sync it to the rate of the animation but since I can't get the exact framerate duration the clock eventually goes out of sync.
Any help would be appreciated!
I found a way to solve this question. But it's not 100% insurance.
First of all, I used SuperTile2Unity. That doesn't seem to be the point.
private void LateUpdate()
{
// I use this variable to monitor the run time of the game
this.totalTime += Time.deltaTime;
}
private void func()
{
// ...
TileBase[] currentTiles = tilemap.GetTilesBlock(new BoundsInt(new Vector3Int(0, 0, 0), new Vector3Int(x, y, 1)));
Dictionary<string, Sprite> tempTiles = new Dictionary<string, Sprite>();
//I use SuperTiled2Unity. But it doesn't matter, the point is to find animated tile
foreach (SuperTiled2Unity.SuperTile tile in currentTiles)
{
if (tile == null)
{
continue;
}
if (tile.m_AnimationSprites.Length > 1 && !tempTiles.ContainsKey(tile.name))
{
// find animated tile current frame
// You can easily find that the way SuperTile2Unity is used to process animation is to generate a sprite array based on the time of each frame set by Tiled animation and the value of AnimationFrameRate parameter.
// The length of array is always n times of AnimationFrameRate. You can debug to find this.
tempTiles.Add(tile.name, tile.m_AnimationSprites[GetProbablyFrameIndex(tile.m_AnimationSprites.Length)]);
}
}
//...
}
private int GetProbablyFrameIndex(int totalFrame)
{
//According to the total running time and the total length of tile animation and AnimationFrameRate, the approximate frame index can be deduced.
int overFrameTime = (int)(totalTime * animationFrameRate);
return overFrameTime % totalFrame;
}
I have done some tests. At least in 30 minutes, there will be no deviation in animations, but there may be a critical value. If the critical time is exceeded, there may be errors. It depends on the size of AnimationFrameRate and the accumulation mode of totalTime. After all, we don't know when and how the unity deals with animatedTile.
You could try using implementation presented in [1] which looks as follows:
MyAnimator.GetCurrentAnimatorClipInfo(0)[0].clip.length * (MyAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime % 1) * MyAnimator.GetCurrentAnimatorClipInfo(0)[0].clip.frameRate;
[1] https://gamedev.stackexchange.com/questions/165289/how-to-fetch-a-frame-number-from-animation-clip
I wrote a function which, on zoomstart, (should) do_something1 if I zoomIn and do_something2 when I zoomOut:
'var prevzoom = map.getZoom;
map.on('zoomstart', do_something);
function do_something(e){
var currzoom = map.getZoom();
var diff = currzoom - prevzoom;
if(diff > 0){do_something1}
else {do_something2}
prevzoom = currzoom;'
It behaves as follows:
1)when loading, if I zoomOut it do_something2 (and the second, third... time as well
2)when loading if I zoomIn it do_something2 (uncorrectly) but it do_something1 the second, third... time and so on...
3) when I change from ZoomIn to ZoomOut and viceversa the first time it behaves as the previous time and it behaves correctly on the second, third...time when zooming in the same way (i.e. In or Out).
I read in the post "Detecting if is zoom-in or zoom-out with leaflet" its it not possible to know if zoomIn or Out when the event zoomstart is triggered.
In my case it seems to work most of the time (which is not good anyway).
What I am missing/did wrong?
I have no knowledge in coding: thank you for help.
Giuseppe
Running Xcode 8.2 on a Swift Playground. It's also making Xcode not respond. This is the error I'm getting.
Context leak detected, msgtracer returned -1
I've traced the problem to these lines of code
while (true){
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 1)
crank.run(rotate)
}
When I comment out the while loop and leave just the inside it works fine.
The while (true){ part will make that loop execute forever and your code probably will not proceed beyond that point. If you want an action to repeat forever there is an SKAction method for that - take a look at repeat.
If you remove the while (true){ condition and instead use a repeat action, I believe your code should run fine.
#Fahim is 100% correct, by the code posted you are stuck in an endless loop. So the question for you is how to get out of it?
The question for you is - how to make this loop do what you need. I'll guess that you want to change the condition to something like while (someVar == true) and put in a condition inside your loop to change someVar to false. But only you know what that condition is. The syntax would be:
let someVar = true
while (someVar == true){
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 1)
crank.run(rotate)
if someCondition == isMet { // THIS IS FOR YOU TO DECIDE!
someVar = false
}
}
I ended up rewriting it completely and using SKAction.repeatForever to run an SKAction (to rotate) and that terminated only if the condition was false.
crank.run(
SKAction.repeatForever (
SKAction.sequence([
SKAction.wait(forDuration: 0.1),
SKAction.run({
let angle = atan2(self.point.y - crank.position.y , self.point.x - crank.position.x)
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 0.25)
crank.run(rotate)
if(!(crank.frame.contains(self.point))) {
self.removeAction(forKey: "New Thread")
}
})
])
),
withKey: "New Thread"
)
I think the existing answers are missing the point here. You have two "threads" you are dealing with: the active thread running the routine (which will keep pumping new rotate actions onto the actions list for the node as fast as it can) and the action processing thread (which is really not a "thread" per se but can be imagined as one for the purpose of this question) which is where the rotate actions are actually executed.
Essentially, in a given timeslice, the "main" execution thread might pump 100k "rotate" actions onto your node's action list, and the SpriteKit engine will process all of them at the same time as efficiently as it can (I believe by removing duplicate actions but I'm not positive on that).
The effect is that you are doing a lot more work than you have to, and burning CPU in both your spin loop and in the SKAction processing code trying to recover from the millions of actions added to the node every time it comes up to process.
Replace it with a repeat action, as Fahim suggested, and you'll be good. Then you just need a trigger of some sort to stop the action (give it a name to end just the one action, or stop all actions on this node when the trigger fires).
I solve this problem using more singletong. For example SKView() inside the loop in singletong.
I'm trying to write a little app, where on the main screen, I animate a flying "bubble". This animation has to be continuous. (I reuse the bubbles, which fly off the screen) I heard that animations have to run on the main thread, as does every operation which changes the UI. Is this true? When I try to show a UIAlertView on this screen, it's animation becomes very discursive because of the continuous bubble animation. (this is a custom alertview with an indicator) The device is an iPhone 4, so I don't think it should be a problem to show a normal UIAlertView.
And I would like to ask if I use the correct method for the bubble animation. So first of all, I use an NSTimer, which invokes the startAnimation method in every 0.01 seconds (I start it in the controller's viewDidAppear: method). In the startAnimation method, at first I generate bubbles with random x and y coordinates (to see bubbles on the screen right after the viewdidappear), and I generate bubbles on the bottom with random x and y = 460 coordinates. In the startAnimation method, I run a counter (called frames), and when the value of this counter equals 35, I call the bubble generate method again.
The problem:
I store the generated bubbles in an array, and the 'gone' bubbles (which are off the screen) in another array. First I try to reuse the bubbles in the gonebubbles array, then if the array is run out, I generate new bubbles. While this operation is processed, the continuous animation stops, then continues. The break is about one second, but this is very disturbing.
Can anyone help in this problem? Thanks in advice, madik
- (void)viewDidAppear {
.
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(startAnimation) userInfo:nil repeats:YES];
.
}
- (void)startAnimation {
self.current = [NSDate timeIntervalSinceReferenceDate];
double diff = (self.start - self.current);
if ( diff < 0 ) {
diff = (-1) * diff;
}
self.start = self.current;
frames++;
if ( shouldMoveBubbles ) {
[mug moveBubbles:diff];
}
if ( frames == 35 ) {
DebugLog(#"################################################################");
DebugLog(#"####################### FRAME = 35 ###########################");
DebugLog(#"################################################################");
[mug createNewBubbleOnTheBottomOfView:self.view];
frames = 0;
}
}
In the Mug class:
- (void)moveBubbles:(double)millisElapsed {
for (Bubble *bubble in bubbles) {
int bubbleSpeed = bubble.speed;
float deltaX = (float)(bubbleSpeed * -degrees_sinus * millisElapsed * 100);
float deltaY = (float)(bubbleSpeed * -degrees_cosinus * millisElapsed);
DebugLog(#"movebubbles x: %f, y:%f, speed: %d, sin:%f, cos:%f", deltaX, deltaY, bubbleSpeed, degrees_sinus, degrees_cosinus);
[bubble moveBubbleX:deltaX Y:deltaY];
}
}
And in the Bubble class:
- (void)moveBubbleX:(float)deltaX Y:(float)deltaY {
self.bubbleImage.center = CGPointMake(self.bubbleImage.center.x + deltaX, self.bubbleImage.center.y + deltaY);
}
This sounds like a memory problem. Slow UIAlertView animation is a sure sign of this. It sounds like the way you are generating bubbles is causing the problem. You mentioned the you keep two arrays of bubbles. You never say if you limit the number of bubbles that can be in either array at once. You also don't mention when you clean up these bubbles. It sounds like a memory "black hole". I'd recommend setting a maximum number of bubbles that you can show on screen at once.
Also, you mention a custom alert view. If you're modifying the UIAlertView, you're going to run into problems since that's not officially supported. Additionally, I've seen UIAlertView animation become slow when memory is tight. If you solve the memory issues with your bubbles, you'll probably solve this one too.
Finally, a word of advice. Making an animated game in UIKit is probably not a good idea. NSTimers are not as accurate as many people would like to think. UIImages are relatively expensive to load. Touching moving buttons is known to be unreliable at worst, hackish at best. I suggest looking into a game framework, such as Cocos2d-iphone.
Good luck!