MORPHEUS-WEB

WEB IS MY LIFE… le blog de alterego.

Archive pour juillet 2010

[UDK] Début d’un projet

Vendredi 30 juillet 2010

Depuis quelques semaines une idée a germé dans ma tête, telecharger l’UDK et essayé de me mettre a l’épreuve en créant un petit jeu (a vrai dire les tutoriels de belzaran m’ont aussi donné envie).

C’est une sorte d’épreuve pour moi, a part avoir fait beaucoup de mapping sur l’unreal engine 2 (principalement pour Postal 2 d’ailleur) je n’ai aucune ou très peu de connaissance en unreal script, modelisation, animation … Ce sera pour moi l’occasion de prendre des compétences dans toute ces choses ! Je débute donc ici une suite d’articles qui ont pour but de vous relater mes pérégrinations de débutant dans le monde de l’UDK.

J’ai tout d’abord réfléchi au type de jeu que je voulais faire, j’ai choisi de faire un “side scrolling shooter”. Des images de R-type pleins la tête, j’ai téléchargé l’UDK et j’ai commencer mon projet. Pour une version gratuite de l’unreal engine, je trouve que l’UDK est bien fourni coté exemple et contenu, pas besoin de tout créer en partant de rien. Il y a aussi une communauté assez active et une documentation officiel qui semble plutôt fourni (tuto, exemples, guides …).

La première étape pour mon jeu sera de transformé le FPS de base en sidescroller. Via une petite recherche sur internet j’ai fini par trouver un bout de script appelé UT2D qui répondais en partie a mes besoins.

Code:
class NM_Game extends UTGame;

//Do not allow mutators
static function bool AllowMutator(string MutatorClassName)
{
	return false;
}

//DefaultProperties
defaultproperties
{
	//Naming
	MapPrefixes(0)="DM"

	//Game
	DefaultPawnClass=Class'NM.NMPawn'
	PlayerControllerClass=Class'NM.NM_PlayerController'
	BotClass=class'NM.NMBot'

	//Inventory
	DefaultInventory(0)=none
	DefaultInventory(1)=none
}
Code:
class NM_PlayerController extends UT2DController_Player
	dependson(UT2DController_Player);
Code:
//-----------------------------------------------------------
// UT2DController_Player
//
// Copyright 2008 Joe Bates
//
// This file is part of UT2D.
//
// UT2D is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// UT2D is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with UT2D.  If not, see <http://www.gnu.org/licenses/>.
//
// Last Modified: October 28, 2008
//-----------------------------------------------------------
class UT2DController_Player extends UTPlayerController;

//Camera Calculations
var vector LastViewLoc;
var vector CameraOffset;
var float LookingOffset;

//Aiming
var vector Look;
var float LookSensitivity, TurningThreshold;

//Simulating analog jump and crouch
var float AnalogJumpThreshold, AnalogDuckThreshold;
var bool bAnalogJumped;

//Ease slowly into crouch-looking
var float DuckTransitionTime;
var float BeganDucking;
var float StoppedDucking;

//Face the direction of newly spawned pawns
event Possess(Pawn inPawn, bool bVehicleTransition)
{
	Look = vector(inPawn.Rotation);
	super.Possess(inPawn, bVehicleTransition);
}

//Force third-person
function SetBehindView(bool bNewBehindView)
{
	super.SetBehindView(true);
}

//Third-person camera
simulated event GetPlayerViewPoint(out vector POVLocation, out Rotator POVRotation)
{
	local UT2DPawn P;
	local rotator rot;
	local vector vHeadOffset, vAimingOffset;
	local float scale;

	if(ViewTarget != none)
	{
		P = UT2DPawn(ViewTarget);
		if(!IsInState('Dead') && !IsInState('RoundEnded') && P != none && P.IsLocallyControlled())
		{
			if(IsInState('Spectating'))
			{
				rot = P.Rotation;
				rot.Pitch = P.RemoteViewPitch << 8;
				Look = vector(rot);
			}

			vHeadOffset.Z = P.HeadOffset;
			LastViewLoc = P.Location + vHeadOffset;

			scale = 1;
			if(abs(Look.X) < TurningThreshold)
				scale += abs(Look.X) / TurningThreshold - 1;

			if(Look.X < 0)
				scale *= -1;

			vAimingOffset.X = LookingOffset * Scale;

			//Ducking
			if(BeganDucking != 0)
				scale = fmin(WorldInfo.TimeSeconds - BeganDucking, DuckTransitionTime) / DuckTransitionTime;
			else
                                scale = 1 - fmin(WorldInfo.TimeSeconds - StoppedDucking, DuckTransitionTime) / DuckTransitionTime;

			vAimingOffset.Z += P.EyeHeight - P.default.EyeHeight + LookingOffset * Look.Z * scale;

			LastViewLoc.Y = 0;
			POVLocation = LastViewLoc + vAimingOffset + CameraOffset;
		}
		else
		{
			if(!ViewTarget.IsA('UTGib'))
				LastViewLoc = ViewTarget.Location;

			if(ViewTarget == self)
			{
				if(LastViewLoc.Y == 0)
				{
					LastViewLoc += CameraOffset * 2;
					SetLocation(LastViewLoc);
				}

				bCollideWorld = true;
				POVLocation = LastViewLoc;

				LastViewLoc -= CameraOffset * 2;
			}
			else
			{
				LastViewLoc.Y = 0;
				POVLocation = LastViewLoc + CameraOffset;
			}
		}
	}
	else
		POVLocation = LastViewLoc + CameraOffset;

	POVRotation = rotator(-CameraOffset);

	StopViewShaking();

	if(CameraEffect != none)
		CameraEffect.UpdateLocation(POVLocation, POVRotation, GetFOVAngle());
}

//GetAdjustedAimFor
function rotator GetAdjustedAimFor(Weapon W, vector StartFireLoc)
{
	return Rotation;
}

//UpdateRotation
function UpdateRotation(float DeltaTime)
{
	local rotator DeltaRot;

	//Forget look inversion
	if(PlayerInput.bInvertMouse)
        	PlayerInput.aLookup = -PlayerInput.aLookup;
        if(PlayerInput.bInvertTurn)
        	PlayerInput.aTurn = -PlayerInput.aTurn;

	//Fake out UpdateRotation to rotate as our aiming system expects
	Look.Z += PlayerInput.aLookup * LookSensitivity;
	Look.X += PlayerInput.aTurn * LookSensitivity;
	Look = normal(Look);
	DeltaRot = rotator(Look) - Rotation;

	PlayerInput.aLookUp = DeltaRot.Pitch;
	PlayerInput.aTurn = DeltaRot.Yaw;

	super.UpdateRotation(DeltaTime);
}

//PlayerWalking
state PlayerWalking
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		local byte bOrigDuck;

		//Remember the original duck
		bOrigDuck = bDuck;

		//Analog up to jump
		if(PlayerInput.aBaseY > AnalogJumpThreshold)
		{
			if(!bAnalogJumped)
				bPressedJump = true;
			bAnalogJumped = true;
		}
		else
			bAnalogJumped = false;

		//Analog down to duck
		if(PlayerInput.aBaseY < AnalogDuckThreshold)
			bDuck = 1;

		if(Pawn.Physics == PHYS_Walking && BeganDucking == 0 && bDuck == 1)
		{
			BeganDucking = WorldInfo.TimeSeconds;
			if(BeganDucking - StoppedDucking < DuckTransitionTime)
				BeganDucking -= DuckTransitionTime - (BeganDucking - StoppedDucking);
		}
		else if(BeganDucking != 0 && (bDuck == 0 || Pawn.Physics != PHYS_Walking))
		{
			StoppedDucking = WorldInfo.TimeSeconds;
			if(StoppedDucking - BeganDucking < DuckTransitionTime)
				StoppedDucking -= DuckTransitionTime - (StoppedDucking - BeganDucking);

			BeganDucking = 0;
		}

		PlayerInput.aForward = 0;

		//Move based on side-scrolling
		Pawn.SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);

		//Forget analog duck
		bDuck = bOrigDuck;
	}
}

//PlayerSwimming
state PlayerSwimming
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Analog up to jump
		if(PlayerInput.aBaseY > AnalogJumpThreshold)
		{
			if(!bAnalogJumped)
				bPressedJump = true;
			bAnalogJumped = true;
		}
		else
			bAnalogJumped = false;

		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
		SetRotation(Pawn.Rotation);
	}
}

//PlayerClimbing
state PlayerClimbing
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
		SetRotation(Pawn.Rotation);
	}
}

//PlayerFlying
state PlayerFlying
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);-
		SetRotation(Pawn.Rotation);
	}
}

//PlayerWaiting
auto state PlayerWaiting
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
	}
}

state WaitingForPawn
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
	}
}

state Dead
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
	}
}

state InQueue
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
	}
}

//unreliable server function ServerViewSelf()
//{
//	SetLocation(LastViewLoc + CameraOffset * 2);
//	SetViewTarget(self);
//}

state Spectating
{
	//PlayerMove
	function PlayerMove(float DeltaTime)
	{
		//Move based on side-scrolling
		PlayerInput.aUp += PlayerInput.aForward;
		PlayerInput.aForward = 0;

		SetRotation(rot(0, -16384, 0));
		super.PlayerMove(DeltaTime);
	}

	exec function StartAltFire(optional byte FireModeNum)
	{
		ServerViewSelf();
	}
}

//DefaultProperties
defaultproperties
{
	//Cheating
	//CheatClass=class'UT2DCheatManager'

	//Camera
	bBehindView=true;
	CameraOffset=(X=0,Y=400,Z=32)
	LookingOffset=300

	//Aiming
	Look=(X=1,Y=0,Z=0)
	LookSensitivity=0.0005
	TurningThreshold=0.85

	//Analog Simulation
	AnalogJumpThreshold=1000
	AnalogDuckThreshold=-1000

	//Ducking
	DuckTransitionTime=0.3
	BeganDucking=0
	StoppedDucking=-0.3
}
Code:
class NMPawn extends UT2DPawn
	dependson(UT2DPawn);

defaultproperties
{
	ControllerClass=class'NM.NMBot'
}
Code:
//-----------------------------------------------------------
// UT2DPawn
//
// Copyright 2008 Joe Bates
//
// This file is part of UT2D.
//
// UT2D is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// UT2D is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with UT2D.  If not, see <http://www.gnu.org/licenses/>.
//
// Last Modified: October 28, 2008
//-----------------------------------------------------------
class UT2DPawn extends UTPawn;

var rotator LastRotation;

var float LastTrans;

var float WallDodgeSpeed;
var float WallDodgeSpeedZ;

//Tick
simulated function Tick(float DeltaTime)
{
	local vector loc;
	local rotator rot;
	local int deltaPitch;
	local bool bTurned;

	super.Tick(DeltaTime);

	if(bBlockActors)
		SetCollision(bCollideActors, false);

	//Clamp to Y = 0.
	loc = Location;
	loc.Y = 0;
	SetLocation(loc);

	//Swimming/Flying Fix
	if(Physics == PHYS_Swimming || Physics == PHYS_Flying)
	{
		rot = Rotation;
		rot.Pitch = 0;
		SetRotation(rot);
	}

	//Bot Hacks
	if(UTBot(Controller) == none)
		return;

	//rot = rotator(Controller.FocalPoint - (Location + vect(0, 0, 1) * HeadOffset));
	rot = rotator(Location + vect(0, 0, 1) * HeadOffset);

	//Clamp to 2D
	if(abs(rot.Yaw) < 16384)
		rot.Yaw = 0;
	else
		rot.Yaw = 32768;

	deltaPitch = RotationRate.Pitch * DeltaTime * 3;

	//If it's looking at the side it wants to
	if(rot.Yaw == LastRotation.Yaw)
	{
		if(abs(rot.Pitch - LastRotation.Pitch) <= deltaPitch)
			LastRotation = rot; //Just look at an enemy if its close
		else if(rot.Pitch > LastRotation.Pitch)
			LastRotation.Pitch += deltaPitch; //Move up towards it
		else
			LastRotation.Pitch -= deltaPitch; //Move down towards it
	}
	else
	{
		if(rot.Pitch >= 0)
			LastRotation.Pitch += deltaPitch; //Move up towards it
		else
			LastRotation.Pitch -= deltaPitch; //Move down towards it
	}

	//Turn around
	if(LastRotation.Pitch > 16384)
	{
		LastRotation.Pitch = 32768 - LastRotation.Pitch;
		bTurned = true;
	}
	else if(LastRotation.Pitch < -16384)
	{
		LastRotation.Pitch = -32768 - LastRotation.Pitch;
		bTurned = true;
	}

	if(bTurned)
	{
		if(abs(LastRotation.Yaw) < 16384)
			LastRotation.Yaw = 32768;
		else
			LastRotation.Yaw = 0;
	}

	Controller.SetRotation(LastRotation);

	rot = LastRotation;
	SetRemoteViewPitch(rot.Pitch);
	rot.Pitch = 0;
	SetRotation(rot);
}

//Bot Hack
function bool NeedToTurn(vector Target)
{
	return false;
}

//Bounce into 2D
simulated event HitWall(vector HitNormal, Actor Wall, PrimitiveComponent WallComp)
{
	if(HitNormal.X != 0)
	{
		HitNormal.Y = 0;
		HitNormal = normal(HitNormal);
	}

	super.HitWall(HitNormal, Wall, WallComp);
}

//AddVelocity
function AddVelocity(vector NewVelocity, vector HitLocation, class<DamageType> damageType, optional TraceHitInfo HitInfo)
{
	local float magnitude;

	//Clamp to 2D.
	magnitude = vsize(NewVelocity);
	NewVelocity.Y = 0;
	NewVelocity = normal(NewVelocity) * magnitude;

	super.AddVelocity(NewVelocity, HitLocation, damageType, HitInfo);
}

//UpdateEyeHeight
event UpdateEyeHeight(float DeltaTime)
{
	if(!bIsCrouched)
		super.UpdateEyeHeight(DeltaTime);
}

//Dodge
function bool Dodge(eDoubleClickDir DoubleClickMove)
{
	local vector TraceStart, TraceEnd, Dir, Cross, HitLocation, HitNormal;
	local Actor HitActor;
	local bool bFalling, out;

	if(bIsCrouched || bWantsToCrouch || (Physics != PHYS_Walking && Physics != PHYS_Falling))
		return false;

	if(Physics == PHYS_Falling)
	{
		bFalling = true;

		if(DoubleClickMove == DCLICK_Left)
			TraceEnd = vect(1, 0, 0);
		else if(DoubleClickMove == DCLICK_Right)
			TraceEnd = vect(-1, 0, 0);

		TraceStart = Location - (CylinderComponent.CollisionHeight - 16) * vect(0, 0, 1) + TraceEnd * (CylinderComponent.CollisionRadius - 16);
		TraceEnd = TraceStart + TraceEnd * 40;
		HitActor = Trace(HitLocation, HitNormal, TraceEnd, TraceStart, false, vect(16, 16, 16));

		HitNormal.Y = 0;
		HitNormal = normal(HitNormal);

		if((HitActor == none) || (HitNormal.Z < -0.1))
			return false;
		if(!HitActor.bWorldGeometry)
		{
			if(!HitActor.bBlockActors)
				return false;
			if((Pawn(HitActor) != none) && (Vehicle(HitActor) == none))
				return false;
		}
	}

	if(DoubleClickMove == DCLICK_Left)
	{
		Dir = vect(-1, 0, 0);
		Cross = vect(0, 1, 0);
	}
	else if(DoubleClickMove == DCLICK_Right)
	{
		Dir = vect(1, 0, 0);
		Cross = vect(0, 1, 0);
	}

	if(bFalling)
	{
		Velocity = vect(0, 0, 0);
		Acceleration = vect(0, 0, 0);
	}

	out = PerformDodge(DoubleClickMove, Dir, Cross);

	return out;
}

//Jump out of water always.
function bool CheckWaterJump(out vector WallNormal)
{
	return true;
}

//Block living things.
simulated function SetPawnRBChannels(bool bRagdollMode)
{
	super.SetPawnRBChannels(bRagdollMode);
	Mesh.SetRBCollidesWithChannel(RBCC_Untitled2, Health > 0);
}

state FeigningDeath
{
	function AddVelocity(vector NewVelocity, vector HitLocation, class<DamageType> damageType, optional TraceHitInfo HitInfo);
}

//DefaultProperties
defaultproperties
{
	//ControllerClass=class'UT2D.UT2DBot'
	bCanStrafe=false

	//Jumping
	JumpZ=550
	MultiJumpBoost=2
	OutofWaterZ=550
	MaxJumpHeight=132
	MaxDoubleJumpHeight=260

	SightRadius=500
}
Code:
class NMBot extends UT2DBot
	dependson(UT2DBot);

defaultproperties
{

}
Code:
//-----------------------------------------------------------
// UT2DBot
//
// Copyright 2008 Joe Bates
//
// This file is part of UT2D.
//
// UT2D is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// UT2D is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with UT2D.  If not, see <http://www.gnu.org/licenses/>.
//
// Last Modified: October 28, 2008
//-----------------------------------------------------------
class UT2DBot extends UTBot;

//Should never strafe
function bool ShouldStrafeTo(Actor WayPoint)
{
	return false;
}

//Adjust towards, not around
//function bool AdjustAround(Pawn Other)
//{
//	//AdjustLoc = Other.Location;
//	AdjustPosition = Other.Location;
//	return true;
//}

//No need to clear paths when players don't collide
function ClearPathFor(Controller C)
{
}

//GetAdjustedAimFor
function rotator GetAdjustedAimFor(Weapon W, vector StartFireLoc)
{
	return Rotation;
}

//Make AI more aggressive
function bool FireWeaponAt(Actor A)
{
	Focus = A;
	return super.FireWeaponAt(A);
}
state Fallback
{
	function bool FireWeaponAt(Actor A)
	{
		Focus = A;
		return super.FireWeaponAt(A);
	}
}

//DefaultProperties
defaultproperties
{
}

Ce bout de code cependant agissait bizarrement, par exemple il forçait le joueur a spawner obligatoirement en coordonnée Y=0 … assez inhabituel. J’ai fini par trouver la ligne de code dans la fonction tick de UT2DPawn qui redéfinissais la position du joueur. Maintenant le joueur spawnais ou je voulais, mais la caméra restait définitivement bloqué au même endroit, ne tenant compte de la position du joueur que sur les axes X et Z … Après pas mal de tâtonnement j’ai fini par trouver la ligne de code qui me posait problème dans UT2DController_Player. Je m’etait focalisé sur la partie de code ou le viewTarget etait sur le joueur, alors qu’en fait le problème venais du très court laps de temps ou le joueur n’est pas encore dans la partie au tout début. J’était déca beaucoup plus près du rendu que je souhaite même si il me reste encore quelques problème a résoudre…

Une petite vidéo de ce que ça donne jusqu’a présent ?
YouTube Preview Image

Je suis content de voir que grâce a internet et un peu de connaissance j’ai réussi a pas mal avancer en seulement quelques soirées de travail. Mais a mon avis le plus dur reste a venir …

Les prochaines étapes de travail sur le script :

  • Mettre en place le défilement/déplacement du personnage vers la droite automatiquement
  • Mettre en place des boundary pour que le joueur ne puisse ce déplacer que dans les limites de l’écran
  • Mettre en place le fait que le joueur vole
  • Virer le viseur

Il me reste encore beaucoup a apprendre et de travail a faire avant d’arrivé a mon but (un petit side scrolling shooter de 2 ou 3 niveau). J’essayerais de vous faire part de mes avancées/découvertes assez régulièrement.