ДОМАШНЯЯ | ФАЙЛЫ | СТАТЬИ | ГОСТЕВАЯ | АВТОРЫ

Создание простого мутатора на примере 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)

/> О всех ошибках, найденных в статье, прошу сообщать мне по почте.
Hosted by uCoz