Wednesday, October 3, 2012

CH7.UnrealScript-Polish the Game&Script Usage

1.Enemy
2.Weapon
3.Set Timer
4.Imporve Enemy
5.Script Usage(event,out,local,exec)
 
A. Enemy :
敵人在遊戲結束後還會移動,在一般遊戲裡這應該是不正常的,因此修改FunnyGameInfo.uc的ScoreObjective:
function ScoreObjective(PlayerReplicationInfo playerScore,int score)
{
local i;
EnemiesLeft--;
super.ScoreObjective(playerScore,score);
if(EnemiesLeft==0)
{
for(i=0;i<enemySpawnPoint[i].length;i++)
enemySpawnPoint[i].FreezeEnemy();
}
}
為敵人增加Freeze條件,當達成遊戲目標則發生,新增一些變數和SimpleEnemy在EnemySpawnPoint.uc:
宣告變數:
var SimpleEnemy simpleEnemy;
改變SpawnEnemy() function:
simpleEnemy=spawn(class'SimpleEnemy',self,,Location);
增加FreezeEnemy() function:
function FreezeEnemy()
{
if(simpleEnemy != none)
simpleEnemy.Freeze();
}
當呼叫到FreezeEnemy()時,使用SimpleEnemy 的行為停止,所以要進SimpleEnemy.uc修改:
宣告變數:
var bool bFreeze;
新增Freeze() function:
function freeze()
{
bFreeze=true;
}
接著就是修改敵人甚麼時候需要Freeze 狀態,更改Tick() function:
function Tick(float DeltaTime)
{
local CustomTopDownController player;
local vector NewLocation;
if(PCEnemy == none)
{
foreach LocalPlayerControllers(class'CustomTopDownController',player)
{
if(Player.Pawn != none)
{
PCEnemy= Player.Pawn;
`log("Enemy Target is :" @ PCEnemy);
}
}
}
else if(!bFreeze && VSize(Location - PCEnemy.Location) < ChaseDistance)
{
if(VSize(Location-PCEnemy.Location) < AttackDistance)
{
PCEnemy.Bump(self,CollisionComponent,vect(0,0,0));
}
else
{
NewLocation=Location;
NewLocation +=(PCEnemy.Location - Location) * DeltaTime;
SetLocation(NewLocation);
}
}
}
Compiler後執行,當結束後敵人動作即停止,運用這概念可以使用在當達成目標後,敵人應該自行Destroy、或自行跑走撤退等判斷物件的狀態。
 
B. Weapon :
武器不只可以做攻擊速率的改變,也可以進行爆破半徑的傷害或傷害值的變化,新增在CustomRifleWeapon.uc:
simulated function Projectile ProjectileFire()
{
local Projectile myProj;
myProj=super.ProjectileFire();
return myProj;
}
myProj是一個短暫使用的變數,這裡用local是剛剛好的,在這裡可以進行Proj的範圍和傷害修改(Proj即是武器攻擊時所產生出來的武器彈道等):
simulated function Projectile ProjectileFire()
{
local Projectile myProj;
myProj=super.ProjectileFire();
myProj.DamageRadius=10000;
myProj.Damage=1000;
return myProj;
}
當武器爆發後,玩家即死亡,因為傷害和傷害範圍波及到自身,這可利用前面判斷當玩家拿到幾個Weapon Upgrade Actor時,在做傷害和傷害範圍修改。
 
C. Seting Timer:
將遊戲改成固定秒數產生一個新的Enemy,可以使用UDK裡面的SetTimer(),現在先設定成10秒就會產生一個新的敵人,先修改EnemySpawnPoint.uc並增加:
function TimedEnemySpawn()
{
SetTimer(10,false,'SpawnEnemy');
}
第一個參數是呼叫的間隔時間,第二個是一個optional bool 主要是設定是否連續跑回圈,第三個是選擇哪個function name要被執行。
現在設定成當一個敵人生產出來的時候,其他敵人不應該生產出來除非玩家已經把剛剛生產出來的敵人殺掉了,更改SpanwEnemy():
function SpawnEnemy()
{
if(simpleEnemy == none)
simpleEnemy=spawn(class'SimpleEnemy',self,,Location);
}
這樣就是當沒有敵人等於none時才生產出一個敵人。
現在必須要指定誰去呼叫TimeEnemySpawn(),因此要修改FunnyGameInfo.uc裡的ActiveSpawner();
現在Compiler後,在遊戲開始10秒後開始產生敵人,但每個Spawner actor只會產生一個。
使用亂數讓敵人出現規律多點變化,FRand()他是一個float:
function TimedEnemySpawn()
{
SetTimer(5.0+FRand()* 5.0,false,'SpawnEnemy');
}
這樣遊戲開始後,敵人生出來的時間會介於5~10sec。
更改EnemyDied():
function EnemyDied()
{
TimedEnemySpawn();
}
接著在修改FreezeEnemy():
function FreezeEnemy()
{
if(simpleEnemy != none)
simpleEnemy.Freeze();
ClearTimer('SpawnEnemy');
}
這樣SetTimer基本上就完成了,除了SetTimer還有ClearAllTimers,PauseTimer,GetRemainingTimeForTimer可以使用UnCodeX查Actor.uc。
 
D.Imporve Enemy:
之前的設計是敵人會自行生產出來,不會突然生產出來,現在來做點變化
將SimpleEnemy.uc的:
else if(!bFreeze && VSize(Location - PCEnemy.Location) < ChaseDistance)
改成
else if(!bFreeze)
這樣敵人就會一直朝向玩家移動,現在宣告常數:
var float MoveMentSpeed;
並給予初始值:
MovementSpeed=256.0
再來要改善敵人當近玩家時會突然慢下來,因為玩家的移動量變小:
NewLocation=Location;
NewLocation += normal(PCEnemy.Location - Location) *MovementSpeed* DeltaTime;
SetLocation(NewLocation);
經過這些修改目前Tick() function:
function Tick(float DeltaTime)
{
local CustomTopDownController player;
local vector NewLocation;

if(PCEnemy == none)
{
foreach LocalPlayerControllers(class'CustomTopDownController',player)
{
if(Player.Pawn != none)
{
PCEnemy= Player.Pawn;
`log("Enemy Target is :" @ PCEnemy);
}
}
}
else if(!bFreeze && VSize(Location - PCEnemy.Location) < ChaseDistance)
{
if(VSize(Location-PCEnemy.Location) < AttackDistance)
{
PCEnemy.Bump(self,CollisionComponent,vect(0,0,0));
}
else
{
NewLocation=Location;
NewLocation += normal(PCEnemy.Location - Location) *MovementSpeed* DeltaTime;
SetLocation(NewLocation);
}
}
}
現在敵人會以一定得速度追擊直到到達了攻擊範圍才停止。
之前使用SetTimer可以讓敵人以一定的時間內不斷出現,可用於Tower Defend或meta game不斷出身的小兵等,現在將SetTime移除掉,使敵人在距離於一定範圍內才產生敵人,如一般動作遊戲到達某個區域才有敵人,或是到達一定距離後才不斷產生怪物,這裡先spawn actor距離玩家一定距離才出現敵人,移除EnemySpawnPoint.uc裡有關Timer的程式碼:
class EnemySpawnPoint extends AlexGameActor
placeable;
var SimpleEnemy simpleEnemy;
function SpawnEnemy()
{
if(simpleEnemy == none)
simpleEnemy=spawn(class'SimpleEnemy',self,,Location);
}
function EnemyDied()
{
SpawnEnemy();
}
function FreezeEnemy()
{
if(simpleEnemy != none)
simpleEnemy.Freeze();
}
DefaultProperties
{
Begin Object Class=SpriteComponent Name=mySprite
Sprite=Texture2D'EditorResources.S_NavP'
HiddenGame=true;
End Object
Components.Add(mySprite)
}
把FunnyGameInfo.uc的ActiveSpawner移除:
function ActiveSpawner()
{
local int i;
for(i=0; i< enemySpawnPoint.length;i++)
enemySpawnPoint[i].SpawnEnemy();
}
Compiler後應該都還不會有錯誤。
在FunnyGameInfo.uc宣告變數:
var float minSpawnerDistance,maxSpawnerDistance;
給予起始值:
minSpawnerDistance=1700.0
maxSpawnerDistance=3000.0
改變ActiveSpawner() function:
function ActiveSpawner()
{
local int i;
local array<EnemySpawnPoint> inRangeSpawners;
local CustomTopDownController player;
foreach LocalPlayerControllers(class'CustomTopDownController',player)
break;
if(player.Pawn==none)
{
SetTimer(1.0,false,'ActiveSpawner');
return;
}
}
其中foreach會尋找整個Level裡面的playerController找到後就會break,再去判斷player是否有Pawn有的話就啟動SetTimer,繼續增加:
for(i=0;i < enemySpawnPoint.length;i++)
{
if(VSize(player.Pawn.Location - enemySpawnPoint[i].Location) > minSpawnerDistance && VSize(player.Pawn.Location - enemySpawnPoint[i].Location) < maxSpawnerDistance)
{
if(enemySpawnPoint[i].CanSpawnEnemy())
inRangeSpawners[inRangeSpawners.length] = enemySpawnPoint[i];
}
}
接著要在EnemySpawnPoint.uc定義CanSpawnEnemy() function:
function bool CanSpawnEnemy()
{
return simple==none;
}
這能判斷是否有沒有enemySpawnPoint。
再增加:
if(inRangeSpawners.length==0)
{
`log("No Player was here with Range");
SetTimer(1.0,false,'ActiveSpawner');
return;
}
如果spawners在arry裡,則隨意在一個spawner point生產一個enemy:
inRangeSpawners[Rand(inRangeSpawners.length)].SpawnEnemy();
最後再加上生成的亂數變數:
SetTimer(1.0+FRand() * 3.0,false,'ActiveSpawner');
整個 ActiveSpawner() function:
function ActiveSpawner()
{
local int i;
local array<EnemySpawnPoint> inRangeSpawners;
local CustomTopDownController player;

foreach LocalPlayerControllers(class'CustomTopDownController',player)
break;
if(player.Pawn==none)
{
SetTimer(1.0,false,'ActiveSpawner');
return;
}

for(i=0;i < enemySpawnPoint.length;i++)
{
if(VSize(player.Pawn.Location - enemySpawnPoint[i].Location) > minSpawnerDistance && VSize(player.Pawn.Location - enemySpawnPoint[i].Location) < maxSpawnerDistance)
{
if(enemySpawnPoint[i].CanSpawnEnemy())
inRangeSpawners[inRangeSpawners.length] = enemySpawnPoint[i];
}
}

if(inRangeSpawners.length==0)
{
`log("No Player was here with Range");
SetTimer(1.0,false,'ActiveSpawner');
return;
}
inRangeSpawners[Rand(inRangeSpawners.length)].SpawnEnemy();
SetTimer(1.0+FRand() * 3.0,false,'ActiveSpawner');
}
接著再改變 PostBeginPlayer() function的最後一行:
SetTimer(1.0,FRand() * 3.0,false,'ActiveSpawner');
改變ScoreObjective() function :
function ScoreObjective(PlayerReplicationInfo playerScore,int score)
{
local int i;
EnemiesLeft--;
super.ScoreObjective(playerScore,score);
if(EnemiesLeft==0)
{
for(i=0;i<enemySpawnPoint.length;i++)
enemySpawnPoint[i].FreezeEnemy();
ClearTimer('ActiveSpawner');
}
}
E. GetRemainingTimeForTimer:
顯示敵人的時間
FunnyGameInfo.uc,宣告:
var bool bFirstEnemySpawned;
ActiveSpawner() function:
function ActiveSpawner()
{
local int i;
local array<EnemySpawnPoint> inRangeSpawners;
local CustomTopDownController player;
bFirstEnemySpawned=true;

}
在CustomHUD.uc裡的DrawHUD() function:
if(FunnyGameInfo(WorldInfo.Game) != none)
{
Canvas.SetPos(Canvas.ClipX *0.1,Canvas.ClipY *0.25);
Canvas.DrawText("Time Left To First Pawn:" @ FunnyGameInfo(WorldInfo.Game).GetRemainingTimeForTimer('ActiveSpawner'));
}
 
F. Script Usage:
Local:
前面已使用過local int i;在UScript裡相當於區域變數,var int i;則是全域變數,且var int i;不能使用在function如:
simulated function PostBeginPlay()
{
var EnemySpawnPoint ESPs;//Error
super.PostBeginPlay();
}
且如果使用local 宣告,不能被其他Function:
simulated function PostBeginPlay()
{
Local int myInt;
super.PostBeginPlay();
}
function Tick()
{
`log(“It must have error”@myInt);//Error
}
還有一種情況,當宣告變數後,在local後宣告一樣的名字,則將會有Warning:
Var int myInt;
simulated function PostBeginPlay()
{
Local int myInt;
super.PostBeginPlay();
}
另一種使用情況,當把 Actor當作是local :
var array<EnemySpawnPoint> enemySpawnPoint;
simulated function PostBeginPlay()
{
local EnemySpawnPoint ESPs;
super.PostBeginPlay();

foreach DynamicActors(class'EnemySpawnPoint',ESPs)
enemySpawnPoint[enemySpawnPoint.length]=ESPs;

}
Local的ESPs只是被當作一個中間過程使用到的,讀出Actor(EnemySpawnPoin)指定到ESPs後,馬上給予到enemySpawnPoint陣列裡。
Function Modifiers:
Function 在前面有用到 simulated 或event .等。
Override:
在這裡很多Script有用到PostBeginPlay()假如將它宣告成:
function PostBeginPlay(int i)
{
}
則會發生一定發生Error 因為當作override 時,它沒有parameter時,不能再子類別去新增parameter,如果再override Tick()時:
function Tick(float DeltaTime)
{
}
一定會需要一個parameter,在override時以定義好,當然也可以:
function Tick(float youlike)
{
`log(youlike);
}
optional:
overLoading在UScript裡跟一般程式語言不太一樣,必須使用”Optional”這關鍵字:
function DoSomeThing(float K,optional bool I)
{
...
}
當呼叫Function時:
DoSomeThing(100,true);
DoSomeThing(100);
都能Compiler成功,之前程式碼:
function SpawnEnemy()
{
simpleEnemy=spawn(class'SimpleEnemy',self,,Location);
}
Spawn的parameters很多。
out:
可以去修改變數和把變數傳回去:
function DoSomething(out float myfloat,out int myInt)
{
myfloat=5.0;
myInt=10;
}
function PostBeginPlay()
{
local float myfloat;
local int myInt;
super.PostBeginPlay();
DoSomething(myfloat,myInt)
`log(myfloat @ myInt);
}
顯示log會是 ScriptLog: 5.000 10,out會把function內的值傳出來,若改成
function DoSomething( float myfloat,int myInt)
{
myfloat=5.0;
myInt=10;
}
則結果將是ScriptLog: 0.000 0。
Return:
方式跟一般程式語言一樣。
Event:
它通常跟Engine相關的,當override像Touch Bump 這些時會加event,因為Engine已經有Event,通常自行定義的Function是不會用到event,使用到它基本上都跟Engine原生碼有關,自行再用時可以忽略掉event如PostBeginPlay原本是:
Event PostBeginPlay()
{..}

Function PostBeginPlay()
{..}
是一樣都可以用。
Simulated,Server,Client,Reliable,unReliable:
這些都跟network有關,在製作多人遊戲這些關鍵字就必須要注意,除非有些Compiler會提醒使用simulated的function就需要增加,但基本上只要知道這些事跟network有關即可。
Singular:
這主要是防止function的無限迴圈,不過只要注意到自己程式碼的執行流程通常都不會用到這個
singular function DoSomething()
{
Next();
}
Function Next()
{
DoOther();
DoSomething();
}
在這DoSomething將不會做任何事。
Exec:
這通常剛玩家有關,玩家的input都會使用到像是jumping、crouching、firing,PlayerController和自己繼承的CustomTopDownPlayerController當偵測玩家輸入時要做的function會加exec,關於Input的Key都位於C:\UDK\UDK-2012-07\UDKGame\Config\DefaultInput.ini。
 
File:CH7.7z

No comments:

Post a Comment