第一款自己的RPG游戏--生成心中的世界(一)

版权声明:虽然都是基础,但都是开源的,哈哈。 https://blog.csdn.net/qq_41490433/article/details/85033440

    角色扮演游戏(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:  在此处引用程序需要的其他头文件

这个时候我们进行编译,就能够进行菜单的上下切换,但是我们在游戏开始的时候按回车没有用,这是因为我们还没游戏地图的界面。这里的游戏地图就不是之前在代码上写死的,而是写在文件里,通过代码读取就可以了。

猜你喜欢

转载自blog.csdn.net/qq_41490433/article/details/85033440