I posted this question three days ago, but I received the feedback that the question was not clear so I would like to ask again. Also, the scope of the problem has changed as I researched it further (no Unity issue!)
I'm making a game for iOS where you rotate an object left and right by touching the left or right side of the screen. The Object rotates as long as the display is touched and stops rotating when the touch ends. When I run the game on an actual iPad/iPhone for a while every few touches the Rotation of the Objects stutters for about 2 seconds. This happends in about 5% of the touches. All other touches work perfectly smooth.
All other Game Actions work fine at the set frame rate of 60 fps. The only thing that doesn't move smoothly is the rotated object, while the rest of the game is all running perfectly smooth. I tried to visualize the problem on the attached image. See here
It visually looks like the Touch Refresh Rate is freezed for two seconds.
What may be the cause for this and how do I fix this?
I created the game with the game engine Unity. I use these versions:
- Unity: 2019.3.15f1
- Xcode: 11
- Device: iPhone x
- iOS Version: 13.5.1
After a lot of research I found out, that this is not an issue related to Unity. Also, there is no issue when building the game on an Android device.
Steps to reproduce:
int rotation
private void FixedUpdate(){
for (int i = 0; i < Input.touchCount; i++)
{
Vector3 touchPos = Camera.main.ScreenToWorldPoint(Input.touches[i].position);
if (touchPos.x < 0)
{
rotation += 10;
}
else if (touchPos.x > 0)
{
rotation -= 10;
}
}
transform.Rotate(0, 0, rotation);
rotation = 0;
}
Coding the Touch Input via c# in Unity (see above)
Building the Project on iOS Platform (creating an xcodeproject)
Open the Project in XCode and running it on the iPhone
Does anybody have a solution for this?
Any "Input" class should be called in Update(), instead of FixedUpdate().
The Input data loss is expected if you are trying to get them in FixedUpdate().
Just for example, FixedUpdate can be execute twice in one frame, or skipped in one frame. That's how it causes data lost or inaccurate.
The proper solution will be sth like below.
int rotation
private void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Vector3 touchPos = Camera.main.ScreenToWorldPoint(Input.touches[i].position);
if (touchPos.x < 0)
{
rotation += 10 * time.deltaTime;
}
else if (touchPos.x > 0)
{
rotation -= 10 * time.deltaTime;
}
}
transform.Rotate(0, 0, rotation);
rotation = 0;
}
and please be noted that your current rotation direction is determined by a ray casting from camera.
If you want to rotate left/right by screen space, this should work better.
private void Update()
{
int rotation = 0;
for (int i = 0; i < Input.touchCount; i++)
{
if (Input.touches[i].position.x < Screen.width/2)
{
rotation += 10 * time.deltaTime;
}
else
{
rotation -= 10 * time.deltaTime;
}
}
transform.Rotate(0, 0, rotation);
}
In general, I prefer a simple rotation script by checking last touch.
private void Update()
{
if(Input.touchCount > 0)
{
if (Input.touches[Input.touchCount-1].position.x < Screen.width/2)
{
transform.Rotate(0, 0, 10f * time.deltaTime);
}
else
{
transform.Rotate(0, 0, -10f * time.deltaTime);
}
}
}
Or, if you really want to sync the input with FixedUpdate(), you can do sth like below.
bool HasTouch = false;
float SpeedRot = 0;
private void Update()
{
if(Input.touchCount > 0)
{
HasTouch = true;
if (Input.touches[Input.touchCount-1].position.x < Screen.width/2)
{
SpeedRot = 10f;
}
else
{
SpeedRot = -10f;
}
}
else
{
HasTouch = false;
}
}
private void FixedUpdate()
{
if(HasTouch) transform.Rotate(0, 0, SpeedRot * Time.fixedDeltaTime);
}
Related
Works perfect in iPhoneX. In iPhone6 when I tilt in landscape mode then accelerometer value comes even in Portrait game. How to get accelerometer value in portrait tilt ? Game is in Portrait. But accelerometer treat it as landscape..
Here is Code:
Device::setAccelerometerEnabled(true);
mAccelerometerListener = EventListenerAcceleration::create(CC_CALLBACK_2( GBGameScene::onAcceleration, this));
_eventDispatcher->addEventListenerWithSceneGraphPriority(mAccelerometerListener, this);
Accelerometer Code:
void GBGameScene::onAcceleration(Acceleration* acc, Event* event)
{
// Processing logic here
float deceleration = 1.0f;
float maxVelocity = 100;
playerVelocity.x = playerVelocity.x * deceleration + acc->x;
playerVelocity.y = playerVelocity.y * deceleration + acc->y;
if (playerVelocity.x > maxVelocity)
{
playerVelocity.x = maxVelocity;
}
else if (playerVelocity.x < -maxVelocity)
{
playerVelocity.x = -maxVelocity;
}
if (playerVelocity.y > maxVelocity)
{
playerVelocity.y = maxVelocity;
}
else if (playerVelocity.y < -maxVelocity)
{
playerVelocity.y = -maxVelocity;
}
// Point pos = mGravityBall->getPosition();
// pos.x += playerVelocity.x;
// mGravityBall->setPosition(pos);
}
I think nothing wrong with code as its working perfect in iphoneX. Why not its tilted in iPhone6 ?
The accelerometer API operates along fixed axes. If you want to treat it as if it's always in landscape, you'll need to use the y axis instead.
https://developer.apple.com/documentation/coremotion/getting_raw_accelerometer_events
I'm trying to detect which sprite was touched in an array of sprites and when it is touched to run an animation at the sprite which was touched. I have all the sprites placed in the map already but when the user touches any other sprite except for the first one the animation doesn't run. I'm using cocos2dx 3.5. Any help is appreciated thank you.
this is my onTouchesEnded
void BattleScreen::onTouchesEnded(const std::vector<cocos2d::Touch *> &touches, cocos2d::Event *event)
{
for (auto touch : touches)
{
auto tapped = touch->getLocationInView();
for (int i = 0; i < defenseSpots.size(); i++) {
if(defenseSpot[i]->getBoundingBox().containsPoint(tapped))
{
sealAnimationSprite = Sprite::create("sealanimation5.png");
sealAnimationSprite->setPosition(Point(defenseSpot[i]->getPosition().x, defenseSpot[i]->getPosition().y));
this->addChild(sealAnimationSprite,20);
// now lets create animation frames
Vector<SpriteFrame*> sealAnimationFrames;
sealAnimationFrames.reserve(5);
sealAnimationFrames.pushBack(SpriteFrame::create("sealanimation5.png", Rect(0,0,100,100)));
sealAnimationFrames.pushBack(SpriteFrame::create("sealanimation4.png", Rect(0,0,100,100)));
sealAnimationFrames.pushBack(SpriteFrame::create("sealanimation3.png", Rect(0,0,100,100)));
sealAnimationFrames.pushBack(SpriteFrame::create("sealanimation2.png", Rect(0,0,100,100)));
sealAnimationFrames.pushBack(SpriteFrame::create("sealanimation.png", Rect(0,0,100,100)));
// create the animation out of the frames
Animation* sealAnimation = Animation::createWithSpriteFrames(sealAnimationFrames, 0.01f);
animateSeal = Animate::create(sealAnimation);
// run it and repeat it forever
sealAnimationSprite->runAction((animateSeal));
}
}
}
}
and here is my method where i create the sprites and place them
void BattleScreen::createDefenseSpots()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
for (int i = 0; i < 4;i++)
{
defenseSpot[i] = Sprite::create("placement.png");
if(i == 0)
defenseSpot[i]->setPosition(Point(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
if (i == 1)
defenseSpot[i]->setPosition(Point(visibleSize.width / 1.4 + origin.x, visibleSize.height / 3+ origin.y));
if (i == 2)
defenseSpot[i]->setPosition(Point(visibleSize.width / 3.5 + origin.x, visibleSize.height / 1.5 + origin.y));
if (i == 3)
defenseSpot[i]->setPosition(Point(visibleSize.width / 2.5 + origin.x, visibleSize.height / 4+ origin.y));
this->addChild(defenseSpot[i]);
defenseSpots.push_back(defenseSpot[i]);
}
}
Everything adds to the scene correctly but the only sprite the will run the animation is the first one. Im guessing the loop isn't executing correctly but I'm not sure how to fix it?
I've also tried looping through like this
for(int i = 0;i < 4; i++){
...
}
I figured it out , I was using
auto tapped = touch->getLocationInView();
when i should have been using
auto tapped = touch->getLocation();
Just in case anyone out there is making my same mistake.
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.
Im using a technique to control a sprite by rotating left/right and then accelerating forward. I have 2 questions regarding it. (The code it pasted together from different classes due to polymorphism. If it doesn't make sense, let me know. The movement works well and the off screen detection as well.)
When player moves off screen i call the Bounce method. I want the player not to be able to move off screen but to change direction and go back. This works on top and bottom but left and right edge very seldom. Mostly it does a wierd bounce and leaves the screen.
I would like to modify the accelerate algorithm so that i can set a max speed AND a acceleration speed. Atm the TangentalVelocity does both.
float TangentalVelocity = 8f;
//Called when up arrow is down
private void Accelerate()
{
Velocity.X = (float)Math.Cos(Rotation) * TangentalVelocity;
Velocity.Y = (float)Math.Sin(Rotation) * TangentalVelocity;
}
//Called once per update
private void Deccelerate()
{
Velocity.X = Velocity.X -= Friction * Velocity.X;
Velocity.Y = Velocity.Y -= Friction * Velocity.Y;
}
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1;
SoundManager.Vulture.Play();
}
//screen edge detection
public void CheckForOutOfScreen()
{
//Check if ABOVE screen
if (Position.Y - Origin.Y / 2 < GameEngine.Viewport.Y) { OnExitScreen(); }
else
//Check if BELOW screen
if (Position.Y + Origin.Y / 2 > GameEngine.Viewport.Height) { OnExitScreen(); }
else
//Check if RIGHT of screen
if (this.Position.X + Origin.X / 2 > GameEngine.Viewport.Width) { OnExitScreen(); }
else
//Check if LEFT of screen
if (this.Position.X - Origin.X / 2 < GameEngine.Viewport.X) { OnExitScreen(); }
else
{
if (OnScreen == false)
OnScreen = true;
}
}
virtual public void OnExitScreen()
{
OnScreen = false;
Bounce();
}
Let's see if I understood correctly. First, you rotate your sprite. After that, you accelerate it forward. In that case:
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1; //I THINK THIS IS THE PROBLEM
SoundManager.Vulture.Play();
}
Let's suposse your sprite has no rotation when it looks up. In that case, if it's looking right it has rotated 90º, and its speed is v = (x, 0), with x > 0. When it goes out of the screen, its rotation becomes -90º and the speed v = (-x, 0). BUT you're pressing the up key and Accelerate method is called so immediately the speed becomes v = (x, 0) again. The sprite goes out of the screen again, changes its velocity to v = (-x, 0), etc. That produces the weird bounce.
I would try doing this:
private void Bounce()
{
Rotation = Rotation * -1;
SoundManager.Vulture.Play();
}
and check if it works also up and bottom. I think it will work. If not, use two different Bounce methods, one for top/bottom and another one for left/right.
Your second question... It's a bit difficult. In Physics, things reach a max speed because air friction force (or another force) is speed-dependent. So if you increase your speed, the force also increases... at the end, that force will balance the other and the speed will be constant. I think the best way to simulate a terminal speed is using this concept. If you want to read more about terminal velocity, take a look on Wikipedia: http://en.wikipedia.org/wiki/Terminal_velocity
private void Accelerate()
{
Acceleration.X = Math.abs(MotorForce - airFriction.X);
Acceleration.Y = Math.abs(MotorForce - airFriction.Y);
if (Acceleration.X < 0)
{
Acceleration.X = 0;
}
if (Acceleration.Y < 0)
{
Acceleration.Y = 0;
}
Velocity.X += (float)Math.Cos(Rotation) * Acceleration.X
Velocity.Y += (float)Math.Sin(Rotation) * Acceleration.Y
airFriction.X = Math.abs(airFrictionConstant * Velocity.X);
airFriction.Y = Math.abs(airFrictionConstant * Velocity.Y);
}
First, we calculate the accelartion using a "MotorForce" and the air friction. The MotorForce is the force we make to move our sprite. The air friction always tries to "eliminate" the movement, so is always postive. We finally take absolute values because the rotation give us the direction of the vector. If the acceleration is lower than 0, that means that the air friction is greater than our MotorForce. It's a friction, so it can't do that: if acceleration < 0, we make it 0 -the air force reached our motor force and the speed becomes constant.
After that, the velocity will increase using the acceleration. Finally, we update the air friction value.
One thing more: you may update also the value of airFriction in the Deccelarate method, even if you don't consider it in that method.
If you have any problem with this, or you don't understand something (sometimes my English is not very good ^^"), say it =)
I know this question might get asked a lot and for that I am sorry. But I have had trouble with collisions in my game for a while and I would like some help.
First off, the game is a 2D Platformer. Each solid is put in a list. I have this code for collision detection which works pretty good for me:
if (player.rectangle.Intersects(rect))
{
player1Collision = true;
colSolid = solid;
colRectangle = rect;
}
if (player1Collision)
{
Vector2 pos = player.position;
Vector2 pLeft = new Vector2(player.BoundingBox.Left, 0);
Vector2 pRight = new Vector2(player.BoundingBox.Right, 0);
Vector2 pTop = new Vector2(0, player.BoundingBox.Top);
Vector2 pBottom = new Vector2(0, player.BoundingBox.Bottom);
Vector2 sLeft = new Vector2(colSolid.BoundingBox.Left, 0);
Vector2 sRight = new Vector2(colSolid.BoundingBox.Right, 0);
Vector2 sTop = new Vector2(0, colSolid.BoundingBox.Top);
Vector2 sBottom = new Vector2(0, colSolid.BoundingBox.Bottom);
if (player.rectangle.Intersects(colRectangle))
{
if (player.velocity.X > 0 && Vector2.Distance(pRight, sLeft) < player.texture.Width / 2)//left
{
player.velocity.X = 0f;
pos.X = colSolid.BoundingBox.Left - player.BoundingBox.Width;
}
else if (player.velocity.X < 0 && Vector2.Distance(pLeft, sRight) < player.texture.Width / 2)//right
{
player.velocity.X = 0f;
pos.X = colSolid.BoundingBox.Right;
}
if (player.velocity.Y > 0 && Vector2.Distance(pBottom, sTop) < player.texture.Height/ 2) //top
{
player.velocity.Y = 0f;
player.gravityOn = false;
pos.Y = colSolid.BoundingBox.Top - player.BoundingBox.Height;
}
else if (player.velocity.Y < 0 && Vector2.Distance(pTop, sBottom) < player.texture.Height / 2)//bottom
{
player.velocity.Y = 0f;
pos.Y = colSolid.BoundingBox.Bottom;
}
player.position = pos;
}
else
{
player.gravitySpeed = 0.15f;
player.gravityOn = true;
}
}
However the problem is that if the player is not intersecting with the rectangle I set the gravity to on, therefore he falls continuously as he collides with the solid and then is put on top to not collide with it. All I need to know is: how can I avoid this? Is there any other way that I could set the gravity to on without the player falling towards the solid continuously, only to be put back on top of the solid to fall again?
Any help is appreciated.
The way I address this problem may not be optimal (in fact I'm sure it probably isn't) but it has worked for me in all my 2D platforming projects so far.
I begin by defining a second rectangle for the sprite class. This rectangle will have the same Width and X coordinate as the main bounding box, but it will be slightly taller (in my case 2 or 3). You will also need to offset it so that the bottom edges of both rectangles are inline, to illustrate:
Rectangle boundingRect = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
Rectangle gravityRect = new Rectangle((int)boundingRect.X, (int)boundingRect.Y - 3, texture.Width, texture.Height + 3);
The sprite class also needs a bool to keep track of if the player should be falling. And one to keep track of whether it is solid (which you obviously assign as desired, during initialization).
public bool isGrounded = false;
bool isSolid;
At the top of my Game1 class, I declare 2 ints:
int gravityTotalI = 0;
int gravityCounterI = 0;
When initializing my sprites, I usually add them all to a List. So that I can do this:
foreach (Sprite s in spriteList)
{
if (s.isSolid)
{
gravityTotalI++;
}
}
Now, I use this bit of logic in the Game1 Update Method:
foreach (Sprite s in spriteList)
{
if (!s.Equals(player)
{
if (player.boundingRect.Intersects(s.boundingRect) || player.boundingRect.Intersects(s.gravityRect))
{
player.isGrounded = true;
gravityCounterI = 0;
}
else
{
gravCounterI++;
if (gravCounterI >= gravTotalI)
{
player.isGrounded = false;
gravCounterI = 0;
}
}
if (player.boundingRect.Intersects(s.boundingRect))
{
player.position.Y -= 2f; //set the resistance of the platform here
}
}
} //end of foreach loop.
if (!player.isGrounded)
{
player.position.Y += 2f; //set the force of gravity here.
}
Building a decent directional collision engine is a different thing, but this technique will handle the basics (and get rid of that infernal bouncing).
Hope this isn't too long-winded/doesn't miss out anything important, and I really hope it helps - I struggled with exactly the same problem as you for a long time, and I know how frustrating it can be!
I'm looking forward to seeing others' techniques for handling this!