Moving player using keyboard listeners - c++

I've got a player class, having MoveUp, MoveLeft and MoveRight functions.
At MainScene.cpp (my only scene so far), I've got a listener
auto keyboardListener = EventListenerKeyboard::create();
keyboardListener->onKeyPressed = CC_CALLBACK_2(MainScene::keyPressed, this);
keyboardListener->onKeyReleased = CC_CALLBACK_2(MainScene::keyReleased, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(keyboardListener, this);
Also, I've got a pointer player, defined by
this->player = rootNode->getChildByName<Player*>("Player1");
Also, I've got a function keyPressed and keyReleased in MainScene.
In keyPressed function, I was scheduling functions with if's:
if (keyCode == cocos2d::EventKeyboard::KeyCode::KEY_D) { schedule(schedule_selector(MainScene::MoveRight)); }
But there was a problem, when I tried to do the same thing with objects instead of sprites and with functions located in separated class, not within the same file.
If I try to run code like this in MainScene::keyPressed:
if (keyCode == cocos2d::EventKeyboard::KeyCode::KEY_W) { this->player->MoveLeft(5); }
, player moves only once per key press (I want it to move till I release that key), and if I try to schedule it or do something like this, it doesn't work or there are errors.
I tried to make something with CallFunc and CCCallFunc, but nothing seems to work.
Could you please help me? :)

Why not create a method startMoving() and stopMoving() in Player class?
Something like this:
void Player::init(){
scheduleUpdate()
}
void Play::startMoving(){
isMoving = true;
}
void Player::stopMoving(){
isMoving = false;
}
void Player::update(float delta){
if(isMoving){
//move player here
sprite->setPositionX(sprite->getPositionX() + speed * delta);
}
}
and then call them from keyPressed/keyReleased?

Related

Unreal Engine 4: C++ Delegate not being called

I've been working on converting some blueprint logic over to C++. One of the things I have is a button. The button can be pressed in VR and has a delegate that is called to notify any registered functions that the button press occurred. Here is how the delegate is declared in the AButtonItem.h class.
#pragma once
#include "BaseItem.h"
#include "ButtonItem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonItemPressedSignatrue);
UCLASS()
class AButtonItem : public ABaseItem
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Touch)
float myMaxButtonPress;
public:
UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
};
The delegate's broadcast function is then being called when the button is pressed like so:
ButtonItem_OnPressed.Broadcast();
(This function should defiantly be called because I have a debug statement that prints right before the call. Its also important to note this was all working when it was blueprint logic.)
Here is where I try to register with the delegate and how I declared the function that will be called:
WeaponMaker.h:
UFUNCTION()
void OnNextBladeButtonPressed();
WeaponMaker.cpp:
void AWeaponMaker::BeginPlay()
{
Super::BeginPlay();
TArray<USceneComponent*> weaponMakerComponents;
this->GetRootComponent()->GetChildrenComponents(true, weaponMakerComponents);
for (int componentIndex = 0; componentIndex < weaponMakerComponents.Num(); componentIndex++)
{
if (weaponMakerComponents[componentIndex]->GetName().Equals("NextBladeButton") == true)
{
myNextBladeButton = (AButtonItem*)weaponMakerComponents[componentIndex];
break;
}
}
if (myNextBladeButton != NULL)
{
myNextBladeButton->ButtonItem_OnPressed.AddDynamic(this, &AWeaponMaker::OnNextBladeButtonPressed);
}
}
I put a breakpoint and a print statement in the function OnNextBladeButtonPressed so I should immediately know when it works but its never happening. I also re-created the blueprint itself from scratch but still no luck. Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.
Edit: Here is where I call the broadcast function in my AButtonItem code. It seems to be getting called since i see the UE_LOG output in the console:
void AButtonItem::Tick(float deltaTime)
{
FTransform buttonWorldTransform;
FVector buttonLocalSpacePos;
FVector ownerLocalSpacePos;
FVector localDiff;
float buttonPressAmount;
if (myHasStarted == true)
{
Super::Tick(deltaTime);
if (myButtonComponent != NULL)
{
if (myPrimaryHand != NULL)
{
//Get the world space location of the button.
buttonWorldTransform = myButtonComponent->GetComponentTransform();
//Convert the location of the button and the location of the hand to local space.
buttonLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myInitialOverlapPosition);
ownerLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myPrimaryHand->GetControllerLocation() + (myPrimaryHand->GetControllerRotation().Vector() * myPrimaryHand->GetReachDistance()));
//Vector distance between button and hand in local space.
localDiff = ownerLocalSpacePos - buttonLocalSpacePos;
//Only interested in the z value difference.
buttonPressAmount = FMath::Clamp(FMath::Abs(localDiff.Z), 0.0f, myMaxButtonPress);
localDiff.Set(0.0f, 0.0f, buttonPressAmount);
//Set the new relative position of button based on the hand and the start button position.
myButtonComponent->SetRelativeLocation(myButtonInitialPosition - localDiff);
//UE_LOG(LogTemp, Error, TEXT("buttonPressAmount:%f"), buttonPressAmount);
if (buttonPressAmount >= myMaxButtonPress)
{
if (myHasBeenTouchedOnce == false)
{
//Fire button pressed delegate
if (ButtonItem_OnPressed.IsBound() == true)
{
ButtonItem_OnPressed.Broadcast();
AsyncTask(ENamedThreads::GameThread, [=]()
{
ButtonItem_OnPressed.Broadcast();
});
}
myHasBeenTouchedOnce = true;
myButtonComponent->SetScalarParameterValueOnMaterials("State", 1.0f);
Super::VibrateTouchingHands(EVibrationType::VE_TOUCH);
}
}
}
else
{
//Slowly reset the button position back to the initial position when not being touched.
FVector newPosition = FMath::VInterpTo(myButtonComponent->GetRelativeTransform().GetLocation(), myButtonInitialPosition, deltaTime, 10.0f);
myButtonComponent->SetRelativeLocation(newPosition);
}
}
}
}
First of all:
UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
This should be:
UPROPERTY(BlueprintAssignable, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
For convenience.
Secondly the tick function may be called before begin play is executed for a number of reasons. Your even't won't be broadcasted if the game hasn't begin play yet. So to avoid just add a check in your tick function.
if(bHasBegunPlay)
{
// .. your logics ...
}
Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.
I don't see any issue in the code from the question. At my glance, the issue could be in different location. I would suspect that AWeaponMaker had been deleted at moment of broadcasting.

SFML objects won't draw when its parent class is reinitialised

I'm working on a new project and an implementing a basic scene change. I have the different scenes setup as their own classes, with the intialisation function being used to create and reposition different SFML objects. I saw this answer and have written my scene switcher similarly:
// Create scene monitoring variable
int scene[2];
scene[0] = 0; // Set current scene to menu
scene[1] = 0; // Set scene change to no
...
// Check for scene change
if(scene[1] == 0) {
// Run tick function based on current scene
switch(scene[0]) {
case 0:
// Main menu - run tick function
menu.tick();
}
}
if(scene[1] == 1) {
// Reset scene that you've changed to
switch(scene[0]) {
case 0:
// Main menu - reset it
menu = Menu(window, scene); // <-- Reinitialise menu here
}
// Set change variable to 0
scene[1] = 0;
}
You can see the full code on the github repository.
However, this doesn't seem to work properly - as soon as a scene change is made, the screen goes blank. The class is reintialised (I added a cout to check), the draw function is still run and mouse clicks are still processed, yet nothing appears in the window.
Am I doing something wrong here?
Doing things that way can lead into leak memory errors. I suggest you a different approach: the StateStack
How this works?
The basics of having a StateStack object is store each possible state of your game/app into a stack. This way, you can process each one in the stack order.
What is an State?
An State is something that can be updated, drawn and handle events. We can make an interface or an abstract class to make our screens behave like a State.
Which are the advantages?
With a stack structure, you can easily control how your different scenes are going to handle the three different processing methods. For instance. If you have a mouse click while you're in a pause menu, you won't that click event to reach the menu state or the "game" state. To achieve this, the solution is really easy, simply return false in your handleEvent method if you don't want the event go further this particular state. Note that this idea is also expandable to draw or update methods. In your pause menu, you won't update your "game" state. In your "game" state you won't draw tour menu state.
Example
With this points in mind, this is one possible way of implementation. First, the State interface:
class State{
public:
virtual bool update() = 0;
virtual bool draw(sf::RenderTarget& target) const = 0;
// We will use a vector instead a stack because we can iterate vectors (for drawing, update, etc)
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack) = 0;
};
Following this interface we can have a example MenuState and PauseState:
MenuState
class MenuState : public State{
public:
MenuState(){
m_count = 0;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("MenuState: " + std::to_string(m_count));
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
m_count++;
m_text.setString("MenuState: " + std::to_string(m_count));
return true;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
return true;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::P){
stack.push_back(new PauseState());
return true;
}
}
return true;
}
private:
sf::Font m_font;
sf::Text m_text;
unsigned int m_count;
};
PauseState
class PauseState : public State{
public:
PauseState(){
sf::Font f;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("PauseState");
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
// By returning false, we prevent States UNDER Pause to update too
return false;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
// By returning false, we prevent States UNDER Pause to draw too
return false;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::Escape){
stack.pop_back();
return true;
}
}
return false;
}
private:
sf::Font m_font;
sf::Text m_text;
};
By the way, while I was doing this, I notice that you must have the fonts as an attribute of the class in order to keep the reference. If not, when your text is drawn, its font is lost ant then it fails. Another way to face this is using a resource holder, which is much more efficient and robust.
Said this, our main will look like:
Main
int main() {
// Create window object
sf::RenderWindow window(sf::VideoMode(720, 720), "OpenTMS");
// Set window frame rate
window.setFramerateLimit(60);
std::vector<State*> stack;
// Create menu
stack.push_back(new MenuState());
// Main window loops
while (window.isOpen()) {
// Create events object
sf::Event event;
// Loop through events
while (window.pollEvent(event)) {
// Close window
if (event.type == sf::Event::Closed) {
window.close();
}
handleEventStack(event, stack);
}
updateStack(stack);
// Clear window
window.clear(sf::Color::Black);
drawStack(window, stack);
// Display window contents
window.display();
}
return 0;
}
The stack functions are simple for-loop but, with the detail that iterate the vector backwards. This is the way to imitate that stack behavior, starting from top (size-1 index) and ending at 0.
Stack functions
void handleEventStack(sf::Event e, std::vector<State*> &stack){
for (int i = stack.size()-1; i >=0; --i){
if (!stack[i]->handleEvent(e, stack)){
break;
}
}
}
void updateStack(std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->update()){
break;
}
}
}
void drawStack(sf::RenderTarget &target, std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->draw(target)){
break;
}
}
}
You can learn more about StateStacks and gamedev in general with this book

SFML sf::Text::setFillColor is broken or am I doing something wrong?

The code I've written displays the sf::Drawable objects only for the top state of the state stack. Rendering works fine for everything, except the sf::Text type, that does not change the color of the text when button.getText().setFillColor(sf::Color:Red) is called. However, when I construct a button with a red text, whenever I try to set another color to that button, I only get a white text, no matter what color I request.
Here's where I change the color of a button:
void GameState_MainMenu::handleRealTimeInput()
{
for each (TextButton button in mButtons)
{
if (button.isSpriteClicked())
{
button.getText().setFillColor(sf::Color::Red);
button.triggerAction();
sf::Clock wait;
sf::Time timer = sf::Time::Zero;
timer = sf::seconds(0.15f);
while (wait.getElapsedTime() < timer)
{
}
wait.restart();
}
}
}
and this is my Game::render() method:
void Game::render()
{
GameState *currentState = getActiveState();
if (currentState != nullptr)
{
mWindow.clear();
currentState->draw();
}
mWindow.display();
}
Lastly, this is the draw method of the MainMenu state:
void GameState_MainMenu::draw()
{
game->mWindow.draw(game->mBackground);
game->mWindow.draw(mSelector.mSprite);
for each (TextButton button in mButtons)
{
game->mWindow.draw(button.getText());
}
}
It's probably because you have a while loop in the GameState_MainMenu::handleRealTimeInput that the program is getting stuck in.
You can try to use threads, though that way could get pretty messy. I suggest revising your code.
Okay, so I figured out this had something to do with c++'s for each instruction. As soon as I switched to the classic array-like traversal, my buttons started changing colors. I'm not saying this is THE solution, just that it worked for me. If anyone has the same problem, you might want to check that.

How do I remove a node from current scene in Coco2d-x

There are three messageboxes in the current scene, here I named them _grannyMessage, _grannyMessage2, _grannyMessage3 respectively. I want to make _grannyMessage disappear from the scene when _grannyMessage3 is created in the scene. I am trying to use the "this->removeChild(_grannyMessage);" function but it seems it's not working, am I calling the wrong function anyway? Thanks a lot
auto grannyListener = EventListenerTouchOneByOne::create();
grannyListener -> setSwallowTouches(true);
grannyListener -> onTouchBegan =[this](Touch *touch, Event *event){
MessageBoxes *_grannyMessage =
MessageBoxes::create("The hen can lay an egg everyday");
if(i==0){
_grannyMessage->setPosition(Vec2(600, 450));
addChild(_grannyMessage);
}
else if (i==1)
{
MessageBoxes *_grannyMessage2 =
MessageBoxes::create("2 yuan, that's all I can offer you for the hen");
_grannyMessage2->setPosition(Vec2(400, 450));
addChild(_grannyMessage2);
}
else if (i==2)
{
this->removeChild(_grannyMessage);
MessageBoxes *_grannyMessage3 =
MessageBoxes::create("Well");
_grannyMessage3->setPosition(Vec2(800, 450));
addChild(_grannyMessage3);
}
else
{
return false;
}
i++;
return false;
};
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(grannyListener, this);
It doesn't work because you create new _grannyMessage every time the user touches the screen and only the first one is added to the scene. Then with the third one you try to remove a child that is not on the screen (because it was just created, at the begining of the touch handler).
This happens because when the method ends, the _grannyMessage variable goes out of scope and is forgotten (the first one is retained though, as you added it to the scene).
To solve your problem, you need to store the first _grannyMessage, for example like this :
In *.h of your class add something like this inside your class :
private MessageBoxes *_grannyMessage;
Then change your touch handler to this :
grannyListener -> onTouchBegan =[this](Touch *touch, Event *event){
if(i==0){
MessageBoxes *_grannyMessage = MessageBoxes::create("The hen can lay an egg everyday");
_grannyMessage->setPosition(Vec2(600, 450));
this->_grannyMessage = _grannyMessage; // store the message that we want to remove;
addChild(_grannyMessage);
}
else if (i==1)
{
MessageBoxes *_grannyMessage2 =
MessageBoxes::create("2 yuan, that's all I can offer you for the hen");
_grannyMessage2->setPosition(Vec2(400, 450));
addChild(_grannyMessage2);
}
else if (i==2)
{
this->removeChild(this->_grannyMessage); //remove the stored message
MessageBoxes *_grannyMessage3 =
MessageBoxes::create("Well");
_grannyMessage3->setPosition(Vec2(800, 450));
addChild(_grannyMessage3);
}
else
{
return false;
}
i++;
return false;
};
Just a thought. Can you keep the same message pointer, and update the message label and position?
It might be something like this:
else if (i==2)
{
//_grannyMessage->clear(); // if clear() is available
_grannyMessage->setPosition(Vec2(800, 450)); // new position
_grannyMessage->setLable("New message"); // new message
}

EventListener for buttons

I'm developing a 2D car game using Cocos2d-x and I have a problem.
I want to use Box2D to make collisions and movement managing easier, but before that I want to get my 4 buttons working.
The point is that I have something like this
RaceScene.cpp
bool Race::init() {
//...
auto forwardArrow = MenuItemImage::create("forward_arrow.png", "forward_arrow.png", CC_CALLBACK_1(Race::forward, this));
forwardArrow->setAnchorPoint(Vec2(0,0));
forwardArrow->setPosition(Vec2(origin.x + visibleSize.width - forwardArrow->getContentSize().width*1.2 ,
origin.y + forwardArrow->getContentSize().height));
//...
}
void Race::forward(Ref* pSender)
{
/*MessageBox("YOU PRESSED THE FORWARD BUTTON","Alert");*/
}
But I've noticed this is not the way because I can't neither press more than 1 button at a time nor change speed according to the time the button is pressed.
So I've googled and checked Cocos documentation, and everybody talks about that EventListeners but I don't find how to add it to my problem.
I don't need to handle screen touches, I need to handle EACH button touch.
Thanks.
So, after some hours of researching and trying, as well as receiving some help from Cocos2d-x forums, I got the solution.
RaceScene.cpp
bool Race::init() {
...
forwardArrow = Sprite::create("forward_arrow.png");
forwardArrow->setAnchorPoint(Vec2(0,0));
forwardArrow->setPosition(Vec2(origin.x + visibleSize.width - forwardArrow->getContentSize().width*1.2 ,
origin.y + forwardArrow->getContentSize().height));
this->addChild(forwardArrow);
...
auto listenerForwardButton = EventListenerTouchOneByOne::create();
listenerForwardButton->setEnabled(true);
listenerForwardButton->setSwallowTouches(true);
listenerForwardButton->onTouchBegan = CC_CALLBACK_2(Race::onTouchesForwardBegan, this);
// trigger when you let up
listenerForwardButton->onTouchEnded = CC_CALLBACK_2(Race::onTouchesForwardEnded, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, forwardArrow);
}
bool Race::onTouchesForwardBegan(cocos2d::Touch* touch, cocos2d::Event* event)
{
Vec2 touchPoint = touch->getLocation();
Rect boundingBoxArrow = this->forwardArrow->getBoundingBox();
if (boundingBoxArrow.containsPoint(touchPoint)){
return true;
}
return false;
}
void Race::onTouchesForwardEnded(cocos2d::Touch* touch, cocos2d::Event* event)
{
cocos2d::log("You touched %f, %f", touch->getLocationInView().x, touch->getLocationInView().y);
MessageBox("YOU PRESSED THE FORWARD BUTTON","Alert");
auto forward = MoveBy::create(1,Vec2(-5,0));
mainSprite->runAction(forward);
}
RaceScene.h
class Race : public cocos2d::Layer {
public:
virtual bool init();
cocos2d::Sprite* forwardArrow;
...
private:
bool onTouchesForwardBegan(cocos2d::Touch* touch, cocos2d::Event* event);
void onTouchesForwardEnded(cocos2d::Touch* touch, cocos2d::Event* event);
...
};
Please note that you need a listener->onTouchBegan which CC_CALLBACK_X redirects to a bool function. When this one returns true, then the onTouchEnd function works, it doesn't when this one returns false.
Hope it helps someone.