This article introduces the track module in LNDanmakuMaster that controls the core animation of the barrage. The precursors of this article are: LNDanmakuMaster

Division of responsibilities of tracks

In essence, the bullet-screen frame is a tool to assist users in animation: the user gives the view they need to put on the screen, and the bullet-screen frame runs the animation for the target view, so that it can be dynamically displayed on the screen. The core of animation is the bullet screen track.

Track component is divided into two parts: Track/TrackController. Track is more like a kind of animation rule, and TrackController is the real executor of animation. A convenient way to understand the relationship between the two is as follows: A TrackController is a person who does the work, and a Track is a tool. If Track is scissors, TrackController is the person who uses scissors. TrackController decides what to cut and how to cut. The meaning of this is that the same scissors can be used by different people, and the same person can also use other tools. So the Track we’re talking about here is a combination of a TrackController plus a Track.

We’ve already shown that Clock encapsulates CADisplayLink and gives constant callbacks to those little chunks of time that are gone. With these callbacks, we’re constantly subtracting the remaining time from the Attributes of each bulleted individual, just like human life, when their time is set to zero. This means that the play cycle of this barrage is over. In the process of continuous progress of bullet screen life, TrackController can always update all the animation states it contains according to the mapping between animation state and time schedule provided by Track. When all tracks in a danmu player are driven by the same Clock, all tracks run at the same time rate.

Track

In a horizontal rail, for example, the most common LNDanmakuHorizontalMoveTrack properties are as follows:

@interface LNDanmakuHorizontalMoveTrack : LNDanmakuAbstractTrack
@property (nonatomic, assign) CGPoint startPosition;
@property (nonatomic, assign) CGFloat width;
@end
Copy the code

A horizontal track contains two properties: We can draw a line segment on any view according to these two Attributes. When a barrage is placed on this track, it will move from the right side of this line segment to the left side. The duration of this process is the totalAliveTime specified in the Attributes of this barrage. In this process, Track provides the danmu update function:

@implementation LNDanmakuHorizontalMoveTrack - (void)updateAttributes:(LNDanmakuAbstractAttributes *)attributes { [super  updateAttributes:attributes]; float percent = attributes.currentPercent; CGFloat totalDistance = self.width + attributes.size.width; CGFloat currentDistance = totalDistance * percent; CGFloat currentX = self.startPosition.x + self.width - currentDistance; CGFloat currentY = self.startPosition.y - attributes.size.height/2.f; attributes.position = CGPointMake(currentX, currentY); } @endCopy the code

The calculation process of this function:

  1. For any incoming barrage attributes, we first calculate the total distance that the barrage needs to move, which is (the width of the screen + the width of the barrage), because the conditions for the emergence and disappearance of the barrage are the first appearance of the left side and the first disappearance of the right side.
  2. Based on the total distance and the updated percent of the barrage (referred to in the Attributes article as the ratio of live time to total live time), the current distance that the barrage should move is calculated.
  3. The current position of the barrage is the sum of the distance that the barrage should move from the right starting point.
  4. Update spatial information in Attributes.

The above is the update process of bar Track to a barrage.

TrackController

Same bar track, for example, bar rail inherited from a Base moving track, because all the movement type track need to pursue problem to avoid overlapping barrage, therefore I abstract out of the Base to keep those similar mobile track moving orbit inheritance, after sine shape or circular orbits are inherited from the Base class; Let’s ignore the additional features of this base class for the moment and just consider how its refresh process works on the barrage:

@interface LNDanmakuHorizontalMoveTrackController : LNDanmakuBaseMoveTrackController
@property (nonatomic, strong, readonly) LNDanmakuHorizontalMoveTrack *horizontalTrack;
@end
Copy the code
@interface LNDanmakuBaseMoveTrackController : LNDanmakuAbstractTrackController
@property (nonatomic, assign) NSTimeInterval spaceTimeInterval;
@end
Copy the code

SpaceTimeInterval is the minimum time interval between two rounds. As mentioned above, all the controls of LNDanmakuMaster take time as the unique criterion for processing, so this interval is also calculated in time unit, and the time interval between different rounds is consistent.

@implementation LNDanmakuBaseMoveTrackController - (void)update:(NSTimeInterval)elapsingTime { if (elapsingTime < 0.f) {  return; } NSMutableSet<LNDanmakuAbstractAttributes *> *shouldUnloadAttributes = [[NSMutableSet alloc] init]; for (LNDanmakuAbstractAttributes *attributes in self.currentAttributesMSet) { attributes.currentAliveTime += elapsingTime; NSTimeInterval restAliveTime = attributes.totalAliveTime - attributes.currentAliveTime; if (restAliveTime <= 0) { [shouldUnloadAttributes addObject:attributes]; } else { [self.track updateAttributes:attributes]; } } for (LNDanmakuAbstractAttributes *attributes in shouldUnloadAttributes) { [self unloadAttributes:attributes]; } [self checkCanLoadCandidate]; } @endCopy the code

This function is a simplified update method for the TrackController, and the general flow is as follows:

  • The orbit controller receives a small elapsed time (usually 0.0167).
  • Traverses all the shells in the current orbit, adding the elapsed time to the duration of each shell.
  • If the live time of the bullet barrage exceeds its total live time, the bullet barrage is put into an offload set and unloaded together at the end of the function.
  • If the live time of this barrage does not reach the total live time after the increase of this period, TrackController calls its own Track to update the position of this barrage.

The above is the process of updating bullet screen with a TrackController and a Track. Because Track is abstracted, such a TrackController can be matched with a variety of tracks to achieve different motion effects. For example, the internal implementation of bar Track and circular Track is as follows:

@interface LNDanmakuHorizontalMoveTrackController : LNDanmakuBaseMoveTrackController @property (nonatomic, strong) LNDanmakuHorizontalMoveTrack *horizontalTrack; @end @implementation LNDanmakuHorizontalMoveTrackController - (LNDanmakuAbstractTrack *)track { return self.horizontalTrack; } - (LNDanmakuHorizontalMoveTrack *)horizontalTrack { if (! _horizontalTrack) { _horizontalTrack = [[LNDanmakuHorizontalMoveTrack alloc] init]; } return _horizontalTrack; } @endCopy the code
@interface LNDanmakuCircleTrackController : LNDanmakuBaseMoveTrackController @property (nonatomic, strong, readonly) LNDanmakuCircleTrack *circleTrack; @end @implementation LNDanmakuCircleTrackController - (LNDanmakuAbstractTrack *)track { return self.circleTrack; } - (LNDanmakuCircleTrack *)circleTrack { if (! _circleTrack) { _circleTrack = [[LNDanmakuCircleTrack alloc] init]; } return _circleTrack; } @endCopy the code

In fact, they both inherit from the moving track base class, but in the track method return different types of track, the performance of the animation is very different, this combination of TrackController and track have been reused:

Other orbitals

LNDanmakuMaster provides 3 basetrackControllers:

  • The movement type: LNDanmakuBaseMoveTrackController
  • Fixed-point types: LNDanmakuBaseStableTrackController
  • Free type: LNDanmakuBaseFreeTrackController

Of these, the first two can generally meet most barrage requirements.

LNDanmakuMaster also natively implements 6 TrackController+Track combinations:

  • Horizontal movement: LNDanmakuHorizontalMoveTrackController (most common)
  • Fixed orbit – LNDanmakuStableTrackController (similar to B stand the middle column, this can be set at any point)
  • Heart-shaped orbit: LNDanmakuChristinaTrackController (a heart-shaped pattern)
  • Circular orbit: LNDanmakuCircleTrackController (a circular pattern)
  • Pop: animated orbit LNDanmakuPopTrackController (attenuation sine curve was used to simulate the spring of animation, usually using a set of numerical is already good static values, so you don’t need to worry about calculating the performance problem)
  • Wave track: LNDanmakuSinTrackController (the shape of the sine function, and the bar is very similar, with the ups and downs and the rotation of the barrage)

Therefore, the effect of changing the size, position, opacity, and transform properties provided by Attributes already covers most usage scenarios; Even if users have very specific needs, Attributes can be inherited, custom TrackControllers can be implemented, and any component that inherits from the Abstract class can be integrated into the framework.

Finally, the origin of Christina’s track: WHEN I was writing the code, I suddenly remembered a knowledge point about Descartes popularized by Teacher Zhang Yu ina class in 2016: “Do you know who the beggar is in the advertisement of Centenarian Mountain? “Yes, that’s Descartes,” and told us the whole story passionately. Although I googled it later, it might not be true, BUT I still thought it was romantic. Maybe I could code something romantic, I thought, “Christina is the princess’s name.”