Структура программы и ее реализация.
Программа была реализована в виде сложного приложения C++ с использованием ООП, в частности классов и наследования.
В программе использовались как элементы стандартной библиотеки(vector, map), так и элементы подключаемой библиотеки sfml. Для загрузки файла используется функция loadFromFile, setTexture для присвоения текстуры спрайтам, setTextString для присвоения тексту определенной строки и т.д.
Библиотека SFML несмотря на свою компактность в реализации многих сложных действий ( например в sfml создание анимации занимает 4 строки, в то время как с помощью OpenGL реализация занимает сотни строк кода), многофункциональна и содержит более чем достаточно классов и методов для решения задач, которые ставит перед собой программист, выбирая в качестве проекта мультимедиа приложение, к которому можно отнести и игру.
Объектно- ориентированное программирование значительно облегчило задачу обработки всевозможных взаимодействий объектов игры. Наследование позволило создать класс ENTITY, содержащий метод update() и производные классы PLAYER, ENEMY, MOVINGPLATFORM при этом у них весьма похожие методы, как скажем, тот же update и писать его заново не пришлось.
Класс level дает возможность облегчить создание нового уровня, просто создав объект level lvl1 мы уже имеем все необходимые поля, присущие уровням нашей игры. Остается лишь открыть файл методом lvl.LoadFromFile(filename). Так же класс для анимации хранит в себе основные методы для быстрого ввода новой анимации в программу.
Самое важное было не погрязнуть в рутине классов и структур. После их написания, разработка сводилась к оперированию готовыми классами и работе уже с геймплеем, а не заботами об отрисовке отдельных элементов.
Интерфейс приложения.
При запуске приложения пользователь попадает на начальный экран с главным меню, где на заднем фоне воспроизводится звуковое сопровождение игры. (Рисунок 5.)
Рисунок 5.Главное меню.
Само меню состоит из трех пунктов: «New game», «About», «Exit». (Меню игры выделено красным цветом на Рисунке 5) В данный момент активным является пункт «New game», для активного состояния характерно изменение цвета и размера надписи, чтобы пользователь мог отчетливо видеть, что именно им сейчас выбрано. При передвижении по пунктам меню издается характерный звук. При нажатии на клавишу [E] можно соответственно начать новую игру, прочитать информацию о приложении (Рисунок 6.) либо выйти из игры.
Фиолетовым цветом (Рисунок 5.) выделена часть, которая демонстрирует пользователю его прогресс, то есть количество собранных им за игру монет (из 17 возможных, при сборе 17 из 17 победа в игре), при начале новой игры прогресс обнуляется. Надпись «Game» показывает пользователю, в каком состоянии сейчас игра, если эта надпись выделена зеленым цветом (Рисунок 7.), то пользователь сейчас в процессе игры. По окончании игры вместо этой надписи появится надпись «Victory!» (Рисунок 6.), в случае выполнения условия прохождения игры, либо «Game over», в случае поражения (смерти). Зеленым цветом (Рисунок 5.) выделено краткое руководство для пользователя, оно наглядно демонстрирует, какие клавиши необходимо нажимать пользователю для навигации по пунктам меню, выбора соответствующего пункта меню или управления персонажем.
Рисунок 6.Выбран пункт меню «About», пользователю демонстрируется, что он одержал победу в игре.
При выборе пользователем пункта «New game» он автоматически попадает на карту игры, где находится персонаж, монетки («Coin»), которые необходимо собирать для победы в игре и враги, которые могут быть уничтожены игроком посредством прыжка на них сверху или выстрелить в них, при нажатии клавиши [space]. (Рисунок 7.) Так же слева указывается количество собранных им монет, то, что пользователь находится в игре и количество жизней. (Выделено желтым цветом на Рисунке 7.) Теперь пользователь может непосредственно начать игровой процесс, управляя персонажем для достижения основной цели игры. Игрок взаимодействует с окружением, то есть при прохождении через монетку, она исчезает, при соприкосновении с врагом, персонаж начинает мигать и издавать характерный звук, при этом количество его жизней постепенно уменьшается, однако враги могут быть уничтожены, как было сказано выше. При попадании на шипы здоровье игрока так же начинает уменьшаться, но с большей скоростью. (Рисунок 8.) Если количество жизней игрока опускается до нуля, то игра считается оконченной проигрышем, так же будет считаться проигрыш, если пользователь во время игры нажмет на клавишу [Esc], что вернет его в главное меню. При сборе последней монеты пользователь автоматически возвращается в главное меню, де у него есть возможность начать игру снова либо выйти из игры.
Рисунок 7.Карта игры.
Рисунок 8.Ситуация попадания персонажа на шипы, при этом воспроизводится соответствующая анимация и звуковое сопровождение, а здоровье игрока начинает стремительно понижаться.
Руководство пользователя.
Здравствуйте! Мы рады приветствовать вас в нашем приложении. Данное руководство предназначается для ознакомления с игрой и игровым процессом. При запуске приложения, вы попадаете в главное меню. (Рисунок 9.) Здесь вы можете выбрать дальнейшие свои действия посредством выбора одного из трех пунктов: «New game» - начать новую игру, «About» - узнать больше о данном приложении, «Exit» - выход из приложения.
Рисунок 9.Меню игры.
Кроме того, на экране отображается ваш прогресс в игре в строке «Coin», а именно показывается, сколько монет вы собрали за игру. Основной целью является сбор всех 17 монет на уровне. Ниже будет отображаться одна из двух надписей по окончании игры: «Victory!» в случае вашего выигрыша либо «Game over» в случае смерти. Следует заметить, что если вы выйдите в главное меню во время непосредственно игрового процесса, то ваш результат отобразится, но будет засчитано поражение в игре. Так же справа вы можете увидеть краткое руководство, своеобразную подсказку вам. Там представлен список клавиш, которые вам необходимо нажимать для выполнения различных действий: [E] для выбора необходимого пункта меню, [W][A][S][D] навигация по пунктам меню и передвижение персонажа на уровне, [space] для стрельбы, [Esc] для возвращения в главное меню во время прохождения уровня. При выборе пункта меню «New game» вы попадаете на уровень, где вам необходимо, избегая монстров и шипов, собрать все 17 монет. (Рисунок 10.)
Рисунок 10. Карта игры.
Слева на экране отображается количество собранных вами монет («Coin») и ваше здоровье («Health»). От двух этих параметров зависит ваш успех или проигрыш в игре. А именно, при сборе всех монет, как уже говорилось выше, вы одержите победу, а при потере всего здоровья (то есть, когда уровень здоровья опустится со 100 до 0), игра будет проиграна. Здоровье вы можете потерять вследствие контакта с врагами (круглые роботы, передвигающиеся по уровню) либо при попадании на шипы. (Рисунок 11.) В этих случаях вы так же услышите характерный звук, издаваемый персонажем. Заметим, что при попадании на шипы здоровье будет уменьшаться с гораздо большей скоростью. Используя соответствующие клавиши вам необходимо передвигаться по уровню, преодолевая препятствия и уничтожая врагов. Убить врага можно двумя способами: выстрелить в него, нажав соответствующую клавишу (стрелять вы можете стоя на месте, во время бега и карабкаясь по лестнице), либо прыгнуть на него сверху. Заметим, что в обоих случаях урон вам наноситься не будет. Так же на уровне существует секретное место, в которое вам обязательно нужно попасть для прохождения игры. При получении последней монетки или смерти вы автоматически попадете в главное меню, где сможете выбрать ваши дальнейшие действия (начать игру сначала или выйти из игры). Во время прохождения уровня вы сами можете вернуться в главное меню, нажав клавишу [Esc].
Желаем вам приятной игры!
Рисунок 11. Попадание персонажа на шипы, что может привести к скорой смерти и проигрышу.
Рекомендуемые системные требования:
· OS: windows 7, 8, 8.1.
· RAM: 54 Мб
· CPU: 1 ГГц
· GPU: Open Gl совместимый.
· Наличие Microsoft VisualC Redist.
· Клавиатура.
Заключение.
В ходе проделанной работы было создано простейшее игровое 2D приложение. Главная цель курсового проекта и задачи, поставленные изначально, были успешно выполнены. Проект можно назвать полноценным игровым приложением, которое имеет дальнейшие перспективы усовершенствования. Основные перспективы:
1. Возможность пользователя отключать звук во время игры.
2. Наличие нескольких уровней.
3. Возможность пользователя вводить свое имя перед началом игры.
4. Возможность пользователя сравнивать свой результат с результатами предыдущих прохождений.
5. Доработка графической составляющей (усовершенствование графики).
6. Возможность пользователя выбирать внешний вид персонажа, карту для прохождения и уровень сложности.
7. Добавление сюжетной канвы.
8. Нелинейность уровней.
9. Скелетная анимация.
10. Подключение мыши.
Приложения.
Полный код программы.
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <iostream>
#include <vector>
#include <list>
#include "string.h"
#include <stdio.h>
#include <sstream>//преобразование int в string
#include <string>
#include <windows.h>
#include "level.hpp"
#include "anim.hpp"
using namespace sf;
bool ingame=0,newgame=1,reit=0,esc=0,reite=0;
std::string ress,ress2,winn;
int res=0,reset,buf=100,win=100;
static inline std::string int2Str(int x)
{
std::stringstream type;
type << x;
return type.str();
}
class Entity
{
public:
float x,y,dx,dy,w,h;
AnimationManager anim;
std::vector<Object> obj;
bool life, dir;
float timer, timer_end;
String Name;
int Health;
Entity(AnimationManager &A,int X, int Y)
{
anim = A;
x = X;
y = Y;
dir = 0;
life=true;
timer=0;
timer_end=0;
dx=dy=0;
}
virtual void update(float time) = 0;
void draw(RenderWindow &window)
{
anim.draw(window,x,y+h);
}
FloatRect getRect()
{
return FloatRect(x,y,w,h);
}
void option(String NAME, float SPEED=0, int HEALTH=10, String FIRST_ANIM="")
{
Name = NAME;
if (FIRST_ANIM!="") anim.set(FIRST_ANIM);
w = anim.getW();
h = anim.getH();
dx = SPEED;
Health = HEALTH;
}
};
class Bullet:public Entity
{
public:
Bullet(AnimationManager &a, Level &lev,int x,int y,bool dir):Entity(a,x,y)
{
option("Bullet", 0.3, 10, "move");
if (dir) dx=-0.3;
obj = lev.GetObjects("solid");
}
void update(float time)
{
x+=dx*time;
for (int i=0;i<obj.size();i++)
if (getRect().intersects(obj[i].rect))
{Health=0;}
if (Health<=0) {anim.set("explode");dx=0;
if (anim.isPlaying()==false) life=false;
}
anim.tick(time);
}
};
class PLAYER: public Entity
{
public:
enum {stay,walk,duck,jump,climb,swim} STATE;
bool onLadder, shoot, hit;
std::map<std::string,bool> key;
PLAYER(AnimationManager &a, Level &lev,int x,int y):Entity(a,x,y)
{
option("Player",0,100,"stay");
STATE=stay; hit=0;
obj = lev.GetAllObjects();
}
void KeyCheck()
{
if (key["L"])
{
dir=1;
if (STATE!=duck) dx=-0.1;
if (STATE==stay) STATE=walk;
}
if (key["R"])
{
dir=0;
if (STATE!=duck) dx=0.1;
if (STATE==stay) STATE=walk;
}
if (key["Up"])
{
if (onLadder) STATE=climb;
if (STATE==stay || STATE==walk) { dy=-0.27; STATE=jump; anim.play("jump");}
if (STATE==swim || STATE==climb) dy=-0.05;
}
if (key["Down"])
{
if (STATE==stay || STATE==walk) { STATE=duck; dx=0;}
if (STATE==swim || STATE==climb) dy=0.05;
}
if (key["Space"])
{
shoot=true;
}
/////////////////////если клавиша отпущена///////////////////////////
if (!(key["R"] || key["L"]))
{
dx=0;
if (STATE==walk) STATE=stay;
}
if (!(key["Up"] || key["Down"]))
{
if (STATE==swim || STATE==climb) dy=0;
}
if (!key["Down"])
{
if (STATE==duck) { STATE=stay;}
}
if (!key["Space"])
{
shoot=false;
}
}
void update(float time)
{
KeyCheck();
if (STATE==stay) anim.set("stay");
if (STATE==walk) anim.set("walk");
if (STATE==jump) anim.set("jump");
if (STATE==duck) anim.set("duck");
if (STATE==climb) {anim.set("climb"); anim.pause(); if (dy!=0) anim.play(); if (!onLadder) STATE=stay;}
if (shoot) { anim.set("shoot");
if (STATE==walk) anim.set("shootAndWalk");}
if (hit) { timer+=time;
if (timer>1000) {hit=false; timer=0;}
anim.set("hit");}
if (dir) anim.flip();
x += dx * time;
Collision(0);
if (STATE!=climb) dy+=0.0005*time;
y += dy*time; onLadder=false;
Collision(1);
anim.tick(time);
key["R"]=key["L"]=key["Up"]=key["Down"]=key["Space"]=false;
}
void Collision(int num)
{
for (int i=0;i<obj.size();i++)
if (getRect().intersects(obj[i].rect))
{ if(obj[i].name=="do")
{
}
if(obj[i].name=="trap")
{
hit=1;
Health-=1;
}
if (obj[i].name=="solid")
{
if (dy>0 && num==1) { y = obj[i].rect.top - h; dy=0; STATE=stay;}
if (dy<0 && num==1) { y = obj[i].rect.top + obj[i].rect.height ; dy=0;}
if (dx>0 && num==0) { x = obj[i].rect.left - w; }
if (dx<0 && num==0) { x = obj[i].rect.left + obj[i].rect.width ;}
}
if (obj[i].name=="ladder") { onLadder=true; }
if (obj[i].name=="SlopeLeft")
{ FloatRect r = obj[i].rect;
int y0 = (x+w/2-r.left) * r.height/r.width+ r.top - h;
if (y>y0)
if (x+w/2>r.left)
{y = y0; dy=0; STATE=stay;}
}
if (obj[i].name=="SlopeRight")
{ FloatRect r = obj[i].rect;
int y0 = - (x+w/2-r.left) * r.height/r.width + r.top+r.height - h;
if (y > y0)
if (x+w/2<r.left+r.width)
{y = y0 ; dy=0; STATE=stay;}
}
}
}
};
class ENEMY: public Entity
{
public:
ENEMY(AnimationManager &a, Level &lev,int x,int y):Entity(a,x,y)
{
option("Enemy", 0.01, 15, "move");
}
void update(float time)
{
x += dx * time;
timer+=time;
if (timer>3200) {dx*=-1;timer=0;}
if (Health<=0) {anim.set("dead"); dx=0;
timer_end+=time;
if (timer_end>4000) life=false;
}
anim.tick(time);
}
};
class COIN: public Entity
{
public:
COIN(AnimationManager &a, Level &lev,int x,int y):Entity(a,x,y)
{
option("Coin", 0, 1, "move");
}
void update(float time)
{
x += dx * time;
timer+=time;
if (timer>3200) {dx*=-1;timer=0;}
if (Health<=0) {anim.set("dead"); dx=0;
timer_end+=time;
if (timer_end>4000) life=false;
}
anim.tick(time);
}
};
class MovingPlatform: public Entity
{
public:
MovingPlatform(AnimationManager &a, Level &lev,int x,int y):Entity(a,x,y)
{
option("MovingPlatform", 0.05, 0, "move");
}
void update(float time)
{
x += dx * time;
timer+=time;
if (timer>6000) {dx*=-1;timer=0;}
anim.tick(time);
}
};
int main()
{
Music music,jump,shoot,hit,enter,change;
jump.openFromFile("files/jump.wav");
shoot.openFromFile("files/shoot.wav");
music.openFromFile("files/main.wav");
hit.openFromFile("files/hit.wav");
enter.openFromFile("files/enter.wav");
change.openFromFile("files/change.wav");
Text text,//новая игра
text2,//результаты
text3,//выход
text4,//очки на экран
text5,//гейм овер
text6,//жизнь на экран
text7,//жизнь
text8,//about
text9,//win
text10,// /
text11,//управление
text12;//about выбрано
Font font;
font.loadFromFile("sansation.ttf");
text.setFont(font);
text.setString("New Game");
text.setCharacterSize(24);
text2.setFont(font);
text2.setString("Coin:");
text2.setCharacterSize(24);
text3.setFont(font);
text3.setString("Exit");
text3.setCharacterSize(24);
text4.setFont(font);
text4.setCharacterSize(24);
text5.setFont(font);
text5.setString("Game");
text5.setCharacterSize(24);
text6.setFont(font);
text6.setCharacterSize(24);
text7.setFont(font);
text7.setCharacterSize(24);
text7.setString("Health:");
text8.setFont(font);
text8.setCharacterSize(24);
text8.setString("About");
text9.setFont(font);
text9.setCharacterSize(24);
text10.setFont(font);
text10.setCharacterSize(24);
text10.setString("/");
text11.setFont(font);
text11.setCharacterSize(24);
text11.setString("Press [E] to select\nUse [W][A][S][D] to move\nPress [space] to shoot\nPress [Esc] to return to menu");
text12.setFont(font);
text12.setCharacterSize(24);
text12.setString("Was created by Antonova Evgenia\n Mashukow Nikolay");
///////////// инициализация ///////////////////////////
RenderWindow window(VideoMode(1280, 720), "My Game");
Style::None;
pro1:
View view( FloatRect(0, 0, 1280, 720) );
Level lvl;
lvl.LoadFromFile("level.tmx");
Texture tileSet, moveplatform, megaman, fang,coinn,menu,pict;
Sprite menuspr,pictspr;
menu.loadFromFile("files/menu.png");
menuspr.setTexture(menu);
menuspr.setTextureRect(IntRect(0,0,1280,720));
pict.loadFromFile("files/ingame.png");
pictspr.setTexture(pict);
pictspr.setTextureRect(IntRect(0,0,1280,720));
tileSet.loadFromFile("files/enemy.png");
moveplatform.loadFromFile("files/movingPlatform.png");
megaman.loadFromFile("files//megaman.png");
fang.loadFromFile("files/bullet.png");
coinn.loadFromFile("files/coin.png");
AnimationManager anim;
anim.loadFromXML("files/megaman_anim.xml",megaman);
anim.animList["jump"].loop = 0;
AnimationManager anim2;
anim2.create("move",fang,16,0,16,4,1,0);
anim2.create("explode",fang,6,0,16,16,4,0.01,29,false);
AnimationManager anim3;
anim3.create("move",tileSet,0,0,32,32,2,0.002,32);
anim3.create("dead",tileSet,64,0,32,32,2,0.0005,32);
AnimationManager anim4;
anim4.create("move",moveplatform,0,0,95,22,1,0);
AnimationManager anim5;
anim5.create("move",coinn,0,0,32,32,2,0.002,32);
anim5.create("dead",coinn,64,0,32,32,2,0.0005,32);
std::list<Entity*> entities;
std::list<Entity*>::iterator it;
std::vector<Object> e;
e= lvl.GetObjects("enemy");
for (int i=0;i < e.size();i++)
entities.push_back(new ENEMY(anim3, lvl, e[i].rect.left, e[i].rect.top) );
e = lvl.GetObjects("MovingPlatform");
for (int i=0;i < e.size();i++)
entities.push_back(new MovingPlatform(anim4, lvl, e[i].rect.left, e[i].rect.top) );
e = lvl.GetObjects("Coin");
for (int i=0;i < e.size();i++)
entities.push_back(new COIN(anim5, lvl, e[i].rect.left, e[i].rect.top) );
win=e.size();
Object pl = lvl.GetObject("player");
PLAYER player1(anim, lvl, pl.rect.left, pl.rect.top);
music.play();
Clock clock;
Clock musicc;
/////////////////// основной цикл /////////////////////
while (window.isOpen())
{
ress=int2Str(res);
winn=int2Str(win);
ress2=int2Str(player1.Health);
text4.setString(ress);
text6.setString(ress2);
text9.setString(winn);
float time = clock.getElapsedTime().asMicroseconds();
float musiccc=musicc.getElapsedTime().asSeconds();
clock.restart();
time = time/600;
if (time > 400) time = 400;
if(musiccc>=10)
{
music.play();
musicc.restart();
}
Event event;
while (window.pollEvent(event))
{
if (event.type == Event::Closed)
window.close();
if(ingame==0)
{
if (event.type == Event::KeyPressed)
if (event.key.code==Keyboard::Escape)
window.close();
if(newgame==1)
{
if (event.type == Event::KeyPressed)
if (event.key.code==Keyboard::E)
{ enter.play();
res=0;
ingame=1;
reset=0;
text5.setCharacterSize(24);
}
}
if(esc)
{
if (event.type == Event::KeyPressed)
if (event.key.code==Keyboard::E)
{
window.close();
enter.play();
}
}
}
if (event.type == Event::KeyPressed)
if (event.key.code==Keyboard::Space)
{entities.push_back(new Bullet(anim2,lvl,player1.x+18,player1.y+18,player1.dir) ); shoot.play(); }
}
if(ingame==1)
{
if (Keyboard::isKeyPressed(Keyboard::A)) player1.key["L"]=true;
if (Keyboard::isKeyPressed(Keyboard::D)) player1.key["R"]=true;
if (Keyboard::isKeyPressed(Keyboard::W)){ player1.key["Up"]=true;if(!player1.onLadder) jump.play();}
if (Keyboard::isKeyPressed(Keyboard::S)) player1.key["Down"]=true;
if (Keyboard::isKeyPressed(Keyboard::Escape))
{ enter.play();
ingame=0;
newgame=1;
text5.setString("Game");
text5.setColor(Color::White);
reset=1;
goto pro1;
}
}
if(ingame==0)
{
if(newgame==1)
{
text.setColor(Color::Green);
text8.setColor(Color::White);
text3.setColor(Color::White);
if (Keyboard::isKeyPressed(Keyboard::D))
{ change.play();
newgame=0;
reite=0;
esc=0;
reit=1;
ingame=0;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{ change.play();
reite=0;
esc=1;
reit=0;
ingame=0;
}
}
if(reit==1)
{
text.setColor(Color::White);
text8.setColor(Color::Green);
text3.setColor(Color::White);
if (Keyboard::isKeyPressed(Keyboard::S))
{change.play();
reit=0;
reite=0;
esc=1;
newgame=0;
ingame=0;
}
if (Keyboard::isKeyPressed(Keyboard::A))
{ change.play();
reit=0;
reite=0;
esc=0;
ingame=0;
newgame=1;
}
if (Keyboard::isKeyPressed(Keyboard::E))
{enter.play();
reite=1;
}
}
if(esc)
{
newgame=0;
reit=0;
text8.setColor(Color::White);
text.setColor(Color::White);
text3.setColor(Color::Green);
if (Keyboard::isKeyPressed(Keyboard::W))
{change.play();
reite=0;
reit=0;
newgame=1;
esc=0;
}
}
}
for(it=entities.begin();it!=entities.end();)
{
Entity *b = *it;
b->update(time);
if (b->life==false) { it = entities.erase(it); delete b;}
else it++;
}
player1.update(time);
if(buf!=player1.Health)
{
buf=player1.Health;
hit.play();
}
if(player1.Health<=0)
{
text5.setString("Game over");
text5.setColor(Color::Red);
text5.setCharacterSize(50);
player1.Health=10;
ingame=0;
newgame=1;
reset=1;
buf=100;
goto pro1;
}
if(newgame)
{
text.setCharacterSize(32);
text8.setCharacterSize(24);
text3.setCharacterSize(24);
}
if(reit)
{
text.setCharacterSize(24);
text8.setCharacterSize(32);
text3.setCharacterSize(24);
}
if(esc)
{
text.setCharacterSize(24);
text8.setCharacterSize(24);
text3.setCharacterSize(32);
}
if(player1.Health>0&&ingame==1)
{
text5.setString("Game");
text5.setColor(Color::Green);
}
menuspr.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2, window.getView().getCenter().y - window.getView().getSize().y/2);
pictspr.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2, window.getView().getCenter().y - window.getView().getSize().y/2);
text.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 100);
text8.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 280-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 100);
text3.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 140);
text2.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 180);
text4.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 180-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 180);
text5.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 220);
text6.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 180-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 260);
text7.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 260);
text9.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 210-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 180);
text10.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 200-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 180);
text11.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 100-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 380);
text12.setPosition( window.getView().getCenter().x - window.getView().getSize().x/2 + 280-53, window.getView().getCenter().y - window.getView().getSize().y/2 + 140);
for(it=entities.begin();it!=entities.end();it++)
{
if ((*it)->Name=="Enemy")
{
Entity *enemy = *it;
if (enemy->Health<=0) continue;
if (player1.getRect().intersects( enemy->getRect() ))
if (player1.dy>0) { enemy->dx=0; player1.dy=-0.2; enemy->Health=0;}
else if (!player1.hit) { player1.Health-=5; player1.hit=true;
if (player1.dir) player1.x+=10; else player1.x-=10;}
for (std::list<Entity*>::iterator it2=entities.begin(); it2!=entities.end(); it2++)
{
Entity *bullet = *it2;
if (bullet->Name=="Bullet")
if ( bullet->Health>0)
if (bullet->getRect().intersects( enemy->getRect() ) )
{ bullet->Health=0; enemy->Health-=5;}
}
}
if ((*it)->Name=="MovingPlatform")
{
Entity *movPlat = *it;
if (player1.getRect().intersects( movPlat->getRect() ) )
if (player1.dy>0)
if (player1.y+player1.h<movPlat->y+movPlat->h)
{player1.y=movPlat->y - player1.h + 3; player1.x+=movPlat->dx*time; player1.dy=0; player1.STATE=PLAYER::stay;}
}
if ((*it)->Name=="Coin")
{
Entity *coin = *it;
if (player1.getRect().intersects( coin->getRect() ) )
{
if(coin->Health!=0)
res++;
coin->Health=0;
if(win==res)
{
text5.setString("Victory!");
text5.setColor(Color::Blue);
text5.setCharacterSize(50);
ingame=0;
newgame=1;
esc=0;
reit=0;
goto pro1;
}
}
}
/*рисуем*/
view.setCenter( player1.x,player1.y);
window.setView(view);
window.clear(Color(107,140,255));
}
if(ingame==0)
{
window.draw(menuspr);
window.draw(text);
window.draw(text2);
window.draw(text3);
window.draw(text4);
window.draw(text5);
window.draw(text8);
window.draw(text9);
window.draw(text10);
window.draw(text11);
if(reite)
window.draw(text12);
}
if(ingame)
{
window.draw(pictspr);
lvl.Draw(window);
for(it=entities.begin();it!=entities.end();it++)
(*it)->draw(window);
player1.draw(window);
window.draw(text2);
window.draw(text4);
window.draw(text5);
window.draw(text6);
window.draw(text7);
window.draw(text9);
window.draw(text10);
}
window.display();
}
return 0;
}
#ifndef LEVEL_H
#define LEVEL_H
#include <string>
#include <vector>
#include <map>
#include <SFML/Graphics.hpp>
#include <iostream>
#include "TinyXML/tinyxml.h"
struct Object
{
int GetPropertyInt(std::string name);
float GetPropertyFloat(std::string name);
std::string GetPropertyString(std::string name);
std::string name;
std::string type;
sf::Rect<float> rect;
std::map<std::string, std::string> properties;
sf::Sprite sprite;
};
struct Layer
{
int opacity;
std::vector<sf::Sprite> tiles;
};
class Level
{
public:
bool LoadFromFile(std::string filename);
Object GetObject(std::string name);
std::vector<Object> GetObjects(std::string name);
std::vector<Object> GetAllObjects();
void Draw(sf::RenderWindow &window);
sf::Vector2i GetTileSize();
private:
int width, height, tileWidth, tileHeight;
int firstTileID;
sf::Rect<float> drawingBounds;
sf::Texture tilesetImage;
std::vector<Object> objects;
std::vector<Layer> layers;
};
///////////////////////////////////////
int Object::GetPropertyInt(std::string name)
{
return atoi(properties[name].c_str());
}
float Object::GetPropertyFloat(std::string name)
{
return strtod(properties[name].c_str(), NULL);
}
std::string Object::GetPropertyString(std::string name)
{
return properties[name];
}
bool Level::LoadFromFile(std::string filename)
{
TiXmlDocument levelFile(filename.c_str());
// Загружаем XML-карту
if(!levelFile.LoadFile())
{
std::cout << "Loading level \"" << filename << "\" failed." << std::endl;
return false;
}
// Работаем с контейнером map
TiXmlElement *map;
map = levelFile.FirstChildElement("map");
// Пример карты: <map version="1.0" orientation="orthogonal"
// width="10" height="10" tilewidth="34" tileheight="34">
width = atoi(map->Attribute("width"));
height = atoi(map->Attribute("height"));
tileWidth = atoi(map->Attribute("tilewidth"));
tileHeight = atoi(map->Attribute("tileheight"));
// Берем описание тайлсета и идентификатор первого тайла
TiXmlElement *tilesetElement;
tilesetElement = map->FirstChildElement("tileset");
firstTileID = atoi(tilesetElement->Attribute("firstgid"));
// source - путь до картинки в контейнере image
TiXmlElement *image;
image = tilesetElement->FirstChildElement("image");
std::string imagepath = image->Attribute("source");
// Пытаемся загрузить тайлсет
sf::Image img;
if(!img.loadFromFile(imagepath))
{
std::cout << "Failed to load tile sheet." << std::endl;
return false;
}
img.createMaskFromColor(sf::Color(255, 255, 255));
tilesetImage.loadFromImage(img);
tilesetImage.setSmooth(false);
// Получаем количество столбцов и строк тайлсета
int columns = tilesetImage.getSize().x / tileWidth;
int rows = tilesetImage.getSize().y / tileHeight;
// Вектор из прямоугольников изображений (TextureRect)
std::vector<sf::Rect<int>> subRects;
for(int y = 0; y < rows; y++)
for(int x = 0; x < columns; x++)
{
sf::Rect<int> rect;
rect.top = y * tileHeight;
rect.height = tileHeight;
rect.left = x * tileWidth;
rect.width = tileWidth;
subRects.push_back(rect);
}
// Работа со слоями
TiXmlElement *layerElement;
layerElement = map->FirstChildElement("layer");
while(layerElement)
{
Layer layer;
// Если присутствует opacity, то задаем прозрачность слоя, иначе он полностью непрозрачен
if (layerElement->Attribute("opacity") != NULL)
{
float opacity = strtod(layerElement->Attribute("opacity"), NULL);
layer.opacity = 255 * opacity;
}
else
{
layer.opacity = 255;
}
// Контейнер <data>
TiXmlElement *layerDataElement;
layerDataElement = layerElement->FirstChildElement("data");
if(layerDataElement == NULL)
{
std::cout << "Bad map. No layer information found." << std::endl;
}
// Контейнер <tile> - описание тайлов каждого слоя
TiXmlElement *tileElement;
tileElement = layerDataElement->FirstChildElement("tile");
if(tileElement == NULL)
{
std::cout << "Bad map. No tile information found." << std::endl;
return false;
}
int x = 0;
int y = 0;
while(tileElement)
{
int tileGID = atoi(tileElement->Attribute("gid"));
int subRectToUse = tileGID - firstTileID;
// Устанавливаем TextureRect каждого тайла
if (subRectToUse >= 0)
{
sf::Sprite sprite;
sprite.setTexture(tilesetImage);
sprite.setTextureRect(subRects[subRectToUse]);
sprite.setPosition(x * tileWidth, y * tileHeight);
sprite.setColor(sf::Color(255, 255, 255, layer.opacity));
layer.tiles.push_back(sprite);
}
tileElement = tileElement->NextSiblingElement("tile");
x++;
if (x >= width)
{
x = 0;
y++;
if(y >= height)
y = 0;
}
}
layers.push_back(layer);
layerElement = layerElement->NextSiblingElement("layer");
}
// Работа с объектами
TiXmlElement *objectGroupElement;
// Если есть слои объектов
if (map->FirstChildElement("objectgroup") != NULL)
{
objectGroupElement = map->FirstChildElement("objectgroup");
while (objectGroupElement)
{
// Контейнер <object>
TiXmlElement *objectElement;
objectElement = objectGroupElement->FirstChildElement("object");
while(objectElement)
{
// Получаем все данные - тип, имя, позиция, etc
std::string objectType;
if (objectElement->Attribute("type") != NULL)
{
objectType = objectElement->Attribute("type");
}
std::string objectName;
if (objectElement->Attribute("name") != NULL)
{
objectName = objectElement->Attribute("name");
}
int x = atoi(objectElement->Attribute("x"));
int y = atoi(objectElement->Attribute("y"));
int width, height;
sf::Sprite sprite;
sprite.setTexture(tilesetImage);
sprite.setTextureRect(sf::Rect<int>(0,0,0,0));
sprite.setPosition(x, y);
if (objectElement->Attribute("width") != NULL)
{
width = atoi(objectElement->Attribute("width"));
height = atoi(objectElement->Attribute("height"));
}
else
{
width = subRects[atoi(objectElement->Attribute("gid")) - firstTileID].width;
height = subRects[atoi(objectElement->Attribute("gid")) - firstTileID].height;
sprite.setTextureRect(subRects[atoi(objectElement->Attribute("gid")) - firstTileID]);
}
// Экземпляр объекта
Object object;
object.name = objectName;
object.type = objectType;
object.sprite = sprite;
sf::Rect <float> objectRect;
objectRect.top = y;
objectRect.left = x;
objectRect.height = height;
objectRect.width = width;
object.rect = objectRect;
// "Переменные" объекта
TiXmlElement *properties;
properties = objectElement->FirstChildElement("properties");
if (properties != NULL)
{
TiXmlElement *prop;
prop = properties->FirstChildElement("property");
if (prop != NULL)
{
while(prop)
{
std::string propertyName = prop->Attribute("name");
std::string propertyValue = prop->Attribute("value");
object.properties[propertyName] = propertyValue;
prop = prop->NextSiblingElement("property");
}
}
}
objects.push_back(object);
objectElement = objectElement->NextSiblingElement("object");
}
objectGroupElement = objectGroupElement->NextSiblingElement("objectgroup");
}
}
else
{
std::cout << "No object layers found..." << std::endl;
}
return true;
}
Object Level::GetObject(std::string name)
{
// Только первый объект с заданным именем
for (int i = 0; i < objects.size(); i++)
if (objects[i].name == name)
return objects[i];
}
std::vector<Object> Level::GetObjects(std::string name)
{
// Все объекты с заданным именем
std::vector<Object> vec;
for(int i = 0; i < objects.size(); i++)
if(objects[i].name == name)
vec.push_back(objects[i]);
return vec;
}
std::vector<Object> Level::GetAllObjects()
{
return objects;
};
sf::Vector2i Level::GetTileSize()
{
return sf::Vector2i(tileWidth, tileHeight);
}
void Level::Draw(sf::RenderWindow &window)
{
// Рисуем все тайлы (объекты НЕ рисуем!)
for(int layer = 0; layer < layers.size(); layer++)
for(int tile = 0; tile < layers[layer].tiles.size(); tile++)
window.draw(layers[layer].tiles[tile]);
}
#endif
#ifndef ANIM_H
#define ANIM_H
#include "TinyXML/tinyxml.h"
#include <SFML/Graphics.hpp>
using namespace sf;
class Animation
{
public:
std::vector<IntRect> frames, frames_flip;
float currentFrame, speed;
bool loop, flip, isPlaying; // loop показвает зациклена ли анимация. Например анимация взрыва должна проиграться один раз и остановиться, loop=false
Sprite sprite;
Animation()
{
currentFrame = 0;
isPlaying=true;
flip=false;
}
void tick(float time)
{
if (!isPlaying) return;
currentFrame += speed * time;
if (currentFrame > frames.size()) { currentFrame -= frames.size();
if (!loop) {isPlaying=false; return;}
}
int i = currentFrame;
sprite.setTextureRect( frames[i] );
if (flip) sprite.setTextureRect( frames_flip[i] );
}
};
class AnimationManager
{
public:
String currentAnim;
std::map<String, Animation> animList;
AnimationManager()
{}
~AnimationManager()
{ animList.clear();
}
//создание анимаций вручную
void create(String name, Texture &texture, int x, int y, int w, int h, int count, float speed, int step=0, bool Loop=true)
{
Animation a;
a.speed = speed;
a.loop = Loop;
a.sprite.setTexture(texture);
a.sprite.setOrigin(0,h);
for (int i=0;i<count;i++)
{
a.frames.push_back( IntRect(x+i*step, y, w, h) );
a.frames_flip.push_back( IntRect(x+i*step+w, y, -w, h) );
}
animList[name] = a;
currentAnim = name;
}
//загрузка из файла XML
void loadFromXML(std::string fileName,Texture &t)
{
TiXmlDocument animFile(fileName.c_str());
animFile.LoadFile();
TiXmlElement *head;
head = animFile.FirstChildElement("sprites");
TiXmlElement *animElement;
animElement = head->FirstChildElement("animation");
while(animElement)
{
Animation anim;
currentAnim = animElement->Attribute("title");
int delay = atoi(animElement->Attribute("delay"));
anim.speed = 1.0/delay; anim.sprite.setTexture(t);
TiXmlElement *cut;
cut = animElement->FirstChildElement("cut");
while (cut)
{
int x = atoi(cut->Attribute("x"));
int y = atoi(cut->Attribute("y"));
int w = atoi(cut->Attribute("w"));
int h = atoi(cut->Attribute("h"));
anim.frames.push_back( IntRect(x,y,w,h) );
anim.frames_flip.push_back( IntRect(x+w,y,-w,h) );
cut = cut->NextSiblingElement("cut");
}
anim.sprite.setOrigin(0,anim.frames[0].height);
animList[currentAnim] = anim;
animElement = animElement->NextSiblingElement("animation");
}
}
void set(String name)
{
currentAnim = name;
animList[currentAnim].flip=0;
}
void draw(RenderWindow &window,int x=0, int y=0)
{
animList[currentAnim].sprite.setPosition(x,y);
window.draw( animList[currentAnim].sprite );
}
void flip(bool b=1) {animList[currentAnim].flip = b;}
void tick(float time) {animList[currentAnim].tick(time);}
void pause() {animList[currentAnim].isPlaying=false;}
void play() {animList[currentAnim].isPlaying=true;}
void play(String name) {animList[name].isPlaying=true;}
bool isPlaying() {return animList[currentAnim].isPlaying;}
float getH() {return animList[currentAnim].frames[0].height;}
float getW() {return animList[currentAnim].frames[0].width;}
};
#endif ANIM_H