Announcement

Collapse
No announcement yet.

Basic sidescroller using classic HUD

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Basic sidescroller using classic HUD

    Update 1:
    For a way to fix movement, so pressing A will always move to the left- and D always to the right of the screen, see:
    http://forums.epicgames.com/showpost...81&postcount=3

    This also sort of fixes the problem of 4. on the list below in the original post.

    Update 2:
    For a way to fix character rotation when there is no background in the map to trace against(as can happen with sidescrollers) and fix the aiming problem(at the expense of introducing a new bug), see:
    http://forums.epicgames.com/showpost...97&postcount=8

    ---------- Original post ---------

    I started working on a sidescroller project for the first time yesterday, a forum search revealed nothing clearly marked as a tutorial, aside from one which linked to a non existing website... hmm

    So I figured, I'd post my version of how to handle a sidescroller camera.
    Things to note:
    1. Not designed for Multiplayer, it might work, might not.
    2. Aiming and firing isn't 100% accurately aimed at the crosshair, but accurately enough to not pose too much problem, if it does you'll have to fix it yourself, since I don't intend to expand into shooter territory myself.
    3. This was tested to work on UDK May 2011 build.
    4. There may be other minor bugs, like aiming below your character, then moving, he will keep flipping, these too I probably won't be fixing unless I need to. ("Fix" here: http://forums.epicgames.com/showpost...81&postcount=3)
    5. This tutorial assumes you have your project set up blank and are ready to rock and know how to set the GameInfo to use.

    Right so, this tutorial will be using 6 classes, if you want to get started on creating those first, prefix can be anything you want, so replace My_ with anything you want:
    • My_GameInfo
    • My_PlayerController
    • My_PlayerInput
    • My_PlayerPawn
    • My_HUD
    • My_Camera


    First let's start with the GameInfo:
    Code:
    class My_GameInfo extends UTDeathmatch;
    /**
      * Don't override this gametype if chosen as DefaultGameType in .ini or on command line
      */
    static event class<GameInfo> SetGameType(string MapName, string Options, string Portal)
    {
    	return default.class;
    }
    
    DefaultProperties
    {
    	Acronym="MY"
    	MapPrefixes[0]="MY"
    
    	// Default set of options to publish to the online service
    	OnlineGameSettingsClass=class'UTGame.UTGameSettingsDM'
    
    	// Deathmatch games don't care about teams for voice chat
    	bIgnoreTeamForVoiceChat=true
    	
    	bGivePhysicsGun=false
    	bDelayedStart=false
    	bUseClassicHUD=true
    
    	PlayerControllerClass=class'My_PlayerController'
    	DefaultPawnClass=class'My_PlayerPawn'
    	HUDType=class'My_HUD'
    }
    There may be some values here you'll want to mess around with, you may also want to extend something else than UTGame yourself, but this'll work and right now that's what matters, anyway not much interesting to see there, you just need it to set the right classes to use, as well tell it to use the Classic HUD or else it won't load our new HUD.

    Next up is the PlayerController:
    Code:
    class My_PlayerController extends UTPlayerController;
    
    var Vector      MousePosWorldLocation;      //Hold deprojected mouse location in 3d world coordinates.
    var Vector      MousePosWorldDirection;        //Hold deprojected mouse location normal. 
    
    event Possess(Pawn inPawn, bool bVehicleTransition)
    {
    	super.Possess(inPawn, bVehicleTransition);
    	
    	// Makes the pawn visible so we can see it with our side scroller camera, do this in Possess so it will be done when respawning too
    	SetBehindView(true);
    }
    
    event PlayerTick( float DeltaTime )
    {
    	local Vector AdjustedMousePosition, HitNormal;
    	local Rotator NewRotation;
    
    	// Ignore any attempt to strafe
    	PlayerInput.aStrafe	= 0.f;
    
        super.PlayerTick(DeltaTime);
    
    	// Trace from MouseLocation to the world position where the mouse actually is
    	Trace(MousePosWorldLocation, HitNormal, MousePosWorldLocation + MousePosWorldDirection * 65536.f, MousePosWorldLocation);
    
    	AdjustedMousePosition = MousePosWorldLocation;
    
    	// Adjust the mouse position so the Y axis matches the pawns Y axis
    	AdjustedMousePosition.Y = Pawn.Location.Y;
    
    	// Create a rotation from Pawn's location to the Adjusted mouse position
    	NewRotation = Rotator(AdjustedMousePosition - Pawn.Location);
    
    	// This handles the direction the pawn is looking in, if the new rotation Yaw is more than 90 degrees or less than -90 degrees, the new rotation yaw should be 180.f, otherwise it should be 0.f
    	NewRotation.Yaw = (NewRotation.Yaw > 90 * DegToUnrRot || NewRotation.Yaw < -90 * DegToUnrRot) ? 180.f * DegToUnrRot : 0.f;
    
    	// Set the rotation on the controller
    	SetRotation(NewRotation);
    
    	// Adjust pitch to 0 for pawns rotation
    	NewRotation.Pitch = 0;
    
    	// Then set pawns rotation as well
    	Pawn.SetRotation(NewRotation);
    }
    
    // Overriden to tell it to always use the Camera's viewpoint as the player view point
    simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
    {
    	PlayerCamera.GetCameraViewPoint(out_Location, out_Rotation);
    }
    
    /**
     * Reset Camera Mode to default
     */
    event ResetCameraMode()
    {
    	if ( Pawn != None )
    		SetCameraMode( Pawn.GetDefaultCameraMode( Self ) ); // If we have a pawn, let's ask it which camera mode we should use
    	else
    		SetCameraMode( 'FirstPerson' ); // otherwise default to first person view.
    }
    
    // Override to do nothing
    function UpdateRotation( float DeltaTime );
    
    // Override to do nothing
    function ProcessViewRotation( float DeltaTime, out Rotator out_ViewRotation, Rotator DeltaRot );
    
    DefaultProperties
    {
    	CameraClass=class'My_Camera'
    	InputClass=class'My_PlayerInput'
    }
    This'll set our camera and custom input class and handles the new rotation which we'll need because of the change in perspective. Also strafing is disabled(though mind that the pawn can still be moved on the Y axis regardless)

    It'll also ensure the player will use our camera viewpoint.
    See the comments for more information.

    Next up, the input, which is based on the Gem post on the UDK documentation.
    Code:
    class My_PlayerInput extends UTPlayerInput within My_PlayerController;
    
    var PrivateWrite IntPoint MousePosition; 
    
    event PlayerInput(float DeltaTime)
    {
    	if (myHUD != None) 
    	{
    		MousePosition.X = Clamp(MousePosition.X + aMouseX, 0, myHUD.SizeX); 
    		MousePosition.Y = Clamp(MousePosition.Y - aMouseY, 0, myHUD.SizeY); 
    	}
    
    	Super.PlayerInput(DeltaTime);
    }
    
    defaultproperties
    {
    }
    This handles storing the MousePosition which we'll need in the HUD, but first let's take a look at the Pawn:
    Code:
    class My_PlayerPawn extends UTPawn;
    
    simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
    {
    	return 'SideScroller';
    }
    
    /**
     * Overriden only to add Camera style SideScroller as a FreeCam type, important so the pawn will shoot forward instead of towards the ground
     */
    simulated event bool InFreeCam()
    {
    	local PlayerController	PC;
    
    	PC = PlayerController(Controller);
    	return (PC != None && PC.PlayerCamera != None && (PC.PlayerCamera.CameraStyle == 'FreeCam' || PC.PlayerCamera.CameraStyle == 'FreeCam_Default' || PC.PlayerCamera.CameraStyle == 'SideScroller') );
    }
    
    DefaultProperties
    {
    }
    This doesn't add much to the existing pawn functionality, it just adds the new CameraStyle as a FreeCam type, if we don't do this the pawn will end up shooting towards the ground.

    Right, next the HUD which we will use to paint the crosshair on the correct position and to set the MouseWorldPosition and MouseWorldDirection:
    Code:
    class My_HUD extends UTHUD;
    
    var private const Texture2D CursorTexture;
    var private const Color CursorColor;
    var private const TextureCoordinates CursorCoordinates;
    var private const int CursorSize;
    
    event PostRender()
    {
    	local My_PlayerInput PlayerInput;
    	local My_PlayerController PlayerController;
    	local Vector2D MousePosition;
    
    	Super.PostRender();
    
    	PlayerController = My_PlayerController(PlayerOwner);
    
    	// Ensure that we have a valid PlayerOwner
    	if (PlayerController != none) 
    	{
    		PlayerInput = My_PlayerInput(PlayerController.PlayerInput); 
    		if (PlayerInput != None)
    		{
    			MousePosition.X = PlayerInput.MousePosition.X;
    			MousePosition.Y = PlayerInput.MousePosition.Y;
    
    			Canvas.SetPos(MousePosition.X - CursorSize / 2, MousePosition.Y - CursorSize / 2); 
    			Canvas.DrawColor = CursorColor;
    			Canvas.DrawTile(CursorTexture, CursorSize, CursorSize, CursorCoordinates.U, CursorCoordinates.V, CursorCoordinates.UL, CursorCoordinates.VL,, true);
    
    			// Turn the mouse position into a world position
    			Canvas.DeProject(MousePosition, PlayerController.MousePosWorldLocation, PlayerController.MousePosWorldDirection);
    		}
    	}
    
    	PreCalcValues();
    }
    
    DefaultProperties
    {
    	CursorTexture=Texture2D'UI_HUD.HUD.UTCrossHairs'
    	CursorColor=(R=255,G=255,B=255,A=255)
    	CursorCoordinates=(U=384,V=0,UL=64,VL=64)
    	CursorSize=64
    }
    To turn off the crosshair in the middle of the screen, open DefaultGame.ini and in the [UTGame.PlayerController] section add: bNoCrosshair=true

    Now finally on to the camera and everything should be ready to rock!
    Code:
    class My_Camera extends Camera;
    
    var Rotator CameraAngle;
    var float CameraInterpolationRate; // Interpolation rate of the camera, speed of moving from one location to the desired location
    
    /** Overriden to set starting rotation and location of camera */
    function PostBeginPlay()
    {
    	super.PostBeginPlay();
    	
    	CameraAngle.Pitch   = 0;
    	CameraAngle.Roll    = 0;
    	CameraAngle.Yaw     = -90.f * DegToUnrRot; // side view
    
    	ViewTarget.POV.Rotation = CameraAngle;
    	ViewTarget.POV.FOV = DefaultFOV;
    }
    
    function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
    {
    	local Vector CurrentLocation, DesiredLocation;
    
    	// Determine the current camera location
    	CurrentLocation = ViewTarget.POV.Location;
    
    	// Determine the desired camera location
    	DesiredLocation = ViewTarget.Target.Location;
    	DesiredLocation -= Vector(CameraAngle) * FreeCamDistance;
    
            // Set the camera position a little higher
    	DesiredLocation.Z += 50;
    
    	// If we haven't reached the desired location, interpolate to it
    	if(CurrentLocation != DesiredLocation)
    		ViewTarget.POV.Location += (DesiredLocation - CurrentLocation) * DeltaTime * CameraInterpolationRate;
    }
    
    DefaultProperties
    {
    	DefaultFOV=90.f
    	FreeCamDistance=512.f
    	CameraInterpolationRate=7.5f
    }
    This'll set the camera angle, keep the location updated and it uses interpoliation too, feel free to mess with the settings to create a different feel for the camera.

    I hope this'll help people having trouble finding up to date tutorials and having to piece things together themselves slowly or by scouring the forums to find little bits of info.

    Feel free to leave (constructive ) critiscm or other comments.

    #2
    Good tutorial bro ! I think it's a good base for someone who wants to do his custom sidescroller.

    Comment


      #3
      After fixing up my controls a bit I realised that even with A and D now set to be forward and backward, if I face a different direction, that's annoying to use.

      So here's a little expansion upon the above tutorial in case you need proper forward/backward movement:

      First find the GBA_ section in DefaultInput.ini
      Replace the first 4 movement keys to be:
      Code:
      .Bindings=(Name="GBA_MoveForward",Command="Jump | Axis aUp Speed=+1.0 AbsoluteAxis=100")
      .Bindings=(Name="GBA_Backward",Command="Duck | onrelease UnDuck | Axis aUp Speed=-1.0  AbsoluteAxis=100")
      .Bindings=(Name="GBA_StrafeLeft",Command="Axis aBaseY Speed=-1.0")
      .Bindings=(Name="GBA_StrafeRight",Command="Axis aBaseY Speed=1.0")
      This'll set A and D to be backwards/forwards and W to jump, S to crouch(or whatever you have bound those keys to)

      Now we need to update the PlayerTick function inside the PlayerController, it also requires some shifting of the code to work, so I'm providing the full function here:
      Code:
      event PlayerTick( float DeltaTime )
      {
      	local Vector AdjustedMousePosition, HitNormal;
      	local Rotator NewRotation;
      
      	// Trace from MouseLocation to the world position where the mouse actually is
      	Trace(MousePosWorldLocation, HitNormal, MousePosWorldLocation + MousePosWorldDirection * 65536.f, MousePosWorldLocation);
      
      	AdjustedMousePosition = MousePosWorldLocation;
      
      	// Adjust the mouse position so the Y axis matches the pawns Y axis
      	AdjustedMousePosition.Y = Pawn.Location.Y;
      
      	// Create a rotation from Pawn's location to the Adjusted mouse position
      	NewRotation = Rotator(AdjustedMousePosition - Pawn.Location);
      
      	// This handles the direction the pawn is looking in, if the new rotation Yaw is more than 90 degrees or less than -90 degrees, the new rotation yaw should be 180.f, otherwise it should be 0.f
      	NewRotation.Yaw = (NewRotation.Yaw > 90 * DegToUnrRot || NewRotation.Yaw < -90 * DegToUnrRot) ? 180.f * DegToUnrRot : 0.f;
      
      	// If the rotation yaw isn't 0 flip the Y axis movement
      	if(NewRotation.Yaw != 0.f)
      		PlayerInput.aBaseY = -PlayerInput.aBaseY;
      
      	// Ignore any attempt to strafe
      	PlayerInput.aStrafe	= 0.f;
      
          super.PlayerTick(DeltaTime);
      
      	// Set the rotation on the controller
      	SetRotation(NewRotation);
      
      	// Adjust pitch to 0 for pawns rotation
      	NewRotation.Pitch = 0;
      
      	// Then set pawns rotation as well
      	Pawn.SetRotation(NewRotation);
      }
      Upon closer inspection you'll see that the part for setting the NewRotation value is now done before the call to super.PlayerTick, but actually applying that is still done after.

      This is because of the new part, which I'll highlight for you here now:
      Code:
      // If the rotation yaw isn't 0 flip the Y axis movement
      	if(NewRotation.Yaw != 0.f)
      		PlayerInput.aBaseY = -PlayerInput.aBaseY;
      It requires knowing the rotation and doing this must be done before calling super.

      It checks the rotation the pawn will have, if it's not 0, forward movement becomes backwards and backwards becomes forwards. This also fixes the flipping problem in terms of movement mentioned at point 4 in my original post.

      Now you should be able to always move to the left when pressing A and to the right when pressing D.

      Comment


        #4
        thanks, very good i will try it very soon ...

        Comment


          #5
          Originally posted by DuckSauce View Post
          I hope this'll help people having trouble finding up to date tutorials and having to piece things together themselves slowly or by scouring the forums to find little bits of info.
          It definitely did :P

          I started trying to set up something just like this last month by piecing together what I could from other tutorials and guides online, but my lack of scripting knowledge made it really difficult for me to know where to put certain lines of script, the correct syntax to use in different places and all the different classes I needed and how to use them, so I was unable to get anything to work quite how I wanted. I gave things a rest for a while and just now came back to working on it with the intention of asking for help in these forums.

          But lo and behold, youve created this very helpful tutorial which works great and I now have the basics sorted out for what I am trying to accomplish. Now I have something to build on properly. Thank you, DuckSauce. You're a legend.

          Comment


            #6
            Great to hear

            Comment


              #7
              Had a go at this worked quite well apart from the aim was glitchy, seemed not to track the mouse cursor in the top right of the screen.

              Very cool thanks a ton!

              Does anyone know any good sources for reading on how to deal with aim and the vector math needed for 2d sidescrollers?

              All the best

              Greg

              Comment


                #8
                Got back on this recently and decided to try and fix an issue I had with it, namely that when there are no backgrounds walls, the mouse world position trace would be 0,0,0 and when holding the mouse in the middle of the axis of the screen the pawn would do a ballerina dance fast forwarded x10.

                After some messing around I managed to fix the "no walls" problem as well as the aiming problem and the ballerina problem, but only individually, I couldn't find a solution to combine the last two, here's why:

                The aim fix involved changing one of the locations for calculating the pawn's rotation, which is also used for direction of the shot. Previously this location was the pawn location, which is somewhere around the center of the head of the pawn(I think), instead of using the pawn location, using the muzzle flash location of the weapon held in hand is part of the solution, the other part is the fix I did for the walls problem, to completely fix the aiming.

                However, changing the Pawn location to the muzzle flash location to calculate the rotation, meant that, once the mouse in the x axis center of the screen, the pawn would flip because the muzzle flash location is either on the left or right side of the character, after the 180 flip, since the mouse is still in the center, the character would flip again and again and again, each tick.

                I couldn't find a way to fix that, but using the Pawn location instead of muzzle flash didn't have this problem, but then the aiming is off, one potential fix is only updating the rotation firing with a calculation and forcing a rotation change when the left or right key is pressed(Trine uses this for the Thief character, she only rotates when shooting and faces left/right depending on key presses, not mouse location)

                Another alternative could be to perhaps try to snap the rotation just before firing a shot, or just outright using a custom rotation, both might end up looking wrong, the former might see the character snapping forth and back when firing, the latter will make the shots come out wrongly out of the gun, perhaps even going through it.

                Regardless, those will be your problems if you use this code, depending on what you're making you won't find it a problem.

                So here without further ado will I post the updated code, all of the changed code is inside the PlayerTick function inside your Custom_PlayerController, so overwrite my old PlayerTick code with the new code:
                Code:
                event PlayerTick( float DeltaTime )
                {
                	local Custom_PlayerInput MyPlayerInput;
                	local Vector HitLocation, HitNormal, MuzzleLocation;
                	local Rotator NewRotation;
                
                	MyPlayerInput = Custom_PlayerInput(PlayerInput);
                	if(MyPlayerInput == none)
                		return;
                
                	AdjustedMousePosWorldPosition = MousePosWorldLocation + MousePosWorldDirection * PlayerCamera.FreeCamDistance;
                
                	// Trace from MouseLocation to the world position where the mouse actually is
                	Trace(HitLocation, HitNormal, AdjustedMousePosWorldPosition, MousePosWorldLocation);
                
                	// If the trace hit something, use the HitLocation, otherwise keep current value
                	if(HitLocation != vect(0,0,0))
                		AdjustedMousePosWorldPosition = HitLocation;
                
                	MuzzleLocation = Pawn.Weapon.GetMuzzleLoc();
                
                	// Adjust the mouse position so the Y axis matches the muzzle flash location Y axis
                	AdjustedMousePosWorldPosition.Y = MuzzleLocation.Y;
                
                	// Create a rotation from Pawn's muzzle flash location to the Adjusted mouse position
                	NewRotation = Rotator(AdjustedMousePosWorldPosition - MuzzleLocation);
                
                	// If the rotation yaw isn't 0 flip the Y axis movement
                	if(NewRotation.Yaw != 0.f)
                		MyPlayerInput.aBaseY = -MyPlayerInput.aBaseY;
                
                	// Ignore any attempt to strafe
                	MyPlayerInput.aStrafe = 0.f;
                
                    super.PlayerTick(DeltaTime);
                
                	// Set the rotation on the controller
                	SetRotation(NewRotation);
                
                	// Adjust pitch to 0 for pawns rotation
                	NewRotation.Pitch = 0;
                
                	// Then set pawns rotation as well
                	Pawn.SetRotation(NewRotation);
                }
                Note that, this updated PlayerTick function includes the code for the first Update, if you haven't done that update, controls may not respond as you expect, so for setting the controls. be sure to look back at:
                http://forums.epicgames.com/showpost...81&postcount=3

                This code fixes:
                - Character rotating wrong when there is no background wall to trace against
                - Aim when firing being off.

                If you would like to fix the ballerina problem at the expense of fixing the aim problem(but still fixing the character rotation problem) then you can replace:
                Code:
                MuzzleLocation = Pawn.Weapon.GetMuzzleLoc();
                With:
                Code:
                MuzzleLocation = Pawn.Location;
                And perhaps rename the MuzzleLocation local var if you want, this will fix the ballerina problem, but now the character aims using the Pawn Location(somewhere around eyeheight) instead of the end of the gun and thus shots will be "off" from the crosshair.

                Comment


                  #9
                  I tried to modify this script to make it possible to aim at all directions. What I mean is that you could shoot at foreground and background too.
                  I commented out this line
                  Code:
                  AdjustedMousePosWorldPosition.Y = MuzzleLocation.Y;
                  but it didnt do the trick. It kinda works, but only for foreground. And aiming is little bit off. Any idea why the aiming is little bit off and why I cant aim in the background?

                  Comment


                    #10
                    Sorry for necro threading, but I have to say, I was trying to get an aiming system like this working for ages!!!
                    And this helped so much!
                    ThankYou so much DuckSauce! :3

                    Also AnoPrkl, here's something for you =]

                    Declare this at the top

                    Code:
                    var int         extraPosisitioning;
                    //Create these two functions
                    Code:
                    exec function background()
                    {
                        if(extraPosisitioning < 500)
                        {
                            extraPosisitioning += 1;
                        }
                    }
                    
                    exec function foreground()
                    {
                        if(extraPosisitioning > -500)
                        {
                            extraPosisitioning -= 1;
                        }
                    }
                    and in PlayerTick
                    replace

                    Code:
                    AdjustedMousePosWorldPosition.Y = MuzzleLocation.Y;
                    with

                    Code:
                    AdjustedMousePosWorldPosition.Y = MuzzleLocation.Y + extraPosisitioning;
                    and initialize it in your defaults as

                    Code:
                    extraPosisitioning = 0
                    and then lastly, assign those two functions to scrollup for background
                    and scrolldown to foreground.

                    You may need to fuss around with the values, e.g. the '500's can be whatever suits you, and the +=1 or -=1 can be changed to make it faster....
                    Yea, hope it works, haven't tried it

                    Comment

                    Working...
                    X