I have populated an array of b2bodys. Using the following method:
-(void)populateBodiesToDestroy:(b2Body*)body {
NSValue *bodyValue = [NSValue valueWithPointer:body];
[bodiesArray addObject:bodyValue];
}
Each of the bodies are joint one to another to form a chain. Using the above method I have populated the array.
I update the following method within the Sprite class every 1/60 of a second in the HelloWorldLayer to destroy the bodies and remove their CCPhysicsSprites.
-(void)updateBodies {
if (bodiesArray) {
for (int i = 0; i < bodiesArray.count; i++) {
b2Body *removeLinkBody = (b2Body*) [[bodiesArray objectAtIndex:i] pointerValue];
bWorld->DestroyBody(removeLinkBody); //signal SIGABRT happens here
removeLinkBody = NULL;
[self removeChildByTag:10 + i];
}
}
}
I keep getting a signal SIGABRT on the line:
bWorld->DestroyBody(removeLinkBody); //signal SIGABRT happens here
The chain also freezes. I am not sure whether the all or some bodies have been destroyed and it's just the CCPhyscisSprites which is appearing. How can I solve this?
When you destroy a body, you must also remove it from the array otherwise the pointer previously pointing to the body will become garbage:
b2Body *removeLinkBody = (b2Body*) [[bodiesArray objectAtIndex:i] pointerValue];
bWorld->DestroyBody(removeLinkBody);
[bodiesArray removeObjectAtIndex:i];
Related
I was trying to make a program that updated the amount of hearts the player has every turn (full, half and empty hearts). When I was doing this I instantiated a gameobject of that prefab as a variable and then assigned it to my UI panel in unity. However (I'm not sure but I think that) the variables used in the update just before are still being referenced after being destroyed in the next loop giving me the error:
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Here is the update loop:
void Update()
{
if (HP <= 0)
{
anim.SetBool("Death", true);
Destroy(GameObject.Find("Hearts"));
Destroy(GameObject.Find("Inventory"));
text.alignment = TextAnchor.LowerCenter;
text.text = "\n YOU DIED";
text.fontSize = 150;
text.color = new Color(255, 0, 0);
} else {
foreach (Transform child in GameObject.Find("Hearts").transform)
{
Destroy(child.gameObject);
}
for (var i = 0; i<(int)HP; i++)
{
GameObject Heart = Instantiate(heart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
Heart.transform.SetParent(GameObject.Find("Hearts").transform);
}
if (HP - (float)((int)HP) == 0.5F) {
GameObject HalfHeart = Instantiate(halfheart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
HalfHeart.transform.SetParent(GameObject.Find("Hearts").transform);
}
for (var i =0; i<Mathf.Floor(MaxHP-HP); i++)
{
GameObject EmptyHeart = Instantiate(emptyheart, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
EmptyHeart.transform.SetParent(GameObject.Find("Hearts").transform);
}
}
Is there a way to instantiate a prefab without making a variable?, or a way to make the variable reference temporary so it only lasts one update?
Thank you for your help in advance!
The problem is that once HP goes below zero, every subsequent update will enter the first if-statement and try to delete the "Hearts" and "Inventory" objects over and over. You can solve this by adding a bool called isDead and change the statement to if (HP <= 0 && !isDead) and then set isDead = true inside the block. This will prevent it from entering it twice.
Frankly though, your way of solving things is entirely backwards. As others have pointed out, deleting and instantiating objects every frame is very inefficient, and Transform.Find is also slow. You don't really need to destroy anything at all - you can rather just have a list of hearts and enable/disable an appropriate amount whenever the HP changes. You can have a single half-heart at the end of the list and enable/disable it when appropriate - if you are using a HorizontalLayoutGroup, it will still align correctly. You might want to make it so that you can only change the HP using a property or function (something like ModifyHealth(float amount)), and put the logic for updating the hearts display in there.
Using Cocos2d-x and C++, I'm trying to play and pause an animation for a sprite.
I'm using version 3.15.1 of Cocos2dx.
I have a class called PlayerSprite which is derrived from the cocos2d::Sprite class. Inside PlayerSprite initialization, I've setup my animation with the following code:
SpriteBatchNode* playerSpriteBatch = SpriteBatchNode::create("player.png");
SpriteFrameCache* spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("player.plist");
Vector<SpriteFrame*> animFrames(2);
char str[18] = { 0 };
for (int i = 1; i < 3; i++) {
sprintf(str, "player_idle_%d.png", i);
SpriteFrame* frame = spriteFrameCache->getSpriteFrameByName(str);
animFrames.pushBack(frame);
}
Animation* idleAnim = Animation::createWithSpriteFrames(animFrames, 0.8f);
self->idleAction = self->runAction(RepeatForever::create(Animate::create(idleAnim)));
self->idleAction->setTag(0);
When I run the code, it works fine and the animation loops correctly.
In my void update() method, I am trying to pause/play the action/animation based of weather the player is moving or idle.
I do this with the following code:
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
this->runAction(idleAction);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
When I run this code now, my character falls, and as soon as he hits the gound, I get an error at this->runAction(idleAction); saying:
Access violation reading location 0xDDDDDDE5
I believe this is caused due to this->stopActionByTag(0) deleting the action pointer. I've tried to clone the action to avoid this but have had no success.
I know this is a bit late and you might already have solved this but here goes...
Your problem is that you cannot use one instance of Action (idleAction) multiple times. So, once you have run it and removed it, it is released and cannot be used. So, you have 2 options now,
Either create a new idleAction Action every time before running the action.
Or, have an idleAction retained and don't run it ever. Instead, create a clone of this idleAction and run a new clone each time. i.e.
idleAction->retain();
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
Action idleActionClone = idleAction->clone();
this->runAction(idleActionClone);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
Solution: call retain() to keep your action.
It's a matter of memory management of cocos2d-x.
In create() function of your RepeatForever class (derived from Ref), the reference count is set to 1 and there is a line of code ret->autorelease() before returning the object, which means this object will be released automatically at the end of this frame.
You called runAction() function the same frame you created it, the action is retained by ActionManager, it's reference count set to 2, and then 1 at the end of the frame (autorelease).
After your stopping it, it's released by ActionManager, reference count set to 0 and it's deleted. After this you use your action, it will be an access violation method.
*Edit: don't forget to release the action manually when PlayerSprite is deleted, or it's a leak.
When you stop action it's being recycled from memory. In order to play action once more, you have to recreate it. So you may just make a creator function, which returns your animation. The downside is you're recreating animation each time and it'll also play from the beginning (technically you can rewind it).
But I've developed a simpler technique to pause/resume animations:
Let's say you have an action:
action = MoveBy::create(5.0f, Vec2(50, 100));
Now, you can embed this action into Speed action like this:
action = Speed::create(MoveBy::create(5.0f, Vec2(50, 100)), 1.0f);
1.0f - is speed, so it's normal action rate. Now to pause just call:
action->setSpeed(0.0f);
and to resume:
action->setSpeed(1.0f);
you can also use different speed if you need it for some reason or another ;)
I've tried looking for a solution for this issue everywhere to no avail. I've also tried many different approaches to try and resolve this problem myself but, nothing worked.
Everytime I try to delete a body from the world, I get a read access violation at the IsLocked method in Box2d.
I have tried creating a vector list and then deleting all of the bodies from the world that are in that list. Before deleting I make sure to check that I'm not stepping the world and that there are no duplicates in my list and that the world isn't locked.
I add them to the list like so:
for (size_t i = 0; i < m_PlankObjects.size(); i++)
{
m_Game->m_DestroyObjectList.push_back(m_PlankObjects[i].GetBody());
}
This is the GetBody() method:
b2Body * GameObject::GetBody()
{
return m_Body;
}
m_Body is defined like so:
b2Body* m_Body;
And destroy like so:
if (m_UpdateWorld)
{
World.Step(1 / 60.f, 8, 3);
}
else
{
if (!World.IsLocked())
{
if (m_DestroyObjectList.size() != 0)
{
for (size_t i = 0; i < m_DestroyObjectList.size(); i++)
{
World.DestroyBody(m_DestroyObjectList[i]);
m_DestroyObjectList.erase(m_DestroyObjectList.begin() + i);
}
}
}
}
After a night's sleep I went back to the issue and debugged it. I found out that I was not clearing the m_PlankObjects array and therefore in the next game loop update it was being accessed again, but since there were no bodies to access, Box2d was throwing an exception.
I am porting a game from cocos2d-iphone 2.x to cocos2d-x 3.x.
Have to solve a few problems, including a major crash - the subject of this post.
It has been determined that the crash happens because SOMETIMES, my replaceScene call results in a messed-up important public variable.
My class:
class Player : public cocos2d::Sprite
{
public:
....
cocos2d::Vec2 desiredPosition;
....
My Layer methods:
Scene* GameLevelLayer::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = GameLevelLayer::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
bool GameLevelLayer::init()
{
// super init first
if ( !Layer::init() )
{
return false;
}
....
player = (Player*) cocos2d::Sprite::create("sprite_idle_right#2x.png");
player->setPosition(Vec2(100, 50));
player->desiredPosition = player->getPosition();
....
this->schedule(schedule_selector(GameLevelLayer::update), 1.0/60.0);
....
return true;
}
void GameLevelLayer::endGame(bool won) {
....
MenuItem* display;
if (currentLevel < lastLevel && won) {
++currentLevel;
display = MenuItemImage::create("next.png" ,"next.png" ,"next.png",
CC_CALLBACK_1(GameLevelLayer::replaceSceneCallback, this));
} else {
// Lost the game
currentLevel = 1;
display = MenuItemImage::create("replay.png", "replay.png", "replay.png",
CC_CALLBACK_1(GameLevelLayer::replaceSceneCallback, this));
}
....
}
void GameLevelLayer::replaceSceneCallback(Ref* sender) {
Director::getInstance()->replaceScene(this->createScene());
}
The member being messed is the desiredPosition. It is changed inside update() method. The problem is that update() gets an already messed-up desired position. It is only messed-up after a scene was being replaced. The problem happens once in 10 runs, or so. It even appears that when update() is called first time after the scene has been replaced, desiredPosition set to some garbage. is I was unable to learn more.
My Player class does not have a separate constructor.
Please advise.
I forgot to initialize another instance variable. That instance variable is used to calculate the desiredPosition.
Please help me, i am trying to remove b2Body from world but getting assertion error as
"Assertion failed: (m_world->IsLocked() == false)" in the following code:
-(void)beginContact:(b2Contact *)contact{
for (int i=0; i<10; i++) {
b2Body *bodyA=contact->GetFixtureA()->GetBody();
b2Body *bodyB=contact->GetFixtureB()->GetBody();
if((bodyA&&bodyA==monsterBody[i])||(bodyB&&bodyB==monsterBody[i]))
{ [self removeChild:(CCSprite*)monsterBody[i]->GetUserData() cleanup:YES];
NSLog(#"%d",_world->IsLocked());
_world->DestroyBody(monsterBody[i]);
break;}
}}
Contact listener callback methods are executed during (within) a world step time. So deleting a body at this time will cause Assertion failed error.
What you can do is set a bool like isOkToDelete in the body's user data. Then inside the beginContact() callback just update the bool to yes. And then you can do delete outside the step method like even inside the update() tick after cocos2d renders the sprite of the body, or you can insert the body into an array and delete later etc.