Создание простого
мутатора на примере Vampire
(Страницы:1, 2)
Подключаем конфигурационный файл
Иногда необходимо, чтобы ваши настройки для какого-либо мода сохранялись
при выходе из Unreal Tournament в конфигурационном файле. Существует
очень простой способ, позволяющий классам UnrealScript сохранять
и загружать данные, используя при этом INI-файлы. Это очень удобно,
так как вы можете конфигурировать ваш класс (например, менять значения
некоторых полей и свойств) без повторной компиляции. Такой механизм
обращения к INI-файлам реализуется с помощью ключевого слова Config,
которое и указывает, какую переменную и в каком файле сохранять.
Для подключения конфигурационного файла к нашему мутатору достаточно
добавить несколько дополнительных слов в файл Vampire.uc в разделе
описания класса и переменных:
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you
do to
// an enemy.
//===================================
class Vampire expands Mutator config(Vampire);
var bool Initialized;
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;
var() config float LifeStolenPerHit;
var() config int MaxLife;
var() config bool bInflictedDamageOnly;
var() config bool bIsRegenEffect;
Как видите, в строке описания класса добавилось слово config.
Это же слово появилось и в описании некоторых переменных.
Ключевое слово config в описании класса означает,
что значения по умолчанию полей данного класса будут сохраняться
и загружаться из INI-файла. Если после слова config,
которое стоит в строке определения класса вставлены скобки с каким-либо
именем в них, то значения полей будут сохраняться в INI-файл с таким
именем (в папке System), а если скобок нет, то данные будут автоматически
сохраняться в файле UnrealTournament.ini в секции с именем, состоящим
из названия вашего пакаджа и имени класса:
[<Имя пакаджа>.<Имя класса>]
Слово config, стоящее в описании переменной означает,
что данная переменная класса будет сохраняться в конфигурационном
файле.
Вот и всё, что необходимо было сделать.
Добавляем интерфейс
Что бы наш мутатор принял профессиональный вид и мог легко конфигурироваться,
можно ввести также поддержку интерфейса пользователя. UnrealScript
позволяет довольно быстро создавать окна настроек мутаторов и модов.
Эти окна настроек вызываются из меню Mod игры. Мы рассмотрим этот
процесс на примере небольшого окна с малым количеством элементов
управления.
Для того что бы окно корректно работало необходимо описать как минимум
3 класса:
- новая единица в меню Mod;
- фрейм окна;
- клиентская область окна.
Для этого создайте в папке Vampire три текстовых файла и назовите
их:
- VampireModMenuItem.uc
- VampireConfigWindow.uc
- VampireClientWindow.uc
Откройте эти файлы поочерёдно и вставьте в них следующие строки:
1. Текст файла VampireModMenuItem.uc:
//===================================
// VampireModMenuItem ; This is script for menu item
// "Vampire Configuration" in the Mod menu.
//===================================
class VampireModMenuItem expands UMenuModMenuItem;
function Execute()
{
MenuItem.Owner.Root.CreateWindow(class'
VampireConfigWindow',10,10,320,175);
}
Описание:
Этот класс описывает пункт меню, при нажатии на который будет вызвано
окно нашего мутатора. Функция Execute обрабатывает
нажатие на данный пункт и создаёт окно настроек с помощью функции
CreateWindow.
2. Текст файла VampireConfigWindow.uc:
//===================================
// VampireConfigWindow ; This is frame for
// Vampire's configuration window.
//===================================
class VampireConfigWindow expands UWindowFramedWindow;
function BeginPlay()
{
Super.BeginPlay();
WindowTitle = "Vampire Configuration";
ClientClass = class'Vampire.VampireClientWindow';
bSizable = False;
}
function Created()
{
Super.Created();
SetSize(320, 175);
WinLeft = (Root.WinWidth - WinWidth) / 2;
WinTop = (Root.WinHeight - WinHeight) / 2;
}
Описание:
Данный класс отвечает за создание окна (прямоугольной области с
заголовком). Тут устанавливается заголовок окна, его размеры, положение
на экране и т.д.
3. Текст файла VampireClientWindow.uc:
//===================================
// VampireClientWindow ; This is client part of
// Vampire's configuration window.
//===================================
class VampireClientWindow expands UWindowDialogClientWindow;
var UWindowSmallCloseButton CloseButton;
var UWindowEditControl Edit[2];
var UWindowCheckbox OverDamage_Check,RegenEffect_Check;
var string EditBoxString[2];
var string OverDamageCheckString;
var string RegenEffectCheckString;
var string EditBoxHelpString[2];
var string OverDamageCheckHelpString;
var string RegenEffectCheckHelpString;
Var UWindowLabelControl TitleLabel;
Var UWindowLabelControl PermissionsLabel;
Var UWindowLabelControl CopyrightLabel;
Var UWindowLabelControl SplitLabel;
// called after object has been created...add content
function Created()
{
local int i,j,X,Y,Width;
EditBoxString[0] = "Life stolen per hit (%)";
EditBoxString[1] = "Maximum amount of hit points (0 = No limit)";
OverDamageCheckString = "Only inflicted damage comes to life";
RegenEffectCheckString = "Show regeneration effect";
EditBoxHelpString[0] = "Life stolen per hit (%)";
EditBoxHelpString[1] = "Max life";
OverDamageCheckHelpString = "Only inflicted damage
comes to life (Damage <= Victim's life)";
RegenEffectCheckHelpString = "Show regeneration effect";
CloseButton = UWindowSmallCloseButton(CreateControl(class
'UWindowSmallCloseButton', WinWidth-50, WinHeight-18, 48, 16));
CloseButton.SetHelpText("Close config window
and save your current settings!");
X = 10;
Y = 10;
for(i=0;i<2;i++)
{
Edit[i] = UWindowEditControl(CreateControl(class
'UWindowEditControl', X,Y, 300, 1));
Edit[i].SetText(EditBoxString[i]);
Edit[i].SetHelpText(EditBoxHelpString[i]);
Edit[i].SetFont(F_Normal);
Edit[i].SetNumericOnly(True);
Edit[i].SetMaxLength(3);
Edit[i].Align = TA_Left;
Edit[i].EditBoxWidth = 40;
Y += 20;
}
Edit[0].SetValue(string(int(100*class'Vampire'
.Default.LifeStolenPerHit)));
Edit[1].SetValue(string(class'Vampire'.Default.MaxLife));
OverDamage_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bInflictedDamageOnly)
OverDamage_Check.bChecked = true; else OverDamage_Check.bChecked
= false;
OverDamage_Check.SetText(OverDamageCheckString);
OverDamage_Check.SetHelpText(OverDamageCheckHelpString);
OverDamage_Check.SetFont(F_Normal);
OverDamage_Check.Align = TA_Left;
Y += 20;
RegenEffect_Check = UWindowCheckbox
(CreateControl(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bIsRegenEffect)
RegenEffect_Check.bChecked = true; else
RegenEffect_Check.bChecked = false;
RegenEffect_Check.SetText(RegenEffectCheckString);
RegenEffect_Check.SetHelpText(RegenEffectCheckHelpString);
RegenEffect_Check.SetFont(F_Normal);
RegenEffect_Check.Align = TA_Left;
Y += 20;
SplitLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 10, Y, 300, 1));
SplitLabel.SetText("----------------------
-----------------------------------------------------");
SplitLabel.SetFont(F_Bold);
Y += 10;
TitleLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
TitleLabel.SetText("Vampire - Mutator V1.00");
TitleLabel.SetFont(F_Bold);
Y += 10;
PermissionsLabel = UWindowLabelControl
(CreateControl(class 'UWindowLabelControl', 20, Y, 300, 1));
PermissionsLabel.SetText("Free for noncommercial
use and distribution.");
Y += 10;
CopyrightLabel = UWindowLabelControl
(CreateControl(class 'UWindowLabelControl', 20, Y, 300, 1));
CopyrightLabel.SetText("Copyright 2002
by Your_Name 'Your_Nick' Your_SecondName.");
Y += 10;
Super.Created();
}
///////////////////////////////////////////////////
//when a control changes, Notify is called with the changed control
///////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
{
local int i;
super.Notify(C, E);
if(E==DE_Change)
for (i=0;i < ArrayCount(Edit);i++)
{
if(C==Edit[i])
ApplyEdit(i);
}
switch(E)
{
case DE_Change:
switch(C)
{
case CloseButton: Close(); break;
case OverDamage_Check: ApplyOverDamage(); break;
case RegenEffect_Check: ApplyRegenEffect(); break;
}
}
}
function ApplyOverDamage()
{
class'Vampire'.default.bInflictedDamageOnly =
OverDamage_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyRegenEffect()
{
class'Vampire'.default.bIsRegenEffect = RegenEffect_
Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyEdit(int i)
{
switch(i)
{
case 0: class'Vampire'.default.
LifeStolenPerHit = 0.01*float(int(Edit[i].GetValue())); break;
case 1: class'Vampire'.default.MaxLife = int
(Edit[i].GetValue()); break;
}
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
///////////////////////////////////////////////////
function Close(optional bool bByParent)
{
Super.Close(bByParent);
}
///////////////////////////////////////////////////
defaultproperties
{
}
Описание:
Этот класс определяет клиентскую область окна, то есть область,
на которой создаются все элементы управления. Также тут обрабатываются
все события, которые вызывает пользователь при работе с элементами
интерфейса. Сначала все элементы должны быть описаны подобно переменным.
В функции Created элементы интерфейса создаются
на клиентской области и инициализируются (устанавливаются координаты,
индивидуальные свойства и т.д.)
В функции Notify происходит обработка событий вызываемых
при нажатии на кнопку или изменении числа в поле ввода. На вход
в качестве параметров эта функция принимает имя элемента интерфейса
и число, которое определяет тип вызванного события. Также необходимо
что бы игра «знала» о новом пункте в меню Mod. Для этого открываем
наш Vampire.int файл и добавляем туда строчку, отвечающую за пункт
меню. После изменения файл должен выглядеть следующим образом:
[Public]
Object=(Name=Vampire.Vampire,Class=Class,MetaClass=
Engine.Mutator,Description="Vampire,You gain life equal to
the amount of damage you do to an enemy.")
Object=(Name=Vampire.VampireModMenuItem,Class=Class,
MetaClass=UMenu.UMenuModMenuItem,Description="Vampire Configuration")
Как видите была добавлена ещё одна строчка, которая начинается
так же как и первая со слова Object, но описывает
не мутатор, а пункт меню.
Конечный результат
В конце мы должны получить пять файлов в папке Vampire:
1. Vampire.uc
2. VampireShell.uc
3. VampireModMenuItem.uc
4. VampireConfigWindow.uc
5. VampireClientWindow.uc
Эти файлы должны иметь такой вид:
1.) Текст файла Vampire.uc
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you
do to
// an enemy.
//===================================
class Vampire expands Mutator config(Vampire);
var bool Initialized;
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;
var() config float LifeStolenPerHit;
var() config int MaxLife;
var() config bool bInflictedDamageOnly;
var() config bool bIsRegenEffect;
function PostBeginPlay()
{
if (Initialized)
return;
Initialized = True;
Level.Game.RegisterDamageMutator( Self );
}
function MutatorTakeDamage( out int ActualDamage,
Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn'))
&& (Victim != InstigatedBy) && (InstigatedBy !=
None) && (Victim != None))
{
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;
if (bIsRegenEffect && (MaxLife != 0 ))
{
FlashShell(InstigatedBy);
if ( InstigatedBy.IsA('PlayerPawn') )
PlayerPawn(InstigatedBy).ClientInstantFlash(InstFlash, InstFog);
}
if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
}
if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage,
Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}
function FlashShell(pawn Other)
{
if (VampireShellEffect == None)
{
VampireShellEffect = Spawn(ShellType, Other,,Other.Location, Other.Rotation);
}
if ( VampireShellEffect != None )
{
VampireShellEffect.Mesh = Other.Mesh;
VampireShellEffect.DrawScale = Other.Drawscale;
VampireShellEffect.Texture = ShellSkin;
}
}
defaultproperties
{
LifeStolenPerHit=1.000000
MaxLife=200
bInflictedDamageOnly=True
bIsRegenEffect=True
InstFog=(X=475.000000,Y=325.000000,Z=145.000000)
InstFlash=-0.400000
ShellSkin=FireTexture'UnrealShare.Belt_fx.ShieldBelt.RedShield'
ShellType=Class'Vampire.VampireShell'
}
2.) Текст файла VampireShell.uc
//===================================
// VampireShell ; This is effect script.
// Description: Red shell around damage instigator.
//===================================
class VampireShell expands Effects;
defaultproperties
{
bAnimByOwner=True
bOwnerNoSee=True
bNetTemporary=False
bTrailerSameRotation=True
Physics=PHYS_Trailer
RemoteRole=ROLE_SimulatedProxy
LODBias=0.500000
DrawType=DT_Mesh
Style=STY_Translucent
Texture=None
ScaleGlow=0.500000
AmbientGlow=64
Fatness=157
bUnlit=True
bMeshEnviroMap=True
LifeSpan=0.500000
}
3.) Текст файла VampireModMenuItem.uc
//===================================
// VampireModMenuItem ; This is script for menu item
// "Vampire Configuration" in the Mod menu.
//===================================
class VampireModMenuItem expands UMenuModMenuItem;
function Execute()
{
MenuItem.Owner.Root.CreateWindow(class
'VampireConfigWindow',10,10,320,175);
}
4.) Текст файла VampireConfigWindow.uc
//===================================
// VampireConfigWindow ; This is frame for
// Vampire's configuration window.
//===================================
class VampireConfigWindow expands UWindowFramedWindow;
function BeginPlay()
{
Super.BeginPlay();
WindowTitle = "Vampire Configuration";
ClientClass = class'Vampire.VampireClientWindow';
bSizable = False;
}
function Created()
{
Super.Created();
SetSize(320, 175);
WinLeft = (Root.WinWidth - WinWidth) / 2;
WinTop = (Root.WinHeight - WinHeight) / 2;
5.) Текст файла VampireClientWindow.uc
//===================================
// VampireClientWindow ; This is client part of
// Vampire's configuration window.
//===================================
class VampireClientWindow expands UWindowDialogClientWindow;
var UWindowSmallCloseButton CloseButton;
var UWindowEditControl Edit[2];
var UWindowCheckbox OverDamage_Check,RegenEffect_Check;
var string EditBoxString[2];
var string OverDamageCheckString;
var string RegenEffectCheckString;
var string EditBoxHelpString[2];
var string OverDamageCheckHelpString;
var string RegenEffectCheckHelpString;
Var UWindowLabelControl TitleLabel;
Var UWindowLabelControl PermissionsLabel;
Var UWindowLabelControl CopyrightLabel;
Var UWindowLabelControl SplitLabel;
// called after object has been created...add content
function Created()
{
local int i,j,X,Y,Width;
EditBoxString[0] = "Life stolen per hit (%)";
EditBoxString[1] = "Maximum amount of hit points (0 = No limit)";
OverDamageCheckString = "Only inflicted damage comes to life";
RegenEffectCheckString = "Show regeneration effect";
EditBoxHelpString[0] = "Life stolen per hit (%)";
EditBoxHelpString[1] = "Max life";
OverDamageCheckHelpString = "Only inflicted damage comes to
life
(Damage <= Victim's life)";
RegenEffectCheckHelpString = "Show regeneration effect";
CloseButton = UWindowSmallCloseButton
(CreateControl(class'UWindowSmallCloseButton',
WinWidth-50, WinHeight-18, 48, 16));
CloseButton.SetHelpText("Close config window
and save your current settings!");
X = 10;
Y = 10;
for(i=0;i<2;i++)
{
Edit[i] = UWindowEditControl(CreateControl
(class'UWindowEditControl', X,Y, 300, 1));
Edit[i].SetText(EditBoxString[i]);
Edit[i].SetHelpText(EditBoxHelpString[i]);
Edit[i].SetFont(F_Normal);
Edit[i].SetNumericOnly(True);
Edit[i].SetMaxLength(3);
Edit[i].Align = TA_Left;
Edit[i].EditBoxWidth = 40;
Y += 20;
}
Edit[0].SetValue(string(int(100*class'Vampire'
.Default.LifeStolenPerHit)));
Edit[1].SetValue(string(class'Vampire'.Default.MaxLife));
OverDamage_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bInflictedDamageOnly)
OverDamage_Check.bChecked = true; else
OverDamage_Check.bChecked = false;
OverDamage_Check.SetText(OverDamageCheckString);
OverDamage_Check.SetHelpText(OverDamageCheckHelpString);
OverDamage_Check.SetFont(F_Normal);
OverDamage_Check.Align = TA_Left;
Y += 20;
RegenEffect_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bIsRegenEffect)
RegenEffect_Check.bChecked = true;
else RegenEffect_Check.bChecked = false;
RegenEffect_Check.SetText(RegenEffectCheckString);
RegenEffect_Check.SetHelpText(RegenEffectCheckHelpString);
RegenEffect_Check.SetFont(F_Normal);
RegenEffect_Check.Align = TA_Left;
Y += 20;
SplitLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 10, Y, 300, 1));
SplitLabel.SetText
("--------------------------------------
-------------------------------------");
SplitLabel.SetFont(F_Bold);
Y += 10;
TitleLabel = UWindowLabelControl(CreateControl(class
'UWindowLabelControl', 20, Y, 300, 1));
TitleLabel.SetText("Vampire - Mutator V1.00");
TitleLabel.SetFont(F_Bold);
Y += 10;
PermissionsLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
PermissionsLabel.SetText("Free for noncommercial
use and distribution.");
Y += 10;
CopyrightLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
CopyrightLabel.SetText("Copyright 2002
by Your_Name 'Your_Nick' Your_SecondName.");
Y += 10;
Super.Created();
}
///////////////////////////////////////////////////
//when a control changes, Notify is called with the changed control
///////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
{
local int i;
super.Notify(C, E);
if(E==DE_Change)
for (i=0;i < ArrayCount(Edit);i++)
{
if(C==Edit[i])
ApplyEdit(i);
}
switch(E)
{
case DE_Change:
switch(C)
{
case CloseButton: Close(); break;
case OverDamage_Check: ApplyOverDamage(); break;
case RegenEffect_Check: ApplyRegenEffect(); break;
}
}
}
function ApplyOverDamage()
{
class'Vampire'.default.bInflictedDamageOnly =
OverDamage_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyRegenEffect()
{
class'Vampire'.default.bIsRegenEffect = RegenEffect_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyEdit(int i)
{
switch(i)
{
case 0: class'Vampire'.default.LifeStolenPerHit = 0.01*float
(int(Edit[i].GetValue())); break;
case 1: class'Vampire'.default.MaxLife = int(Edit[i].GetValue());
break;
}
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
///////////////////////////////////////////////////
function Close(optional bool bByParent)
{
Super.Close(bByParent);
}
///////////////////////////////////////////////////
defaultproperties
{
}
Также мы имеем файл Vampire.int в папке System, который должен
выглядеть так:
[Public]
Object=(Name=Vampire.Vampire,Class=Class,MetaClass=Engine.
Mutator,Description="Vampire,You gain life equal to the
amount of damage you do to an enemy.")
Object=(Name=Vampire.VampireModMenuItem,Class=Class,
MetaClass=UMenu.UMenuModMenuItem,Description="Vampire Configuration")
Вот и всё – осталось только скомпилировать исходный код и начать
играть.
Надеюсь вы узнали из этого гайда что-нибудь интересное для себя.
Если у вас возникнут какие-либо вопросы – присылайте письма на мой
E-Mail: Sand@ua.fm.
(Страницы:1, 2)
|