SpriteKit 中的持续移动
Posted
技术标签:
【中文标题】SpriteKit 中的持续移动【英文标题】:Constant movement in SpriteKit 【发布时间】:2013-10-10 03:34:03 【问题描述】:我有一个游戏,我想根据用户是触摸屏幕的左侧还是右侧来向左或向右移动 SKNode。如何在用户触摸屏幕时对节点应用持续移动?
【问题讨论】:
只是冲动 = 距离 / deltaTime .. 就是这样。 【参考方案1】:您确实有三种选择来保持速度。
一个是在每次更新时简单地将速度重置回您想要的数量。在这种情况下,我选择了 200,200。这将使您的动作完全静止。
-(void)update:(NSTimeInterval)currentTime
node.physicsBody.velocity=CGVectorMake(200, 200);
第二种选择是保留速度,但使用速率因子为运动失真留出空间。下面的示例将速度保持为 200,200,但不会立即保持。相反,这将取决于利率。这将使您的动作更加逼真,并且更少静态。例如,假设您有一个角色以恒定速度移动并且您改变方向,当它更改为新的恒定速度值时,您会看到一个微小的加速度,从而使运动的变化更少静态而更具动态性。我强烈推荐这个选项。
-(void)update:(NSTimeInterval)currentTime
CGFloat rate = .05;
CGVector relativeVelocity = CGVectorMake(200-node.physicsBody.velocity.dx, 200-node.physicsBody.velocity.dy);
node.physicsBody.velocity=CGVectorMake(node.physicsBody.velocity.dx+relativeVelocity.dx*rate, node.physicsBody.velocity.dy+relativeVelocity.dy*rate);
您的第三个选项是设置速度或施加一个脉冲ONCE(与我们上面所做的每一帧相反),但关闭它的重力和空气阻力。这种方法的问题是它不会总是保持你的速度。它只会保持恒定的速度,直到受到外力的作用(即摩擦、与另一个物体碰撞等)。这种方法适用于您希望某个对象以恒定速度移动但仍保持完全动态的情况。如果您希望控制和/或保持对象速度,我根本不推荐此选项。我能想到的唯一场景可能是游戏中的小行星以匀速在屏幕上漂浮,但仍受到外力的影响(即 2 颗小行星相撞,这将导致新的匀速)。
node.physicsBody.velocity=CGVectorMake(200, 200);
node.physicsBody.linearDamping = 0; //You may want to remove the air-resistance external force.
node.physicsBody.affectedByGravity = false; //You may want to remove the gravity external force
这是我使用选项 2 在 Swift 中编写的示例项目。我尝试与您的描述相匹配。它只是以恒定的速度向左或向右移动节点。请务必尝试使用速率因子。
import SpriteKit
class GameScene: SKScene
var sprite: SKSpriteNode!
var xVelocity: CGFloat = 0
override func didMoveToView(view: SKView)
sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.physicsBody.affectedByGravity = false
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if (location.x<self.size.width/2.0) xVelocity = -200
else xVelocity = 200
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!)
xVelocity = 0
override func update(currentTime: CFTimeInterval)
let rate: CGFloat = 0.5; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody.velocity.dx, dy:0);
sprite.physicsBody.velocity=CGVector(dx:sprite.physicsBody.velocity.dx+relativeVelocity.dx*rate, dy:0);
注意:尽量远离 SKActions 进行实时运动。仅将它们用于动画。
我所说的实时运动是指在模拟的每一步中,对象的位置和速度对您来说都很重要的运动。而在动画中,您实际上只关心一段时间内的起点和终点。例如,具有移动角色的游戏需要实时运动。您需要能够在模拟的每个步骤中监控角色的位置和速度,并可能在特定时间间隔进行调整。但是对于像移动 UI 元素这样简单的事情,你不需要在每一步都跟踪它们的动作,所以使用动画
【讨论】:
你能区分实时运动和动画吗? 当然。通过实时运动,我的意思是在模拟的每一步中,对象的位置和速度对您来说都很重要。而在动画中,您实际上只关心一段时间内的起点和终点。例如,具有移动角色的游戏需要实时运动。您需要能够在模拟的每个步骤中监控角色的位置和速度,并可能在特定时间间隔进行调整。但是对于像移动 UI 元素这样简单的事情,你不需要在每一步都跟踪它们的运动,所以使用动画 调整SKAction
的speed
怎么样?这与sprite.physicsBody.velocity
有何不同?
SKAction 的 speed 属性是动画的速度,而不是物理体的速度。
@EpicByte 如何通过实时运动确保突然且受控的停止?换句话说,如何确保精灵在一定时间后停止所有运动(即,将速度设置为 0)?示例:使精灵以 (200, 100) 的速度矢量移动 0.10 秒。这似乎只有使用 SKAction 才能实现。如果您愿意,也可以发布一个新问题。谢谢!【参考方案2】:
添加一个 ivar:
CGPoint velocity;
设置运动,例如向右移动:
velocity.x = 5;
整合每一步的速度:
-(void) update:(NSTimeInterval)currentTime
CGPoint pos = self.position;
pos.x += velocity.x;
pos.y += velocity.y;
self.position = pos;
PS:动作对于连续操作,特别是运动来说是出了名的糟糕,因为它们是基于时间的,即应该在一定的持续时间后结束。但是如果动作的时间在到达目的地之前就用完了怎么办?
【讨论】:
您是否还应该跟踪上次更新:被调用的时间,以及自上次更新以来的增量时间? 不是真的,我知道人们常说将增量时间应用于运动,但对于游戏玩法来说,它几乎总是你想要在游戏中避免的事情——因为帧率下降你不希望游戏表现好像它继续渲染 60 帧,因为这只会让游戏变得更难,如果不是对玩家不公平的话 如果角色因为帧率波动而减速和加速,并且速度与此相关,这是否会让玩家更容易/更有趣?当然,理想的情况是您的帧速率不会减慢到这在任何一种方法中都会成为问题的程度。但如果游戏在每秒 30 到 60 帧的范围内波动,如果不使用时间增量来计算运动,差异会更加明显。 增量背后的整个想法是您正在调整移动到当前帧速率,而不是表现得像以 60fps 渲染。您使用增量来计算自上一帧以来角色应该行进多远。使用时间增量,您始终可以设置一个阈值来限制自上一帧以来的最长时间,在最坏的情况下,它的行为与此实现几乎相同。在这种情况下,如果帧速率变慢,角色只会变慢,而不是大块移动。 一个例子是,给定的游戏可以在 40 或 60fps 下正常运行。因此,使用较小设备获得 40fps 的人实际上可能获得的游戏体验与使用更好设备获得 60fps 的人大不相同。然而,随着时间增量,这种差异几乎不会被注意到,因为玩家在任一设备上每秒仍会移动相同的距离。【参考方案3】:我直接设置velocity.dx,但是我不推荐这种方法,因为它完全忽略了之前的速度。
public func setSpeed(speed: CGFloat)
physicsBody.velocity.dx = speed
我想为您提供一个与现有 spritekit 物理引擎更好地配合的解决方案,但我没有,我将提供一个赏金,因为我也需要一个解决方案。
对于 Swift 中的触摸,您可以这样做:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
player.setSpeed(100) // You can use touches.anyObject().position to see the touch position and implement left/right movement
【讨论】:
【参考方案4】:这正是你在数学上的做法......
所以,在这个例子中,手指想要移动项目。
这准确定义了您希望它在帧时间内移动多少。
(在许多(当然不是全部)情况下,您的质量将被归一化为“一公斤”。(如果您的物理学正在研究“抽象”物体 - 您正在推动“图标”, “blob”之类的 - 这样做,使用 1.0 kg。当然,如果你的物理是坦克、足球或其他任何东西,你必须使用真实世界的值。)对于“一公斤”质量,这就是你的做法。 )
享受吧!
【讨论】:
【参考方案5】:取决于您是否要使用物理力或动作。
类似的东西
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
for (UITouch *touch in touches)
UITouch* touch = [touches anyObject];
CGPoint loc = [touch locationInNode:self];
if (loc.x > self.frame.size.width * 0.5)
NSLog(@"move right");
// move the sprite using an action
// or set physics body and apply a force
else
NSLog(@"move left");
如果您决定使用动作,请在 touchesEnded 中从您正在移动的精灵中删除所有动作。
如果您在 touchesBegan 中施加较小的力,精灵应该会停止。
【讨论】:
【参考方案6】:使用physicsBody的速度属性。变量 right 是触摸点减去对象(在本例中为 self)并提供相关的速度
-(void)segmentedTouchBegan:(NSValue *)p
CGPoint point = [p CGPointValue];
int right = (point.x - self.screenWidth/2.0f)/ABS((point.x - self.screenWidth/2.0f)); //1 if right -1 if left
self.physicsBody.velocity = CGVectorMake(SHIFT*right, 0);
-(void)segmentedTouchMoved:(NSValue *)p
CGPoint point = [p CGPointValue];
int right = (point.x - self.screenWidth/2.0f)/ABS((point.x - self.screenWidth/2.0f)); //1 if right -1 if left
self.physicsBody.velocity = CGVectorMake(SHIFT*right, 0);
-(void)segmentedTouchEnded:(NSValue *)p
self.physicsBody.velocity = CGVectorMake(0, 0);
【讨论】:
【参考方案7】:如果您的SKNode
有physicsBody
,则对其施加强制:
@interface GameScene()
@property (nonatomic) CGVector force;
@end
@implementation GameScene
- (void)update:(CFTimeInterval)currentTime
[self.player.physicsBody applyForce:self.force];
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
self.sideForce = CGVectorMake(3000, 0);
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
self.sideForce = CGVectorMake(0, 0);
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
self.sideForce = CGVectorMake(0, 0);
@end
如果没有,请使用 LearnCocos2D 的答案。
【讨论】:
【参考方案8】:很简单,比如每秒移动100px:
func touchUp(atPoint pos : CGPoint)
node.removeAllActions()
let pxPerSecond : CGFloat = 100.0;
var length = (pos - node.position).length()
var totalSeconds = length / pxPerSecond;
let moveAction = SKAction.move(to: pos, duration: Double(totalSeconds))
node.run(moveAction)
你可以在这篇文章https://www.raywenderlich.com/71-spritekit-tutorial-for-beginners中获得减法和规范化操作的覆盖
【讨论】:
【参考方案9】:如果您必须从左到右在固定位置停止运动。
这适用于 Swift 和 Objective-c。
你必须在physicsBody中使用:
node.position = CGPoint(X: ..... , Y: .....) node.physicsBody = nil
【讨论】:
【参考方案10】:-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
if (touchLocation.x > 50)
// Do whatever..
_timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(movePlayer:) userInfo:nil repeats:YES];
NSLog(@"Right");
else if (touchLocation.x < 50)
////// use another NSTimer to call other function////
NSLog(@"left");
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
if (_timer != nil)
[_timer invalidate];
_timer = nil;
-(void)movePlayer:(id)sender
hero.physicsBody.velocity = CGVectorMake(400, 0);
【讨论】:
以上是关于SpriteKit 中的持续移动的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 count-Swift SpriteKit 更改运动的持续时间?
SpriteKit:场景中的静态背景与移动的 SKCameraNode