Создание графического меню (С изображениями)

From Valve Developer Community
Jump to: navigation, search
English (en)Русский (ru)
... Icon-Important.png

Введение

Требования

  • Хорошее зание VGUI документации;
  • Начальное/Среднее занание языка C++;
  • Готовые изображения для использования в Вашем меню;
  • VS.NET 2003 и HL2 SP SDK;
  • Знание и знакомство с SDK.
Note.pngNote:This was made with the HL2 SP SDK, but it should take little to no editing for it to work in a MP game.

Что Вы узнаете

  • Как Изменять и управлять VGUI, а также создать своё меню в игре;
  • Как создавать изображения;
  • Как создать новую панель.

Известные баги

Отсутствуют; Однако, пожалуй, нет места для оптимизации кода.

Menu: До и после

Use of this Page

Please feel free to update the code once you've tested it and made sure there are no memory leaks. As of right now, this code does work.

Первые шаги

Шаг 1: Ваши изображения

Подразумевается, что Вы хотите использовать изображения в своём меню вместо текста, который стоит по умолчанию. Поэтому, Вам необходимо создать и сохранить в формате VTF и VMT файлы для следующих пунктов меню:

  • Новая игра
  • Загрузить игру
  • Настройки
  • Выход
  • Сохранить игру
  • Вернуться в игру
  • Друзья
    Note.pngNote:В этой статье, пункт Друзья не используется, но добавить его достаточно просто.

Каждый пункт меню должен иметь два изображения. Первое изображение, будет отображаться, когда указатель мыши не находиться на нём, а другое будет отображаться при наведении курсора мыши на него. Вы можете добавить только одно изображение, но в таком случае гарантия того, что меню будет правильно отображаться, нет.

В этой статье не будет написано о том, как создать TGA, VTF или VMT файлы. Поэтому, пожалуйста, прочтите статью Создание материала чтобы узнать как создать материалы. После создания, поместите эти изображения в директорию Вашего мода/игры materials/vgui/. Или, в крайнем случае, поместите туда Ваши VMT файлы.

Вот пример списка файлов для пункта "Новая игра":

menu_newgame.vtf
menu_newgame.vmt
menu_newgame_over.vtf
menu_newgame_over.vmt

Заметка

При компиляции Вашего TGA файлы, в image.txt напишите следующее:

"nonice" "1"
"nolod" "1"
"nomip" "1"

Это позволит Вашим изображениям не зависеть от пункта Детализация в Настройках игры, то есть качество изображений будет хорошее даже, если настройки игры установлены на минимальные.

Ваш VMT файл, должен иметь строку "$translucent" 1, чтобы изображения были прозрачными. Пример:

"UnlitGeneric"
{
	"$baseTexture" "vgui/menu_newgame"
	"$translucent" 1
}

Photoshop CS не добавляет прозрачности (автоматически) для TGA файлов. Это, в принципе, можно сделать в другой программе. Сначала сохраните изображения в другой формат, а затем экспортируйте в TGA файл в другой программе (например PNG или ImageReady). Чтобы сделать изображением прозрачным, просто выберите область. Затем выберите в верхнем меню пункт Select и нажмите Inverse. Теперь в окне, где вы видите ваши слои, появятся некоторые новые вкладки.

Перейдите на вкладку "channels". Вы увидите красный, зеленый, синий, и RGB каналы. В низу можно увидеть некоторые кнопки одна из них выглядит как квадрат с кругом в внутри. Нажмите, чтобы создать новый канал с заголовком Alpha1 или Alpha. После чего, Alpha сделает так, будет отображаться белый, а всё что было чёрным, станет прозрачным. Сохранить файл в формате TGA. Вот и всё! Вы создали текстуру с прозрачным фоном.

Шаг 2: GameMenu.res

Если в директории your_mod/resource/GameMenu.res нет файла GameMenu.res, то создайте его. Вот пример содержимого этого файла:

Note.pngNote:Если в Ваше меню Вы напишите на русском языке и увидите в игре вместо русских букв какие-то символы, то откройте этот файл в любом текстовом редакторе и измените кодировку на UTF-8, сохраните файл и, если игра во время этого была открыта, перезагрузите игру.
"GameMenu"
{
	"1"
	{
		"label" "Вернуться в игру"
		"command" "ResumeGame"
		"OnlyInGame" "1"
	}
	"5"
	{
		"label" "Новая игра"
		"command" "OpenNewGameDialog"
		"notmulti" "1"
	}	
	"6"
	{
		"label" "Загрузить игру"
		"command" "OpenLoadGameDialog"
		"notmulti" "1"
	}
	"7"
	{
		"label" "Сохранить игру"
		"command" "OpenSaveGameDialog"
		"notmulti" "1"
		"OnlyInGame" "1"
	}
	"12"
	{
		"label" "Насйтроки"
		"command" "OpenOptionsDialog"
	}
	"13"
	{
		"label" "Выход"
		"command" "Quit"
	}
}

Note that there is no Friends option; it wasn't necessary for the project this tutorial was based on. Adding it should be fairly trivial, however.

Шаг 3: Создание невидимых панелей

Существует хорошая статья по созданию собственных панелей . Прочитайте её, чтобы лучше понять следующий код.

Подробные разъяснения этого кода.

vgui_Panel_MainMenu.cpp
Создано в VS.NET 2003:

//========= Copyright © 2006, Valve Productions, All rights reserved. ============//
//
// Purpose: Display Main Menu images, handles rollovers as well
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "vgui_Panel_MainMenu.h"
#include "vgui_controls/Frame.h"
#include <vgui/ISurface.h>
#include <vgui/IVGui.h>
#include <vgui/IInput.h>

#include "vgui_controls/Button.h"
#include "vgui_controls/ImagePanel.h"

using namespace vgui;

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Purpose: Displays the logo panel
//-----------------------------------------------------------------------------
class CMainMenu : public vgui::Frame
{
	DECLARE_CLASS_SIMPLE(CMainMenu, vgui::Frame);

public:
	CMainMenu( vgui::VPANEL parent );
	~CMainMenu();

	virtual void OnCommand(const char *command);

	virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
	{
		
		BaseClass::ApplySchemeSettings( pScheme );
	}

	// The panel background image should be square, not rounded.
	virtual void PaintBackground()
	{
		SetBgColor(Color(0,0,0,0));
		SetPaintBackgroundType( 0 );
		BaseClass::PaintBackground();
	}
	virtual void PerformLayout()
	{
		// re-position
		SetPos(vgui::scheme()->GetProportionalScaledValue(defaultX), vgui::scheme()->GetProportionalScaledValue(defaultY));

		BaseClass::PerformLayout();
	}
	void CMainMenu::PerformDefaultLayout()
	{
		m_pButtonBegin->SetPos(0, 0);
		m_pImgBegin->SetPos(0,0);
		m_pButtonLoad->SetPos(0, 40);
		m_pImgLoad->SetPos(0,40);
		m_pButtonOptions->SetPos(0, 80);
		m_pImgOptions->SetPos(0,80);
		m_pButtonLeave->SetPos(0, 120);
		m_pImgLeave->SetPos(0,120);

		m_pImgSave->SetVisible(false);
		m_pButtonSave->SetVisible(false);

		m_pImgResume->SetVisible(false);
		m_pButtonResume->SetVisible(false);

		InRolloverResume=false;
		InRolloverBegin=false;
		InRolloverLoad=false;
		InRolloverOptions=false;
		InRolloverLeave=false;
	}

	virtual void OnThink()
	{
		// In-game, everything will be in different places than at the root menu!
		if (InGame() && !InGameLayout) {
			DevMsg("Performing menu layout\n");
			int dy = 40; // delta y, shift value
			int x,y;
			// Resume
			m_pButtonResume->SetPos(0,0);
			m_pImgResume->SetPos(0,0);
			m_pButtonResume->SetVisible(true);
			m_pImgResume->SetVisible(true);

			m_pButtonBegin->GetPos(x,y);
			m_pButtonBegin->SetPos(x,y+dy);
			m_pImgBegin->GetPos(x,y);
			m_pImgBegin->SetPos(x,y+dy);

			m_pButtonLoad->GetPos(x,y);
			m_pButtonLoad->SetPos(x,y+dy);
			m_pImgLoad->GetPos(x,y);
			m_pImgLoad->SetPos(x,y+dy);

			// Save game
			m_pButtonSave->SetPos(x,y+(2*dy));
			m_pImgSave->SetPos(x,y+(2*dy));
			m_pButtonSave->SetVisible(true);
			m_pImgSave->SetVisible(true);

			m_pButtonOptions->GetPos(x,y);
			m_pButtonOptions->SetPos(x,y+(2*dy));
			m_pImgOptions->GetPos(x,y);
			m_pImgOptions->SetPos(x,y+(2*dy)); // Options moves under Save game, so twice as far

			m_pButtonLeave->GetPos(x,y);
			m_pButtonLeave->SetPos(x,y+(2*dy));
			m_pImgLeave->GetPos(x,y);
			m_pImgLeave->SetPos(x,y+(2*dy)); // Leave game moves under Save game, so twice as far

			InGameLayout = true;
		}
		if (!InGame() && InGameLayout)
		{
			PerformDefaultLayout();
			InGameLayout = false;
		}

		// Get mouse coords
		int x,y;
		vgui::input()->GetCursorPos(x,y);

		int fx,fy; // frame xpos, ypos

		GetPos(fx,fy);

		CheckRolloverBegin(x,y,fx,fy);
		CheckRolloverResume(x,y,fx,fy);
		CheckRolloverLoad(x,y,fx,fy);
		CheckRolloverSave(x,y,fx,fy);
		CheckRolloverOptions(x,y,fx,fy);
		CheckRolloverLeave(x,y,fx,fy);
		
		BaseClass::OnThink();		
	}

	void CheckRolloverBegin(int x,int y, int fx, int fy)
	{
		int bx,by,bw,bh; // button xpos, ypos, width, height

		m_pButtonBegin->GetPos(bx,by);
		m_pButtonBegin->GetSize(bw,bh);

		bx = bx+fx; // xpos for button (rel to screen)
		by = by+fy; // ypos for button (rel to screen)

		// Check and see if mouse cursor is within button bounds
		if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
		{
			if(!InRolloverBegin) {
				m_pImgBegin->SetImage("menu_begin_over");
				InRolloverBegin = true;
			}
		} else {
			if(InRolloverBegin) {
				m_pImgBegin->SetImage("menu_begin");
				InRolloverBegin = false;
			}
		}
	}

	void CheckRolloverResume(int x,int y, int fx, int fy)
	{
		if(m_pButtonResume->IsVisible()) {
			int bx,by,bw,bh; // button xpos, ypos, width, height

			m_pButtonResume->GetPos(bx,by);
			m_pButtonResume->GetSize(bw,bh);

			bx = bx+fx; // xpos for button (rel to screen)
			by = by+fy; // ypos for button (rel to screen)

			// Check and see if mouse cursor is within button bounds
			if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
			{
				if(!InRolloverResume) {
					m_pImgResume->SetImage("menu_Resume_over");
					InRolloverResume = true;
				}
			} else {
				if(InRolloverResume) {
					m_pImgResume->SetImage("menu_Resume");
					InRolloverResume = false;
				}
			}
		}
	}
	void CheckRolloverLoad(int x,int y, int fx, int fy)
	{
		int bx,by,bw,bh; // button xpos, ypos, width, height

		m_pButtonLoad->GetPos(bx,by);
		m_pButtonLoad->GetSize(bw,bh);

		bx = bx+fx; // xpos for button (rel to screen)
		by = by+fy; // ypos for button (rel to screen)

		// Check and see if mouse cursor is within button bounds
		if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
		{
			if(!InRolloverLoad) {
				m_pImgLoad->SetImage("menu_load_over");
				InRolloverLoad = true;
			}
		} else {
			if(InRolloverLoad) {
				m_pImgLoad->SetImage("menu_load");
				InRolloverLoad = false;
			}
		}
	}
	void CheckRolloverSave(int x,int y, int fx, int fy)
	{
		if(m_pButtonSave->IsVisible()) {
			int bx,by,bw,bh; // button xpos, ypos, width, height

			m_pButtonSave->GetPos(bx,by);
			m_pButtonSave->GetSize(bw,bh);

			bx = bx+fx; // xpos for button (rel to screen)
			by = by+fy; // ypos for button (rel to screen)

			// Check and see if mouse cursor is within button bounds
			if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
			{
				if(!InRolloverSave) {
					m_pImgSave->SetImage("menu_Save_over");
					InRolloverSave = true;
				}
			} else {
				if(InRolloverSave) {
					m_pImgSave->SetImage("menu_Save");
					InRolloverSave = false;
				}
			}
		}
	}
	void CheckRolloverOptions(int x,int y, int fx, int fy)
	{
		int bx,by,bw,bh; // button xpos, ypos, width, height

		m_pButtonOptions->GetPos(bx,by);
		m_pButtonOptions->GetSize(bw,bh);

		bx = bx+fx; // xpos for button (rel to screen)
		by = by+fy; // ypos for button (rel to screen)

		// Check and see if mouse cursor is within button bounds
		if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
		{
			if(!InRolloverOptions) {
				m_pImgOptions->SetImage("menu_Options_over");
				InRolloverOptions = true;
			}
		} else {
			if(InRolloverOptions) {
				m_pImgOptions->SetImage("menu_Options");
				InRolloverOptions = false;
			}
		}
	}
	void CheckRolloverLeave(int x,int y, int fx, int fy)
	{
		int bx,by,bw,bh; // button xpos, ypos, width, height

		m_pButtonLeave->GetPos(bx,by);
		m_pButtonLeave->GetSize(bw,bh);

		bx = bx+fx; // xpos for button (rel to screen)
		by = by+fy; // ypos for button (rel to screen)

		// Check and see if mouse cursor is within button bounds
		if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
		{
			if(!InRolloverLeave) {
				m_pImgLeave->SetImage("menu_Leave_over");
				InRolloverLeave = true;
			}
		} else {
			if(InRolloverLeave) {
				m_pImgLeave->SetImage("menu_Leave");
				InRolloverLeave = false;
			}
		}
	}
	bool CMainMenu::InGame()
	{
		C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();

		if(pPlayer && IsVisible())
		{
			return true;
		} else {
			return false;
		}
	}

private:
	vgui::ImagePanel *m_pImgBegin;
	vgui::ImagePanel *m_pImgResume;
	vgui::ImagePanel *m_pImgLoad;
	vgui::ImagePanel *m_pImgSave;
	vgui::ImagePanel *m_pImgOptions;
	vgui::ImagePanel *m_pImgLeave;
	vgui::Button *m_pButtonBegin;
	vgui::Button *m_pButtonResume;
	vgui::Button *m_pButtonLoad;
	vgui::Button *m_pButtonSave;
	vgui::Button *m_pButtonOptions;
	vgui::Button *m_pButtonLeave;

	int defaultX;
	int defaultY;
	bool InGameLayout;
	bool InRolloverBegin;
	bool InRolloverResume;
	bool InRolloverLoad;
	bool InRolloverSave;
	bool InRolloverOptions;
	bool InRolloverLeave;
};

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CMainMenu::CMainMenu( vgui::VPANEL parent ) : BaseClass( NULL, "CMainMenu" )
{
	LoadControlSettings( "resource/UI/MainMenu.res" ); // Optional, don't need this

	SetParent( parent );
	SetTitleBarVisible( false );
	SetMinimizeButtonVisible( false );
	SetMaximizeButtonVisible( false );
	SetCloseButtonVisible( false );
	SetSizeable( false );
	SetMoveable( false );
	SetProportional( true );
	SetVisible( true );
	SetKeyBoardInputEnabled( false );
	SetMouseInputEnabled( false );
	//ActivateBuildMode();
	SetScheme("MenuScheme.res");

        // These coords are relative to a 640x480 screen
        // Good to test in a 1024x768 resolution.
	defaultX = 60; // x-coord for our position
	defaultY = 240; // y-coord for our position
	InGameLayout = false;

	// Size of the panel
	SetSize(512,512);
	SetZPos(-1); // we're behind everything

	// Load invisi buttons
        // Initialize images
	m_pImgBegin = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Begin"));
	m_pImgResume = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Resume"));
	m_pImgLoad = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Load"));
	m_pImgSave = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Save"));
	m_pImgOptions = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Options"));
	m_pImgLeave = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Leave"));

	// New game
	
	m_pButtonBegin = vgui::SETUP_PANEL(new vgui::Button(this, "btnBegin", ""));	
	m_pButtonBegin->SetSize(300, 28);
	m_pButtonBegin->SetPaintBorderEnabled(false);
	m_pButtonBegin->SetPaintEnabled(false);
	m_pImgBegin->SetImage("menu_begin");

	// Resume
	m_pButtonResume = vgui::SETUP_PANEL(new vgui::Button(this, "btnResume", ""));	
	m_pButtonResume->SetSize(170, 28);
	m_pButtonResume->SetPaintBorderEnabled(false);
	m_pButtonResume->SetPaintEnabled(false);
	m_pImgResume->SetImage("menu_resume");

	// Load
	m_pButtonLoad = vgui::SETUP_PANEL(new vgui::Button(this, "btnLoad", ""));
	m_pButtonLoad->SetSize(190, 28);
	m_pButtonLoad->SetPaintBorderEnabled(false);
	m_pButtonLoad->SetPaintEnabled(false);
	m_pImgLoad->SetImage("menu_load");

	// Save
	m_pButtonSave = vgui::SETUP_PANEL(new vgui::Button(this, "btnSave", ""));
	m_pButtonSave->SetSize(190, 28);
	m_pButtonSave->SetPaintBorderEnabled(false);
	m_pButtonSave->SetPaintEnabled(false);
	m_pImgSave->SetImage("menu_save");

	// Options
	m_pButtonOptions = vgui::SETUP_PANEL(new vgui::Button(this, "btnOptions", ""));
	m_pButtonOptions->SetSize(170, 28);
	m_pButtonOptions->SetPaintBorderEnabled(false);
	m_pButtonOptions->SetPaintEnabled(false);
	m_pImgOptions->SetImage("menu_options");

	// Leave
	m_pButtonLeave = vgui::SETUP_PANEL(new vgui::Button(this, "btnLeave", ""));
	m_pButtonLeave->SetSize(180, 28);
	m_pButtonLeave->SetPaintBorderEnabled(false);
	m_pButtonLeave->SetPaintEnabled(false);
	m_pImgLeave->SetImage("menu_leave");

	PerformDefaultLayout();
}

void CMainMenu::OnCommand(const char *command)
{

	BaseClass::OnCommand(command);
}


//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CMainMenu::~CMainMenu()
{
}

// Class
// Change CSMenu to CModMenu if you want. Salient is the name of the source mod, 
// hence SMenu. If you change CSMenu, change ISMenu too where they all appear.
class CSMenu : public ISMenu
{
private:
	CMainMenu *MainMenu;
	vgui::VPANEL m_hParent;

public:
	CSMenu( void )
	{
		MainMenu = NULL;
	}

	void Create( vgui::VPANEL parent )
	{
		// Create immediately
		MainMenu = new CMainMenu(parent);
	}

	void Destroy( void )
	{
		if ( MainMenu )
		{
			MainMenu->SetParent( (vgui::Panel *)NULL );
			delete MainMenu;
		}
	}

};

static CSMenu g_SMenu;
ISMenu *SMenu = ( ISMenu * )&g_SMenu;

vgui_Panel_MainMenu.h

#include <vgui/VGUI.h>

namespace vgui
{
	class Panel;
}

class ISMenu
{
public:
	virtual void		Create( vgui::VPANEL parent ) = 0;
	virtual void		Destroy( void ) = 0;
};

extern ISMenu *SMenu;

vgui_int.cpp
Откройте vgui_int.cpp и добавьте туда следующий код:

#include "vgui_Panel_MainMenu.h" // Menu Panel to your includes, at the top.

SMenu->Create( GameUiDll ); // to the VGui_CreateGlobalPanels function. If you don't have GameUiDll defined, then add: 
VPANEL GameUiDll = enginevgui->GetPanel( PANEL_GAMEUIDLL); // in the same function.

SMenu->Destroy(); // to VGui_Shutdown().
Note.pngNote:Приведенный выше код был настроен для конкретного мода; Вам, возможно, потребуется настроить этот код под Ваш мод.

Логика

Панели для размещения ваших изображений в меню созданы. This panel will be aligned with the Menu panel, and appear behind it. Then the original Menu panel will be rendered invisible by editing GameMenu.res again to get rid of the text but not the menu items. That way, it will appear to the user as if you created a new menu system when in reality all you did was put an invisible panel behind the GameMenu panel.

Настройка для Вашего мода

Вам необходимо отредактировать следующее:

        vgui::ImagePanel *m_pImgBegin;
	vgui::ImagePanel *m_pImgResume;
	vgui::ImagePanel *m_pImgLoad;
	vgui::ImagePanel *m_pImgSave;
	vgui::ImagePanel *m_pImgOptions;
	vgui::ImagePanel *m_pImgLeave;
	vgui::Button *m_pButtonBegin;
	vgui::Button *m_pButtonResume;
	vgui::Button *m_pButtonLoad;
	vgui::Button *m_pButtonSave;
	vgui::Button *m_pButtonOptions;
	vgui::Button *m_pButtonLeave;

	int defaultX;
	int defaultY;
	bool InGameLayout;
	bool InRolloverBegin;
	bool InRolloverResume;
	bool InRolloverLoad;
	bool InRolloverSave;
	bool InRolloverOptions;
	bool InRolloverLeave;

Замените Button's и ImagePanel's параметрами, которые нужны для Вашего мода. The Boolean values concern the individual rollover states of your menu options.

После того как Вы сделали это, Вы изменили имена некоторых переменных, поэтому, Вам необходимо выбрать опцию в Вашем текстовом редакторе Find & Replace, и сделать "Заменить все", для старых имён переменных, чтобы заменить их на новые.

Далее, Вам нужно будет инициализировать свои кнопки и ImagePanels:

// Load invisi buttons

	m_pImgBegin = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Begin"));
	m_pImgResume = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Resume"));
	m_pImgLoad = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Load"));
	m_pImgSave = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Save"));
	m_pImgOptions = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Options"));
	m_pImgLeave = vgui::SETUP_PANEL(new vgui::ImagePanel(this, "Leave"));

	// New game
	
	m_pButtonBegin = vgui::SETUP_PANEL(new vgui::Button(this, "btnBegin", ""));	
	m_pButtonBegin->SetSize(300, 28);
	m_pButtonBegin->SetPaintBorderEnabled(false);
	m_pButtonBegin->SetPaintEnabled(false);
	m_pImgBegin->SetImage("menu_begin");

	// Resume
	m_pButtonResume = vgui::SETUP_PANEL(new vgui::Button(this, "btnResume", ""));	
	m_pButtonResume->SetSize(170, 28);
	m_pButtonResume->SetPaintBorderEnabled(false);
	m_pButtonResume->SetPaintEnabled(false);
	m_pImgResume->SetImage("menu_resume");

	// Load
	m_pButtonLoad = vgui::SETUP_PANEL(new vgui::Button(this, "btnLoad", ""));
	m_pButtonLoad->SetSize(190, 28);
	m_pButtonLoad->SetPaintBorderEnabled(false);
	m_pButtonLoad->SetPaintEnabled(false);
	m_pImgLoad->SetImage("menu_load");

	// Save
	m_pButtonSave = vgui::SETUP_PANEL(new vgui::Button(this, "btnSave", ""));
	m_pButtonSave->SetSize(190, 28);
	m_pButtonSave->SetPaintBorderEnabled(false);
	m_pButtonSave->SetPaintEnabled(false);
	m_pImgSave->SetImage("menu_save");

	// Options
	m_pButtonOptions = vgui::SETUP_PANEL(new vgui::Button(this, "btnOptions", ""));
	m_pButtonOptions->SetSize(170, 28);
	m_pButtonOptions->SetPaintBorderEnabled(false);
	m_pButtonOptions->SetPaintEnabled(false);
	m_pImgOptions->SetImage("menu_options");

	// Leave
	m_pButtonLeave = vgui::SETUP_PANEL(new vgui::Button(this, "btnLeave", ""));
	m_pButtonLeave->SetSize(180, 28);
	m_pButtonLeave->SetPaintBorderEnabled(false);
	m_pButtonLeave->SetPaintEnabled(false);
	m_pImgLeave->SetImage("menu_leave");

Вам нужно будет проверить, чтобы пути к изображениям были правильными по отношению к директории materials/vgui/.

При тестировании кнопок на ширину и высоту, установите переменную SetPaintBorderEnabled(false) на SetPaintBorderEnabled(true) чтобы Ваши кнопки были достаточно большими. После тестирования, Вы можете установить значение переменной на false.

Теперь о PerformDefaultLayout:

	void CMainMenu::PerformDefaultLayout()
	{
		m_pButtonBegin->SetPos(0, 0);
		m_pImgBegin->SetPos(0,0);
		m_pButtonLoad->SetPos(0, 40);
		m_pImgLoad->SetPos(0,40);
		m_pButtonOptions->SetPos(0, 80);
		m_pImgOptions->SetPos(0,80);
		m_pButtonLeave->SetPos(0, 120);
		m_pImgLeave->SetPos(0,120);

		m_pImgSave->SetVisible(false);
		m_pButtonSave->SetVisible(false);

		m_pImgResume->SetVisible(false);
		m_pButtonResume->SetVisible(false);

		InRolloverResume=false;
		InRolloverBegin=false;
		InRolloverLoad=false;
		InRolloverOptions=false;
		InRolloverLeave=false;
	}

Эта функция предназначена для сброса настроек меню для макета по умолчанию; например при выхода из игры или при её запуске.

Опять же, убедитесь, что Ваши метки (Названия переменных) правильные. Сохраните, кнопки и изображения не будут установлены в положение по умолчанию, поскольку они появляются только когда Мы в игре игре. В другой функции Мы устанавливаем позицию этого меню и кнопок. Все это делает Вас уверенным в том, что макеты невидимы, когда мы в главном меню.

О функции PerformLayout: Функция проверяет, выравнивание меню для разрешения экрана. Это идеальная функция, но она работает.


OnThink

Вот в каком файле позиция макета включается в игру.

Layout Positioning

		// In-game, everything will be in different places than at the root menu!
		if (InGame() && !InGameLayout) {
			//DevMsg("Performing menu layout\n");
			int dy = 40; // delta y, shift value
			int x,y;
			// Resume
			m_pButtonResume->SetPos(0,0);
			m_pImgResume->SetPos(0,0);
			m_pButtonResume->SetVisible(true);
			m_pImgResume->SetVisible(true);

			m_pButtonBegin->GetPos(x,y);
			m_pButtonBegin->SetPos(x,y+dy);
			m_pImgBegin->GetPos(x,y);
			m_pImgBegin->SetPos(x,y+dy);

			m_pButtonLoad->GetPos(x,y);
			m_pButtonLoad->SetPos(x,y+dy);
			m_pImgLoad->GetPos(x,y);
			m_pImgLoad->SetPos(x,y+dy);

			// Save game
			m_pButtonSave->SetPos(x,y+(2*dy));
			m_pImgSave->SetPos(x,y+(2*dy));
			m_pButtonSave->SetVisible(true);
			m_pImgSave->SetVisible(true);

			m_pButtonOptions->GetPos(x,y);
			m_pButtonOptions->SetPos(x,y+(2*dy));
			m_pImgOptions->GetPos(x,y);
			m_pImgOptions->SetPos(x,y+(2*dy)); // Options moves under Save game, so twice as far

			m_pButtonLeave->GetPos(x,y);
			m_pButtonLeave->SetPos(x,y+(2*dy));
			m_pImgLeave->GetPos(x,y);
			m_pImgLeave->SetPos(x,y+(2*dy)); // Leave game moves under Save game, so twice as far

			InGameLayout = true;
		}
		if (!InGame() && InGameLayout)
		{
			PerformDefaultLayout();
			InGameLayout = false;
		}

This code is for in-game. In HL2 SP, there are two new menu options, Resume Game and Save Game. This makes sure the custom menu options re-align to compensate.

For your mod, you will need to change dy to a better value. For the mod this tutorial was taken from, a 40 pixel difference between menu options was required for the images to be aligned correctly. You will need to make this more or less depending on your needs.

For this tutorial all the layout changes reflect the GameMenu.res. If you added options or removed options, you need to edit this code. Basically the logic is this:

Get Previous position (x,y) Set new position to be same x distance, but move to y + our shift value. So, move the position 40 pixels down because 'Resume Game' pushed us down.

For menu options underneath 'Save Game' the position needs to move twice as far down because 'Save Game' took up a slot above us.

Otherwise, the rest shouldn't need to be edited (again, make sure labels are correct). The second 'if' statement checks to see if the mod is on the menu screen and had switched layouts earlier. If so, it needs to reset the layout to the default.

Rollovers

Creating rollovers was initially troublesome. Using just one function, passing the button and image pointers, resulted in a memory leak that eventually caused hl2.exe to crash. Individual functions were required to avoid this problem. If any of this code needs improving, it's this part. But it's functional.

// Get mouse coords
		int x,y;
		vgui::input()->GetCursorPos(x,y);

		int fx,fy; // frame xpos, ypos

		GetPos(fx,fy);

		CheckRolloverBegin(x,y,fx,fy);
		CheckRolloverResume(x,y,fx,fy);
		CheckRolloverLoad(x,y,fx,fy);
		CheckRolloverSave(x,y,fx,fy);
		CheckRolloverOptions(x,y,fx,fy);
		CheckRolloverLeave(x,y,fx,fy);

This code is fairly self-explanatory; get the cursor's coordinates, then call the individual button rollover handlers. This tutorial will only outline one handler, as they are all basically the same.

The code also gets the position of our frame on screen to make sure the "capture rectangles" are correct. Then it calls the rollover functions:

void CheckRolloverBegin(int x,int y, int fx, int fy)
	{
		int bx,by,bw,bh; // button xpos, ypos, width, height

		m_pButtonBegin->GetPos(bx,by);
		m_pButtonBegin->GetSize(bw,bh);

		bx = bx+fx; // xpos for button (rel to screen)
		by = by+fy; // ypos for button (rel to screen)

		// Check and see if mouse cursor is within button bounds
		if ((x > bx && x < bx+bw) && (y > by && y < by+bh))
		{
			if(!InRolloverBegin) {
				m_pImgBegin->SetImage("menu_begin_over");
				InRolloverBegin = true;
			}
		} else {
			if(InRolloverBegin) {
				m_pImgBegin->SetImage("menu_begin");
				InRolloverBegin = false;
			}
		}
	}

Here's the logic for the rollover code:

If the mouse is within the bounds of our specific Button, switch the image to the rollover image. If not, keep it off.

The InRolloverBegin Boolean value is put in because when it avoids the memory leak in hl2.exe. It has something to do with the SetImage function; what, though, is the question. While this hack works for now, it definitely could use additional work.

A note on the Save and Resume rollover function: It checks to make sure the buttons are visible before executing the code. There's no use if the user can't see it.


Aligning

To align your Menu panel correctly, it will take require some trial and error where defaultX and defaultY is concerned. defaultX and defaultY are the coordinates where your top-left corner of the menu will be located. It basically needs to be aligned to the main menu so your first image (New Game) appears underneath the text "New Game."

Properly aligned

If your images are bigger than the default font size, you will need to increase the font size to compensate. This can be done in SourceScheme.res. Edit the font MenuLarge and change the "size" accordingly.

Other tips:

In GameMenu.res, change the labels to "N_________". Meaning, put the first letter of the menu option and then underscore until you find the correct length. Keep change defaultX and defaultY until your first image aligns with the first menu option text.

It is also useful to use PaintBorderEnabled(true) for your buttons so you can see the border of your buttons.

You might also like to change the height (spacing) of the HL2 menu items. To do so, open up SourceScheme.res and change MainMenu.MenuItemHeight to an appropriate value.

Then, for positioning your next image (in PerformDefaultLayout), try using the MenuItemHeight value. You only need to find the spacing once and then replace my values with the double, triple, etc. For example, suppose you figure out you need your second image positioned at (x, 20). Then your third will be (x, 40), and your fourth, (x,60), etc. The dy value in OnThink will be 20 then.


Завершение

После того как Вы закончили все выше описанные шаги, Мы должны заменить в файле GameMenu.res некоторые названия пунктов меню. Это важно, поскольку в противном случае пользователь не сможет нажать на Ваши пункты меню. Поскольку переменные отображаются в верхней части Ваших изображений, Вы должны употребить в своём названии пробелы, которые бы охватывали всю Вашу картинку. Пробелы действуют как невидимые буквы, так что, когда игрок наведёт указатель мыши на изображение в меню, он сможет нажать не него.

Пример пункта меню:

 "label" "                 "
 "command" "OpenNewGameDialog"

Если у Вас есть картинка для пункта "Новая игра", которая имеет длину 200 пикселей, то название это пункта должно быть с употреблением пробелов, так чтобы пользователь мог выбрать пункт меню. Лучший способ узнать навели ли Вы указать мыши на меню, то это воспроизведение какого-нибудь звука. Если Вы хотите добавит звук, то читайте материал ниже.

Для Вашего Source Мода меню должно быть примерно таким:

"GameMenu"
{
	"1"
	{
		"label" "              "
		"command" "ResumeGame"
		"OnlyInGame" "1"
	}
	"5"
	{
		"label" "                            "
		"command" "engine ToggleNewGame"
		"notmulti" "1"
	}	
	"6"
	{
		"label" "              "
		"command" "OpenLoadGameDialog"
		"notmulti" "1"
	}
	"7"
	{
		"label" "              "
		"command" "OpenSaveGameDialog"
		"notmulti" "1"
		"OnlyInGame" "1"
	}
	"12"
	{
		"label" "          "
		"command" "OpenOptionsDialog"
	}
	"13"
	{
		"label" "               "
		"command" "Quit"
	}
}
Note.pngNote:Если оставить название пункта пустым, "label" "" , то это будет работать только, если Ваша картинка не слишком длинная. По умолчанию пустые пункты меню охватывают около 60 пикселей, так что Вам будет нужно добавить пробелов для меню, если Ваша картинка длинее 60 пикселей, иначе пункт меню не возможно будет использовать.

Правильная расстановка.

Итог

Вот и всё! Теперь у Вас есть собственное красочное меню.