Chat Room

First Project Game


Mini-Game Demo:
http://www.mediafire.com/?fhn8iehmf78tx98
http://www.mediafire.com/?4ytu6z5rxxrs5t4
http://www.mediafire.com/?0033hblgfl7nd08

Source Code:
http://www.mediafire.com/?ybcznvtkzmjdxld





อธิบาย

StartInfo.uc คือโผล่มาจะเป็น การเริ่มเกมครับ แต่ยังไม่ได้กดอะไรนะครับ ในนี้นี้จะสังเกตุว่าผม

HUDType นี่เป็นการเรียก หรือเชื่อมต่อกับ HUD นะครับ โดยเชื่อต่อกับ Class StartGamePlay
เรามาดูใน StartGamePlay กัน


simulated function PostBeginPlay() {
  super.PostBeginPlay();
  HudMovie = new class'GFxStartGame';
  HudMovie.SetTimingMode(TM_Real);
  HudMovie.start();
}

ซึ่ง Method ที่ผมโชว์เป็นตัวเรียก นะครับ

HudMovie = new class'GFxStartGame'; // new class คือการเชื่อมต่อกับ Object นะครับ
HudMovie.SetTimingMode(TM_Real); // ทำงาน method SetTimingMode ใน Class GFxStartGame ซึ่ง
จริงแล้ว SetTimingMode ไม่มี ใน GFxStartGame  หรอกครับแต่ GFxStartGame  มัน สืบทอดมาอีกที
HudMovie.start(); // เริ่มการทำงานครับ จะเห็นว่า GFxStartGame มี parameter ทำไมไม่ใส่ จริงๆแล้วไม่ต้องใส่ครับ เพราะมันเป็นสิ่งที่บอกว่า จะใส่ก็ได้หรือไม่ใส่ก็ได้ optional ครับ

เรามาดู GFxStartGame กันดีกว่า


(ซัดดีคอนเจน สองเม็ด สมองรั่นป๋อแล้ว)


function StartGamePlay(){
Consolecommand("open Mario?Game=MyGame.TestGame");
}

อ่าสังเกตุบรรทัดนี้ครับ

ไปดู file flash ของผม จะตังเกตุว่า มันได้อ้างถึงชื่อ function นี้แสดงว่า ว่า Btm_play.onress เมื่อทำการ
กดลงไปนั่นเอง เมื่อทำการ กดลงไปให้ทำงานวิ่งมาหาใน UDK ครับแสดงว่าส่งค่าให้ script

มาทำงานใน UDK แล้ว แสดงว่ามันสั่งให้มารันที่ function StartGamePlay นั่นแหละ
เนื่องจาก ใน Object ตัวนี้สามารถใช้ ConsoleCommand ได้อ่า ผมก็ใช้ใน ส่วน command ในส่วนของ

บทที่ 1 ได้เลยนิครับ open เปิด แผนที่ ชื่อว่า mario?Game=Folder.Class info ที่จะต้องการรันนะครับ
ก็จะได้เทคนิคในการเปลี่ยน map ในส่วนของบทที่ 6 ที่ผมได้สอนไปครับ
อ่ะคราวนี้เรามาดูในส่วนของ TestGame



เมื่อมันเรียก Class TestGame แล้วมันจะไปไหนต่อ มะเรามาดูกันตรงที่

var int readyEndgame; ตัวนี้ดีกว่า ผมกำหนดใน Default ไว้ให้มันเป็น 0 นะครับ ให้เราลองเข้า

CreaditEndgame ดูครับ สังเกตุ บรรทัด


function Tick(float Time){
if(TestGame(worldinfo.game).readyEndgame == 1){
HudMovie = new class'GFxEndgame';
  HudMovie.SetTimingMode(TM_Real);
  HudMovie.start();
}
}
อ่า สังเกตุบรรทัดแดงๆครับ readyEndgame == 1 จึงจะให้เข้าไปทำงาน HudMovie ครับ
สาเหตุ function Tick มันจะทวนทำงานตลอด ครับมันจะคอยเป็นหน้าที่ ดัก buffer ข้อมูลครับ
ไม่เชื่อว่ามันทำงานตลอดให้เลง ประกาศ log มันออกไปแล้วไปดู console log ครับแล้วท่านจะเห็น
เอง

อ่าเรากลับมากันดีกว่า แล้วเราก็ไปต่อกันที่ MyPawn ดีกว่า

เรามาดู ที่ function กล้องก่อนดีกว่า


simulated function bool CalcCamera(float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV)
{
out_CamLoc = Location;
out_CamLoc.X = -600;
out_CamLoc.Z = 300;
out_CamRot.roll = 0;
out_CamRot.Pitch = 0;
out_CamRot.Yaw = 0;

if(mem == 0){
mem = 1;
savepoint_cam = out_camLoc;
}

if(out_camLoc.Y >= savepoint_cam.Y){

savepoint_cam.Y = out_camLoc.Y;
}

else

{
out_camLoc.Y = savepoint_cam.Y;
}
return true;
}

เนื่องจาก function นี้ได้รับ buffer ตลอดครับเลยทำให้ทำงานคอยตรวจสอบตลอดครับ


โผล่มาผมก็ lock องศากล้องคงที่เลยครับ


out_CamLoc = Location;
out_CamLoc.X = -600;
out_CamLoc.Z = 300;
out_CamRot.roll = 0;
out_CamRot.Pitch = 0;
out_CamRot.Yaw = 0;

เมื่อผมตั้งค่ากล้องอะไรเรียบร้อยแล้ว คือผมไม่อยากให้กล้องวิ่งกลับ
mem ตัวนี้ผมสร้างขึ้นมาเพื่อจดจำว่า จะให้ savepoint_cam จำค่ากล้องครั้งแรกเมื่อทำการเข้าเกม
if(out_camLoc.Y >= savepoint_cam.Y){ ก็คือ ถ้า ตัวแปลที่ผม savepoint ตัวนี้ทำการวิ่ง ถ้า ตัวแปลของกล้องตัวจริงๆ นี่วิ่งผ่านไปมากกว่า savepoint_cam.Y แนว Y ก็จะทำการ ให้ savepoint_cam.Y เนีย
จำว่า savepoint_cam.Y มีค่าเท่าไหร่ แต่ถ้ามันน้อยกว่า ก็คือ out_camLoc.Y จะมีค่าเท่ากับ savepoint_cam.Y และมันจะไม่ทำให้กล้องวิ่งกลับไปตาม Player ครับ

return true คือการยอมรับว่า เราจะใช้การตั้งค่าตัวนี้ครับ

เรามาดู บรรทัด PostBeginPlay กันต่อดีกว่า

var array<NavigationPoint> MonsterSpawnPoints;

สิ่งแรกถ้าใครเข้า map ผมจะสังเกตุว่าเห้ย มีแอบเปิ้ลด้วย จริงๆแล้วมันชื่อว่า path node ครับไว้คอย
ทำหน้าที่ ที่ว่าหาจุดให้ Bot เกิดครับ

ซึ่ง variable ข้างบนที่ผมตั้ง นั้นผมจะตั้งเป็น array ครับเนื่องจากว่า เพราะ pathnode มันมีแค่ ตัวแปลเดียว
แต่มันมีหลายๆ ตัวอยู่ในตัวแปลเดียว

ผมก็เลย ใช้ Foreach


simulated function postBeginPlay(){
local PathNode PS;
super.PostBeginPlay();
bCanDoubleJump=true;


foreach WorldInfo.AllNavigationPoints(class'PathNode', PS)
{

MonsterSpawnPoints.addItem(PS);

}

}

AllNavigationPoints ก็ึิืคือหา class ที่เป็น NavigationPoints ที่มัน Extend มานั่นก็คือ PathNode ครับ
ยัดเข้าไปในตัวแปล PS ครับซึ่งผมได้สร้างไว้รอแล้ว

โดยการนำ หน้าที่ MonsterSpawnPoints มารับ

มันมี สองวิธีครับ โดยการใช้ i++ แต่ในที่นี้ ผมเลือกใช้ addItem เนื่องจากว่า มันสดวกและง่ายครับ

และมันยังสามารถ เรียงลำดับ array ให้เหมือน การนั่งนับ array ต้นแบบได้อีกด้วย

ข้อเสียคือ มันนับข้ามไม่ได้ครับ จะนับก็คือ 0-อินฟรี ไปเลยครับ

ซึ่งนับมาเก็บไว้ ครับสามารถ ตรวจสอบ โดยการ for i++ ก็ได้ครับโดยการใช้วิธี บทที่ 1.2.1 ครับ

ในที่นี้เราจะประกาศ ในรูปแบบ ของ log สามารถ ย้อนกลับไปดูใน content ใน Editor เลือก Tab scense ครับ

แล้วจะสังเกตุว่า ตรงช่อง Actor ครับ ให้เลื่อนลงมาเลื่อยๆครับ จนกว่าจะเจอ Pathnode ที่ผมสร้างไว้ นั่นแสดงว่า สามารถ กดเข้าไปดูได้ด้วยนะครับ ว่า มันอยู่ ตำแหน่งตรงไหน ครับ
ส่วนถ้าจะสร้างใหม่ให้ท่านทำการ คลิกขวา แล้วเลือก Add actor จะเจอกับ Pathnode เอง

อันนี้ คือ Basic สำหรับการ วางจุด ให้ การ Pawn ครับส่วน

เมื่อได้จุดแล้ว

มาดู Event Tick ครับ

เรามาดูสองตัวอย่างกันดีกว่า


if(bot == 0 && vsize(MonsterSpawnPoints[1].location - location) < 560){
bots = spawn(class'BotController');
bots.pawn = spawn(class'Snake',,,MonsterSpawnPoints[1].location,);
bots.Possess(bots.pawn,false);
bot++;
}

เรามาทำความเข้าใจกับ Vsize กันก่อน vsize เป็นการ กำหนดระยะทาง vector ครับ สามารถ เอามาลบกัน
ทำให้เกิดผล การใกล้การ ไกลได้ ตัวนี้อย่าพึ่งสนใจ เรามาสนใจตรง

bot == 0 นี่คือ bot ตัวที่ 1 ครับ ถ้า bot == 0 แน่นอนว่า ไม่ได้ตั้งค่า ให้มัน มันก็ 0อยู่แล้ว
ดังนั้น จะต้อง vsize(MonsterSpawnPoints[1].location - location) << หมายถึง ระยะทางของ point ที่ monster จะเกิด กับระยะทางที่ Player เดินเข้าไป จะต้อง < น้อยกว่า 560 ถึงจะเข้าไปทำการ

เรียก bot ให้เกิด โดยจะต้องสร้างวิญญาณให้มันก่อน นั่นก็คือ Controller เอามารองรับ
พอได้วิญญาณ มันเสร็จ ให้ทำการ ตั้ง Pawn ในบรรทัด

bots.pawn = spawn(class'Snake',,,MonsterSpawnPoints[1].location,);

สังเกตุ MonsterSpawnPoints[1].location อันนี้ผมกำหนด ตายตัวไว้เลย บอทตัวนี้จะต้องเกิด ใน pathnode array ที่ 1 ครับ ซึ่งใน pathnode array ที่หนึ่งที่โดนยัดมาเนีย จะไม่จำเป็นต้อง มีค่าตรงกับ ใน scene ครับ
เราจะรู้ด้วยการประกาศ log ออกมาแล้วไปเทียบใน Editor อีกที ถ้าเราเลือก ตำแหน่ง ใน scene

ที่ pathnode_1 แต่ array ที่เรายัดมาดันไปอยู่ array ที่ 2-3 ก็มี ดังนั้นควรตรวจสอบก่อนจะเอาลงครับ
bots.Possess(bots.pawn,false); ก็ลองไปเปิดใน BotController ในส่วนนี้จะเป็นการทำงาน ในส่วนให้บอท

ประมาณว่า ให้สิงค์ วิญญาณ ของ bot เข้ากับ ตัว บอทแล้วครับ แล้ว ก็ bot ++ ก็คือ ให้ ตัวแปร bot มีค่า
เท่ากับ 1 ครับนั่นก็หมายความว่า มันจะไป รอที่ ระยะทางของ Vsize อีกแล้ว อ่า

จุดที่สองที่แตกต่างก็คือ


bots.range = 300; // ระยะทาง
bots.pointera = MonsterSpawnPoints[0].location; // จุดกึ่งกลาง ที่จะต้องวิ่งกลับมา

ในที่นี้ผมสร้างไว้ก็คือ วิ่ง ไปข้างหน้า 300 จุดกึ่งกลาง คือ bots.pointera ตัวนี้ผมสร้างขึ้นโดยผมต้อง
การให้บอทรู้ ให้มันวิ่งกลับมา ข้างหลังไม่งั้น วิ่งทะลุหลัก หมื่นเป็นแน่ไม่ยอมวิ่งกลับ อิอิ

โดย จะใช้ทั้งสอง ตัวนี้มาลบกันแล้วมาหา ค่าว่า มันมากกว่า Range ที่กำหนดไว้หรือไม่

อ่าเรามาดูที่ Bot Controller กันดีกว่า



Class BotController extends UDKBot;

var Pawn MoveFollow;
var vector movenext;
var vector pointera;
var float range;

simulated function PostBeginPlay()
{
local PathNode path; // ไม่เกี่ยว
local int i;// ไม่เกี่ยว

}

event Possess(Pawn iP,bool bV){
super.Possess(iP,bV);
Pawn.setMovementPhysics();
if(Pawn == Snake(Pawn)){ // ถ้าเป็น class Snake ให้เข้าไปทำงาน ข้างใน
movenext = Snake(Pawn).Location; // รับค่าพื้นที่ ที่จุด snake อยู่ จะรับแค่ครั้งเดียวเท่านั้น
gotostate('Snake'); // วิ่งไป state ของ Snake
}
if(Pawn == turtle(Pawn)){ // ถ้าเป็น class turtle ถึงจะให้ไปทำงานข้างใน
movenext = turtle(Pawn).Location; // เหมือนข้างบนเลย
Pawn.GroundSpeed = 400; // กำหนดความเร็ว
gotostate('turtle'); // วิ่งไป state ของ turtle
}

if(Pawn == flying(Pawn)){ // ไม่เกี่ยว

//Pawn.bCanJump = true; // ไม่เกี่ยว
//pawn.SetPhysics(PHYS_Falling); // ไม่เกี่ยว

} // ไม่เกี่ยว

}

state Snake{
begin:
movenext.Y -= 20; // ให้วิ่ง ไปทาง ซ้าย ทีละ 20
MoveTo(movenext,,0); // กำหนด function ในการวิ่ง
goto('begin'); // วิ่งไปเลื่อยๆ ก็หมายถึง ให้กลับมา begin ต่อ
}

state turtle{

begin:

movenext.Y = pointera.Y - range; // เอาค่า จุดกึ่งกลาง มาลบ กับระยะทาง ก็จะได้ออกมา นี่ไปด้านซ้าย
MoveTo(movenext,,0); // กำหนดให้ วิ่งโดยเอาค่าข้างบนมาใช้ จะวิ่งให้ถึงจุดก่อนถึงจะทำ งานบรรทัดต่อไปได้
movenext.Y = pointera.Y + range; // เหมือนด้านบน แต่อันนี้ไปด้านขวาแทน
MoveTo(movenext,,0);
goto('begin'); // เริ่ม begin ใหม่อีกรอบ

}

event Tick(float Time){
if(Pawn == flying(Pawn)){ // ตรวจสอบว่า เป็น class Flying หรือเปล่า
if(Pawn.Location.Z <= -50){ // เมื่อ ตัวละคร ตกไปอยู่ ต่ำกว่า -50
Pawn.JumpZ = 900; // ความแรงในการโดด
Pawn.Velocity.Z = 1000; // แรงโน้มถ่วง Z ประมาณว่า ผลัก ขึ้นมา 1000
}
}
}

DefaultProperties{
}


กลับมาที่ Pawn เรามาดู Do jump เป็นขั้นตอนสุดท้ายแล้ว

นั่นก็หมายความว่า กระโดด เหยียบหัว ถึงจะเด้ง

Dojump เป็นค่า Native อยู่แล้วจะไม่เหมือน Bot ซึ่ง bot สามารถ กำหนด แรงโน้มถ่วงได้เลย

แต่ Player จะมีตัว Dojump เจ้าปัญหา คอยดักเวลากระโดดอยู่ ฉะนั้นเราจะต้องมาแก้ไขใน DoJump

อีกรอบ


function bool DoJump( bool bUpdating )
{
// This extra jump allows a jumping or dodging pawn to jump again mid-air
// (via thrusters). The pawn must be within +/- DoubleJumpThreshold velocity units of the
// apex of the jump to do this special move.
if(MultiJumpRemaining > 1){
if ( PlayerController(Controller) != None )
PlayerController(Controller).bDoubleJump = true;
DoDoubleJump(bUpdating);
MultiJumpRemaining -= 1;

//*************** ตรงนี้ ผมไปดึงมาจาก UTPawn นั่นเป็นการกระโดด ธรรมดาๆ เฉยๆนะครับ *******//

if ( Physics == PHYS_Spider )
Velocity = JumpZ * Floor;
else if ( Physics == PHYS_Ladder )
Velocity.Z = 0;
else if ( bIsWalking )
Velocity.Z = Default.JumpZ;
else
Velocity.Z = JumpZ;
if (Base != None && !Base.bWorldGeometry && Base.Velocity.Z > 0.f)
{
if ( (WorldInfo.WorldGravityZ != WorldInfo.DefaultGravityZ) && (GetGravityZ() == WorldInfo.WorldGravityZ) )
{
Velocity.Z += Base.Velocity.Z * sqrt(GetGravityZ()/WorldInfo.DefaultGravityZ);
}
else
{
Velocity.Z += Base.Velocity.Z;
}
}
SetPhysics(PHYS_Falling);
bReadyToDoubleJump = true;
return true;
}else{
super.DoJump(bUpdating);
return true;
}

}

เราไปดู class Input

ซึ่งใน Class Input นั้นที่ผมเขียน จะเน้นเป็นการ ตรวจสอบพื้นที่ ในการขยับมากกว่า

คือถ้าวิ่งต่ำกว่าที่กำหนด คือจะวิ่งถอยหลังไม่ได้แล้ว






1 ความคิดเห็น:

Jukkrit กล่าวว่า...

น่าสนใจมากเลยครับ