I'm creating a game, and I have a array of pointers to hero objects. Only one hero should be active at a time, so I have a separate pointer to the active hero.
If it matters, I'm using Qt, and the Hero class inherits from QgraphicsItem.
it is important that I am able to switch between Hero's, so I've written the following piece of code:
void Game::toggleHero()
{
if(activeHero==hero[0])
{
activeHero =hero[1];
hero[1]->setFocus();
}
else if(activeHero == hero[1])
{
activeHero = hero[0];
hero[0]->setFocus();
}
}
The problem is, that that method does what I expect it to do, but only the first time it is called. The activeHero newer changes back to the original object.
The array of Hero objects are members of a game class, and are initialized in the game object's constructor. The only time activeHero is ever referenced is when a hook item is added, or when the setView() method is called.
class Game : public QGraphicsView
{
public:
Game();
Terrain *ground[63];
Wall *wall[63];
Ladder *ladder[63];
Bushes * bush;
QGraphicsScene * scene;
Hero *hero[1];
Hero *activeHero;
void buildLevel(int level);
void setView();
void toggleHero();
};
From the game object's constructor:
//add hero
hero[0] = new Hero();
hero[0]->setFlag(QGraphicsItem::ItemIsFocusable);
hero[0]->setFocus();
scene->addItem(hero[0]);
hero[0]->setPos(5,1200);
// activeHero = hero[0];
hero[1] = new Hero();
hero[1]->setFlag(QGraphicsItem::ItemIsFocusable);
hero[1]->setFocus();
scene->addItem(hero[1]);
hero[1]->setPos(10,1300);
activeHero = hero[0];
The setView() method checks if the active hero is in view, and if it isn't it moves the view so that the activeHero is at the correct spot relative to the view.
void Game::setView()
{
if((!(activeHero->pos().y()+activeHero- >rect().height()/2==sceneRect().y()+sceneRect().height()/2-400)&&((activeHero- >y()+activeHero->rect().height()/2-400>0))&&(activeHero->y()+activeHero->rect().height()-400<1600))){
setSceneRect(sceneRect().x(),activeHero->pos().y()+activeHero->rect().height()/2-400,800,600);
}
if((!(activeHero->pos().x()+activeHero->rect().width()/2==sceneRect().x()+sceneRect().width()/2))&&((activeHero->x()+activeHero->rect().width()-450>0)&&(activeHero->x()+activeHero->rect().width()+350<3200))){
setSceneRect(activeHero->pos().x()+activeHero->rect().width()/2-400,sceneRect().y(),800,600);
}
And finally, a Hook object uses the activeHero to position itself in the scene.
Hook::Hook(int direction)
{
hookdirection = direction;
switch(hookdirection)
{
case -1:
{
setRect(0,0,16,16);
setPos(game->activeHero->pos());
break;
}
case 1:
{
setRect(0,0,16,16);
setPos(game->activeHero->pos().x()+game->activeHero->rect().width()- rect().width(),game->activeHero->pos().y());
break;
}
default:
{
setRect(0,0,16,16);
break;
}
}
timer = new QTimer;
connect(timer,SIGNAL(timeout()),this,SLOT(move()));
timer->start(10);
lineItem = new QGraphicsLineItem();
lineItem->setParentItem(this);
game->activeHero->scene()->addItem(lineItem);
}
The objective of the toggleHero method is to switch between hero[0], and hero[1] so that each of them is the activeHero in the methods above.
If hero is a pointer you need decide whether to compare the heroes (eg. by name with overloaded operators) or the adresses. So it should be:
if(*activeHero==hero[0])
{
*activeHero =hero[1];
hero[1]->setFocus();
}
else if(*activeHero == hero[1])
{
*activeHero = hero[0];
hero[0]->setFocus();
}
or it should be
if(activeHero==&hero[0])//address of hero[0]
{
activeHero =&hero[1];
hero[1]->setFocus();
}
else if(activeHero == &hero[1])
{
activeHero = &hero[0];
hero[0]->setFocus();
}
Related
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
My instantiations stop after 1 or 2 spawns, but I want them to keep spawning forever thru the game.It's because I have the destroy piece of code in this script, but what can I do I need them to die after collision or there will be hundreds on screen like before. As usual when dead an enemy must die and disappear. So what do I do? The destroy code is destroying the entire enemy player /prefab not just the dead instance like I want.
I want the enemies to keep spawning but of course when killed by player they must disappear.
2d game I have this script on the enemy prefab and in scene enemies
I want the enemy on screen to die one by one when collision but the destroy is killing the entire object / prefab I think and it will not instantiate /spawn any more
I do have a few scripts on the enemy here is the instantiation script
i have used a off-screen instance (tried) but these enemies have moving scripts on them ..i put a off-screen object like you said but all the objects have a moving script since the enemy's point is to fly thru the screen so player tries to kill as many enemies as he can so if I take off the moving script they do spawn constantly but when I put the move script on they die and do not spawn, and again I need them to move thru the screen, so im stuck I still cant figure this one out
public class spn2 : MonoBehaviour {
GameObject Enemy;
//public GameObject EasyEnemey;
public GameObject MediumEnemey;
public GameObject HardEnemey;
public Transform[] SpawnPoints;
public float TimeBetweenSpawns;
public int NumberOfEnemiesToSpawn;
public int NumberOfMediumEnemiesToSpawn;
public float EasyChance;
public float MediumChance;
public float HardChance;
private int waveNumber;
private float spawnTimer;
private int numberOfEnemies;
private int numberOfMediumEnemies;
// Use this for initialization
void Start()
{
//this below is the time to spawn so if 4 , every 4 seconds 1 will spawn etc
this.spawnTimer = 3.0f;
this.waveNumber = 0;
float totalChance = this.EasyChance + this.MediumChance + this.HardChance;
if(Mathf.Abs(totalChance-1.0f)>0.0001f) {
Debug.LogWarning("Warning: The chances should add up to 1.0 ("+totalChance+" currently)");
}
}
// Update is called once per frame
void Update()
{
this.spawnTimer -= Time.deltaTime;
if(this.spawnTimer<=0.0f)
{
Transform spawnPoint = this.SpawnPoints[Random.Range(0, this.SpawnPoints.Length)];
Vector2 spawnPos = spawnPoint.position;
Quaternion spawnRot = spawnPoint.rotation;
switch(this.waveNumber)
{
case 0:
//Instantiate(EasyEnemey, spawnPos,spawnRot);
Instantiate(Resources.Load(Enemy) as GameObject, spawnPos, spawnRot);
this.numberOfEnemies++;
if(this.numberOfEnemies>=this.NumberOfEnemiesToSpawn)
{
this.waveNumber++;
}
break;
case 1:
Instantiate(MediumEnemey, spawnPos, spawnRot);
this.numberOfMediumEnemies++;
if (this.numberOfMediumEnemies >= this.NumberOfMediumEnemiesToSpawn)
{
this.waveNumber++;
}
break;
case 2:
float randomFloat = Random.value;
if(randomFloat<this.EasyChance)
{
Instantiate(Enemy, spawnPos, spawnRot);
}
else if(randomFloat<this.EasyChance+this.MediumChance)
{
Instantiate(MediumEnemey, spawnPos, spawnRot);
}
else
{
Instantiate(HardEnemey, spawnPos, spawnRot);
}
break;
}
this.spawnTimer = this.TimeBetweenSpawns;
Destroy (gameObject, .7f);
}
}
}
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.
http://qt-project.org/doc/qt-4.8/qgraphicsscene.html#addItem
said
If the item is already in a different scene, it will first be removed from its old scene, and then added to this scene as a
top-level.
I want to keep the item in old scene.
how can I do this?
myscene1.addItem(item);
myscene2.addItem(item);// I don't want to remove item from myscene1
You can make a copy of the item :
myscene1.addItem(item);
myscene2.addItem(item->clone());
An item cannot occupy two scenes at the same time, in the way that you cannot be in two places at the same time.
The only way to do this is to make a copy of the item and place it in the second scene.
What you could do is to create a new class. e.g.
class Position
{
...
QPoinfF pos;
...
}
Then you can add that class to your item.
class Item : public QGraphicsItem
{
...
public:
void setSharedPos(Position *pos)
{
sharedPosition = pos;
}
//implement the paint(...) function
//its beeing called by the scene
void paint(...)
{
//set the shared position here
setPos(sharedPos);
//paint the item
...
}
protected:
void QGraphicsItem::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event )
{
//get the position from the item that could have been moved
//you could also check if the position actually changed
sharedPosition->pos = pos();
}
private
Position *sharedPostion;
...
}
You would no have to create two items and give them both the same pointer to a Position object.
Item *item1 = new Item;
Item *item2 = new Item;
Position *sharedPos = new Position;
item1->setSharedPos(sharedPos);
item2->setSharedPos(sharedPos);
myScene1->addItem(item1);
myScene2->addItem(item2);
They should no at least share their position in the scenes.
If this works then you'll have to change the Position class to fit your needs and it should be working.
I am not quite shure if setting the position in the paint() function works. But thats how I would try to synchronise the items. If it does not work then you'll have to look for another place to update the settings of the items.
Or you could give the items a Pointer to each other and let them change the positions/settings directly.
e.g.
class Item : public QGraphicsItem
{
...
void QGraphicsItem::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event )
{
otherItem->setPos(pos());
}
...
void setOtherItem(Item *item)
{
otherItem = item;
}
private:
Item *otherItem;
}
Item *item1 = new Item;
Item *item2 = new Item;
item1->setOtherItem(item2);
item2->setOtherItem(item1);
myScene1->addItem(item1);
myScene2->addItem(item2);
I'm trying to write an OO menu system for a game, loosely based on the idea of a Model,View,Controller. In my app so far I've named the views "renderers" and the models are without a suffix. I created a generic menu class which stores the items of a menu, which are menu_item objects, and there is also a menu renderer class which creates renderers for each item and renders them. The problem is I'm not sure where to store the data and logic to do with where on the screen each item should be positioned, and how to check if it is being hovered over, etc. My original idea was to store and set a selected property on each menu item, which could be rendered differently by different views, but even then how to I deal with positioning the graphical elements that make up the button?
Code excerpts so far follow: (more code at https://gist.github.com/3422226)
/**
* Abstract menu model
*
* Menus have many items and have properties such as a title
*/
class menu {
protected:
std::string _title;
std::vector<menu_item*> _items;
public:
std::string get_title();
void set_title(std::string);
std::vector<menu_item*> get_items();
};
class menu_controller: public controller {
private:
menu* _menu;
public:
menu_controller(menu*);
virtual void update();
};
class menu_item {
protected:
std::string _title;
public:
menu_item(std::string title);
virtual ~menu_item();
std::string get_title();
};
class menu_renderer: public renderer {
private:
menu* _menu;
bitmap _background_bitmap;
static font _title_font;
std::map<menu_item*, menu_item_renderer*> _item_renderers;
public:
menu_renderer(menu*);
virtual void render();
};
font menu_renderer::_title_font = NULL;
menu_renderer::menu_renderer(menu* menu) {
_menu = menu;
_background_bitmap = ::load_bitmap("blackjack_menu_bg.jpg");
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",48);
}
void menu_renderer::render() {
::draw_bitmap(_background_bitmap, 0, 0);
/* Draw the menu title */
const char* title = _menu->get_title().c_str();
int title_width = ::text_width(_title_font, title);
::draw_text(title, color_white, _title_font, screen_width() - title_width - 20, 20);
/* Render each menu item */
std::vector<menu_item*> items = _menu->get_items();
for (std::vector<menu_item*>::iterator it = items.begin(); it != items.end(); ++it) {
menu_item* item = *it;
if (!_item_renderers.count(item))
_item_renderers[item] = new menu_item_renderer(item, it - items.begin());
_item_renderers[item]->render();
}
}
class menu_item_renderer: public renderer {
private:
unsigned _order;
menu_item* _item;
static font _title_font;
public:
menu_item_renderer(menu_item*, unsigned);
virtual ~menu_item_renderer();
virtual void render();
};
font menu_item_renderer::_title_font = NULL;
menu_item_renderer::menu_item_renderer(menu_item* item, unsigned order) {
_item = item;
_order = order;
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",24);
}
menu_item_renderer::~menu_item_renderer() {
// TODO Auto-generated destructor stub
}
void menu_item_renderer::render() {
const char* title = _item->get_title().c_str();
int title_width = ::text_width(_title_font, title);
unsigned y = 44 * _order + 20;
::fill_rectangle(color_red, 20, y, title_width + 40, 34);
::draw_text(title, color_white, _title_font, 30, y + 5);
}
Your Model class menu needs a add_view(menuview *v) and update() method.
Then you can delegate the update of your widget to the derived View (menu_renderer or a cli_menu_renderer).
The Controller needs to know the Model (as member), when the Controller runs (or executes a Command) and has to update the Model with a Setter (like m_menu_model->set_selected(item, state)) and the Model calls update() on Setters.
Your Controller menu_controller has a update method, there you could also ask for Input, like if (menuview->toggle_select()) m_menu_model->toggle_selected(); (which all menuviews have to implement) and invoke the setter, but thats a inflexible coupling of View and Controller (you could check MVC with the Command Pattern for a more advanced combination).
For the Position you can set member vars like int m_x, m_y, m_w, m_h.
But these members are specific to a View with GUI, so only the derived View needs them.
Then you could use these values to compare against mouse Positions and use a MouseOver Detection Method like this:
// View menu_item
bool menu_item::over()
{
if (::mouse_x > m_x
&& ::mouse_x < m_x + m_w
&& ::mouse_y > m_y
&& ::mouse_y < m_y + m_h) {
return true;
}
return false;
}
// update on gui menu item
bool menu_item::update()
{
if (over()) {
m_over = true;
}
else {
m_over = false;
}
// onclick for the idea
if ((::mouse_b & 1) && m_over) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
// update the command line interface menu item
bool cli_menu_item::update()
{
if ((::enterKeyPressed & 1) && m_selected) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
void menu_item_renderer::render() {
// update widgets
_item->update();
// ...
}
// Model
void menu::add_view(menuview *v) {
m_view=v;
}
void menu::update() {
if (m_view) m_view->update();
}
bool menu::set_selected(int item, int state) {
m_item[index]=state;
update();
}