Simple combo multiplier in sprite-kit - ios

I am making a reaction game, where you can destroy enemys and earn points. Now I would like to have combo points if you destroy them fast and if there is a specific time gap the combo multiplier should go to zero again.
I would like to multiple the points like this: 2 * 2 = 4 * 2 = 8 * 2 = 16 * 2...
(you get 2 points if you destroy an enemy).
I add the points here:
if (CGRectIntersectsRect(enemy.frame, player.frame)) {
points = points + 1;
[enemy removeFromParent];
}
I could always multiply the current points with 2, but I want to reset the combo multiplier if there is specific amount of time without getting points.
I hope someone can help me.
(code in objective c please)

It seems no more complicated than recording the time the last enemy was destroyed and then in the update: method deciding if the combo has elapsed as no more enemies were hit in whatever timeout period you allow.
I am not familiar with Sprite kit, but the update appears to pass the current time; excellent. You will need to record the following:
timeout (time): The current timeout. This will reduce as the game progresses, making it harder.
lastEnemyKillTime (time): the time the last enemy was killed.
comboPoints (integer): How many points the user gets per hit. This will increase as the combo extends.
points (integer): The current score.
So, something like this:
#interface MyClass ()
{
NSTimeInterval _timeout;
NSTimeInterval _lastEnemyKillTime;
BOOL _comboFactor;
NSUInteger _points;
}
#end
I guess Sprite Kit uses an init: method; use it to initialize the variables:
- (id)init
{
self = [super init];
if (self != nil) {
_timeout = 1.0;
_lastEnemyKillTime = 0.0;
_points = 0;
_comboPoints = 1;
}
}
The update: method would be something like:
- (void)update:(NSTimeInterval)currentTime
{
BOOL withinTimeout = currentTime - _lastEnemyKillTime <= _timeout;
if (CGRectIntersectsRect(enemy.frame, player.frame)) {
_inCombo = withinTimeout;
if (_inCombo)
_comboPoints *= 2;
_points += _comboPoint;
_lastEnemyKillTime = currentTime;
[enemy removeFromParent];
} else if (_comboPoints > 1 && !withinTimeout) {
_lastEnemyKillTime = 0.0;
_comboPoints = 1;
}
}

You need to keep track on the last enemy casual timestamp and the factor. When the next kill is processed, you check the timestamp, if it is below threshold, you raise the factor. The time of the current kill replaces the timestamp.
You could create a FightRecorder class as singleton, if you don't have a better place yet (services or sth).
NSDate *newKillTime = new NSDate;
FightRecorder recorder = [FightRecorder instance];
if([newKillTime timeIntervalSinceDate:recorder.lastKillTime] < SCORE_BOUNDS_IN_SEC) {
recorder.factor++; // could also be a method
points = points + [recorder calculateScore]; // do your score math here
}
else {
[recorder reset]; // set the inner state of the fight recorder to no-bonus
}
recorder.lastKillTime = newKillTime; // record the date for the next kill

Related

Error in updating the score in a game

I have problem when i updating score in the game i make i dont know what is the problem but when i shoot something the score should increment by 1 but in my code sometimes it increment by 2 or 3 sometimes 1 it not constant i dont know why this happen here is the code i used
#interface GameScene () {
SKLabelNode* _scoreLabelNode;
NSInteger _score;
}
-(void)didMoveToView:(SKView *)view {
_score = 0;
_scoreLabelNode = [SKLabelNode labelNodeWithFontNamed:#"Silom Regular"];
_scoreLabelNode.fontSize = 50;
_scoreLabelNode.position = CGPointMake(self.size.width - 335 , self.size.height - 60);
_scoreLabelNode.zPosition = 100;
[self addChild:_scoreLabelNode];
_scoreLabelNode.text = [NSString stringWithFormat:#"%d",_score];
}
if (contact.bodyB.categoryBitMask == ObjectCategory) {
_score++;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d",_score];
}
There are few similar issue with this delegate, see if this may fix your issue:
SpriteKit: didBeginContact being called non-stop on iPad
Why are didBeginContact called multiple times?
didBeginContact is being called multiple times for the same SKPhysicsBody
Even if it doesn't solve your problem, you can use a flag variable to handle this score update for once. e.g.,
bool hasScoreUpdated;
- (void)didBeginContact:(SKPhysicsContact * _Nonnull)contact
{
if(!hasScoreUpdated)
{
_score++;
hasScoreUpdated = true;
}
// your rest of the logic
}
- (void)didEndContact:(SKPhysicsContact * _Nonnull)contact
{
hasScoreUpdated = false;
}
EDIT:
Based on your comment above:
i put "NSLog (#"%d", _score )" after "_score++" it increment as should
like 10 11 12 .. etc but the score jumped from 10 to 12
It is possibly due to the very frequently calling the respective event and a very rapidly update of UI element.

Updating the position of a node constantly?

I am experiencing some problems with my game, I am currently using the update method to create the illusion of my Hero character being "dragged" along with a moving platform. The update method works by using a bool that switches on and off whenever my Hero makes contact with the platform and then updates the position to seemingly show that the Hero is being moved. This worked nicely, but as I realize when the framerate drops, so does the character's positioning and it breaks the illusion.
Is there any sort of way to constantly update the positioning of a node without using the update method?
didBeginContact Method
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (((firstBody.categoryBitMask & fPlayerCategory) != 0) && ((secondBody.categoryBitMask & fPlatformCategory) != 0))
{
[_Hero removeActionForKey:#"idleAnimation"];
[_Hero runAction:repeatWalkAnimation withKey:#"walkAnimation"];
syncMove = YES;
}
}
Update Method
-(void)update:(CFTimeInterval)currentTime {
if (gameStart == YES & gameOver == NO) {
if (syncMove == YES) {
_Hero.position = CGPointMake(_Hero.position.x - .83, _Hero.position.y);
}
}
}
Any sort of help would be appreciated.
You can use the currentTime argument in your update function to calculate how much _Hero should have moved. This way _Hero will always move the appropriate amount based on the time since the last update.
The steps to do this are:
Store your last update time
On each update, calculate the difference between the last update time and the current time
Calculate how far _Hero should have moved in that time
Update _Hero's position
Something like:
CFTimeInterval lastUpdateTime = 0;
CGFloat frameRate = 1.0 / 60.0; // Normal SpriteKit frame rate
CGFloat xDistanceForNormalFrameRate = 0.83;
-(void)update:(CFTimeInterval)currentTime {
if (lastUpdateTime == 0) lastUpdateTime = currentTime - frameRate; // Init lastUpdateTime to one frame back
if (gameStart && !gameOver) { // Note: the original question had & here, but it looks like it was a typo
if (syncMove) {
CFTimeInterval timeDifference = currentTime - lastUpdateTime;
CGFloat xDistance = xDistanceForNormalFrameRate * (timeDifference / frameRate);
_Hero.position = CGPointMake(_Hero.position.x - xDistance, _Hero.position.y);
}
}
lastUpdateTime = currentTime;
}
Note: If your frame rate is dropping below ~30FPS, you should probably focus more on why your frame rate is dropping than how to work around it.

using CGRectIntersectsRect for updating score of my game

I am trying to update my score using CGRectIntersectsRect and I want to increment in my score every time two images collide with each other. But, my score does not show periodicity some times it increases by one unit but it increases randomly when two UIimages collide with higher speed. Here is my code:
-(void)Collision{
if (CGRectIntersectsRect(Ball.frame, Player.frame)) {
PlayerScoreNumber = PlayerScoreNumber + 1;
PlayerScore.text = [NSString stringWithFormat:#"%i", PlayerScoreNumber];
Y = arc4random() %5;
Y = 0-Y;
}
The two UIImages are 'ball' and 'racket' , I want to increment in my score every time the ball strikes the racket. Please help...
I suspect you are counting the same collision twice (or more) and so you are going to have to keep state about the collision and remember when you've already seen the current collision:
Create a new instance variable in your implementation file, something like:
#interface MyClass ()
{
BOOL _ballCollidedWithPlayer;
}
and manage the state like this:
-(void)Collision{
BOOL collided = CGRectIntersectsRect(Ball.frame, Player.frame);
if (collided) {
if (_ballCollidedWithPlayer)
return; // Nothing to do; we already know about this collision
PlayerScoreNumber = PlayerScoreNumber + 1;
PlayerScore.text = [NSString stringWithFormat:#"%i", PlayerScoreNumber];
Y = arc4random() %5;
Y = 0-Y;
}
_ballCollidedWithPlayer = collided;
}

How to know when all physics bodies have stopped moving in Cocos2d V3.0 with Chipmunk

The only way I can think to do it is to check velocities for all physics bodies during every collisions.
- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair piece:(CCNode *)pieceA piece:(CCNode *)pieceB{
float x = 0;
float y = 0;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
x = x + [[[_physicsWorld children][i] physicsBody] velocity].x;
y = y + [[[_physicsWorld children][i] physicsBody] velocity].y;
}
if ( x == 0 && y == 0 ) {
NSLog(#"stopped");
}
return YES;
}
This logs “stopped” multiple times when the scene first loads, then doesn’t log “stopped” again, even after physics bodies have clearly started moving and colliding and then come to a stop.
Ideally I'd like a delegate method that would notify me when all physics bodies have stopped moving, but I can't seem to find one.
FYI: I'm using the standard Chipmunk physics engine that's baked into Cocos2d V3.0
Chipmunk has a internal mechanism, which can, if activated, automatically deactivate physics bodies. My approach (I am using cocos2dx 3.11.1 and not -obj version with chipmunk 7.0.1) is:
activate the chipmunk idle mechanism (0.5 second - meaning, if an object is not moving for longer than 0.5 second it will be deactivated):
cpSpaceSetSleepTimeThreshold(space, 0.5f);
You do not need to use
cpSpaceSetIdleSpeedThreshold(space, <speed>);
because chipmunk calculates the threshold speed for you (according the gravitation used).
use this code for determination if all objects are not moving (static and kinetic bodies never sleep):
bool isAnyPhysicsBodyMoving(){
int i = 0; bool isMoving = false;
const Vector<PhysicsBody*>& bodies = getPhysicsWorld()->getAllBodies();
while( i < bodies.size() && !isMoving){
PhysicsBody *body = bodies.at(i);
isMoving = cpBodyGetType(body->getCPBody()) == CP_BODY_TYPE_DYNAMIC
&& !body->isResting();
i++;
}
return isMoving;
}
use static (and not kinetic) body for walls, in order to let objects sleep:
// wall
Size visibleSize = Director::getInstance()->getWinSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
float border = 10.0f;
Size wallBodySize = Size(visibleSize.width+2*border, visibleSize.height+2*border);
PhysicsBody *wallBody = PhysicsBody::createEdgeBox(wallBodySize, PhysicsMaterial(1.0f, 1.0f, 0.5f), border);
Node* wall = Node::create();
wall->addComponent(wallBody);
wall->setAnchorPoint(Vec2(0.5f, 0.5f));
wall->setPosition(Point(visibleSize.width/2+origin.x, visibleSize.height/2+origin.y));
cpVect tt;
tt.x = wall->getPosition().x; tt.y = wall->getPosition().y;
//set position manually and BEFORE adding the object into the space
cpBodySetPosition(wallBody->getCPBody(), tt);
cpBodySetType(wallBody->getCPBody(), CP_BODY_TYPE_STATIC);
addChild(wall);
Any dynamic body connected to a kinetic body (for example laying on) will never sleep.
test it with DEBUG activated
getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
the boxes (their content) must become grey (=sleeping) and not red (=active):
In order to let it work, I have:
added an access method (to get cpSpace) in CCPhysicsWorld.h:
inline cpSpace* getSpace() const { return _cpSpace; }
Fix call of
cpBodySetTorque(body, 0.0f);`
in CCPhysicsBody.cpp to
if (body->t != 0.0f){
cpBodySetTorque(body, 0.0f);
}
Fix call of
cpBodySetPosition(_cpBody, tt);`
in CCPhysicsBody.cpp to
if (!cpveql(tt, cpBodyGetPosition(_cpBody))){
cpBodySetPosition(_cpBody, tt);
}
Steps 2. and 3. are necessary to avoid setting of the same physics body properties, which wake up a sleeping body.
The advantage of this approach is, that the chipmunk does not make any calculations for such physical bodies - saving CPU and battery.
I found something that works.
tl;dr
The basic idea is to keep track of the positions of the sprites myself, and then periodically check them to see if any of them have moved since they were last checked.
Longer version
I created a subclass of CCNode with the class name Piece.
These are my objects that are added to the physics world.
#implementation Piece {
float _previousX;
float _previousY;
}
-(void)updatePreviousScreenXandY{
_previousX = self.position.x;
_previousY = self.position.y;
}
-(BOOL)hasntMoved{
float currentX = self.position.x;
float currentY = self.position.y;
if ( currentX == _previousX && currentY == _previousY ) {
return TRUE;
}else{
return FALSE;
}
}
This is in my CCNode that acts as the game scene
-(void)doStuffAfterPiecesStopMoving:(NSTimer*)timer{
BOOL noPiecesHaveMoved = TRUE;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
if ( [[_physicsWorld children][i] hasntMoved] == FALSE ) {
noPiecesHaveMoved = FALSE;
break;
}
}
if ( noPiecesHaveMoved ) {
[timer invalidate];
NSLog(“Pieces have stopped moving”);
}else{
NSLog(“Pieces are still moving”);
[self updateAllPreviousPiecePositions];
}
}
-(void)updateAllPreviousPiecePositions{
for (int i=0; i < [[_physicsWorld children] count]; i++) {
Piece *piece = (Piece*)[_physicsWorld children][i];
[piece updatePreviousScreenXandY];
}
}
All I have to do is
[NSTimer scheduledTimerWithTimeInterval:TIME_BETWEEN_CHECKS
target:_gamePlay
selector:#selector(doStuffAfterPiecesStopMoving:)
userInfo:nil
repeats:YES];
and it’ll run whatever code I want after all Piece nodes have stopped moving.
The key to getting it to work well is to get the values for the Chipmunk space’s sleepTimeThreshold and the timer above’s time as low as possible.
My experimenting suggests the following settings work okay, but anything lower will cause problems (i.e. collisions not taking place properly):
sleepTimeThreshold = 0.15
my timer = 0.05
If anyone has a different/better solution or improvements to the above code, please post.

How to update x and y position from the amount of tilt?

How can I update x and y position of an object from the amount of tilt?
I'm trying to update the x and y position of my _bg object, based on the amount of a tilt movement.
Also, if the device is put down on a table, the position should go back to it's original position;
I'm trying to do something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_motionManager = [[CMMotionManager alloc] init];
[_motionManager startGyroUpdates];
_timer = [NSTimer scheduledTimerWithTimeInterval:1/30
target:self
selector:#selector(updateGyro)
userInfo:nil repeats:YES];
}
- (void)updateFromGyro
{
self.x += _motionManager.gyroData.rotationRate.x;
self.y += _motionManager.gyroData.rotationRate.y;
_bg.center = CGPointMake(self.x, self.y);
}
Problem is that the object doesn't stop moving, ever!
Thank you!
A rate is the amount of change per unit time. Thus you are setting the coordinates relative to how quickly the device is moving, not its actual offset. You may want to look into its attitude (its actual offset from an arbitrary frame of reference). The documentation is here.
This might be helpful. Not sure though based on the limited data available from the question. You should probably also switch to using absolute position/rotation, not relative change between frames.
Simply set a minimum threshold. This will prevent minute movements from showing as updates:
if( _motionManager.gyroData.rotationRate.x > ROTATION_MIN )
{
self.x += _motionManager.gyroData.rotationRate.x;
}
if( _motionManager.gyroData.rotationRate.y > ROTATION_MIN )
{
self.y += _motionManager.gyroData.rotationRate.y;
}
_bg.center = CGPointMake(self.x, self.y);
I think you are making mistake in setting new center. Try this :
- (void)updateFromGyro
{
self.x = _bg.center.x + _motionManager.gyroData.rotationRate.x;
self.y = _bg.center.y + _motionManager.gyroData.rotationRate.y;
_bg.center = CGPointMake(self.x, self.y);
}
By the way, your application will keep receiving gyro updates even when you put device on table, because table's slope is not guaranteed to be 0 degree.

Resources