First of all thank you for your time and apologize for the long of this post but i couldn't find any other way to make it shorter and also for me english! if you don't understand something, just ask ^^. Hope you can find the error because is driving me crazy.
I'm currently learning DirectX 11 and i'm making This Little Game from this website but applying OOP and DirectX 11 instead of 9 just taking certain things from that project.
Ok, now that you have a little context here is the problem.
I made an abstract class called GameObject which encapsulates all the functionalities concerning to rendering, like storing the image(s), animation, transition between frames, etc. This GameObject class is used to define every other object that will interact in my game.
GameObject Class
////////////////////////////////////////////////////////////////////////////////
// Filename: GameObject.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GAME_OBJECT_H_
#define _GAME_OBJECT_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "Sprite.h"
#include "InputHandler.h"
#include "Timer.h"
class GameObject
{
public:
GameObject();
GameObject(const GameObject& other);
~GameObject();
virtual bool Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen) = 0;
bool Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen, WCHAR* spriteFileName, Bitmap::DimensionType bitmap, Bitmap::DimensionType sprite, int numberOfFramesAcross, int initialFrame, bool useTimer);
virtual void Shutdown();
virtual bool Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX wordMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix);
void Move();
void Move(const D3DXVECTOR2 vector);
virtual void Frame(const InputHandler::ControlsType& controls);
void SortFrameArray(const int* framesOrder, int size);
void SetPosition(const POINT& position);
const POINT GetPosition();
void SetVelocity(const D3DXVECTOR2& velocity);
const D3DXVECTOR2 GetVelocity();
void SetStatus(const bool status);
bool GetStatus();
float GetMovementDelayTime();
void ResetMovementDelayTime();
float GetAnimationDelayTime();
void ResetAnimationDelayTime();
//Both of this objects i think i'll remove them from this class. I don't think they belong here.
ID3D11Device* GetDevice();
HWND GetHWND();
Sprite* GetSprite();
protected:
ID3D11Device* m_device;
HWND m_hwnd;
Sprite* m_Sprite;
Timer* m_Timer;
POINT m_position;
D3DXVECTOR2 m_velocity;
bool m_active;
float m_movementDelay;
float m_animationDelay;
};
#endif
Cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: GameObject.cpp
////////////////////////////////////////////////////////////////////////////////
#include "GameObject.h"
GameObject::GameObject()
{
this->m_Sprite = nullptr;
this->m_Timer = nullptr;
this->m_movementDelay = 0.0f;
this->m_animationDelay = 0.0f;
}
GameObject::GameObject(const GameObject& other)
{
}
GameObject::~GameObject()
{
}
bool GameObject::Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen, WCHAR* spriteFileName, Bitmap::DimensionType bitmap, Bitmap::DimensionType sprite, int numberOfFramesAcross, int initialFrame, bool useTimer)
{
bool result;
this->m_device = device;
this->m_hwnd = hwnd;
this->m_Sprite = new Sprite();
if (!this->m_Sprite)
{
return false;
}
result = this->m_Sprite->Initialize(device, hwnd, screen, spriteFileName, bitmap, sprite, numberOfFramesAcross, initialFrame);
if (!result)
{
return false;
}
if (useTimer)
{
this->m_Timer = new Timer();
if (!this->m_Timer)
{
return false;
}
result = this->m_Timer->Initialize();
if (!result)
{
return false;
}
}
return true;
}
void GameObject::Shutdown()
{
SAFE_SHUTDOWN(this->m_Sprite);
SAFE_DELETE(this->m_Timer);
}
bool GameObject::Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX wordMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
return this->m_Sprite->Render(deviceContext, this->m_position, wordMatrix, viewMatrix, projectionMatrix);
}
void GameObject::Move()
{
this->m_position.x += this->m_velocity.x;
this->m_position.y += this->m_velocity.y;
}
void GameObject::Move(const D3DXVECTOR2 vector)
{
this->m_position.x += vector.x;
this->m_position.y += vector.y;
}
void GameObject::Frame(const InputHandler::ControlsType& controls)
{
if (this->m_Timer)
{
this->m_Timer->Frame();
this->m_movementDelay += this->m_Timer->GetTime();
this->m_animationDelay += this->m_Timer->GetTime();
}
}
void GameObject::SortFrameArray(const int* framesOrder, int size)
{
this->m_Sprite->SortFrameArray(framesOrder, size);
}
void GameObject::SetPosition(const POINT& position)
{
this->m_position = position;
}
const POINT GameObject::GetPosition()
{
return this->m_position;
}
void GameObject::SetVelocity(const D3DXVECTOR2& velocity)
{
this->m_velocity = velocity;
}
const D3DXVECTOR2 GameObject::GetVelocity()
{
return this->m_velocity;
}
void GameObject::SetStatus(const bool status)
{
this->m_active = status;
}
bool GameObject::GetStatus()
{
return this->m_active;
}
Sprite* GameObject::GetSprite()
{
return this->m_Sprite;
}
float GameObject::GetAnimationDelayTime()
{
return this->m_animationDelay;
}
void GameObject::ResetMovementDelayTime()
{
this->m_movementDelay = 0.0f;
}
float GameObject::GetMovementDelayTime()
{
return this->m_animationDelay;
}
void GameObject::ResetAnimationDelayTime()
{
this->m_animationDelay = 0.0f;
}
ID3D11Device* GameObject::GetDevice()
{
return this->m_device;
}
HWND GameObject::GetHWND()
{
return this->m_hwnd;
}
And i made the derived class Fighter which represents my Spaceship and has a FighterFlame which i think is not relevant to the problem, and a list of pointers to pointers of Bullet (m_Bullets) which will be the bullets coming out from the Ship.
////////////////////////////////////////////////////////////////////////////////
// Filename: Fighter.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FIGHTER_H_
#define _FIGHTER_H_
//////////////
// INCLUDES //
//////////////
#include <list>
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "GameObject.h"
#include "Bullet.h"
#include "FighterFlame.h"
class Fighter : public GameObject
{
public:
Fighter();
Fighter(const Fighter& other);
~Fighter();
virtual bool Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen) override;
virtual void Shutdown();
virtual bool Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX wordMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) override;
virtual void Frame(const InputHandler::ControlsType& controls) override;
private:
void GenerateTriBullet();
void ValidateBulletsBounds();
private:
int m_life;
int m_lives;
FighterFlame* m_FighterFlame;
std::list<Bullet**> m_Bullets;
const int SHIP_SPEED = 3;
const float MOVEMENT_DELAY = 16.0f;
const float ANIMATION_DELAY = 20.0f;
const float SHOOT_DELAY = 30.0f;
};
#endif
Cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: Fighter.cpp
////////////////////////////////////////////////////////////////////////////////
#include "Fighter.h"
Fighter::Fighter() : GameObject()
{
this->m_life = 100;
this->m_lives = 3;
this->m_FighterFlame = nullptr;
}
Fighter::Fighter(const Fighter& other)
{
}
Fighter::~Fighter()
{
}
bool Fighter::Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen)
{
bool result;
this->m_life = 100;
this->m_lives = 3;
result = GameObject::Initialize(device, hwnd, screen, L"Fighter.dds", Bitmap::DimensionType{ 1152, 216 }, Bitmap::DimensionType{ 144, 108 }, 8, 7, true);
if (!result)
{
MessageBox(hwnd, L"Could not initialize Fighter", L"Error", MB_OK);
return false;
}
this->m_position = POINT{ 0, 0 };
int order[16] = { 7, 6, 5, 4, 3, 2, 1, 0, 8, 9, 10, 11, 12, 13, 14, 15 };
GameObject::SortFrameArray(order, 16);
this->m_FighterFlame = new FighterFlame();
if (!this->m_FighterFlame)
{
return false;
}
result = this->m_FighterFlame->Initialize(device, hwnd, screen);
if (!result)
{
MessageBox(hwnd, L"Could not initialize FighterFlame", L"Error", MB_OK);
return false;
}
return true;
}
bool Fighter::Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX wordMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
bool result;
result = GameObject::Render(deviceContext, wordMatrix, viewMatrix, projectionMatrix);
if (!result)
{
return false;
}
result = this->m_FighterFlame->Render(deviceContext, wordMatrix, viewMatrix, projectionMatrix);
if (!result)
{
return false;
}
for (Bullet** bullet : this->m_Bullets)
{
if (bullet)
{
result = (*bullet)->Render(deviceContext, wordMatrix, viewMatrix, projectionMatrix);
if (!result)
{
return false;
}
}
}
return true;
}
void Fighter::Shutdown()
{
GameObject::Shutdown();
SAFE_SHUTDOWN(this->m_FighterFlame);
for (Bullet** bullet : this->m_Bullets)
{
SAFE_SHUTDOWN(*bullet);
}
this->m_Bullets.clear();
}
void Fighter::Frame(const InputHandler::ControlsType& controls)
{
GameObject::Frame(controls);
this->m_FighterFlame->SetPosition(POINT{ this->m_position.x - 26, this->m_position.y + 47});
this->m_FighterFlame->Frame(controls);
for (Bullet** bullet : this->m_Bullets)
{
(*bullet)->Frame(controls);
}
if (GameObject::GetMovementDelayTime() > MOVEMENT_DELAY)
{
if (controls.up ^ controls.down)
{
if (controls.up)
{
if (GameObject::GetPosition().y > 0)
{
GameObject::Move(D3DXVECTOR2(0, -SHIP_SPEED));
}
if (GameObject::GetAnimationDelayTime() > ANIMATION_DELAY)
{
GameObject::GetSprite()->IncrementFrame();
GameObject::ResetAnimationDelayTime();
}
}
else if (controls.down)
{
if (GameObject::GetPosition().y < (GameObject::GetSprite()->GetBitmap()->GetScreenDimensions().height - GameObject::GetSprite()->GetBitmap()->GetBitmapDimensions().height))
{
GameObject::Move(D3DXVECTOR2(0, SHIP_SPEED));
}
if (GameObject::GetAnimationDelayTime() > ANIMATION_DELAY)
{
GameObject::GetSprite()->DecrementFrame();
GameObject::ResetAnimationDelayTime();
}
}
}
else
{
if (GameObject::GetSprite()->GetCurrentFrame() > (GameObject::GetSprite()->GetAmountOfFrames() / 2))
{
if (GameObject::GetAnimationDelayTime() > ANIMATION_DELAY)
{
GameObject::GetSprite()->DecrementFrame();
GameObject::ResetAnimationDelayTime();
}
}
if (GameObject::GetSprite()->GetCurrentFrame() < (GameObject::GetSprite()->GetAmountOfFrames() / 2))
{
if (GameObject::GetAnimationDelayTime() > ANIMATION_DELAY)
{
GameObject::GetSprite()->IncrementFrame();
GameObject::ResetAnimationDelayTime();
}
}
}
if (controls.right ^ controls.left)
{
if (controls.right)
{
if (GameObject::GetPosition().x < (GameObject::GetSprite()->GetBitmap()->GetScreenDimensions().width - GameObject::GetSprite()->GetBitmap()->GetBitmapDimensions().width))
{
GameObject::Move(D3DXVECTOR2(SHIP_SPEED, 0));
}
}
else if (controls.left)
{
if (GameObject::GetPosition().x > 0)
{
GameObject::Move(D3DXVECTOR2(-SHIP_SPEED, 0));
}
}
}
GameObject::ResetMovementDelayTime();
}
if (controls.spaceBar)
{
Fighter::GenerateTriBullet();
}
Fighter::ValidateBulletsBounds();
}
void Fighter::GenerateTriBullet()
{
Bullet* upBullet = new Bullet();
upBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
upBullet->SetVelocity(D3DXVECTOR2(20, 2));
upBullet->SetPosition(GameObject::GetPosition());
upBullet->Move();
this->m_Bullets.push_back(&upBullet);
Bullet* middleBullet = new Bullet();
middleBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
middleBullet->SetVelocity(D3DXVECTOR2(20, 0));
middleBullet->SetPosition(GameObject::GetPosition());
middleBullet->Move();
this->m_Bullets.push_back(&middleBullet);
Bullet* downBullet = new Bullet();
downBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
downBullet->SetVelocity(D3DXVECTOR2(20, -2));
downBullet->SetPosition(GameObject::GetPosition());
downBullet->Move();
this->m_Bullets.push_back(&downBullet);
}
void Fighter::ValidateBulletsBounds()
{
for (std::list<Bullet**>::iterator it = this->m_Bullets.begin(); it != this->m_Bullets.end(); it++)
{
if ((*(*(&(it)._Ptr->_Myval)))->GetPosition().x > GameObject::GetSprite()->GetBitmap()->GetScreenDimensions().width)
{
SAFE_SHUTDOWN(**it);
this->m_Bullets.erase(it);
}
}
}
And finally the problematic one, The Bullet class who is also derived from GameObject and will represent the bullets that the spaceship can shoot.
////////////////////////////////////////////////////////////////////////////////
// Filename: Bullet.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _BULLET_H_
#define _BULLET_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "GameObject.h"
class Bullet : public GameObject
{
public:
Bullet();
Bullet(const Bullet& other);
~Bullet();
virtual bool Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen) override;
virtual void Frame(const InputHandler::ControlsType& controls) override;
private:
const float MOVEMENT_DELAY = 16.0f;
};
#endif
Cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: Bullet.cpp
////////////////////////////////////////////////////////////////////////////////
#include "Bullet.h"
Bullet::Bullet() : GameObject()
{
}
Bullet::Bullet(const Bullet& other)
{
}
Bullet::~Bullet()
{
}
bool Bullet::Initialize(ID3D11Device* device, HWND hwnd, Bitmap::DimensionType screen)
{
bool result;
result = GameObject::Initialize(device, hwnd, screen, L"Bullet.dds", Bitmap::DimensionType{ 18, 3 }, Bitmap::DimensionType{ 18, 3 }, 1, 0, true);
if (!result)
{
return false;
}
return true;
}
void Bullet::Frame(const InputHandler::ControlsType& controls)
{
GameObject::Frame(controls);
if (GameObject::GetMovementDelayTime() > MOVEMENT_DELAY)
{
GameObject::Move();
}
}
And the problem:
When the Gameloop is running and i press space bar, this occurs
// this if is from Fighter::Frame
if (controls.spaceBar)
{
Fighter::GenerateTriBullet();
}
Fighter::ValidateBulletsBounds();
It enters to the GenerateTriBullet method, which stores 3 bullets on the m_Bullets list.
void Fighter::GenerateTriBullet()
{
Bullet* upBullet = new Bullet();
upBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
upBullet->SetVelocity(D3DXVECTOR2(20, 2));
upBullet->SetPosition(GameObject::GetPosition());
upBullet->Move();
this->m_Bullets.push_back(&upBullet);
Bullet* middleBullet = new Bullet();
middleBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
middleBullet->SetVelocity(D3DXVECTOR2(20, 0));
middleBullet->SetPosition(GameObject::GetPosition());
middleBullet->Move();
this->m_Bullets.push_back(&middleBullet);
Bullet* downBullet = new Bullet();
downBullet->Initialize(this->m_FighterFlame->GetDevice(), this->m_FighterFlame->GetHWND(), this->m_FighterFlame->GetSprite()->GetBitmap()->GetScreenDimensions());
downBullet->SetVelocity(D3DXVECTOR2(20, -2));
downBullet->SetPosition(GameObject::GetPosition());
downBullet->Move();
this->m_Bullets.push_back(&downBullet);
}
When it leaves the method, i check the list and the bullets are still there as well as before entering the ValidateBulletsBound, but as soon as it enters the method and before doing ANYTHING, the bullets on the list are simply gone, and with this i mean, the m_Bullets list still has three objects, but happens that they are all NULL.
To explain myself a little better, what i want to do is that every time i press space-bar 3 bullets appears on the screen, and I'm trying to do that by asking if the space-bar value is true, add 3 Bullets to the m_Bullet list, then validate that the bullets in the list are still between the screen space, otherwise remove it.
But as you can see, i successfully store the bullets on the list and as soon as i enter to validate, they are gone... poof!
I don't know why any of this is happening, they are different instance of a class who doesn't share anything between them (memory-wise speaking), there's no static method or pointers shared by then, and even though they would it shouldn't be a problem given that they are just entering in another method and no operation has been done in the middle of that "entering the another method" part, or whatsoever. They even are in the same class, same context, no complex operation or leak (that i know of). I really don't know what's going on!
I want to finish by acknowledging that there are some serious design problems like the one's on GenerateTriBullet, and the fact that i'm not using matrices to move the objects. I'm just trying to finish it first (this is the first game i make on DirectX, really exited btw!!! ), then when i can see the big picture, start to put everything where it belongs. Also, how do i get the value from the list iterator, i read that it was (*it) for simple values, but i have a pointer to a pointer, so i thought it would be **it, but it always resolves to nullptr.
I really hope you can help me.
Here's the project, if you feel like you didn't understand and want to go a little further. You just have to run the project, a ship will appear in a little black window, put a breakpoint on line 182 of the Fighter class then press spacebar on the game window, then from there see what happens with m_Bullets when it leaves GenerateTriBullet and Enters ValidateBulletsBounds.
THANK YOU!
One clear problem:
m_Bullets is a list<Bullet**>. When you add to it you are adding the address of a local variable, e.g.,
Bullet* upBullet = new Bullet();
...
this->m_Bullets.push_back(&upBullet);
Went this method returns, the address stored from the &upBullet expression is no longer valid, as that variable no longer exits.
I think you mean to have m_Bullets as list<Bullet*>, and to add to it:
Bullet* upBullet = new Bullet();
...
this->m_Bullets.push_back(upBullet);
I think the best solution is to let list deal with the memory management and just have m_Bullets as list<Bullet>, then:
this->m_Bullets.emplace_back();
However this will probably require rethinking some of the polymorphism.
Related
Using Gtkmm and Cairo, I want to be able to draw different shapes on photos. In the header bar of my window, I have two buttons representing shapes to draw (circle and rectangle). When you click one of them, you can draw its associated shape. Here is mt code:
MyWindow.cpp
#include "MyWindow.h"
MyWindow::MyWindow()
: circleButton("circle"),
rectangleButton("rectangle ") {
set_default_size(700, 700);
set_position(Gtk::WIN_POS_CENTER);
header.set_show_close_button(true);
header.pack_start(rectangleButton);
header.pack_start(circleButton);;
set_titlebar(header);
// Dwg is an instance of Drawing class
circleButton.signal_clicked().connect([&] {
Dwg.switch_to_circle();
});
rectangleButton.signal_clicked().connect([&] {
Dwg.switch_to_rectangle();
});
add(Dwg);
show_all();
}
Drawing.h
#ifndef DRAWING_H
#define DRAWING_H
#include <gtkmm.h>
#include <cairo/cairo.h>
class MyDrawing : public Gtk::Layout {
public:
MyDrawing();
~MyDrawing();
void switch_to_circle();
void switch_to_rectangle();
protected:
virtual bool draw_image(const Cairo::RefPtr<::Cairo::Context> &cr);
virtual bool draw_rectangle(const Cairo::RefPtr<::Cairo::Context> &cr);
virtual bool draw_circle(const Cairo::RefPtr<::Cairo::Context> &cr);
private:
Glib::RefPtr<Gdk::Pixbuf> pix;
double beginPoint_x, beginPoint_y, endPoint_x, endPoint_y, lineWidth,width,height;
bool isDrawRectangle;
};
#endif // DRAWING_H
Drawing.cpp
#include <iostream>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>
MyDrawing::MyDrawing()
: isDrawRectangle(true),
width(20),
height(20) {
pix = Gdk::Pixbuf::create_from_file("file.svg", 500, 500);
if (pix) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_image));
}
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK);
signal_button_press_event().connect([&](GdkEventButton *e) {
this->beginPoint_x = e->x;
this->beginPoint_y = e->y;
if(isDrawRectangle) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_rectangle));
queue_draw();
}
else {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_circle));
queue_draw();
}
return true;
});
signal_motion_notify_event().connect([&](GdkEventMotion *e) {
this->endPoint_x = e->x;
this->endPoint_y = e->y;
width = endPoint_x - beginPoint_x;
height = endPoint_y - beginPoint_y;
if(isDrawRectangle) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_rectangle));
queue_draw();
}
else {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_circle));
queue_draw();
}
return true;
});
}
MyDrawing::~MyDrawing() = default;
bool MyDrawing::draw_image(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal img" << std::endl;
if (pix) {
cr->save();
Gdk::Cairo::set_source_pixbuf(cr, pix, 100, 100);
cr->rectangle(0, 0, get_width(), get_height());
cr->fill();
cr->restore();
}
return false;
}
bool MyDrawing::draw_rectangle(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal square" << std::endl;
cr->save();
cr->set_line_width(10);
cr->set_source_rgba(0., 0., 1., 1.);
cr->rectangle(beginPoint_x, beginPoint_y, width, height);
cr->stroke();
cr->save();
cr->restore();
return false;
}
bool MyDrawing::draw_circle(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal square" << std::endl;
cr->save();
cr->set_line_width(10);
cr->set_source_rgba(0., 0., 1., 1.);
cr->arc(beginPoint_x, beginPoint_y, width, 0, 2 * M_PI);
cr->stroke();
cr->restore();
return false;
}
void MyDrawing::switch_to_circle() {
isDrawRectangle = false;
}
void MyDrawing::switch_to_rectangle() {
isDrawRectangle = true;
}
When I click another shape, the previous shape keeps being displayed on the drawing area and the new shape is drawn on it. On the other hand, when the signal is disconnected, the corresponding shape also disappears from the screen. How could I make sure the shapes keep being displayed?
I am not sure exactly what made you inherit from Gtk::Layout instead of using a standard Gtk::DrawingArea, but I created a simplified (and working) example using a design similar to yours.
The basic idea is that when the user is done drawing a shape (stops the drag and releases the mouse button), the following happens:
The current state of the window (in terms of what is drawn on it) is saved to a Gtk::Pixbuf.
That Gtk::PixBuf is painted on the window.
This means that in 1., the last drawn shaped is also saved in the buffer. When 2. happens, is repainted on the window and hence does not go away. Here is the code, which you will need to adapt a bit to your case. First, a draw helper:
class DrawHelper : public Gtk::Layout
{
public:
DrawHelper();
~DrawHelper();
private:
bool draw_image(const Cairo::RefPtr<::Cairo::Context>& p_context);
bool draw_rectangle(const Cairo::RefPtr<::Cairo::Context>& p_context);
bool add_current_shape(const Cairo::RefPtr<::Cairo::Context>& p_context);
Glib::RefPtr<Gdk::Pixbuf> m_buffer;
double m_startX;
double m_startY;
double m_endX;
double m_endY;
double m_width;
double m_height;
sigc::connection m_drawConnection;
};
which is responsible to do the actual drawing and handle connections. It is implemented like so:
DrawHelper::DrawHelper()
{
// Create a pixel buffer containing the background image:
m_buffer = Gdk::Pixbuf::create_from_file("file.svg", DEFAULT_WIDTH, DEFAULT_HEIGHT);
signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::draw_image));
// Enable signals:
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
// Save initial pointer position when clicked:
signal_button_press_event().connect(
[this](GdkEventButton* p_event)
{
m_startX = p_event->x;
m_startY = p_event->y;
return true;
});
// Update rectangle when mouse is dragged:
signal_motion_notify_event().connect(
[this](GdkEventMotion* p_event)
{
m_endX = p_event->x;
m_endY = p_event->y;
m_width = m_endX - m_startX;
m_height = m_endY - m_startY;
signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::draw_rectangle));
queue_draw();
return true;
});
// Change background so it includes the shape just drawn by
// the user:
signal_button_release_event().connect(
[this](GdkEventButton* p_event)
{
// Notice we save to connection to later disconnect it:
m_drawConnection = signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::add_current_shape));
return true;
});
}
DrawHelper::~DrawHelper() = default;
bool DrawHelper::draw_image(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
return false;
}
bool DrawHelper::draw_rectangle(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
p_context->save();
p_context->set_line_width(2);
p_context->rectangle(m_startX, m_startY, m_width, m_height);
p_context->stroke();
p_context->restore();
return false;
}
bool DrawHelper::add_current_shape(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
// Save the current drawing, including the last drawn
// shape. This will become the new background (which will
// visually preserve the last drawn shape).
m_buffer = Gdk::Pixbuf::create(p_context->get_target(), 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT);
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
// We disconnect the signal because we do not want it
// to keep getting called:
m_drawConnection.disconnect();
return false;
}
Then, a window to hold this helper and display it to the user:
class MyWindow : public Gtk::Window
{
public:
MyWindow();
private:
DrawHelper m_drawHelper;
};
MyWindow::MyWindow()
{
set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Add draw helper:
add(m_drawHelper);
// Show all widgets:
show_all();
}
Then, the main to run it:
#include <gtkmm.h>
#include <cairo/cairo.h>
#include <cairomm/context.h>
#include <cairomm/surface.h>
constexpr int DEFAULT_WIDTH = 500;
constexpr int DEFAULT_HEIGHT = 500;
// DrawHelper here ...
// MyWindow here ...
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
return app->run(window);
}
That being said, I would recommend you use a classic Gtk::DrawingArea instead and overload the on_draw signal handler. This would make all of this easier to understand, and the online documentation would be of more help to you.
If you are still interested, I have another solution for you. Instead of saving the already drawn shape on the background image, you could save their parameters directly and redraw them. I have written an example program that does just this:
#include <memory>
#include <vector>
#include <gtkmm.h>
#include <cairo/cairo.h>
#include <cairomm/context.h>
#include <cairomm/surface.h>
constexpr int DEFAULT_WIDTH = 500;
constexpr int DEFAULT_HEIGHT = 500;
constexpr double LINE_WIDTH = 2.0;
// Free functions for drawing shapes:
namespace
{
void DrawRectangle(const Cairo::RefPtr<Cairo::Context>& p_context,
double p_startX,
double p_startY,
double p_width,
double p_height)
{
p_context->save();
p_context->set_line_width(LINE_WIDTH);
p_context->set_source_rgba(0, 0, 1, 1);
p_context->rectangle(p_startX, p_startY, p_width, p_height);
p_context->stroke();
p_context->restore();
}
void DrawCircle(const Cairo::RefPtr<Cairo::Context>& p_context,
double p_startX,
double p_startY,
double p_width)
{
p_context->save();
p_context->set_line_width(LINE_WIDTH);
p_context->set_source_rgba(0, 0, 1, 1);
p_context->arc(p_startX, p_startY, p_width, 0, 2 * M_PI);
p_context->stroke();
p_context->restore();
}
}
// Shape interface:
//
// A shape represents a 2D geometric shape a user can draw on the
// Drawing area. All shapes implement a 'Draw' method which is where
// the drawing logic resides.
class IShape
{
public:
virtual ~IShape() = default;
virtual void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) = 0;
};
// Rectangle shape:
class Rectangle : public IShape
{
public:
Rectangle(double p_left, double p_up, double p_width, double p_height)
: m_left{p_left}
, m_up{p_up}
, m_width{p_width}
, m_height{p_height}
{}
void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
DrawRectangle(p_context, m_left, m_up, m_width, m_height);
}
private:
double m_up;
double m_left;
double m_width;
double m_height;
};
// Circle shape:
class Circle : public IShape
{
public:
Circle(double p_cX, double p_cY, double p_radius)
: m_cX{p_cX}
, m_cY{p_cY}
, m_radius{p_radius}
{}
void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
DrawCircle(p_context, m_cX, m_cY, m_radius);
}
private:
double m_cX;
double m_cY;
double m_radius;
};
// Draw helper:
//
// This class represents the widget onto which the user can drawn. Under
// the hood, this is a Gtk::Drawing area with some signal handlers defined
// to draw shapes on user action.
//
// All drawing occurs in the 'on_draw' method, and all signal handlers to
// is to handle the data (e.g positions, dimentsions, etc) for the 'on_draw'
// method to work appropriately.
//
// The 'SetCurrentShape' method can be used to tell the helper which shape
// to draw.
class DrawHelper : public Gtk::DrawingArea
{
public:
enum class Shape
{
None,
Rectangle,
Circle,
};
DrawHelper()
{
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
// Click, drag and release signal handlers:
signal_button_press_event().connect( [this](GdkEventButton* p_event){return OnButtonPressed(p_event);} );
signal_motion_notify_event().connect( [this](GdkEventMotion* p_event){return OnMouseMotion(p_event);} );
signal_button_release_event().connect([this](GdkEventButton* p_event){return OnButtonReleased(p_event);});
}
void SetCurrentShape(Shape p_shape)
{
m_currentShape = p_shape;
}
private:
// All drawing occurs here and only here:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
// Draw background:
if(!m_buffer)
{
m_buffer = Gdk::Pixbuf::create_from_file("file.svg", DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
// Draw previously drawn shapes:
for(const auto& shape : m_alreadyDrawn)
{
shape->Draw(p_context);
}
// Draw current shape:
if(m_currentShape == Shape::Rectangle)
{
DrawRectangle(p_context, m_startX, m_startY, m_width, m_height);
}
if(m_currentShape == Shape::Circle)
{
DrawCircle(p_context, m_startX, m_startY, m_width);
}
return false;
}
bool OnButtonPressed(GdkEventButton* p_event)
{
m_startX = p_event->x;
m_startY = p_event->y;
return true;
}
bool OnMouseMotion(GdkEventMotion* p_event)
{
m_endX = p_event->x;
m_endY = p_event->y;
m_width = m_endX - m_startX;
m_height = m_endY - m_startY;
queue_draw();
return true;
}
bool OnButtonReleased(GdkEventButton* p_event)
{
if(m_currentShape == Shape::Rectangle)
{
m_alreadyDrawn.push_back(std::make_unique<Rectangle>(m_startX, m_startY, m_width, m_height));
}
if(m_currentShape == Shape::Circle)
{
m_alreadyDrawn.push_back(std::make_unique<Circle>(m_startX, m_startY, m_width));
}
return true;
}
Shape m_currentShape = Shape::None;
Glib::RefPtr<Gdk::Pixbuf> m_buffer;
double m_startX;
double m_startY;
double m_endX;
double m_endY;
double m_width;
double m_height;
std::vector<std::unique_ptr<IShape>> m_alreadyDrawn;
};
// Main window:
//
// This window holds all widgets. Through it, the user can pick a shape
// to draw and use the mouse to draw it.
class MyWindow : public Gtk::Window
{
public:
MyWindow()
: m_drawRectangleBtn{"Rectangle"}
, m_drawCircleBtn{"Circle"}
{
set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
m_headerBar.set_show_close_button(true);
m_headerBar.pack_start(m_drawRectangleBtn);
m_headerBar.pack_start(m_drawCircleBtn);;
set_titlebar(m_headerBar);
add(m_drawArea);
m_drawRectangleBtn.signal_clicked().connect([this](){OnRectangleBtnClicked();});
m_drawCircleBtn.signal_clicked().connect([this](){OnCircleBtnClicked();});
show_all();
}
private:
Gtk::HeaderBar m_headerBar;
Gtk::Button m_drawRectangleBtn;
Gtk::Button m_drawCircleBtn;
DrawHelper m_drawArea;
void OnRectangleBtnClicked()
{
m_drawArea.SetCurrentShape(DrawHelper::Shape::Rectangle);
}
void OnCircleBtnClicked()
{
m_drawArea.SetCurrentShape(DrawHelper::Shape::Circle);
}
};
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
return app->run(window);
}
Each time the user releases the mouse button, the drawn shape is saved (with the parameters at the time of the release) into an std::vector as an IShape, which has a Draw, method. This method can later be called to redraw the shape. Then, in the on_draw handler, all previously drawn shapes are redrawn, leaving them on the screen. Note that I have used a Gtk::DrawingArea here, which is more typical than your approach. I wanted to show you that alternative which, in my opinion, makes cleaner code (no messing around with the handler callbacks).
On a final note, possible enhancements are possible with this (there are more, theses are just some I was thinking about while writing this):
You could reduce performance costs by caching some stuff instead of redrawing everything every time.
You could reduce performance costs by using parameters in the calls to queue_draw so that the whole widget is not constantly redrawn (only the part that changed).
You could use a factory to create the shapes. This would decouple the shape creation from the rest of the code, which would only know the IShape interface. It would also make you program easier to maintain if you ever want to add shapes.
I'm learning how to use cocos2d-x by following the gamesfromscratch tutorial. I got to this part when I noticed this problem with the positioning:
This basic app basically draws on screen the label "You Touched Here" at the position where you click. Whenever I clicked though, the label would appear well above where I clicked.
In the screenshot above, I clicked at the origin. In the output log, you can see that the touch point (specifically: touch->getLocation(), unconverted) is recorded as (0, 166), where it should be (0, 0).
I tried using other position functions, as well as converting the touch coordinates to other coordinate types, but the problem still persisted.
Below is the code for this simple app:
AppDelegate.h
#pragma once
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application {
public:
AppDelegate();
virtual ~AppDelegate();
virtual bool applicationDidFinishLaunching();
virtual void applicationDidEnterBackground();
virtual void applicationWillEnterForeground();
};
AppDelegate.cpp
#include "AppDelegate.h"
// These header files are not used currently
//#include "HelloWorldScene.h"
//#include "GraphicsScene.h"
//#include "TouchScene.h"
#include "TouchScene2.h"
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate() {
}
bool AppDelegate::applicationDidFinishLaunching() {
auto director = Director::getInstance();
auto glView = director->getOpenGLView();
if (!glView) {
glView = GLViewImpl::create("Hello World");
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
}
auto scene = TouchScene2::createScene();
director->runWithScene(scene);
return true;
}
void AppDelegate::applicationDidEnterBackground() {
}
void AppDelegate::applicationWillEnterForeground() {
}
TouchScene2.h
#pragma once
#include "cocos2d.h"
class TouchScene2 : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchEnded(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchMoved(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchCancelled(cocos2d::Touch*, cocos2d::Event*);
CREATE_FUNC(TouchScene2);
private:
cocos2d::Label* labelTouchInfo;
};
TouchScene2.cpp
#include "TouchScene2.h"
USING_NS_CC;
Scene* TouchScene2::createScene()
{
auto scene = Scene::create();
auto layer = TouchScene2::create();
scene->addChild(layer);
return scene;
}
bool TouchScene2::init()
{
if (!Layer::init())
{
return false;
}
labelTouchInfo = Label::createWithSystemFont("Touch or clicksomewhere to begin", "Arial", 30);
labelTouchInfo->setPosition(Vec2(
Director::getInstance()->getVisibleSize().width / 2,
Director::getInstance()->getVisibleSize().height / 2));
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(TouchScene2::onTouchBegan, this);
touchListener->onTouchEnded = CC_CALLBACK_2(TouchScene2::onTouchEnded, this);
touchListener->onTouchMoved = CC_CALLBACK_2(TouchScene2::onTouchMoved, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(TouchScene2::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
this->addChild(labelTouchInfo);
return true;
}
bool TouchScene2::onTouchBegan(Touch* touch, Event* event)
{
std::stringstream output;
output << "Touch Pos: (" << touch->getLocation().x << ", " << touch- >getLocation().y << ")" << std::endl;
log(output.str().c_str());
labelTouchInfo->setPosition(touch->getLocation());
labelTouchInfo->setString("You Touched Here");
return true;
}
void TouchScene2::onTouchEnded(Touch* touch, Event* event)
{
cocos2d::log("touch ended");
}
void TouchScene2::onTouchMoved(Touch* touch, Event* event)
{
cocos2d::log("touch moved");
}
void TouchScene2::onTouchCancelled(Touch* touch, Event* event)
{
cocos2d::log("touch cancelled");
}
One thing to point out is that the tutorial I'm following is several years old (written in 2015 I believe). The author is using version 3.3 beta, while I'm using the latest version 3.17.1. Could this be part of the problem?
And, regardless, how do I fix this issue so that the origin is (0, 0) as it should be?
your TouchScene2.cpp and TouchScene2.hpp seems fine
Problem is in your AppDelegate.cpp where you set
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
Instead of these try the following code.
You have fixed the FrameSize and hasn't set ContentScaleFactor. Set it as in the Cocos2d-x sample project
director->setOpenGLView(glview);
// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
Size frameSize = glview->getFrameSize();
vector<string> searchPath;
if (frameSize.height > mediumResource.size.height)
{
searchPath.push_back(largeResource.directory);
director->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height, largeResource.size.width/designResolutionSize.width));
}
// If the frame's height is larger than the height of small resource size, select medium resource.
else if (frameSize.height > smallResource.size.height)
{
searchPath.push_back(mediumResource.directory);
director->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height, mediumResource.size.width/designResolutionSize.width));
}
// If the frame's height is smaller than the height of medium resource size, select small resource.
else
{
searchPath.push_back(smallResource.directory);
director->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height, smallResource.size.width/designResolutionSize.width));
}
My problem is that I have written this code inside Game::HandleInput() method but I cannot make the sf::Mouse::getPosition() method to get the mouse coordinates relative to window. Without argument, I don't get an error. However, ship doesn't rotate properly. I have tried getPosition(m_window) and getPosition(&m_window). I am getting this error:
no instance of overloaded function "sf::Mouse::getPosition" matches the argument list
EDIT: UPDATED THE WINDOW.H
Window.h:
class Window{
//Constructers
public:
Window();
Window(const std::string& l_title, const sf::Vector2u& l_size);
...
private:
sf::RenderWindow m_window;
...
}
EDIT: ADDED THE FULL CODE OF WINDOW.CPP:
Window.cpp:
#include "Window.h"
Window::Window() {
Setup("Window", sf::Vector2u(640, 480));
}
Window::Window(const std::string& l_title, const sf::Vector2u& l_size) {
Setup(l_title, l_size);
}
Window::~Window() {
Destroy();
}
void Window::Setup(const std::string& l_title,
const sf::Vector2u& l_size)
{
m_windowTitle = l_title;
m_windowSize = l_size;
m_isFullscreen = false;
m_isDone = false;
Create();
}
void Window::Create() {
auto style = (m_isFullscreen ? sf::Style::Fullscreen
: sf::Style::Default);
m_window.create({ m_windowSize.x, m_windowSize.y, 32 },
m_windowTitle, style);
}
void Window::Destroy() {
m_window.close();
}
void Window::Update() {
sf::Event event;
while (m_window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
m_isDone = true;
}
else if (event.type == sf::Event::KeyPressed &&
event.key.code == sf::Keyboard::F5)
{
ToggleFullscreen();
}
}
}
void Window::ToggleFullscreen() {
m_isFullscreen = !m_isFullscreen;
Destroy();
Create();
}
void Window::BeginDraw() { m_window.clear(sf::Color::Black); }
void Window::EndDraw() { m_window.display(); }
bool Window::IsDone() { return m_isDone; }
bool Window::IsFullscreen() { return m_isFullscreen; }
sf::Vector2u Window::GetWindowSize() { return m_windowSize; }
void Window::Draw(sf::Drawable& l_drawable){
m_window.draw(l_drawable);
}
EDIT: UPDATED THE GAME.H
Game.h:
class Game{
public:
Game();
~Game();
void HandleInput();
void Update();
void Render();
Window* GetWindow();
private:
...
Window m_window;
...
}
EDIT: UPDATED THE GAME.CPP
Game.cpp:
Game::Game() : m_window("Multiplayer Space Shooter Game", sf::Vector2u(800, 600)) {
// Setting up class members.
m_shipText.loadFromFile("C:\\Users\\AliTeo\\Desktop\\Piksel çalışmaları\\ship_pixel2.png");
m_ship.setTexture(m_shipText);
m_ship.setOrigin(m_shipText.getSize().x / 2, m_shipText.getSize().y / 2);
m_ship.setPosition(320, 240);
}
void Game::HandleInput() {
...
//Get the angle between ship and mouse.
//Error if there is an argument in getPosition()
m_angle = atan2(sf::Mouse::getPosition().y - m_ship.getPosition().y, sf::Mouse::getPosition().x - m_ship.getPosition().x); //TO DO: getPosition(&Relative To)
m_angle *= 180 / m_PI;
...
}
Window* Game::GetWindow() { return &m_window; }
EDIT: ADDED THE MAIN.CPP
Main.cpp
int main() {
Game game;
while (!game.GetWindow()->IsDone()) {
game.HandleInput();
game.Update();
game.Render();
}
}
First let me give you what I assume is a minimal example reproducing your problem:
#include <SFML/Graphics.hpp>
class MyWindow {
public:
MyWindow()
: m_window({800, 600, 32}, "my window title") {}
private:
sf::RenderWindow m_window;
};
int main() {
MyWindow window;
sf::Mouse::getPosition(window);
}
This code doesn't compile (and admittedly wouldn't do anything interesting when compiled, but that's not the point). I suspect it'd give you the same error that you're currently having if you tried to compile it.
Please note that this is what we expect when we talk about a MCVE: this code is short, simple, exhibits the error and would compile if not because of it.
Besides, it makes the error painfully clear, and if you tried to come up with a MCVE yourself, you may have solved your problem without having to post a question here, which would certainly save you time.
Contrast with your code:
m_angle = atan2(sf::Mouse::getPosition().y - m_ship.getPosition().y
,sf::Mouse::getPosition().x - m_ship.getPosition().x
);
//TO DO: getPosition(Relative To)
This code is legal, but you explained that it is incorrect and you wanted to turn it into something along those lines:
m_angle = atan2(sf::Mouse::getPosition(m_window).y - m_ship.getPosition().y
,sf::Mouse::getPosition(m_window).x - m_ship.getPosition().x
);
//TO DO: getPosition(Relative To)
... which doesn't compile.
However, in this scope m_window is a Window not a sf::RenderWindow!
The problem is that you're passing a reference to an object (MyWindow in my example, Window in your case) that encapsulates a sf::RenderWindow, but isn't convertible to sf::Window& itself.
Therefore, you can't pass it to sf::Mouse::getPosition which expects either nothing or a sf::Window&, but certainly not a Window& or a MyWindow&.
There are a lot of ways of fixing this. Two of which are presented below:
#include <SFML/Graphics.hpp>
class MyWindow {
public:
MyWindow()
: m_window({800, 600, 32}, "my window title") {}
// you could add an accessor
const sf::Window& getSfmlWindow() const { return m_window; }
// you may also expose a method to get the mouse position
// relatively to this window
const sf::Vector2i getMousePosition() const {
return sf::Mouse::getPosition(m_window);
}
private:
sf::RenderWindow m_window;
};
int main() {
MyWindow window;
sf::Vector2i mouse_position;
// this won't work! window isn't convertible to sf::Window&
// mouse_position = sf::Mouse::getPosition(window);
// using the accessor
mouse_position = sf::Mouse::getPosition(window.getSfmlWindow());
// or the exposed method
mouse_position = window.getMousePosition();
}
After I transition to the HelloWorldScene from the GameLayerScene, the scene (HelloWorldScene)freezes and the buttons are not clickable. Is it wrong to use replaceScene? How can I implement a function to return to the main menu from the main game? I used push and pop as well but not working.
GameLayerScene
Scene* GameLayer::createScene(int level)
{
auto scene = Scene::create();
auto layer = GameLayer::create(level);
scene->addChild(layer);
return scene;
}
GameLayer* GameLayer::create(int level)
{
GameLayer *pRet = new GameLayer();
pRet->init(level);
pRet->autorelease();
return pRet;
}
bool GameLayer::init(int level)
{
if (!Layer::init())
return false;
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->setSwallowTouches(_swallowsTouches);
touchListener->onTouchBegan = CC_CALLBACK_2(GameLayer::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(GameLayer::onTouchMoved, this);
touchListener->onTouchEnded = CC_CALLBACK_2(GameLayer::onTouchEnded, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(GameLayer::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
_level = level;
//This will be shown when the pause is pressed.
auto pauseLayer=LayerColor::create(Color4B::BLACK, WINSIZE.width, WINSIZE.height);
auto pauseLabel=Label::createWithTTF("Paused", "fonts/Marker Felt.ttf", 24);
pauseLabel->setPosition(WINSIZE.width / 2.0, (WINSIZE.height/ 2.0) + 100);
pauseLayer->addChild(pauseLabel);
// Add your required content to pauseLayer like pauseLabel
auto resumeButton = cocos2d::ui::Button::create("resume.png");
resumeButton->setPosition(Vec2(WINSIZE.width / 2.0, WINSIZE.height/ 2.0));
resumeButton->addClickEventListener([pauseLayer](Ref*){
//
if(Director::getInstance()->isPaused()) {
Director::getInstance()->resume();
pauseLayer->setVisible(false);
}
});
auto menuButton = cocos2d::ui::Button::create("menu.png");
menuButton->setPosition(Vec2(WINSIZE.width / 2.0, (WINSIZE.height/ 2.0) - 100));
menuButton->addClickEventListener([pauseLayer](Ref*){
Director::getInstance()->replaceScene(HelloWorldScene::createScene());
// Director::getInstance()->end();
});
pauseLayer->addChild(resumeButton);
pauseLayer->addChild(menuButton);
pauseLayer->setVisible(false);
pauseLayer->setOpacity(220); // so that gameplay is slightly visible
addChild(pauseLayer, ZOrder::Level);
AppDelegate
bool AppDelegate::applicationDidFinishLaunching()
{
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview)
{
glview = GLViewImpl::create("KaratePuzzle");
director->setOpenGLView(glview);
}
director->setDisplayStats(false);
director->setAnimationInterval(1.0 / 60);
glview->setDesignResolutionSize(640, 1136, ResolutionPolicy::FIXED_WIDTH);
auto scene = HelloWorldScene::createScene();
director->runWithScene(scene);
return true;
}
HelloWorldScene
Scene* HelloWorldScene::createScene()
{
auto scene = Scene::create();
auto layer = HelloWorldScene::create();
scene->addChild(layer);
return scene;
}
HelloWorldScene::HelloWorldScene()
{
}
HelloWorldScene::~HelloWorldScene()
{
}
bool HelloWorldScene::init()
{
if (!Layer::init()) {
return false;
}
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("battle.mp3", true);
auto director = Director::getInstance();
auto winSize = director->getWinSize();
auto background = Sprite::create("main_back.png");
background->setPosition(Vec2(winSize.width / 2.0, winSize.height / 2.0));
this->addChild(background);
auto logo = Sprite::create("logo.png");
logo->setPosition(Vec2(winSize.width / 2.0, winSize.height - 150));
this->addChild(logo);
auto menuButton = MenuItemImage::create("start.png","start.png","start.png",CC_CALLBACK_1(HelloWorldScene::ImageButton,this));
// menuButton->setPosition(Vec2(winSize.width / 2.0, winSize.height /2.0));
menuButton->setPosition(Vec2(winSize.width / 2.0, ((winSize.height /2.0) - 300)));
menuButton->setScale(1.5f);
auto blink = Sequence::create(FadeTo::create(0.5, 127),
FadeTo::create(0.5, 255),
NULL);
menuButton->runAction(RepeatForever::create(blink));
this->addChild(menuButton);
auto moreButton = MenuItemImage::create("more.png","more.png","more.png",[](Ref*sender){
std::string url = "https://play.google.com/xxx";
cocos2d::Application::getInstance()->openURL(url);
});
winSize.height /2.0));
moreButton->setPosition(Vec2(winSize.width / 2.0, 90));
this->addChild(moreButton);
auto menu = Menu::create(menuButton,moreButton, NULL);
menu->setPosition(Point(0,0));
this->addChild(menu);
return true;
}
//Click event for menuButton
void HelloWorldScene::ImageButton(cocos2d::Ref *pSender) {
// stop background music
CocosDenshion::SimpleAudioEngine::getInstance()->stopBackgroundMusic();
this->getEventDispatcher()->removeAllEventListeners();
auto delay = DelayTime::create(0.5);
auto startGame = CallFunc::create([]{
auto scene = GameLayer::createScene();
auto transition = TransitionPageTurn::create(0.5, scene, true);
Director::getInstance()->replaceScene(transition);
// CocosDenshion::SimpleAudioEngine::getInstance()->stopBackgroundMusic();
});
this->runAction(Sequence::create(delay,startGame,
NULL));
}
void HelloWorldScene::onEnterTransitionDidFinish()
{
//CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic(AudioUtils::getFileName("title").c_str());
}
HelloWorldScene.h
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorldScene :public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
HelloWorldScene();
virtual ~HelloWorldScene();
bool init() override;
void onEnterTransitionDidFinish() override;
CREATE_FUNC(HelloWorldScene);
void ImageButton(Ref *pSender);
};
#endif // __HELLOWORLD_SCENE_H__
GameLayerScene.h
#include "cocos2d.h"
#include <random>
#include "BallSprite.h"
#include "Character.h"
class GameLayer : public cocos2d::Layer
{
protected:
enum class Direction
{
x,
y,
};
enum ZOrder
{
BgForCharacter = 0,
BgForPuzzle,
Enemy,
EnemyHp,
Char,
CharHp,
Ball,
Level,
Result,
};
std::default_random_engine _engine;
std::discrete_distribution<int> _distForBall;
std::uniform_int_distribution<int> _distForMember;
BallSprite* _movingBall;
bool _movedBall;
bool _touchable;
int _maxRemovedNo;
int _chainNumber;
std::vector<std::map<BallSprite::BallType, int>> _removeNumbers;
Character* _enemyData;
cocos2d::Sprite* _enemy;
cocos2d::ProgressTimer* _hpBarForEnemy;
cocos2d::Vector<Character*> _memberDatum;
cocos2d::Vector<cocos2d::Sprite*> _members;
cocos2d::Vector<cocos2d::ProgressTimer*> _hpBarForMembers;
int _level;
int _nextLevel;
void initBackground();
void initBalls();
BallSprite* newBalls(BallSprite::PositionIndex positionIndex, bool visible);
BallSprite* getTouchBall(cocos2d::Point touchPos, BallSprite::PositionIndex withoutPosIndex = BallSprite::PositionIndex());
void movedBall();
void checksLinedBalls();
bool existsLinedBalls();
cocos2d::Map<int, BallSprite*> getAllBalls();
bool isSameBallType(BallSprite::PositionIndex current, Direction direction);
void initBallParams();
void checkedBall(BallSprite::PositionIndex current, Direction direction);
void removeAndGenerateBalls();
void generateBalls(int xLineNum, int fallCount);
void animationBalls();
void initEnemy();
void initMembers();
void calculateDamage(int &chainNum, int &healing, int &damage, std::set<int> &attackers);
bool isAttacker(BallSprite::BallType type, Character::Element element);
void attackToEnemy(int damage, std::set<int> attackers);
void healMember(int healing);
void attackFromEnemy();
void endAnimation();
cocos2d::Spawn* vibratingAnimation(int afterHp);
void initLevelLayer();
void removeLevelLayer(float dt);
void winAnimation();
void loseAnimation();
void nextScene(float dt);
public:
GameLayer();
virtual bool init(int level);
static GameLayer* create(int level);
static cocos2d::Scene* createScene(int level = 1);
virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchCancelled(cocos2d::Touch* touch, cocos2d::Event* unused_event);
void PauseButton(Ref *pSender);
};
#endif
I've not tested your code but it seems problem in this statement :
auto menu = Menu::create(menuButton,moreButton, NULL);
It'll give runtime exception because menuButton and moreButton already added. It can't be added again.
I was able to solve the problem.The problem was due to the scene still paused without being resumed.
auto menuButton = cocos2d::ui::Button::create("menu.png");
menuButton->setPosition(Vec2(WINSIZE.width / 2.0, (WINSIZE.height/ 2.0) - 100));
menuButton->addClickEventListener([pauseLayer](Ref*){
if (Director::getInstance()->isPaused()) {
Director::getInstance()->resume();
}
Director::getInstance()->replaceScene(HelloWorldScene::createScene());
// Director::getInstance()->end();
});
I'm trying to port my iPhone app to windows 8, I have a problem with this line (it's objective-c) :
[self schedule:#selector(fire:)];
The equivalent on c++ should be :
this->schedule(schedule_selector(AirScene::fire));
Where AirScene is the name of my class, but I have this error returning by Visual Studio 2012 :
error C2064: term does not evaluate to a function taking 1 arguments
So, in other words the function schedule(selector) is not found. It's strange because I have no problem with unschedule method, do you have any idea please ?
EDIT : AirScene.h
#include "cocos2d.h"
#include "Box2D\Box2D.h"
#include "AirSceneDelegate.h"
class AirScene : public cocos2d::CCLayer {
public:
AirScene::~AirScene(void);
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();
static cocos2d::CCScene* scene();
LAYER_NODE_FUNC(AirScene);
//Methods
void selectSpriteForTouch(cocos2d::CCPoint touchLocation);
cocos2d::CCPoint checkPuckPosition(cocos2d::CCPoint newPosition);
void panForTranslation(cocos2d::CCPoint translation);
void updateQuestion(string question);
void restoreScene();
void enableScreensaver();
void disableScreensaver(cocos2d::CCPoint touchPosition);
//Setters
void setDelegate(AirSceneDelegate *delegate);
private:
// Init
void initBackground(cocos2d::CCSize winSize);
void initPuck(cocos2d::CCSize winSize, cocos2d::CCPoint position);
void initHoles(cocos2d::CCSize winSize);
void initQuestion(cocos2d::CCSize winSize);
// Methods
void fire(cocos2d::ccTime dt = 0);
void updateHoles(cocos2d::ccTime dt);
void validateVote(bool isPositiveVote);
float getVelocity();
// Attributes
...
//Delegate
AirSceneDelegate* _delegate;
};
AirScene.cpp
#include <iostream>
#include "AirScene.h"
USING_NS_CC;
#pragma region Delete
AirScene::~AirScene(void)
{
///TODO : DELETE ALL
delete(_delegate);
_delegate = 0;
}
#pragma endregion Delete
#pragma region Init
CCScene* AirScene::scene()
{
// 'scene' is an autorelease object
CCScene *scene = CCScene::node();
// 'layer' is an autorelease object
AirScene *layer = AirScene::node();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool AirScene::init()
{
// Super Init
if (!CCLayer::init())
return false;
//Init Attributes
screenSaverMode = false;
hasVoted = false;
schedule = false;
_delegate = 0;
_selSprite = NULL;
//Create World
b2Vec2 gravity = b2Vec2(0, 0);
_world = new b2World(gravity);
CCSize size = CCDirector::sharedDirector()->getWinSize();
//Inits
AirScene::initBackground(size);
AirScene::initPuck(size, ccp(512, 200));
AirScene::initHoles(size);
AirScene::initQuestion(size);
return true;
}
// Init Background and set walls
void AirScene::initBackground(CCSize winSize)
{
...
}
/** Init Puck : create puck body and shape */
void AirScene::initPuck(CCSize winSize, CCPoint position)
{
...
}
void AirScene::initHoles(CCSize winSize)
{
...
}
// Set Question Label
void AirScene::initQuestion(CCSize winSize)
{
...
}
#pragma endregion Init
#pragma region Private
void AirScene::fire(ccTime dt)
{
_world->Step(dt, 8, 8);
//CCSprite *ballData = ((CCSprite *)_body->GetUserData())
((CCSprite *)_body->GetUserData())->setPosition(ccp(_body->GetPosition().x * PTM_RATIO, _body->GetPosition().y * PTM_RATIO));
_puckShadow->setPosition(ccp(((CCSprite *)_body->GetUserData())->getPosition().x + 2, ((CCSprite *)_body->GetUserData())->getPosition().y - 2));
if (screenSaverMode)
AirScene::updateHoles(0);
}
//Ajust Glow Effect and Validate Vote
void AirScene::updateHoles(cocos2d::ccTime dt)
{
...
}
float AirScene::getVelocity()
{
...
}
void AirScene::validateVote(bool isPositiveVote)
{
...
}
#pragma endregion Private
#pragma region Public
void AirScene::selectSpriteForTouch(CCPoint touchLocation)
{
...
}
// Check if the puck is not outside the view
CCPoint AirScene::checkPuckPosition(CCPoint newPosition)
{
...
}
// Move Puck
void AirScene::panForTranslation(CCPoint translation)
{
...
}
// Update Question
void AirScene::updateQuestion(string question)
{
...
}
void AirScene::restoreScene()
{
...
}
void AirScene::enableScreensaver()
{
screenSaverMode = true;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
//Unschedule actions
this->unscheduleAllSelectors();
//Delete Puck
CCPoint puckPosition = _puck->getPosition();
_puck->removeAllChildrenWithCleanup(true);
_puck->removeFromParentAndCleanup(true);
_puckShadow->removeAllChildrenWithCleanup(true);
_puckShadow->removeFromParentAndCleanup(true);
_world->DestroyBody(_body);
delete(_puck);
delete(_puckShadow);
//RecreatePuck
this->initPuck(winSize, ccp(512, 200));
//Impulse
_body->SetLinearVelocity(b2Vec2(0, 0));
/** ERROR IS CAUSED BY THIS LINE */
this->schedule(schedule_selector(AirScene::fire));
}
void AirScene::disableScreensaver(cocos2d::CCPoint touchPosition)
{
}
#pragma endregion Public
#pragma region Getters & Setters
void AirScene::setDelegate(AirSceneDelegate *delegate)
{
_delegate = delegate;
}
#pragma endregion Getters & Setters
Fixed.
The version of cocos 2d which is used for windows 8 template is an old version, so the good way to schedule a selector is this one :
CCScheduler::sharedScheduler()->scheduleSelector(schedule_selector(AirScene::fire), this, 0, false);