角色扮演游戏(Role-playing game),简称为RPG,是游戏类型的一种。在游戏中,玩家负责扮演这个角色在一个写实或虚构世界中活动。
也就是说,当完成你的RPG游戏之后,你心中的世界就被构造出来了。这个世界不是在《我的世界》里创造你的王国一样,而是真正的你的世界,所有的规则由你定,世界的一花一草的长相、行为与思想都由你定。
------导读
在这里还是先用vs来写 (虽然市面上很少游戏公司会用这个,但是大部分引擎都是使用对C++语言的一个封装,并且如之前所说,当你会用控制台写出游戏,那么使用其他更便利的引擎之后,自然就是轻车熟路了)
要生成一个世界,即使只是很简单的规则,但依然个很庞大的项目,并不是那种写一两个类就能完成的程序。这里就引出一种编程思想------管理模式
先说说为什么要使用这个模式。想象一下,在我国之前的封建社会,皇帝掌管着整个中国,给所有的国民定制规则,规定什么可以做,什么不能做,犯了错要怎样的惩罚。。。。。等等一系列的规则。但是他并是不计算机,不能在一秒钟计算十万次百万次。这时候就需要官了。比如 丞相管行政、太尉管军事、御史大夫是副丞相,执掌群臣奏章,下达皇帝诏令,并负责监察百官。逐级管理,直到管理到平民百姓。
这就可以理解为皇帝通过管理丞相来管理行政,管理太尉来管理军事。
而我们的游戏出了主循环也是有2个主要的逻辑——更新与渲染,这时候我们就只需要在主循环写这2句话就可以了
while (true)
{
system("cls");//清理屏幕,每次清理后重新渲染
UpData();//更新(伪代码)
OnRender();//渲染(伪代码)
}
更新的作用是处理逻辑,比如谁做了什么,主角打开了背包
渲染的作用就是与玩家进行交互,打开背包页面,让玩家知道主角打开了背包。
接下来就是进行准备工作,拿到一个项目不能直接开撸,特别是工程比较大的时候,这时候的准备工作就好比砍柴之前的磨刀。道理不多说,先说说怎么磨刀。
1.首先你要知道你要创造一个怎样的世界,是养成、动作、魔法、还是战争?对单纯的程序员来说可以跳过这一步,因为这不是你考虑的事情
2.然后你要想想要怎么设计你的代码结构,总不能向之前的贪吃蛇那样写在一个类里面吧。
3.设置基类。不是鸡肋。基类的意思可以百度,我的理解就是一个最简单的模板,比如所有的人都有共同点:脑袋加身体、会说话会走路(别杠。。。)这时候我们要创造一个码农的时候,我们只需要继承这个基类,就可以给他ctrl+c和ctrl+v就可以了。是不是省了很多东西
开撸:
对RPG来说,最主要的是窗口的切换,从开始的游戏菜单到切换地图、进入战斗等等,都涉及着窗口的转换,而每个窗口都有着数据更新与渲染2个功能。所以这时候我们先写一个基类
WndBase.h
#pragma once
class CWndBase
{
public:
CWndBase();
~CWndBase();
virtual void UpData();
virtual void OnRender();
};
WinBase.cpp
#include "stdafx.h"
#include "WndBase.h"
CWndBase::CWndBase()
{
}
CWndBase::~CWndBase()
{
}
void CWndBase::UpData()
{
}
void CWndBase::OnRender()
{
}
之后我们写一个新窗口的时候继承这个类就可以了
接着就是管理模式的精髓了。RPG肯定是牵扯到数据与逻辑,所以这时候我们写个数据管理者与游戏管理者。
游戏管理者 GameMgr.h
#pragma once//避免类被重复定义
#include"GameMap.h"//将地图作为单例拿出来,方便后续拿到地图上的东西
#include"WndBase.h"//引入基类头文件
class CGameMgr:public CWndBase//继承基类
{
public:
CGameMgr();//构造函数:对象创建的时候调用,并且只调用一次。初始化
~CGameMgr();
//成员函数
void UpData();
void OnRender();
CGameMap* GetGameMap()
{
return m_pGameMap;
}
void ChangeWnd(CWndBase* pWnd);//切换窗口,里面的参数表示要切换的窗口
static CGameMgr* GetInstance();//静态成员函数
void RestoreWnd();//还原上一个窗口
private:
CGameMap* m_pGameMap;//地图
CWndBase* m_pCurWnd;//当前窗口
static CGameMgr* m_spGameMgr;//生成一个全局单例
vector<CWndBase*>m_VecWnd;//存储切换过窗口的容器
};
//类:创建类的时候如果没有指定访问属性,默认是私有
//结构体:如果没有指定访问属性,默认是公有
注释很详细了,这里简单说一下,游戏管理者主要管理这窗口的切换与窗口的更新和渲染。这里还返回了一个地图的单例,作为后续使用
GameMgr.cpp
#include "stdafx.h"
#include "GameMgr.h"
CGameMgr* CGameMgr::m_spGameMgr = nullptr;//静态成员必须在函数外定义
CGameMgr::CGameMgr()
{
//初始化的时候必须向操作系统申请内存
m_pGameMap = new CGameMap();
m_pCurWnd = new CWndBase();
}
CGameMgr::~CGameMgr()
{
//C++没有垃圾回收机制,只能手动回收
SAFE_DEL(m_pGameMap);
SAFE_DEL(m_pCurWnd);
}
//当前窗口的更新
void CGameMgr::UpData()
{
m_pCurWnd->UpData();
}
//当前窗口的渲染
void CGameMgr::OnRender()
{
m_pCurWnd->OnRender();
}
//切换窗口的实现
void CGameMgr::ChangeWnd(CWndBase* pWnd)
{
m_VecWnd.push_back(m_pCurWnd);//将当前的窗口压入容器,备份用
m_pCurWnd = pWnd;//将传进来的窗口设为当前窗口
}
//还原窗口
void CGameMgr::RestoreWnd()
{
//先判断容器是否为空
if (1 < m_VecWnd.size())
{
//将当前窗口设为压入容器的最后一个窗口
m_pCurWnd = m_VecWnd.back();
//删除最后一个窗口
m_VecWnd.pop_back();
}
}
CGameMgr * CGameMgr::GetInstance()
{
//当管理者未创建的时候,new一个
if (nullptr == m_spGameMgr)
{
m_spGameMgr = new CGameMgr();
}
return m_spGameMgr;
}
数据管理者DataMgr后续再说,再说就说不完了。
这时候我们就该去完善我们的主函数了
#include"GameMgr.h"
#include"GameMenu.h"//游戏菜单
CGameMgr* g_pGameMgr = new CGameMgr();
CGameMenu* pGameMenu = new CGameMenu();
CGameMgr::GetInstance()->ChangeWnd(pGameMenu);//将初始页面设为游戏菜单
while (true)
{
system("cls");
CGameMgr::GetInstance()->UpData();//拿到游戏管理者的单例进行更新
CGameMgr::GetInstance()->OnRender();//拿到游戏管理者的单例进行渲染
}
游戏菜单GameMenu.h
#pragma once
#include"WndBase.h"
class CGameMenu:public CWndBase
{
public:
CGameMenu();
~CGameMenu();
void UpData();
void OnRender();
void gotoxy(short x, short y);//控制台绘制用,可以定点绘制,可以直接复制粘贴
private:
//菜单状态,上下移动使用
int m_iMenuState;
};
GameMenu.cpp
#include "stdafx.h"
#include "GameMenu.h"
CGameMenu::CGameMenu()
{
m_iMenuState = 0;//菜单状态的初始化
}
CGameMenu::~CGameMenu()
{
}
void CGameMenu::UpData()
{
//游戏菜单按上功能键
if (KEY_DOWN(VK_UP))//按了键盘上键
{
m_iMenuState--;
if (E_MENU_START > m_iMenuState)//E_MENU_START为枚举
{
m_iMenuState = E_MENU_END;
}
}
//游戏菜单按下功能键
if (KEY_DOWN(VK_DOWN))
{
m_iMenuState++;
if (E_MENU_END < m_iMenuState)
{
m_iMenuState = E_MENU_START;
}
}
//点击开始游戏进入地图,仅当箭头指向游戏开始时才能使用
if (KEY_DOWN(VK_RETURN))
{
//这里只设置了进入游戏的功能,其他的功能同理。
if (E_MENU_START == m_iMenuState)
{
CGameMap* pGameMap = new CGameMap();
CGameMgr::GetInstance()->ChangeWnd(pGameMap);
}
}
}
void CGameMenu::OnRender()
{
if (E_MENU_START == m_iMenuState)
{
gotoxy(70, 25);//定位
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);//设置颜色
cout << "->游戏开始" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
gotoxy(70, 27);
cout << " 继续游戏" << endl;
gotoxy(70, 29);
cout << " 游戏设置" << endl;
gotoxy(70, 31);
cout << " 游戏结束" << endl;
}
else if (E_MENU_CONTINUE == m_iMenuState)
{
gotoxy(70, 25);
cout << " 游戏开始" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
gotoxy(70, 27);
cout << "->继续游戏" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
gotoxy(70, 29);
cout << " 游戏设置" << endl;
gotoxy(70, 31);
cout << " 游戏结束" << endl;
}
else if (E_MENU_SET== m_iMenuState)
{
gotoxy(70, 25);
cout << " 游戏开始" << endl;
gotoxy(70, 27);
cout << " 继续游戏" << endl;
gotoxy(70,29);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
cout << "->游戏设置" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
gotoxy(70, 31);
cout << " 游戏结束" << endl;
}
else if (E_MENU_END == m_iMenuState)
{
gotoxy(70, 25);
cout << " 游戏开始" << endl;
gotoxy(70, 27);
cout << " 继续游戏" << endl;
gotoxy(70, 29);
cout << " 游戏设置" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
gotoxy(70, 31);
cout << "->游戏结束" << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY);
}
}
//定点绘制的实现
void CGameMenu::gotoxy(short x, short y)
{
COORD position = { x, y };
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOut, position);
}
在这种大型项目,如果有许多类都要用到的枚举或者头文件,可以写在系统生成的stdafx.h之中
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
#include<windows.h>
#include<iostream>
#include"GameMgr.h"
#include"DataMgr.h"
#include<vector>
#include<string>
#include<cstdlib>
#include <conio.h>
#define KEY_DOWN(vk_code) GetAsyncKeyState(vk_code)&0x1?1:0 //按键监听
#define SAFE_DEL(p) if(NULL!=p) { delete p; p=nullptr; } //安全删除
using namespace std;
//场景
enum
{
E_SCENE_MENU,
E_SCENE_MAP,
E_SCENE_END,
E_SCENE_SUCCESS
};
//游戏开始页面菜单状态
enum {
E_START_START,
E_START_SET,
E_START_EXIT
};
enum {
E_MAP_MAP1,
E_MAP_MAP2,
E_MAP_MAP3,
E_MAP_MAP4
};
//菜单
//定义子弹的方向
enum
{
//左上,右上,左下,右下
E_BALL_LU,
E_BALL_RU,
E_BALL_LD,
E_BALL_RD,
};
//游戏开始页面菜单状态
enum
{
E_MENU_START,
E_MENU_CONTINUE,
E_MENU_SET,
E_MENU_END,
};
//绘制地图
enum
{
E_MAP_BK,
E_MAP_WALL,
E_MAP_TREE,
E_MAP_WATER
};
//地图、商店、背包、任务。。
enum
{
E_MAP_MAP,
E_MAP_SHOP,
E_MAP_BAG,
E_MAP_TASK,
E_MAP_FIGHT
};
//方向
enum
{
E_DIRECTION_UP,
E_DIRECTION_DOWN,
E_DIRECTION_LEFT,
E_DIRECTION_RIGHT
};
//战斗时候的退出
enum
{
E_FIGHT_ING,
E_FIGHT_PAUSE,
E_FIGHT_DEATH
};
//道具的种类
enum
{
E_ITEM_NONE,//永久消耗
E_ITEM_CONSUMABLES,//一次性
E_ITEM_WEAPONS,//武器
E_ITEM_ARMOR,//防具
E_ITEM_JEWELRY,//首饰
E_ITEM_SPECIAL//特殊装备
};
// TODO: 在此处引用程序需要的其他头文件
这个时候我们进行编译,就能够进行菜单的上下切换,但是我们在游戏开始的时候按回车没有用,这是因为我们还没游戏地图的界面。这里的游戏地图就不是之前在代码上写死的,而是写在文件里,通过代码读取就可以了。