/** * MobilePlayerInput * * This is the base class for processing input for mobile devices while in the game * * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class MobilePlayerInput extends PlayerInput within GamePlayerController native DependsOn(MobileInputZone) config(Game); /** * This structure contains data for individual touch events queued for a specific touch handle */ struct native TouchDataEvent { /** Holds the type of event */ var ETouchType EventType; /** What touchpad this came from */ var byte TouchpadIndex; /** Holds the current location of the touch */ var vector2D Location; /** Holds the device timestamp of when this event occurred */ var double DeviceTime; }; /** * the MPI keeps track of all touches coming from a device. When the status of a touch changes, it tracks it and then passes it along to * the associated MobileInputZone. */ struct native TouchData { /** Holds the ID of the current touch */ var int Handle; /** What touchpad this came from */ var byte TouchpadIndex; /** Holds the current location of the touch */ var vector2D Location; /** Total distance that the finger moved since it initially touched down */ var float TotalMoveDistance; /** Holds the device timestamp of when the original touch occurred */ var double InitialDeviceTime; /** How long this touch has been active */ var float TouchDuration; /** Device timestamp of most recent event */ var double MoveEventDeviceTime; /** Time delta between the movement events the last time this touch moved */ var float MoveDeltaTime; /** If true, this touch entry is in use, otherwise feel free to use it for touches */ var bool bInUse; /** Holds the zone that is currently processing this touch */ var MobileInputZone Zone; /** Holds the current state of the touch */ var ETouchType State; /** Events queued up for this touch. Because we may receive several touch movement events per tick, we store a list of events and process them in order when we can. */ var array Events; /** Holds the last active time (via TimeSeconds()) that will be used to timeout a zone */ var float LastActiveTime; }; /** * This enum contains the orientations of the interface (not the device) * The order of this currently matches the IPhone and changing it will break that device unless you fix those references */ enum EUIOrientation { UI_Unknown, UI_Portait, UI_PortaitUpsideDown, UI_LandscapeRight, UI_LandscapeLeft, }; /** Keeps track of all of the touches currently active on a device */ const NumTouchDataEntries = 5; var (Input) TouchData Touches[NumTouchDataEntries]; /** * The object that the user is currently interacting with. * e.g. When a user presses on the button, this button is the * interactive object until the user raises her finger and causes * an UnTouch event. */ var MobileMenuObject InteractiveObject; /** Last time interactive object process input */ var double InteractiveObjectLastTime; /** Defines a mobile input group */ struct native MobileInputGroup { /** The name of this group */ var string GroupName; /** The List of zones associated with this group */ var editinline array AssociatedZones; }; /** Holds a list of available groups */ var (Input) EditInline array MobileInputGroups; /** Holds the index of the current group */ var (Input) int CurrentMobileGroup; /** Holds a list of mobile input zones. */ var (Input) editinline array MobileInputZones; /** Record of each MobileInputZone class (and subclasses) instances */ struct native MobileInputZoneClassMap { /* Name of the instance */ var string Name; /* Class type of the instance */ var class ClassType; }; /** Classes that inherit from MobileInputZone - filled in by NativeInitializeInputSystem() */ var array MobileInputZoneClasses; /** Holds the current Tilt value for mobile devices */ var (Input) float MobilePitch; /** Holds the center value for the pitch. */ var (Input) float MobilePitchCenter; /** Pitch sensitivity */ var (Input) float MobilePitchMultiplier; /** Holds the current Yaw value for mobile devices */ var (Input) float MobileYaw; /** Holds the center value for the Yaw. */ var (Input) float MobileYawCenter; /** Pitch sensitivity */ var (Input) float MobileYawMultiplier; /** How much of a dead zone should the pitch have */ var (input) config float MobilePitchDeadzoneSize; /** How much of a dead zone should the yaw have */ var (input) config float MobileYawDeadzoneSize; /** Used to determine if a touch is a double tap */ var (input) config float MobileDoubleTapTime; /** You have to hold down a tap at least this long to register it as a tap */ var (input) config float MobileMinHoldForTap; /** Used to determine how quickly to send repeat events for touch+held */ var (input) config float MobileTapRepeatTime; /** If true, we want to allow input to occur during a cinematic */ var (input) bool bAllowTouchesInCinematic; /** If set to true, then touches will be ignored */ var (input) bool bDisableTouchInput; /** if set pass absolute touch locations instead of relative to the viewport location * (i.e. if this is true don't subtract MobileViewportOffset from touch locations) */ var (input) config bool bAbsoluteTouchLocations; /** Holds the amount of time that a zone can go without input before being consider timed out */ var (input) config float ZoneTimeout; /** If true this will collapse similar mobile inputs so that they won't get processed */ var (input) config bool bCollapseTouchInput; // ********************************************************************************** // The MobilePlayerInput is also the hub for the mobile menu system. // ********************************************************************************** /** This is the menu stack. */ var (menus) array MobileMenuStack; /** Used for debugging native code */ var (debug) string NativeDebugString; /** This will be set in NativeInitializeInputZones if -SimMobile is on the command line. */ var (debug) bool bFakeMobileTouches; // Holds the amount of time the view port has been inactive var (Current) float MobileInactiveTime; // Ability to disable rendering of scenes. var (debug) bool bDisableSceneRender; // ********************************************************************************** // Motion support // ********************************************************************************** /** * A General note about the DeviceMotion* variables. The engine will attempt to use any motion sensing system * built in to the device to fill out these values. For values not supported by the device, we will attempt to generate * values given the capibilites. but so values may not be available / reliable */ /** Holds a list of handlers looking to listen in on Motion events */ var (Current) array MobileSeqEventHandlers; /** Holds a list of handlers looking to listen in on raw touch events */ var (Current) array MobileRawInputSeqEventHandlers; // @DEBUG - Remove if you wish to see all touch events //var array TouchDebug; /** Store the current adjusted viewport size so we can adjust mobile input zones */ var vector2D MobileViewportOffset; var vector2D MobileViewportSize; /** * Process Mobile Input, handles mobile user input * Should be called regardless of game state. */ native function ProcessMobileInput( float DeltaTime ); /** * Function to cancel all mobile input before a Scaleform movie confuses the state */ native function CancelMobileInput(); /** * Overload the super PlayerInput event */ event PlayerInput( float DeltaTime ) { // Process the mobile input, this method is explicitly called regardless // of whether the game is paused so as to allow the mobile touch info // to be updated even when paused. ProcessMobileInput( DeltaTime ); // update any other player input info. Super.PlayerInput( DeltaTime ); } cpptext { /** * Takes a touch and looks up the InputZone that would handle it. * * @param TouchLocation Where the touch occurred * @param TouchpadIndex The index of the touchpad this touch came from * @returns the zone that will be managing this touch */ UMobileInputZone* HitTest(FVector2D TouchLocation, UINT TouchpadIndex); /** * Process an input key event received from the viewport. * * @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(INT ControllerId,FName Key,EInputEvent Event,FLOAT AmountDepressed=1.f,UBOOL bGamepad=FALSE); /** * Process an input axis (joystick, thumbstick, or mouse) event received from the viewport. * * @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(INT ControllerId,FName Key,FLOAT Delta,FLOAT DeltaTime, UBOOL bGamepad=FALSE); /** * Handle a touch event coming from the device. * * NOTE: no processing of the touch happens here. This just tracks the touch in the Touches stack. Processing * happens each tick * * @param Handle the id of the touch * @param Type What type of event is this * @param TouchLocation Where the touch occurred * @param DeviceTimestamp Input event timestamp from the device * @param TouchpadIndex The index of the touchpad this touch came from */ virtual UBOOL InputTouch(INT ControllerId, UINT Handle, ETouchType Type, FVector2D TouchLocation, DOUBLE DeviceTimestamp, UINT TouchpadIndex=0); /** * Update active touches, etc * * @param DeltaTime Much time has elapsed since the last processing */ virtual void Tick(FLOAT DeltaTime); /** * When input comes in to the player input, the first thing we need to do is process it for * the menus. * * @param TouchHandle A unique id for the touch * @param TouchpadIndex The index of the touchpad this touch came from * @param EventType What type of event is this * @param TouchLocation Where the touch occurred * @param DeviceTimestamp Time this event happened. * * @returns true if the menu system swallowed the input */ UBOOL ProcessMenuInput(UINT TouchHandle, UINT TouchpadIndex, ETouchType EventType, FVector2D TouchLocation, DOUBLE DeviceTimestamp); /** * This function will iterate over the MobileSeqEventHandles array and cause them to be updated. * It get's called once per frame. */ void UpdateListeners(); /** * Returns the Global Scaling values * * @return a FVector2 containing the global scale values */ FVector2D GetGlobalScale(); /** * Swap around components of a vector (with rotational values) based on an orientation. * This will swap pitch and roll when a mobile device is rotated 90 degrees, etc * * @param Vec [in/out] Vector to modify * @param Orientation Orientation for which the values were generated in * @param bIsRotation TRUE if the vector represents rotation, not a directional vector */ static void ModifyVectorByOrientation(FVector& Vec, EUIOrientation Orientation, UBOOL bIsRotation) { switch (Orientation) { case UI_Portait: // this is the base orientation, so nothing to do break; case UI_PortaitUpsideDown: if (bIsRotation) { // negate roll and pitch Vec.X = -Vec.X; Vec.Z = -Vec.Z; } else { // negate x/y Vec.X = -Vec.X; Vec.Y = -Vec.Y; } break; case UI_LandscapeRight: if (bIsRotation) { // swap and negate (as needed) roll and pitch FLOAT Temp = Vec.X; Vec.X = -Vec.Z; Vec.Z = Temp; } else { // swap and negate (as needed) x and y FLOAT Temp = Vec.X; Vec.X = -Vec.Y; Vec.Y = Temp; } break; case UI_LandscapeLeft: if (bIsRotation) { // swap and negate (as needed) roll and pitch FLOAT Temp = Vec.X; Vec.X = Vec.Z; Vec.Z = -Temp; } else { // swap and negate (as needed) x and y FLOAT Temp = Vec.X; Vec.X = Vec.Y; Vec.Y = -Temp; } break; } } public: /** * Determine the size of the current interactive canvas * * @param ViewportSize Size of the canvas */ void GetInteractiveViewportSize(FVector2D& ViewportSize); /** * Initialize, or reinitialize a zone * * @param Zone The Zone to initialize * @param ViewportSize Size of the canvas * @param bIsFirstInitialize TRUE if this is the first time the zone is initialized (use FALSE when canvas was resized) */ void NativeInitializeZone(UMobileInputZone* Zone, const FVector2D& ViewportSize, UBOOL bIsFirstInitialize); }; /** * Invoked when the mobile menus did not process an Touch_Began. */ delegate OnTouchNotHandledInMenu(); /** * PreviewTouch X,Y - screenspace coordinates; true/false for handled/not handled. */ delegate bool OnPreviewTouch(float X, float Y, int TouchpadIndex); /** * OnInputTouch is a low level handler for getting the actual touch data */ delegate OnInputTouch(int Handle, ETouchType Type, Vector2D TouchLocation, float DeviceTimestamp, int TouchpadIndex); /** * Perform any native initialization of the subsystem */ native function NativeInitializeInputSystem(); /** * Iterates over the zones and pre-calculates the actual bounds based on the current device resolution */ native function NativeInitializeInputZones(bool bIsFirstInitialize); /** * Update the mobile touch zones if the viewport aspect ratio changes */ native function ConditionalUpdateInputZones(int NewViewportX, int NewViewportY, int NewViewportSizeX, int NewViewportSizeY); /** * Allows the game to send a InputKey event though the viewport. * * @param Key the new of the key we are sending * @param Event the Type of event * @param AmountDepressed the strength of the event */ native function SendInputKey(name Key, EInputEvent Event, float AmountDepressed); /** * Allows the game to send an InputAxis event through the viewport * * @param Key the key we are sending * @param Delta the movement delta for the axis * @param DeltaTime the time (in seconds) since the last axis update. */ native function SendInputAxis(name Key, FLOAT Delta, FLOAT DeltaTime); /** * Handle touch events in the 3D world. To use this assign the OnTapDelegate in a MobileInputZone to this function. * * @param Zone The mobile Input zone that triggered the delegate * @param EventType The type of input event that occurred * @param TouchLocation The screen location of the touch event * * @return true if the world actor swallows the input */ native function bool ProcessWorldTouch(MobileInputZone Zone, ETouchType EventType, Vector2D TouchLocation); /** * The player controller will call this function directly after creating the input system */ function InitInputSystem() { super.InitInputSystem(); InitTouchSystem(); } /** * When the client inits the input system, initialize it's touch system */ function ClientInitInputSystem() { super.ClientInitInputSystem(); InitTouchSystem(); } function InitTouchSystem() { NativeInitializeInputSystem(); // We only want to initialize the if (bFakeMobileTouches || WorldInfo.IsConsoleBuild(CONSOLE_Mobile)) { InitializeInputZones(); } } /** * Initializes the input zones */ function InitializeInputZones() { local int i,j; local MobileInputZone Zone; local class FrameworkGameClass; // In a MP game, this function gets called twice - and that caused the zone // to be initialized twice making its pos/size wrong. if (MobileInputGroups.Length > 0) { return; } FrameworkGameClass = Class(WorldInfo.GRI.GameClass); //`log("Initializing Input zones"); if (FrameworkGameClass != none) { //`log("No of Config Groups:"@FrameworkGameClass.Default.RequiredMobileInputConfigs.Length); // Allocate Space MobileInputGroups.Length = FrameworkGameClass.Default.RequiredMobileInputConfigs.Length; for(i=0;i MobileInputSeqEvents; local Sequence GameSeq; local int i; GameSeq = WorldInfo.GetGameSequence(); if (GameSeq != None) { // Find all SeqEvent_MobileInput objects anywhere and call RegisterEvent on them GameSeq.FindSeqObjectsByClass(class'SeqEvent_MobileBase', TRUE, MobileInputSeqEvents); for (i=0;i< MobileInputSeqEvents.Length; i++) { SeqEvent_MobileBase(MobileInputSeqEvents[i]).RegisterEvent(); } // Find all SeqEvent_MobileRawInput objects anywhere and call RegisterEvent on them MobileInputSeqEvents.length = 0; GameSeq.FindSeqObjectsByClass(class'SeqEvent_MobileRawInput', TRUE, MobileInputSeqEvents); for (i=0;i< MobileInputSeqEvents.Length; i++) { SeqEvent_MobileRawInput(MobileInputSeqEvents[i]).RegisterEvent(); } } } /** * Adds a listen to the mobile handler list. * * @param Handler the MobileMotion sequence event to add to the handler list */ function AddKismetEventHandler(SeqEvent_MobileBase NewHandler) { local int i; //`log("Adding Mobile Kismet handler " @ NewHandler.Name); // More sure this event handler isn't already in the array for (i=0;i ClassType; local int ClassIndex; Zone = FindZone(ZoneName); if (Zone == None) { ClassType = class'MobileInputZone'; // Search for the class type that is associated with ZoneName in the ini file. for (ClassIndex = 0; ClassIndex < MobileInputZoneClasses.length; ClassIndex++) { if (ZoneName == MobileInputZoneClasses[ClassIndex].Name) { ClassType = MobileInputZoneClasses[ClassIndex].ClassType; break; } } Zone = new(none,ZoneName) ClassType; Zone.InputOwner = self; MobileInputZones.AddItem(Zone); } return Zone; } function bool HasZones() { return (MobileInputGroups.Length>0 && CurrentMobileGroup < MobileInputGroups.Length); } function array GetCurrentZones() { return MobileInputGroups[CurrentMobileGroup].AssociatedZones; } exec function ActivateInputGroup(string GroupName) { local int i; for (i=0;i SceneClass, optional string Mode) { local MobileMenuScene Scene; local Vector2D ViewportSize; if (SceneClass != none) { // We have the menu scene, create it. Scene = new(outer) SceneClass; if (Scene != none) { //`log("### OpenMenuScene "@SceneClass); LocalPlayer(Outer.Player).ViewportClient.GetViewportSize(ViewportSize); Scene.InitMenuScene(self, ViewportSize.X, ViewportSize.Y,true); MobileMenuStack.InsertItem(0,Scene); Scene.Opened(Mode); Scene.MadeTopMenu(); return Scene; } else { `log("Could not create menu scene " $ SceneClass); } } return none; } /** * Call this function to close a menu scene. Remove it from the stack and notify the scene/etc. * * @param SceneToClose - The actual scene to close. */ event CloseMenuScene(MobileMenuScene SceneToClose) { local int i,idx; local bool bClosedTopMenu; // Check to make sure the Scene wants to let itself close if (SceneToClose.Closing()) { idx = -1; // Find the scene in the stack for (i=0;i=0) { // did we just remove the top menu? bClosedTopMenu = (idx == 0); MobileMenuStack.Remove(idx,1); SceneToClose.Closed(); // if we closed the top menu, send a MadeTopMenu to the new top menu if (bClosedTopMenu) { if (MobileMenuStack.length > 0) { MobileMenuStack[0].MadeTopMenu(); } } } } } /** * Call this function to close all menus, used to "restart" the stack */ event CloseAllMenus() { while (MobileMenuStack.length > 0) { CloseMenuScene(MobileMenuStack[MobileMenuStack.length -1]); } } /** * Start the rendering chain for the UI Scenes * * @param Canvas - The canvas for drawing */ event RenderMenus(Canvas Canvas,float RenderDelta) { local int i; Canvas.Reset(); if (bDisableSceneRender) { return; } for (i = MobileMenuStack.Length-1; i >= 0; i--) { MobileMenuStack[i].RenderScene(Canvas,RenderDelta); } } /** * We need a PreClientTravel to clean up the menu system. */ function PreClientTravel( string PendingURL, ETravelType TravelType, bool bIsSeamlessTravel) { local int i; Super.PreClientTravel(PendingURL, TravelType, bIsSeamlessTravel); for (i = MobileMenuStack.Length-1; i >= 0; i--) { MobileMenuStack[i].Closed(); } } exec function SceneRenderToggle() { bDisableSceneRender = !bDisableSceneRender; } exec function MobileMenuCommand(string MenuCommand) { local int i; for (i = 0; i < MobileMenuStack.Length; i++) { if (MobileMenuStack[i].MobileMenuCommand(MenuCommand)) { return; } } } /** * Opens a menu by class * * @Param MenuClassName - the name of the class to open */ exec function MobileMenuScene OpenMobileMenu(string MenuClassName) { local class MenuClass; MenuClass = class(DynamicLoadObject(MenuClassName,class'class')); if (MenuClass != none) { return OpenMenuScene(MenuClass); } return none; } /** * Opens a menu by class and passes extra info to the scene * * @Param MenuClassName - the name of the class to open - REQUIRES QUOTES! * @Param Mode - the extra mode information to pass to the scene (two strings in OpenMobileMenu above breaks a.b for class names!) */ exec function MobileMenuScene OpenMobileMenuMode(string MenuClassName, string Mode) { local class MenuClass; MenuClass = class(DynamicLoadObject(MenuClassName,class'class')); if (MenuClass != none) { return OpenMenuScene(MenuClass, Mode); } return none; } // @DEBUG - Remove if you wish to see all touch events /* event AddTouchDebug(int Handle, TouchDataEvent Event) { local string s; if (Event.EventType == Touch_Ended && Event.Location.Y > 400) { TouchDebug.Remove(0,TouchDebug.Length); return; } if (Event.EventType == Touch_Moved) { return; } s = "TOUCH: Hander="@Handle @ "Event=" @ Event.EventType @ Event.Location.X @ Event.Location.Y; AddTouchDebugMsg(s); } event AddTouchDebugMsg(string s) { local int i; i = TouchDebug.Length; TouchDebug.Length = i+1; TouchDebug[i] = s; if (TouchDebug.Length>20) { TouchDebug.Remove(0,1); } } function DrawTouchDebug(canvas Canvas) { local int i; local int y; y = 80; for (i=0;i