/** * A game viewport (FViewport) is a high-level abstract interface for the * platform specific rendering, audio, and input subsystems. * GameViewportClient is the engine's interface to a game viewport. * Exactly one GameViewportClient is created for each instance of the game. The * only case (so far) where you might have a single instance of Engine, but * multiple instances of the game (and thus multiple GameViewportClients) is when * you have more than one PIE window running. * * Responsibilities: * propagating input events to the global interactions list * * * * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class GameViewportClient extends ScriptViewportClient within Engine transient native Inherits(FExec) config(Engine) ; /** The platform-specific viewport which this viewport client is attached to. */ var const pointer Viewport{FViewport}; /** The platform-specific viewport frame which this viewport is contained by. */ var const pointer ViewportFrame{FViewportFrame}; /** A list of interactions which have a chance at all input before the player's interactions. */ var init protected array GlobalInteractions; /** The class for the UI controller */ var class UIControllerClass; /** The viewport's UI controller */ var UIInteraction UIController; /** The viewport's console. Might be null on consoles */ var Console ViewportConsole; /** This struct needs to be the same size as EShowFlags */ struct native ShowFlags_Mirror { var native const qword flags0; var native const qword flags1; }; /** This empty struct is here so we have a non-native struct that can be exported to the header properly */ struct {EShowFlags} ExportShowFlags_Mirror extends ShowFlags_Mirror {}; /** The show flags used by the viewport's players. */ var const ExportShowFlags_Mirror ShowFlags; /** @name Localized transition messages. */ //@{ var localized string LoadingMessage; var localized string SavingMessage; var localized string ConnectingMessage; var localized string PausedMessage; var localized string PrecachingMessage; //@} /** if TRUE then the title safe border is drawn */ var bool bShowTitleSafeZone; /** Max/Recommended screen viewable extents as a percentage */ struct native TitleSafeZoneArea { var float MaxPercentX; var float MaxPercentY; var float RecommendedPercentX; var float RecommendedPercentY; }; /** border of safe area */ var TitleSafeZoneArea TitleSafeZone; //If true, this will cause the hardware mouse cursor to render var transient bool bDisplayHardwareMouseCursor; var transient bool bOverrideDiffuseAndSpecular; /** If TRUE, this viewport is a play in editor viewport */ var transient bool bIsPlayInEditorViewport; /** If TRUE, we will show the OS mouse cursor at all times (only applies to PIE viewports)*/ var transient bool bShowSystemMouseCursor; /** * Enum of the different splitscreen types */ enum ESplitScreenType { eSST_NONE, // No split eSST_2P_HORIZONTAL, // 2 player horizontal split eSST_2P_VERTICAL, // 2 player vertical split eSST_3P_FAVOR_TOP, // 3 Player split with 1 player on top and 2 on bottom eSST_3P_FAVOR_BOTTOM, // 3 Player split with 1 player on bottom and 2 on top eSST_4P, // 4 Player split }; /** * The 4 different kinds of safezones */ enum ESafeZoneType { eSZ_TOP, eSZ_BOTTOM, eSZ_LEFT, eSZ_RIGHT, }; /** * Specifies whether a setting should be toggled, enabled or disabled. */ enum ESetMode { SetMode_Toggle, SetMode_Enable, SetMode_Disable, }; /** * Structure to store splitscreen data. */ struct native PerPlayerSplitscreenData { var float SizeX; var float SizeY; var float OriginX; var float OriginY; }; /** * Structure containing all the player splitscreen datas per splitscreen configuration. */ struct native SplitscreenData { var array PlayerData; }; /** Array of the screen data needed for all the different splitscreen configurations */ var array SplitscreenInfo; /** * The splitscreen layout type that the player wishes to use; this value usually comes from places like the player's profile */ var protected{protected} ESplitScreenType DesiredSplitscreenType; /** * The splitscreen type that is actually being used; takes into account the number of players and other factors (such as cinematic mode) * that could affect the splitscreen mode that is actually used. */ var protected{protected} ESplitscreenType ActiveSplitscreenType; /** Defaults for intances where there are multiple configs for a certain number of players */ var const ESplitScreenType Default2PSplitType; var const ESplitScreenType Default3PSplitType; /** Set to disable world rendering */ var bool bDisableWorldRendering; /** Set to capture the world rendering into an offscreen target */ var bool bCapturedWorldRendering; // Progress Indicator - used by the engine to provide status messages (see SetProgressMessage()) var string ProgressMessage[2]; var float ProgressTimeOut; var float ProgressFadeTime; /** debug property display functionality * to interact with this, use "display", "displayall", "displayclear" */ struct native DebugDisplayProperty { /** the object whose property to display. If this is a class, all objects of that class are drawn. */ var Object Obj; /** name of the property to display */ var name PropertyName; /** whether PropertyName is a "special" value not directly mapping to a real property (e.g. state name) */ var bool bSpecialProperty; }; var array DebugProperties; /** Stores the pointer to any data needed for scaleform (if defined)*/ var native const pointer ScaleformInteraction { UGFxInteraction }; /** DEBUG: If TRUE, the GFx UI will NOT be rendered at runtime. Note that to REMOVE GFx functionality permanently, you should compile with WITH_GFx set to 0. This bool is for debugging only. */ var config bool bDebugNoGFxUI; //@HSL_BEGIN - BWJ - Dingo controller support /** TRUE if input is allowed from multiple controllers */ var bool bAllowInputFromMultipleControllers; /** TRUE if we need to pair a new gamepad due a controller disconnect */ var bool bNeedsNewGamepadPairingForControllerDisconnect; /** TRUE if we need to pair a new gamepad because of a new profile taking over. We aren't sure which gamepad to use */ var bool bNeedsNewGamepadPairingForNewProfile; //@HSL_END cpptext { /** Make sure that the UC mirror struct matches the size of EShowFlags */ checkAtCompileTime( sizeof(FShowFlags_Mirror) == sizeof(EShowFlags), ShowFlags_Mirror__MustMatchSizeOfEShowFlags ); // Constructor. UGameViewportClient(); /** * Cleans up all rooted or referenced objects created or managed by the GameViewportClient. This method is called * when this GameViewportClient has been disassociated with the game engine (i.e. is no longer the engine's GameViewport). */ virtual void DetachViewportClient(); /** * Called every frame to allow the game viewport to update time based state. * @param DeltaTime The time since the last call to Tick. */ void Tick( FLOAT DeltaTime ); // FViewportClient interface. virtual void RedrawRequested(FViewport* InViewport) {} /** * Routes an input key event received from the viewport to the Interactions array for processing. * * @param Viewport the viewport the input event was received from * @param ControllerId gamepad/controller that generated this input event * @param Key the name of the key which an event occured for (KEY_Up, KEY_Down, etc.) * @param EventType the type of event which occured (pressed, released, etc.) * @param AmountDepressed (analog keys only) the depression percent. * @param bGamepad - input came from gamepad (ie xbox controller) * * @return TRUE to consume the key event, FALSE to pass it on. */ virtual UBOOL InputKey(FViewport* Viewport,INT ControllerId,FName Key,EInputEvent EventType,FLOAT AmountDepressed=1.f,UBOOL bGamepad=FALSE); /** * Routes an input axis (joystick, thumbstick, or mouse) event received from the viewport to the Interactions array for processing. * * @param Viewport the viewport the input event was received from * @param ControllerId the controller that generated this input axis event * @param Key the name of the axis that moved (KEY_MouseX, KEY_XboxTypeS_LeftX, etc.) * @param Delta the movement delta for the axis * @param DeltaTime the time (in seconds) since the last axis update. * * @return TRUE to consume the axis event, FALSE to pass it on. */ virtual UBOOL InputAxis(FViewport* Viewport,INT ControllerId,FName Key,FLOAT Delta,FLOAT DeltaTime, UBOOL bGamepad=FALSE); /** * Routes a character input event (typing) received from the viewport to the Interactions array for processing. * * @param Viewport the viewport the input event was received from * @param ControllerId the controller that generated this character input event * @param Character the character that was typed * * @return TRUE to consume the key event, FALSE to pass it on. */ virtual UBOOL InputChar(FViewport* Viewport,INT ControllerId,TCHAR Character); /** * Check a key event received by the viewport. * If the viewport client uses the event, it should return true to consume it. * @param Viewport - The viewport which the event is from. * @param ControllerId - The controller which the key event is from. * @param Handle - Identifier unique to this touch event * @param Type - What kind of touch event this is (see ETouchType) * @param TouchLocation - Screen position of the touch * @param DeviceTimestamp - Timestamp of the event * @param TouchpadIndex - For devices with multiple touchpads, this is the index of which one * @return True to consume the key event, false to pass it on. */ virtual UBOOL InputTouch(FViewport* Viewport, INT ControllerId, UINT Handle, BYTE Type, FVector2D TouchLocation, DOUBLE DeviceTimestamp, UINT TouchpadIndex=0); /** * Each frame, the input system will update the motion data. * * @param Viewport - The viewport which the key event is from. * @param ControllerId - The controller which the key event is from. * @param Tilt The current orientation of the device * @param RotationRate How fast the tilt is changing * @param Gravity Describes the current gravity of the device * @param Acceleration Describes the acceleration of the device * @return True to consume the motion event, false to pass it on. */ virtual UBOOL InputMotion(FViewport* Viewport, INT ControllerId, const FVector& Tilt, const FVector& RotationRate, const FVector& Gravity, const FVector& Acceleration); /** Returns the platform specific forcefeedback manager associated with this viewport */ virtual class UForceFeedbackManager* GetForceFeedbackManager(INT ControllerId); /** * @return the splitscreen type that is currently being used */ FORCEINLINE ESplitScreenType GetCurrentSplitscreenType() const { return static_cast(ActiveSplitscreenType); } /** * Retrieves the cursor that should be displayed by the OS * * @param Viewport the viewport that contains the cursor * @param X the x position of the cursor * @param Y the Y position of the cursor * * @return the cursor that the OS should display */ virtual EMouseCursor GetCursor( FViewport* Viewport, INT X, INT Y ); virtual void Precache(); virtual void Draw(FViewport* Viewport,FCanvas* Canvas); virtual void LostFocus(FViewport* Viewport); virtual void ReceivedFocus(FViewport* Viewport); virtual UBOOL IsFocused(FViewport* Viewport); virtual void CloseRequested(FViewport* Viewport); virtual UBOOL RequiresHitProxyStorage() { return 0; } /** * Determines whether this viewport client should receive calls to InputAxis() if the game's window is not currently capturing the mouse. * Used by the UI system to easily receive calls to InputAxis while the viewport's mouse capture is disabled. */ virtual UBOOL RequiresUncapturedAxisInput() const; // FExec interface. virtual UBOOL Exec(const TCHAR* Cmd,FOutputDevice& Ar); /** * Helper function to toggles, enable or disable the specified show flag. Called by Exec(). * * @param Cmd Exec command line, as passed on from Exec(). * @param Ar Output device used for reporting the result. * @param SetMode Specifies whether the flag should be toggled, enabled or disabled. * @return TRUE if the flag was modified. */ UBOOL SetShowFlags(const TCHAR* Cmd,FOutputDevice& Ar, ESetMode SetMode ); /** * Set this GameViewportClient's viewport and viewport frame to the viewport specified */ virtual void SetViewportFrame( FViewportFrame* InViewportFrame ); /** * Set this GameViewportClient's viewport to the viewport specified */ virtual void SetViewport( FViewport* InViewportFrame ); /** sets bDropDetail and other per-frame detail level flags on the current WorldInfo * @param DeltaSeconds - amount of time passed since last tick */ virtual void SetDropDetail(FLOAT DeltaSeconds); #if __TW_ /** Stub for KF2 level ShowSpawnVolumes (see KFGameViewportClient) */ virtual void ShowSpawnVolumes( ESetMode SetMode ) { } #endif #if WITH_GFx virtual UObject* GetUObject() { return this; } #endif } /** * Provides script-only child classes the opportunity to handle input key events received from the viewport. * This delegate is called before the input key event is passed to the interactions array for processing. * * @param ControllerId the controller that generated this input key event * @param Key the name of the key which an event occured for (KEY_Up, KEY_Down, etc.) * @param EventType the type of event which occured (pressed, released, etc.) * @param AmountDepressed for analog keys, the depression percent. * @param bGamepad input came from gamepad (ie xbox controller) * * @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not * be passed to the interactions array. */ delegate bool HandleInputKey( int ControllerId, name Key, EInputEvent EventType, float AmountDepressed, optional bool bGamepad ); /** * Provides script-only child classes the opportunity to handle input axis events received from the viewport. * This delegate is called before the input axis event is passed to the interactions array for processing. * * @param ControllerId the controller that generated this input axis event * @param Key the name of the axis that moved (KEY_MouseX, KEY_XboxTypeS_LeftX, etc.) * @param Delta the movement delta for the axis * @param DeltaTime the time (in seconds) since the last axis update. * @param bGamepad input came from gamepad (ie xbox controller) * * @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not * be passed to the interactions array. */ delegate bool HandleInputAxis( int ControllerId, name Key, float Delta, float DeltaTime, bool bGamepad); /** * Provides script-only child classes the opportunity to handle character input (typing) events received from the viewport. * This delegate is called before the character event is passed to the interactions array for processing. * * @param ControllerId the controller that generated this character input event * @param Unicode the character that was typed * * @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not * be passed to the interactions array. */ delegate bool HandleInputChar( int ControllerId, string Unicode ); /** * Executes a console command in the context of this viewport. * @param Command - The command to execute. * @return The output of the command will be returned. */ native function string ConsoleCommand(string Command); /** * Retrieve the size of the main viewport. * * @param out_ViewportSize [out] will be filled in with the size of the main viewport */ native final function GetViewportSize( out Vector2D out_ViewportSize ); /** @return Whether or not the main viewport is fullscreen or windowed. */ native final function bool IsFullScreenViewport(); /** @return mouse position in game viewport coordinates (does not account for splitscreen) */ native final function vector2D GetMousePosition(); /** * Determine whether a fullscreen viewport should be used in cases where there are multiple players. * * @return TRUE to use a fullscreen viewport; FALSE to allow each player to have their own area of the viewport. */ native final function bool ShouldForceFullscreenViewport() const; /**Function that allow for custom numbers of interactions dictated in code*/ native function int GetNumCustomInteractions(); /**Defining the above mentioned custom interactions*/ native function class GetCustomInteractionClass(int InIndex); /**Passing the custom interaction object back to native code to do with it as it likes*/ native function SetCustomInteractionObject(Interaction InInteraction); /** Function to notify GFx of a change in the splitscreen layout*/ native function NotifySplitscreenLayoutChanged(); /** Force the mouse cursor to update */ native function ForceUpdateMouseCursor(bool bSetCursor); /** Move the hardware mouse cursor to the designated location*/ native function SetMouse(int X, int Y); /** * Adds a new player. * @param ControllerId - The controller ID the player should accept input from. * @param OutError - If no player is returned, OutError will contain a string describing the reason. * @param SpawnActor - True if an actor should be spawned for the new player. * @return The player which was created. */ event LocalPlayer CreatePlayer(int ControllerId, out string OutError, bool bSpawnActor) { local LocalPlayer NewPlayer; local int InsertIndex; `log("Creating new player with ControllerId" @ ControllerId @ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement'); Assert(LocalPlayerClass != None); NewPlayer = new(Outer) LocalPlayerClass; NewPlayer.ViewportClient = Self; NewPlayer.ControllerId = ControllerId; InsertIndex = AddLocalPlayer(NewPlayer); if ( bSpawnActor && InsertIndex != INDEX_NONE ) { if (GetCurrentWorldInfo().NetMode != NM_Client) { // server; spawn a new PlayerController immediately if (!NewPlayer.SpawnPlayActor("", OutError)) { RemoveLocalPlayer(NewPlayer); NewPlayer = None; } } else { // client; ask the server to let the new player join NewPlayer.SendSplitJoin(); } } if (OutError != "") { `Log("Player creation failed with error:" @ OutError); } else { `log("Successfully created new player with ControllerId" @ ControllerId $ ":" @ NewPlayer @ "- inserted into GamePlayers array at index" @ InsertIndex @ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement'); if ( NewPlayer != None && InsertIndex != INDEX_NONE ) { // let all interactions know about this NotifyPlayerAdded(InsertIndex, NewPlayer); } } return NewPlayer; } /** * Removes a player. * @param Player - The player to remove. * @return whether the player was successfully removed. Removal is not allowed while connected to a server. */ event bool RemovePlayer(LocalPlayer ExPlayer) { local int OldIndex, i; //Mapping of: index into array is new index, value is old index local array IDMappings; // can't destroy viewports while connected to a server if (ExPlayer.Actor.Role == ROLE_Authority) { `log("Removing player" @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "at index" @ GamePlayers.Find(ExPlayer)@ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement'); `if(`isdefined(FIXING_SIGNIN_ISSUES)) ScriptTrace(); `endif // Disassociate this viewport client from the player. ExPlayer.ViewportClient = None; if ( ExPlayer.Actor != None ) { // Destroy the player's actors. ExPlayer.Actor.Destroy(); } // Remove the player from the global and viewport lists of players. OldIndex = RemoveLocalPlayer(ExPlayer); if ( OldIndex != INDEX_NONE ) { // let all interactions know about this // NOTE: This is where GFx will clean up any movies that used to reference that index NotifyPlayerRemoved(OldIndex, ExPlayer); } //Now we need to handle the fact that we may have screwed up the player indices for other gfx movies //If they were equal we would have removed the last element, so we wouldn't need to do anything at all if (OldIndex != GamePlayers.Length) { //Otherwise we have to cleanup for (i = 0; i < GamePlayers.Length; i++) { //If it is below the removal, everything is the same if (i < OldIndex) { IDMappings.AddItem(i); } else //If it is at the removal point or greater everything is one less than what it used to be { IDMappings.AddItem(i+1); } } } //If we constructed any IDMappings, then something must be fixed up, so do so if (IDMappings.Length > 0) { FixupOwnerReferences(IDMappings); } `log("Finished removing player " @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "at index" @ OldIndex@ "(" $ GamePlayers.Length @ "remaining players)",,'PlayerManagement'); return true; } else { `log("Not removing player" @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "because player does not have appropriate role (" $ GetEnum(enum'ENetRole',ExPlayer.Actor.Role) $ ")",,'PlayerManagement'); return false; } } /** * Finds a player by controller ID. * @param ControllerId - The controller ID to search for. * @return None or the player with matching controller ID. */ final event LocalPlayer FindPlayerByControllerId(int ControllerId) { local int PlayerIndex; for(PlayerIndex = 0;PlayerIndex < GamePlayers.Length;PlayerIndex++) { if(GamePlayers[PlayerIndex].ControllerId == ControllerId) { return GamePlayers[PlayerIndex]; } } return None; } `if(`notdefined(ShippingPC)) /** * Debug console command to create a player. * @param ControllerId - The controller ID the player should accept input from. */ exec function DebugCreatePlayer(int ControllerId) { local string Error; CreatePlayer(ControllerId, Error, TRUE); } /** Rotates controller ids among gameplayers, useful for testing splitscreen with only one controller. */ exec function SSSwapControllers() { local int Idx, TmpControllerID; TmpControllerID = GamePlayers[0].ControllerID; for (Idx=0; Idx= 0 && PlayerIndex < GamePlayers.Length) { ViewportConsole.ConsoleTargetPlayer = GamePlayers[PlayerIndex]; } else { ViewportConsole.ConsoleTargetPlayer = None; } } } `endif /** * Initialize the game viewport. * @param OutError - If an error occurs, returns the error description. * @return False if an error occurred, true if the viewport was initialized successfully. */ event bool Init(out string OutError) { local PlayerManagerInteraction PlayerInteraction; local int NumCustomInteractions; local class CustomInteractionClass; local UIInteraction CustomInteraction; local int Idx; assert(Outer.ConsoleClass != None); ActiveSplitscreenType = DesiredSplitscreenType; `if(`notdefined(FINAL_RELEASE)) // Create the viewport's console. ViewportConsole = new(Self) Outer.ConsoleClass; if ( InsertInteraction(ViewportConsole) == -1 ) { OutError = "Failed to add interaction to GlobalInteractions array:" @ ViewportConsole; return false; } `endif // Initialize custom interactions NumCustomInteractions = GetNumCustomInteractions(); for ( Idx = 0; Idx < NumCustomInteractions; Idx++ ) { CustomInteractionClass = GetCustomInteractionClass(Idx); CustomInteraction = new(Self) CustomInteractionClass; if ( InsertInteraction(CustomInteraction) == -1 ) { OutError = "Failed to add interaction to GlobalInteractions array:" @ CustomInteraction; return false; } SetCustomInteractionObject(CustomInteraction); } assert(UIControllerClass != None); // Create a interaction to handle UI input. UIController = new(Self) UIControllerClass; if ( InsertInteraction(UIController) == -1 ) { OutError = "Failed to add interaction to GlobalInteractions array:" @ UIController; return false; } // Create the viewport's player management interaction. PlayerInteraction = new(Self) class'PlayerManagerInteraction'; if ( InsertInteraction(PlayerInteraction) == -1 ) { OutError = "Failed to add interaction to GlobalInteractions array:" @ PlayerInteraction; return false; } // Disable the old UI system, if desired for debugging if( bDebugNoGFxUI ) { DebugSetUISystemEnabled(TRUE, FALSE); } // create the initial player - this is necessary or we can't render anything in-game. return CreateInitialPlayer(OutError); } /** * Create the game's initial player at startup. First search for a player that is signed into the OnlineSubsystem; if none are found, * create a player with a ControllerId of 0. * * @param OutError receives the error string if an error occurs while creating the player. * * @return TRUE if a player was successfully created. */ function bool CreateInitialPlayer( out string OutError ) { local int ControllerId; local bool bFoundInitialGamepad, bResult; for ( ControllerId = 0; ControllerId < class'UIRoot'.const.MAX_SUPPORTED_GAMEPADS; ControllerId++ ) { if ( UIController.IsLoggedIn(ControllerId) ) { bFoundInitialGamepad = true; bResult = CreatePlayer(ControllerId, OutError, false) != None; break; } } if ( !bFoundInitialGamepad || !bResult ) { // find the first connected gamepad for ( ControllerId = 0; ControllerId < class'UIRoot'.const.MAX_SUPPORTED_GAMEPADS; ControllerId++ ) { if ( UIController.IsGamepadConnected(ControllerId) ) { bFoundInitialGamepad = true; bResult = CreatePlayer(ControllerId, OutError, false) != None; break; } } } if ( !bFoundInitialGamepad || !bResult ) { bResult = CreatePlayer(0, OutError, false) != None; } return bResult; } /** * Inserts an interaction into the GlobalInteractions array at the specified index * * @param NewInteraction the interaction that should be inserted into the array * @param Index the position in the GlobalInteractions array to insert the element. * if no value (or -1) is specified, inserts the interaction at the end of the array * * @return the position in the GlobalInteractions array where the element was placed, or -1 if the element wasn't * added to the array for some reason */ event int InsertInteraction( Interaction NewInteraction, optional int InIndex = -1 ) { local int Result; Result = -1; if ( NewInteraction != None ) { // if the specified index is -1, assume that the item should be added to the end of the array if ( InIndex == -1 ) { InIndex = GlobalInteractions.Length; } // if the index is a negative value other than -1, don't add the element as someone made a mistake if ( InIndex >= 0 ) { // clamp the Index to avoid expanding the array needlessly Result = Clamp(InIndex, 0, GlobalInteractions.Length); // now insert the item GlobalInteractions.Insert(Result, 1); GlobalInteractions[Result] = NewInteraction; NewInteraction.Init(); NewInteraction.OnInitialize(); } else { `warn("Invalid insertion index specified:" @ InIndex); } } return Result; } /** * Called when the current map is being unloaded. Cleans up any references which would prevent garbage collection. */ event GameSessionEnded() { local int i; for ( i = 0; i < GlobalInteractions.Length; i++ ) { GlobalInteractions[i].NotifyGameSessionEnded(); } } /** * Sets the screen layout configuration that the player wishes to use when in split-screen mode. */ function SetSplitscreenConfiguration( ESplitScreenType SplitType ) { DesiredSplitscreenType = SplitType; } /** * @return the actual splitscreen type being used, taking into account the number of players. */ function ESplitScreenType GetSplitscreenConfiguration() { return ActiveSplitscreenType; } /** * Sets the value of ActiveSplitscreenConfiguration based on the desired split-screen layout type, current number of players, and any other * factors that might affect the way the screen should be layed out. */ function UpdateActiveSplitscreenType() { local ESplitScreenType SplitType; SplitType = DesiredSplitscreenType; switch ( GamePlayers.Length ) { case 0: case 1: SplitType = eSST_NONE; break; case 2: if ( (SplitType != eSST_2P_HORIZONTAL) && (SplitType != eSST_2P_VERTICAL) ) { SplitType = Default2PSplitType; } break; case 3: if ( (SplitType != eSST_3P_FAVOR_TOP) && (SplitType != eSST_3P_FAVOR_BOTTOM) ) { SplitType = Default3PSplitType; } break; default: SplitType = eSST_4P; break; } ActiveSplitscreenType = SplitType; } /** * Called before rendering to allow the game viewport to allocate subregions to players. */ event LayoutPlayers() { local int Idx; local ESplitScreenType SplitType, PreviousSplitType; PreviousSplitType = GetSplitscreenConfiguration(); UpdateActiveSplitscreenType(); SplitType = GetSplitscreenConfiguration(); // Initialize the players for ( Idx = 0; Idx < GamePlayers.Length; Idx++ ) { if ( SplitType < SplitscreenInfo.Length && Idx < SplitscreenInfo[SplitType].PlayerData.Length ) { GamePlayers[Idx].Size.X = SplitscreenInfo[SplitType].PlayerData[Idx].SizeX; GamePlayers[Idx].Size.Y = SplitscreenInfo[SplitType].PlayerData[Idx].SizeY; GamePlayers[Idx].Origin.X = SplitscreenInfo[SplitType].PlayerData[Idx].OriginX; GamePlayers[Idx].Origin.Y = SplitscreenInfo[SplitType].PlayerData[Idx].OriginY; } else { GamePlayers[Idx].Size.X = 0.f; GamePlayers[Idx].Size.Y = 0.f; GamePlayers[Idx].Origin.X = 0.f; GamePlayers[Idx].Origin.Y = 0.f; } } //If splitscreen type has changed, update GFx if (PreviousSplitType != SplitType) { NotifySplitscreenLayoutChanged(); } } /** called before rending subtitles to allow the game viewport to determine the size of the subtitle area * @param Min top left bounds of subtitle region (0 to 1) * @param Max bottom right bounds of subtitle region (0 to 1) */ event GetSubtitleRegion(out vector2D MinPos, out vector2D MaxPos) { MaxPos.X = 1.0f; MaxPos.Y = (GamePlayers.length == 1) ? 0.9f : 0.5f; } /** * Convert a LocalPlayer to it's index in the GamePlayer array * Returns -1 if the index could not be found. */ final function int ConvertLocalPlayerToGamePlayerIndex( LocalPlayer LPlayer ) { return GamePlayers.Find( LPlayer ); } /** * Whether the player at LocalPlayerIndex's viewport has a "top of viewport" safezone or not. */ final function bool HasTopSafeZone( int LocalPlayerIndex ) { switch ( GetSplitscreenConfiguration() ) { case eSST_NONE: case eSST_2P_VERTICAL: return true; case eSST_2P_HORIZONTAL: case eSST_3P_FAVOR_TOP: return (LocalPlayerIndex == 0) ? true : false; case eSST_3P_FAVOR_BOTTOM: case eSST_4P: return (LocalPlayerIndex < 2) ? true : false; } return false; } /** * Whether the player at LocalPlayerIndex's viewport has a "bottom of viewport" safezone or not. */ final function bool HasBottomSafeZone( int LocalPlayerIndex ) { switch ( GetSplitscreenConfiguration() ) { case eSST_NONE: case eSST_2P_VERTICAL: return true; case eSST_2P_HORIZONTAL: case eSST_3P_FAVOR_TOP: return (LocalPlayerIndex == 0) ? false : true; case eSST_3P_FAVOR_BOTTOM: case eSST_4P: return (LocalPlayerIndex > 1) ? true : false; } return false; } /** * Whether the player at LocalPlayerIndex's viewport has a "left of viewport" safezone or not. */ final function bool HasLeftSafeZone( int LocalPlayerIndex ) { switch ( GetSplitscreenConfiguration() ) { case eSST_NONE: case eSST_2P_HORIZONTAL: return true; case eSST_2P_VERTICAL: return (LocalPlayerIndex == 0) ? true : false; case eSST_3P_FAVOR_TOP: return (LocalPlayerIndex < 2) ? true : false; case eSST_3P_FAVOR_BOTTOM: case eSST_4P: return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? true : false; } return false; } /** * Whether the player at LocalPlayerIndex's viewport has a "right of viewport" safezone or not. */ final function bool HasRightSafeZone( int LocalPlayerIndex ) { switch ( GetSplitscreenConfiguration() ) { case eSST_NONE: case eSST_2P_HORIZONTAL: return true; case eSST_2P_VERTICAL: case eSST_3P_FAVOR_BOTTOM: return (LocalPlayerIndex > 0) ? true : false; case eSST_3P_FAVOR_TOP: return (LocalPlayerIndex == 1) ? false : true; case eSST_4P: return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? false : true; } return false; } /** * Get the total pixel size of the screen. * This is different from the pixel size of the viewport since we could be in splitscreen */ final function GetPixelSizeOfScreen( out float out_Width, out float out_Height, canvas Canvas, int LocalPlayerIndex ) { switch ( GetSplitscreenConfiguration() ) { case eSST_NONE: out_Width = Canvas.ClipX; out_Height = Canvas.ClipY; return; case eSST_2P_HORIZONTAL: out_Width = Canvas.ClipX; out_Height = Canvas.ClipY * 2; return; case eSST_2P_VERTICAL: out_Width = Canvas.ClipX * 2; out_Height = Canvas.ClipY; return; case eSST_3P_FAVOR_TOP: if ( LocalPlayerIndex == 0 ) { out_Width = Canvas.ClipX; } else { out_Width = Canvas.ClipX * 2; } out_Height = Canvas.ClipY * 2; return; case eSST_3P_FAVOR_BOTTOM: if ( LocalPlayerIndex == 2 ) { out_Width = Canvas.ClipX; } else { out_Width = Canvas.ClipX * 2; } out_Height = Canvas.ClipY * 2; return; case eSST_4P: out_Width = Canvas.ClipX * 2; out_Height = Canvas.ClipY * 2; return; } } /** * Calculate the amount of safezone needed for a single side for both vertical and horizontal dimensions */ final function CalculateSafeZoneValues( out float out_Horizontal, out float out_Vertical, canvas Canvas, int LocalPlayerIndex, bool bUseMaxPercent ) { local float ScreenWidth, ScreenHeight, XSafeZoneToUse, YSafeZoneToUse; XSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentX : TitleSafeZone.RecommendedPercentX; YSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentY : TitleSafeZone.RecommendedPercentY; GetPixelSizeOfScreen( ScreenWidth, ScreenHeight, Canvas, LocalPlayerIndex ); out_Horizontal = (ScreenWidth * (1 - XSafeZoneToUse) / 2.0f); out_Vertical = (ScreenHeight * (1 - YSafeZoneToUse) / 2.0); } /* * Return true if the safe zone exists * pixel size of the deadzone for all sides (right/left/top/bottom) based on which local player it is */ final function bool CalculateDeadZoneForAllSides( LocalPlayer LPlayer, Canvas Canvas, out float fTopSafeZone, out float fBottomSafeZone, out float fLeftSafeZone, out float fRightSafeZone, optional bool bUseMaxPercent ) { // save separate - if the split screen is in bottom right, then local bool bHasTopSafeZone, bHasBottomSafeZone, bHasRightSafeZone, bHasLeftSafeZone; local int LocalPlayerIndex; local float HorizSafeZoneValue, VertSafeZoneValue; if ( LPlayer != None ) { LocalPlayerIndex = ConvertLocalPlayerToGamePlayerIndex( LPlayer ); if ( LocalPlayerIndex != -1 ) { // see if this player should have a safe zone for any particular zonetype bHasTopSafeZone = HasTopSafeZone( LocalPlayerIndex ); bHasBottomSafeZone = HasBottomSafeZone( LocalPlayerIndex ); bHasLeftSafeZone = HasLeftSafeZone( LocalPlayerIndex ); bHasRightSafeZone = HasRightSafeZone( LocalPlayerIndex ); // if they need a safezone, then calculate it and save it if ( bHasTopSafeZone || bHasBottomSafeZone || bHasLeftSafeZone || bHasRightSafeZone) { // calculate the safezones CalculateSafeZoneValues( HorizSafeZoneValue, VertSafeZoneValue, Canvas, LocalPlayerIndex, bUseMaxPercent ); if (bHasTopSafeZone) { fTopSafeZone = VertSafeZoneValue; } else { fTopSafeZone = 0.f; } if (bHasBottomSafeZone) { fBottomSafeZone = VertSafeZoneValue; } else { fBottomSafeZone = 0.f; } if (bHasLeftSafeZone) { fLeftSafeZone = HorizSafeZoneValue; } else { fLeftSafeZone = 0.f; } if (bHasRightSafeZone) { fRightSafeZone = HorizSafeZoneValue; } else { fRightSafeZone = 0.f; } return TRUE; } } } return FALSE; } /** * Called every frame to allow the game viewport to update time based state. * @param DeltaTime - The time since the last call to Tick. */ event Tick(float DeltaTime); /** * Draw the safe area using the current TitleSafeZone settings */ function DrawTitleSafeArea( canvas Canvas ) { // red colored max safe area box Canvas.SetDrawColor(255,0,0,255); Canvas.SetPos(Canvas.ClipX * (1 - TitleSafeZone.MaxPercentX) / 2.0, Canvas.ClipY * (1 - TitleSafeZone.MaxPercentY) / 2.0); Canvas.DrawBox(Canvas.ClipX * TitleSafeZone.MaxPercentX, Canvas.ClipY * TitleSafeZone.MaxPercentY); // yellow colored recommended safe area box Canvas.SetDrawColor(255,255,0,255); Canvas.SetPos(Canvas.ClipX * (1 - TitleSafeZone.RecommendedPercentX) / 2.0, Canvas.ClipY * (1 - TitleSafeZone.RecommendedPercentY) / 2.0); Canvas.DrawBox(Canvas.ClipX * TitleSafeZone.RecommendedPercentX, Canvas.ClipY * TitleSafeZone.RecommendedPercentY); } /** * Called after rendering the player views and HUDs to render menus, the console, etc. * This is the last rendering call in the render loop * @param Canvas - The canvas to use for rendering. */ event PostRender(Canvas Canvas) { if( bShowTitleSafeZone ) { DrawTitleSafeArea(Canvas); } if (ViewportConsole != none) { // Render the console. ViewportConsole.PostRender_Console(Canvas); } // Draw the transition screen. DrawTransition(Canvas); } /** * display progress messages in center of screen */ function DisplayProgressMessage(Canvas Canvas) { local int i, LineCount; local float FontDX, FontDY; local float X, Y; local byte Alpha; local float TimeLeft; TimeLeft = ProgressTimeOut - class'Engine'.static.GetCurrentWorldInfo().TimeSeconds; Alpha = (TimeLeft >= ProgressFadeTime) ? 255 : byte((255 * TimeLeft) / ProgressFadeTime); LineCount = 0; for (i = 0; i < ArrayCount(ProgressMessage); i++) { if (ProgressMessage[i] != "") { LineCount++; } } Canvas.Font = class'Engine'.Static.GetMediumFont(); Canvas.TextSize ("A", FontDX, FontDY); X = (0.5 * Canvas.SizeX); Y = (0.5 * Canvas.SizeY); Y -= FontDY * (float(LineCount) / 2.0); Canvas.DrawColor.R = 255; Canvas.DrawColor.G = 255; Canvas.DrawColor.B = 255; for (i = 0; i < ArrayCount(ProgressMessage); i++) { if (ProgressMessage[i] != "") { Canvas.DrawColor.A = Alpha; Canvas.TextSize(ProgressMessage[i], FontDX, FontDY); Canvas.SetPos(X - (FontDX / 2.0), Y); Canvas.DrawText(ProgressMessage[i]); Y += FontDY; } } } /** * Displays the transition screen. * @param Canvas - The canvas to use for rendering. */ function DrawTransition(Canvas Canvas) { switch(Outer.TransitionType) { case TT_Loading: DrawTransitionMessage(Canvas,LoadingMessage); break; case TT_Saving: DrawTransitionMessage(Canvas,SavingMessage); break; case TT_Connecting: DrawTransitionMessage(Canvas,ConnectingMessage); break; case TT_Precaching: DrawTransitionMessage(Canvas,PrecachingMessage); break; case TT_Paused: DrawTransitionMessage(Canvas,PausedMessage); break; } } /** * Print a centered transition message with a drop shadow. */ function DrawTransitionMessage(Canvas Canvas,string Message) { local float XL, YL; Canvas.Font = class'Engine'.Static.GetLargeFont(); Canvas.bCenter = false; Canvas.StrLen( Message, XL, YL ); Canvas.SetPos(0.5 * (Canvas.ClipX - XL) + 1, 0.66 * Canvas.ClipY - YL * 0.5 + 1); Canvas.SetDrawColor(0,0,0); Canvas.DrawText( Message, false ); Canvas.SetPos(0.5 * (Canvas.ClipX - XL), 0.66 * Canvas.ClipY - YL * 0.5); Canvas.SetDrawColor(0,0,255);; Canvas.DrawText( Message, false ); } /** * Notifies all interactions that a new player has been added to the list of active players. * * @param PlayerIndex the index [into the GamePlayers array] where the player was inserted * @param AddedPlayer the player that was added */ final function NotifyPlayerAdded( int PlayerIndex, LocalPlayer AddedPlayer ) { local int InteractionIndex; LayoutPlayers(); for ( InteractionIndex = 0; InteractionIndex < GlobalInteractions.Length; InteractionIndex++ ) { if ( GlobalInteractions[InteractionIndex] != None ) { GlobalInteractions[InteractionIndex].NotifyPlayerAdded(PlayerIndex, AddedPlayer); } } } /** * Notifies all interactions that a new player has been added to the list of active players. * * @param PlayerIndex the index [into the GamePlayers array] where the player was located * @param RemovedPlayer the player that was removed */ final function NotifyPlayerRemoved( int PlayerIndex, LocalPlayer RemovedPlayer ) { local int InteractionIndex; LayoutPlayers(); for ( InteractionIndex = GlobalInteractions.Length - 1; InteractionIndex >= 0; InteractionIndex-- ) { if ( GlobalInteractions[InteractionIndex] != None ) { GlobalInteractions[InteractionIndex].NotifyPlayerRemoved(PlayerIndex, RemovedPlayer); } } } /** * Adds a LocalPlayer to the local and global list of Players. * * @param NewPlayer the player to add */ private final function int AddLocalPlayer( LocalPlayer NewPlayer ) { local int InsertIndex; InsertIndex = INDEX_NONE; if ( NewPlayer != None ) { // add to list InsertIndex = GamePlayers.Length; GamePlayers[InsertIndex] = NewPlayer; } return InsertIndex; } /** * Removes a LocalPlayer from the local and global list of Players. * * @param ExistingPlayer the player to remove */ private final function int RemoveLocalPlayer( LocalPlayer ExistingPlayer ) { local int Index; Index = GamePlayers.Find(ExistingPlayer); if ( Index != INDEX_NONE ) { GamePlayers.Remove(Index,1); } return Index; } /** handler for global state messages, generally network connection related (failures, download progress, etc) */ event SetProgressMessage(EProgressMessageType MessageType, string Message, optional string Title, optional bool bIgnoreFutureNetworkMessages) { local WorldInfo WI; WI = class'Engine'.static.GetCurrentWorldInfo(); if (MessageType == PMT_Clear) { ClearProgressMessages(); } else { if (MessageType == PMT_ConnectionFailure || MessageType == PMT_SocketFailure) { // Attempt to start host migration on connection failure if (WI != None && WI.NetMode == NM_Client && WI.BeginHostMigration()) { `Log(`location @ "MessageType="$MessageType @ "Message="$Message @ ": host migration started.. "$WI.PeerHostMigration.HostMigrationProgress,,'DevNet'); } //@FIXME: bIgnoreNetworkMessages needs to die else if (!Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages) { `Log(`location @ "MessageType="$MessageType @ "Message="$Message @ ": host migration not enabled.. handling connection error.",,'DevNet'); NotifyConnectionError(MessageType, Message, Title); } } else if (MessageType == PMT_PeerHostMigrationFailure) { // Host migration was started but failed so just fallback to network error handling Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages = false; NotifyConnectionError(MessageType, Message, Title); } else { if (Title != "") { ProgressMessage[0] = Title; ProgressMessage[1] = Message; } else { ProgressMessage[1] = ""; ProgressMessage[0] = Message; } } } //@FIXME: bIgnoreNetworkMessages needs to die if (!Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages) { Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages = bIgnoreFutureNetworkMessages; } } /** * Notifies the player that an attempt to connect to a remote server failed, or an existing connection was dropped. * * @param MessageType EProgressMessageType of current connection error * @param Message a description of why the connection was lost * @param Title the title to use in the connection failure message. */ function NotifyConnectionError(EProgressMessageType MessageType, optional string Message=Localize("Errors", "ConnectionFailed", "Engine"), optional string Title=Localize("Errors", "ConnectionFailed_Title", "Engine") ) { local WorldInfo WI; WI = class'Engine'.static.GetCurrentWorldInfo(); `log(`location @ `showvar(Title) @ `showvar(Message) @ `showenum(ENetMode,WI.NetMode,NetMode) @ `showvar(WI.GetURLMap(),Map) ,,'DevNet'); if (WI.NetMode != NM_Standalone) { if ( WI.Game != None ) { // Mark the server as having a problem WI.Game.bHasNetworkError = true; } //@todo: should we have a Travel() function in this class? ConsoleCommand("start ?failed"); } } exec event SetProgressTime(float T) { ProgressTimeOut = T + class'Engine'.static.GetCurrentWorldInfo().TimeSeconds; } exec function ClearProgressMessages() { local int i; for (i=0; i IDMappings); /** Called after a new primary player has been made */ function OnPrimaryPlayerSwitch(LocalPlayer OldPrimaryPlayer, LocalPlayer NewPrimaryPlayer); /** * Makes a player the primary player * @param PlayerIndex - The index of the player to be made into the primary player */ function BecomePrimaryPlayer(int PlayerIndex) { local array OtherPlayers; local LocalPlayer PlayerOwner, NextPlayer, OriginalPrimaryPlayer; local int NumPlayersRemoved; local int i, count; //Mapping of: index into array is new index, value is old index local array IDMappings; if (UIController != None && PlayerIndex > 0 && PlayerIndex < UIController.GetPlayerCount()) { OriginalPrimaryPlayer = GetPlayerOwner(0); // get the player that owns this scene PlayerOwner = GetPlayerOwner(PlayerIndex); if (PlayerOwner == None) { `log("GameViewportClient:BecomePrimaryPlayer has failed to find the player owner for index" @ PlayerIndex @ "ABORTING!!!"); return; } if (PlayerOwner != None) { NextPlayer = OriginalPrimaryPlayer; NumPlayersRemoved = 0; while (NextPlayer != None && NextPlayer != PlayerOwner) { // the easiest way to ensure that everything is updated properly is to simulate the player being removed; // do it manually so that their PlayerController and stuff aren't destroyed. UIController.NotifyPlayerRemoved(0, NextPlayer); UIController.Outer.Outer.GamePlayers.Remove(0, 1); // we need to re-add the player so keep them in a temporary list OtherPlayers.AddItem(NextPlayer); NextPlayer = GetPlayerOwner(0); NumPlayersRemoved++; } //Update mapping for all movies that just got shifted downwards for (i = 0; i < UIController.Outer.Outer.GamePlayers.length; i++) { IDMappings.AddItem(i+NumPlayersRemoved); } // now re-add the previous players to the GamePlayers array. count = 0; while (OtherPlayers.Length > 0) { NextPlayer = OtherPlayers[0]; UIController.Outer.Outer.GamePlayers.AddItem(NextPlayer); UIController.NotifyPlayerAdded(UIController.Outer.Outer.GamePlayers.length-1, NextPlayer); OtherPlayers.Remove(0, 1); IDMappings.AddItem(count); count++; } } // if we have a new primary player, reload their profile so that their settings will be applied and fixup references NextPlayer = GetPlayerOwner(0); if (OriginalPrimaryPlayer != NextPlayer) { // Players switched so reevaluate the viewports LayoutPlayers(); FixupOwnerReferences(IDMappings); NextPlayer.Actor.ReloadProfileSettings(); OnPrimaryPlayerSwitch(OriginalPrimaryPlayer, NextPlayer); } } } /** Function to enable Scaleform processing/rendering */ native final function EnableScaleform(); /** Function to disable Scaleform processing/rendering */ native final function DisableScaleform(); /** Function to find out if Scaleform is enabled/disabled */ native final function bool IsScaleformEnabled(); /** DEBUG: function to easily allow script to turn on / off the two UI systems for developing during the transition from the old UI to the new GFx UI */ native function DebugSetUISystemEnabled(bool bOldUISystemActive, bool bGFxUISystemActive); /** * Function to set the hardware mouse cursor visibility * * @param bIsVisible - whether or not the cursor is visible */ simulated event SetHardwareMouseCursorVisibility(bool bIsVisible) { local Vector2D ViewportSize; //If we are going to be turning on the hardware cursor when it was not already on, we will move the cursor to the middle of the screen if (bIsVisible && !bDisplayHardwareMouseCursor) { GetViewportSize(ViewportSize); SetMouse(ViewportSize.X/2,ViewportSize.Y/2); } bDisplayHardwareMouseCursor = bIsVisible; ForceUpdateMouseCursor(TRUE); } defaultproperties { UIControllerClass=class'Engine.UIInteraction' TitleSafeZone=(MaxPercentX=0.9,MaxPercentY=0.9,RecommendedPercentX=0.8,RecommendedPercentY=0.8) Default2PSplitType=eSST_2P_HORIZONTAL Default3PSplitType=eSST_3P_FAVOR_TOP DesiredSplitscreenType=eSST_NONE bIsPlayInEditorViewport=False bShowSystemMouseCursor=False ProgressFadeTime=1.0 ProgressTimeOut=8.0 SplitscreenInfo(eSST_None)= (PlayerData=((SizeX=1.0f,SizeY=1.0f,OriginX=0.0f,OriginY=0.0f))) SplitscreenInfo(eSST_2P_HORIZONTAL)={(PlayerData=( (SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f), (SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f)) )} SplitscreenInfo(eSST_2P_VERTICAL)={(PlayerData=( (SizeX=0.5f,SizeY=1.0f,OriginX=0.0f,OriginY=0.0f), (SizeX=0.5f,SizeY=1.0f,OriginX=0.5f,OriginY=0.0)) )} SplitscreenInfo(eSST_3P_FAVOR_TOP)={(PlayerData=( (SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.5f)) )} SplitscreenInfo(eSST_3P_FAVOR_BOTTOM)={(PlayerData=( (SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.0f), (SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f)) )} SplitscreenInfo(eSST_4P)={(PlayerData=( (SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.0f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f), (SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.5f)) )} }