├── .gitignore ├── Doc └── Images │ ├── Banner1.png │ └── Banner2.png ├── README.md └── Source ├── CharacterController.py ├── CharacterController_FirstPerson.blend ├── CharacterController_ThirdPerson.blend ├── FirstPersonCamera.py ├── SimpleAnimator.py ├── ThirdPersonCamera.py └── textures ├── Grid-Level2.png ├── PaperboxGuy_d.png └── PaperboxGuy_n.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # Other... 22 | *.blend1 23 | *.blend2 24 | *.pyc 25 | .idea/ 26 | .__pycache__/ 27 | 28 | 29 | # ========================= 30 | # Operating System Files 31 | # ========================= 32 | -------------------------------------------------------------------------------- /Doc/Images/Banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Doc/Images/Banner1.png -------------------------------------------------------------------------------- /Doc/Images/Banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Doc/Images/Banner2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Character Controller** Template | Third/First Person and much more! 2 | ###### For UPBGE 0.2.2+ 3 | This templates was created to help Blender Game Engine (UPBGE) users to create games or any kind of interactive things that requests a Character Controller. Easy to use, easy to attach to your project. 4 | 5 | ![](Doc/Images/Banner1.png) 6 | 7 | To use, just download the files, open it on UPBGE (version 0.2.3 recommended) and you're done! 8 | You can use this template in your projects, even for commercial projects. Just credit me for this.:) 9 | It's very easy to use in your projects: Just load this script into your .blend file (or paste them in the same folder that your .blend is), select the object that you want, and attach the script into the object's components. 10 | 11 | #### Note: 12 | This repository includes some **3D models**, **Animations** and **Textures** created by Guilherme Teres Nunes (me). Again: Don't forget to credit me if you use some of them as well. 13 | [Access my Sketchfab Here!](https://sketchfab.com/unidaystudio) 14 | 15 | ## Character Controller Component 16 | This component will serve as a **Character Controller** for your game. With this, you can easly made an object move using W,A,S,D, run with LSHIFT and Jump with SPACE. 17 | 18 | Create a capsule for your character, set the physics type to "Character" and attach this Component to them. 19 | 20 | It's very simple to configure: 21 | - **Activate**: If you want this component running. 22 | - **Walk Speed**: The character's walk speed. 23 | - **Run Speed**: The character's run speed. 24 | - **Max Jumps**: The character's max jumps. Set to zero (0) if you don't want the character to jump. 25 | - **Static Jump Direction**: If you want to make your character jump in a static direction, activate "Static Jump Direction". It means that, if the player wasn't moving when he pressed Space, the character will jump up and the player will not be able to change this during the jump. The same for when he was moving when pressed Space. The jump direction will be the character direction when the player press space. 26 | - **Static Jump Rotation**: Exactly like the Jump Direction, but for the character rotation. 27 | - **Avoid Sliding**: If your character object have Collision Bounds activated, I'd recommend to enable the "Avoid Sliding" option. If so, the component will avoid the character from sliding on ramps. 28 | - **Smooth Character Movement**: You can make the movement gets more smooth by increasing this value (0.0 to 1.0). 29 | - **Make Object Invisible**: Makes the object invisible ingame (useful if you attach this component to a capsule object that have a armature inside). 30 | 31 | ## First Person Camera Component 32 | This component was created to be attached to your camera to give you a great mouselook control. Very useful for First Person games. 33 | 34 | To use, add a camera in your scene, parent them into your character capsule (you can use the Character controller Component on it), and attach this Component to the camera. Don't forgot to position the camera in a place near the "head" of your character. 35 | 36 | You can configure the mouse sensibility, invert X or Y axis and enable/disable the camera rotation limit. It's very simple to configure: 37 | - **Activate**: If you want this component running. 38 | - **Mouse Sensibility**: The mouse sensibility. 39 | - **Invert Mouse X Axis**: To invert the mouselook on the X axis. 40 | - **Invert Mouse Y Axis**: To invert the mouselook on the Y axis. 41 | - **Limit Camera Rotation**: Limits the camera rotation on the X local axis. Very useful for First Person games to avoid the camera from flip upside down. 42 | 43 | ## Third Person Camera Component 44 | This component was created to be attached to your camera to give you a great third person mouselook control. Very useful for Adventure games, RPGs, Open Worlds, or any kind of games that may require a third person camera. 45 | 46 | To use, add a camera in your scene, parent them into your character capsule (you can use the Character controller Component on it), and attach this Component to the camera. And you're done! The component will do the rest for you. :) 47 | 48 | You can configure the mouse sensibility, invert X or Y axis and enable/disable the camera rotation limit. 49 | It's very simple to configure: 50 | - **Activate**: If you want this component running. 51 | - **Mouse Sensibility**: The mouse sensibility. 52 | - **Invert Mouse X Axis**: To invert the mouselook on the X axis. 53 | - **Invert Mouse Y Axis**: To invert the mouselook on the Y axis. 54 | - **Camera Height**: The height that you want your camera to be (consider height zero = the center of your character). 55 | - **Camera Distance**: How far from the character that you want your camera to be. 56 | - **Camera Crab (Side)**: You can make the camera stay on the side of your character, if you want. Just adjust this variable. 57 | - **Camera Collision**: If you want your camera to have collision (to prevent the camera from traversing walls). 58 | - **Camera Collision Property**: The property that you want your camera to avoid (if you want the camera to avoid all the objects, leave this blank). 59 | - **Align Player to View**: You can define when you want the player (character) to look at the camera view direction: Never, just when the player moves or always. 60 | - **Align Player Smooth**: How smooth you want the player to look at the camera direction. Zero means no smooth and one (1) means maximum smooth possible. 61 | 62 | By using this Component, you can also call some functions using python (from other components) to help you: setCameraAlign(type), setCameraPos(x,y,z), alignPlayerToView(), getCameraView(). Take a look at the implementation to see how these functions works. 63 | 64 | ## Simple Animator Component 65 | This component will automatically align the armature to the move direction of your character, runs the right animations accordding to the speed and if the character is on air or not. 66 | 67 | To use, attach this component to the armature of your character. It's important that the armature is parented with an capsule object with physics type equals to Character. 68 | It's very simple to configure: 69 | - **Activate**: If you want this component running. 70 | - **Max Walk Speed**: Define the max speed that you want while executing the walk animation. After this speed, the character will start interpolating the run animation. (Read the notes at the end). 71 | - **Max Run Speed**: Define the max speed that you want while executing the run animation. After this speed, the animation will not change. 72 | - **Suspend Children's Physic**: Enable this if you want to remove all the physics from the armature's childrens (recursive). Useful to avoid these childrens to collide with the player capsule, causing a physics bug. 73 | - **Align To Move Direction**: Enable this if you want to make you character faces the direction that the player is going. 74 | - **Align Smooth**: How smooth you want to align the character with the direction. 0 Means no smooth and 1 means max smooth. 75 | - **Idle Animation**: Define the name of the Idle (stopped) animation, the frame start and frame end. 76 | - **Walk Animation**: Define the name of the Walk animation, the frame start and frame end. 77 | - **Run Animation**: Define the name of the Run animation, the frame start and frame end. 78 | - **Jump Up Animation**: Define the name of the Jump Up animation, the frame start and frame end. 79 | - **Jump Down Animation**: Define the name of the Jump Down animation, the frame start and frame end. 80 | The **Jump** animations should be divided in two: Jump Up and Jump Down. The first one will be executed when the character is going up. The second, whe the character is falling. Both should be loop animations. 81 | 82 | **NOTE:** The anim interpolation/transition between idle-walk and walk-run according to the speed is not implemented yet. 83 | 84 | ![](Doc/Images/Banner2.png) 85 | 86 | Created by **Guilherme Teres Nunes** 87 | 88 | Access my youtube channel: 89 | ## [Uniday Studio on Youtube](youtube.com/UnidayStudio) -------------------------------------------------------------------------------- /Source/CharacterController.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Character Controller | Template v 1.0 | UPBGE 0.2.3 # 3 | ############################################################################### 4 | # Created by: Guilherme Teres Nunes # 5 | # Access: youtube.com/UnidayStudio # 6 | # github.com/UnidayStudio # 7 | # github.com/UnidayStudio/UPBGE-CharacterController # 8 | ############################################################################### 9 | import bge 10 | from collections import OrderedDict 11 | from mathutils import Vector, Matrix 12 | 13 | def clamp(x, a, b): 14 | return min(max(a, x), b) 15 | 16 | class CharacterController(bge.types.KX_PythonComponent): 17 | """ Character Controller Component: 18 | Create a capsule for your character, set the physics type to "Character" 19 | and attach this Component to them. 20 | You can configure the Walk, Run speed and the Max Jumps on the Component 21 | panel. If your character object have Collision Bounds activated, I'd 22 | recommend to enable the "Avoid Sliding" option. 23 | If you want to make your character jump in a static direction, activate 24 | "Static Jump Direction". It means that, if the player wasn't moving when 25 | he pressed Space, the character will jump up and the player will not be able 26 | to change this during the jump. The same for when he was moving when pressed 27 | Space. Same for the rotation (Static Jump Rotation).""" 28 | 29 | args = OrderedDict([ 30 | ("Activate", True), 31 | ("Walk Speed", 0.1), 32 | ("Run Speed", 0.2), 33 | ("Max Jumps", 1), 34 | ("Avoid Sliding", True), 35 | ("Static Jump Direction", False), 36 | ("Static Jump Rotation", False), 37 | ("Smooth Character Movement", 0.0), 38 | ("Make Object Invisible", False), 39 | ]) 40 | 41 | def start(self, args): 42 | """Start Function""" 43 | 44 | self.active = args["Activate"] 45 | 46 | self.walkSpeed = args["Walk Speed"] 47 | self.runSpeed = args["Run Speed"] 48 | 49 | self.avoidSliding = args["Avoid Sliding"] 50 | self.__lastPosition = self.object.worldPosition.copy() 51 | self.__lastDirection = Vector([0,0,0]) 52 | self.__smoothSlidingFlag = False 53 | 54 | self.__smoothMov = clamp(args["Smooth Character Movement"], 0, 0.99) 55 | self.__smoothLast = Vector([0,0,0]) 56 | 57 | self.staticJump = args["Static Jump Direction"] 58 | self.__jumpDirection = [0,0,0] 59 | 60 | self.staticJumpRot = args["Static Jump Rotation"] 61 | self.__jumpRotation = Matrix.Identity(3) 62 | 63 | self.character = bge.constraints.getCharacter(self.object) 64 | self.character.maxJumps = args["Max Jumps"] 65 | 66 | if self.active: 67 | if args["Make Object Invisible"]: 68 | self.object.visible = False 69 | 70 | def characterMovement(self): 71 | """Makes the character walk with W,A,S,D 72 | (You can run by holding Left Shift)""" 73 | 74 | keyboard = bge.logic.keyboard.inputs 75 | keyTAP = bge.logic.KX_INPUT_JUST_ACTIVATED 76 | 77 | x = 0 78 | y = 0 79 | speed = self.walkSpeed 80 | 81 | if keyboard[bge.events.LEFTSHIFTKEY].active: 82 | speed = self.runSpeed 83 | 84 | if keyboard[bge.events.WKEY].active: y = 1 85 | elif keyboard[bge.events.SKEY].active: y = -1 86 | if keyboard[bge.events.AKEY].active: x = -1 87 | elif keyboard[bge.events.DKEY].active: x = 1 88 | 89 | vec = Vector([x, y, 0]) 90 | self.__smoothSlidingFlag = False 91 | if vec.length != 0: 92 | self.__smoothSlidingFlag = True 93 | # Normalizing the vector. 94 | vec.normalize() 95 | # Multiply by the speed 96 | vec *= speed 97 | 98 | # This part is to make the static jump Direction works. 99 | if not self.character.onGround: 100 | if self.staticJump: 101 | vec = self.__jumpDirection 102 | if self.staticJumpRot: 103 | self.object.worldOrientation = self.__jumpRotation.copy() 104 | else:#elif self.character.onGround: 105 | self.__jumpDirection = vec 106 | self.__jumpRotation = self.object.worldOrientation.copy() 107 | 108 | 109 | 110 | smooth = 1.0 - self.__smoothMov 111 | vec = self.__smoothLast.lerp(vec, smooth) 112 | self.__smoothLast = vec 113 | test = self.object.worldPosition.copy() 114 | self.character.walkDirection = self.object.worldOrientation * vec 115 | 116 | 117 | if vec.length != 0: 118 | self.__lastDirection = self.object.worldPosition - self.__lastPosition 119 | self.__lastPosition = self.object.worldPosition.copy() 120 | 121 | def characterJump(self): 122 | """Makes the Character jump with SPACE.""" 123 | 124 | keyboard = bge.logic.keyboard.inputs 125 | keyTAP = bge.logic.KX_INPUT_JUST_ACTIVATED 126 | 127 | if keyTAP in keyboard[bge.events.SPACEKEY].queue: 128 | self.character.jump() 129 | 130 | # 131 | def avoidSlide(self): 132 | """Avoids the character to slide. This funtion is useful when you have 133 | Collision Bounds activated.""" 134 | 135 | self.object.worldPosition.xy = self.__lastPosition.xy 136 | 137 | other = self.object.worldOrientation * self.__smoothLast 138 | 139 | if self.__lastDirection.length != 0 and other.length != 0: 140 | if self.__lastDirection.angle(other) > 0.5: 141 | if not self.__smoothSlidingFlag: 142 | self.__smoothLast = Vector([0,0,0]) 143 | 144 | def update(self): 145 | """Update Function""" 146 | 147 | if self.active: 148 | self.characterMovement() 149 | self.characterJump() 150 | 151 | if self.avoidSliding: 152 | self.avoidSlide() 153 | 154 | -------------------------------------------------------------------------------- /Source/CharacterController_FirstPerson.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Source/CharacterController_FirstPerson.blend -------------------------------------------------------------------------------- /Source/CharacterController_ThirdPerson.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Source/CharacterController_ThirdPerson.blend -------------------------------------------------------------------------------- /Source/FirstPersonCamera.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # First Person Camera | Template v 1.0 | UPBGE 0.2.3 # 3 | ############################################################################### 4 | # Created by: Guilherme Teres Nunes # 5 | # Access: youtube.com/UnidayStudio # 6 | # github.com/UnidayStudio # 7 | # github.com/UnidayStudio/UPBGE-CharacterController # 8 | ############################################################################### 9 | import bge 10 | from collections import OrderedDict 11 | from mathutils import Vector 12 | 13 | class FirstPersonCamera(bge.types.KX_PythonComponent): 14 | """ First Person Camera Component: 15 | Add a camera in your scene, parent them into your character capsule (you 16 | can use the Character controller Component on it), and attach this Component 17 | to the camera. Don't forgot to position the camera in a place near the "head" 18 | of your character. 19 | You can configure the mouse sensibility, invert X or Y axis and 20 | enable/disable the camera rotation limit.""" 21 | 22 | args = OrderedDict([ 23 | ("Activate", True), 24 | ("Mouse Sensibility", 2.0), 25 | ("Invert Mouse X Axis", False), 26 | ("Invert Mouse Y Axis", False), 27 | ("Limit Camera Rotation", True), 28 | ]) 29 | 30 | def start(self, args): 31 | """Start Function""" 32 | self.active = args["Activate"] 33 | 34 | self.mouseSens = args["Mouse Sensibility"] * (-0.001) 35 | self.invertX = [1, -1][args["Invert Mouse X Axis"]] 36 | self.invertY = [1, -1][args["Invert Mouse Y Axis"]] 37 | self.limitRot = args["Limit Camera Rotation"] 38 | 39 | def mouselook(self): 40 | """Mouselook function: Makes the mouse look at where you move your 41 | mouse.""" 42 | 43 | wSize = Vector([bge.render.getWindowWidth(), 44 | bge.render.getWindowHeight()]) 45 | 46 | wCenter = Vector([int(wSize[0] * 0.5), int(wSize[1] * 0.5)]) 47 | 48 | mPos = Vector(bge.logic.mouse.position) 49 | mPos[0] = int(mPos[0] * wSize[0]) 50 | mPos[1] = int(mPos[1] * wSize[1]) 51 | 52 | mDisp = mPos - wCenter 53 | mDisp *= self.mouseSens 54 | 55 | obj = self.object.parent 56 | if obj == None: 57 | obj = self.object 58 | obj.applyRotation([0, 0, mDisp[0]*self.invertX], False) 59 | self.object.applyRotation([mDisp[1]*self.invertY, 0, 0], True) 60 | 61 | bge.render.setMousePosition(int(wCenter[0]), int(wCenter[1])) 62 | 63 | def cameraLimits(self): 64 | """Camera Limits function: Defines a rotation limit to the camera to 65 | avoid it from rotating too much (and gets upside down)""" 66 | xyz = self.object.localOrientation.to_euler() 67 | 68 | if abs(xyz[0]) > 2.7: 69 | xyz[0] = 2.7 70 | elif abs(xyz[0]) < 0.3: 71 | xyz[0] = 0.3 72 | 73 | self.object.localOrientation = xyz.to_matrix() 74 | 75 | def update(self): 76 | """Update Function""" 77 | if self.active: 78 | self.mouselook() 79 | 80 | if self.limitRot: 81 | self.cameraLimits() -------------------------------------------------------------------------------- /Source/SimpleAnimator.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Simple Animator | Template v 1.0 | UPBGE 0.2.3 # 3 | ############################################################################### 4 | # Created by: Guilherme Teres Nunes # 5 | # Access: youtube.com/UnidayStudio # 6 | # github.com/UnidayStudio # 7 | # github.com/UnidayStudio/UPBGE-CharacterController # 8 | ############################################################################### 9 | import bge 10 | from mathutils import Vector 11 | from math import pi 12 | from collections import OrderedDict 13 | 14 | def clamp(x, a, b): 15 | return min(max(a, x), b) 16 | 17 | class SimpleAnimator(bge.types.KX_PythonComponent): 18 | """ Simple Animator Component: 19 | Attach this component to the armature of your character. It's important 20 | that the armature is parented with an capsule object with physics type equals 21 | to Character. 22 | This component will automatically align the armature to the move direction 23 | of your character, runs the right animations accordding to the speed and if 24 | the character is on air or not.""" 25 | 26 | args = OrderedDict([ 27 | ("Activate", True), 28 | ("Max Walk Speed", 0.1), 29 | ("Max Run Speed", 0.2), 30 | ("Suspend Children's Physics", True), 31 | ("Align To Move Direction", True), 32 | ("Align Smooth", 0.5), 33 | 34 | ("Idle Animation", ""), 35 | ("Idle Frame Start-End", Vector([0,10])), 36 | 37 | ("Walk Animation", ""), 38 | ("Walk Frame Start-End", Vector([0, 10])), 39 | 40 | ("Run Animation", ""), 41 | ("Run Frame Start-End", Vector([0, 10])), 42 | 43 | ("Jump Up Animation", ""), 44 | ("Jump Up Frame Start-End", Vector([0, 10])), 45 | 46 | ("Jump Down Animation", ""), 47 | ("Jump Down Frame Start-End", Vector([0, 10])), 48 | ]) 49 | 50 | def start(self, args): 51 | """Start Function""" 52 | self.active = args["Activate"] 53 | 54 | self.__lastPosition = self.object.worldPosition.copy() 55 | self.__moveDirection = None 56 | self.__alignMoveDir = args["Align To Move Direction"] 57 | self.alignSmooth = 1 - clamp( args["Align Smooth"], 0, 1) 58 | 59 | self.__animIdle = [args["Idle Animation"], args["Idle Frame Start-End"]] 60 | self.__animWalk = [args["Walk Animation"], args["Walk Frame Start-End"]] 61 | self.__animRun = [args["Run Animation"], args["Run Frame Start-End"]] 62 | self.__animJumpUp = [args["Jump Up Animation"], args["Jump Up Frame Start-End"]] 63 | self.__animJumpDown= [args["Jump Down Animation"], args["Jump Down Frame Start-End"]] 64 | 65 | self.maxWalkSpeed = args["Max Walk Speed"] 66 | self.maxRunSpeed = args["Max Run Speed"] 67 | 68 | # Suspend physics: 69 | if args["Suspend Children's Physics"]: 70 | self.object.suspendPhysics() 71 | for child in self.object.children: 72 | child.suspendPhysics() 73 | 74 | self.character = bge.constraints.getCharacter(self.object.parent) 75 | # Error: 76 | if self.character == None: 77 | print("[Animator] Error: Can't get the Character constraint from the armature parent.") 78 | 79 | def __updateMoveDirection(self): 80 | """Updates the move direction""" 81 | self.__moveDirection = self.object.worldPosition - self.__lastPosition 82 | self.__lastPosition = self.object.worldPosition.copy() 83 | 84 | def __animate(self, animData, blend=4): 85 | """Runs an animation""" 86 | self.object.playAction(animData[0], animData[1][0], animData[1][1], blendin=blend) 87 | 88 | def __handleGroundAnimations(self): 89 | """Handles animations on ground (Walk, Run, Idle).""" 90 | 91 | # TO DO: Interpolate between these animations according to the speed. 92 | speed = self.__moveDirection.length 93 | if speed <= 0.003: 94 | self.__animate(self.__animIdle) 95 | elif speed <= self.maxWalkSpeed+0.001: 96 | self.__animate(self.__animWalk) 97 | else: 98 | self.__animate(self.__animRun) 99 | 100 | def __handleAirAnimations(self): 101 | """Handles animations on air (Jump).""" 102 | 103 | if self.__moveDirection[2] > 0: 104 | self.__animate(self.__animJumpUp) 105 | else: 106 | self.__animate(self.__animJumpDown, 10) 107 | 108 | def getMoveDirection(self): 109 | """Returns the current move direction""" 110 | return self.__moveDirection 111 | 112 | def alignToMoveDirection(self): 113 | """Align the armature to the move direction""" 114 | 115 | length = self.__moveDirection.length 116 | if length >= 0.: 117 | # First checks if the move direction is the opposite of the current 118 | # direction. If so, applies a small rotation just to avoid a weird 119 | # delay that alignAxisToVect has in this case. 120 | vec = self.object.worldOrientation * Vector([0,1,0]) 121 | try: 122 | if vec.angle(self.__moveDirection) >= pi-0.01: 123 | self.object.applyRotation([0,0,0.01], False) 124 | except: 125 | pass 126 | 127 | length = clamp(length*20, 0, 1) * self.alignSmooth 128 | self.object.alignAxisToVect(self.__moveDirection, 1, length) 129 | self.object.alignAxisToVect([0,0,1], 2, 1) 130 | 131 | def update(self): 132 | """Update Function""" 133 | 134 | self.__updateMoveDirection() 135 | 136 | if self.active: 137 | if self.__alignMoveDir: 138 | self.alignToMoveDirection() 139 | 140 | if self.character.onGround: 141 | self.__handleGroundAnimations() 142 | else: 143 | self.__handleAirAnimations() -------------------------------------------------------------------------------- /Source/ThirdPersonCamera.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Third Person Camera | Template v 1.0 | UPBGE 0.2.3 # 3 | ############################################################################### 4 | # Created by: Guilherme Teres Nunes # 5 | # Access: youtube.com/UnidayStudio # 6 | # github.com/UnidayStudio # 7 | # github.com/UnidayStudio/UPBGE-CharacterController # 8 | ############################################################################### 9 | import bge 10 | from collections import OrderedDict 11 | from mathutils import Vector, Matrix 12 | 13 | class ThirdPersonCamera(bge.types.KX_PythonComponent): 14 | """ Third Person Camera Component: 15 | Add a camera in your scene, parent them into your character capsule (you 16 | can use the Character controller Component on it), and attach this Component 17 | to the camera. And you're done! The component will do the rest for you. :) 18 | You can configure the mouse sensibility, invert X or Y axis and 19 | enable/disable the camera rotation limit. 20 | Don't forget to configure, as well, the camera position (height, distance 21 | and crab), if you want a camera collision (to prevent the camera from 22 | traversing walls), and if you want to align the player to the Camera View.""" 23 | 24 | args = OrderedDict([ 25 | ("Activate", True), 26 | ("Mouse Sensibility", 2.0), 27 | ("Invert Mouse X Axis", False), 28 | ("Invert Mouse Y Axis", False), 29 | ("Camera Height", 0.7), 30 | ("Camera Distance", 5.0), 31 | ("Camera Crab (Side)", 0.6), 32 | ("Camera Collision", True), 33 | ("Camera Collision Property", "ground"), 34 | ("Align Player to View", {"Never", "On Player Movement", "Always"}), 35 | ("Align Player Smooth", 0.5), 36 | ]) 37 | 38 | def start(self, args): 39 | """Start Function""" 40 | self.active = args["Activate"] 41 | 42 | self.mouseSens = args["Mouse Sensibility"] * (-0.001) 43 | self.invertX = [1, -1][args["Invert Mouse X Axis"]] 44 | self.invertY = [1, -1][args["Invert Mouse Y Axis"]] 45 | 46 | # Camera Position 47 | self.__cameraPos = Vector([0,0,0]) 48 | self.setCameraPos(args["Camera Crab (Side)"], 49 | -args["Camera Distance"], 50 | args["Camera Height"]) 51 | 52 | self.cameraCol = args["Camera Collision"] 53 | self.cameraColProp = args["Camera Collision Property"] 54 | 55 | self.__camAlign = [] 56 | self.setCameraAlign(args["Align Player to View"]) 57 | self.camAlignSmooth = args["Align Player Smooth"] 58 | 59 | # To catch errors 60 | self.__error = (self.object.parent == None) 61 | if self.__error: 62 | print("[Third Person Camera] Error: The camera must be parent to an" 63 | " object.") 64 | 65 | # Private Variables 66 | self.__cameraPan = Matrix.Identity(3) # Rotate around Z axis (global) 67 | self.__cameraTilt = Matrix.Identity(3) # Rotate around X axis (local) 68 | 69 | self.__playerPos = None 70 | if not self.__error: 71 | self.__playerPos = self.object.parent.worldPosition.copy() 72 | 73 | 74 | 75 | ####--###################################################### 76 | 77 | def __pan(self, angle): 78 | """Private function: Rotate on the Z axis (pan)""" 79 | xyz = self.__cameraPan.to_euler() 80 | xyz[2] += angle 81 | self.__cameraPan = xyz.to_matrix() 82 | 83 | def __tilt(self, angle): 84 | """Private function: Rotate on the X axis (Tilt)""" 85 | xyz = self.__cameraTilt.to_euler() 86 | xyz[0] += angle 87 | self.__cameraTilt = xyz.to_matrix() 88 | 89 | def __getWorldCameraPos(self): 90 | """Private function: Gets the world camera position (based on the tilt 91 | and pan)""" 92 | vec = self.__cameraPos.copy() 93 | vec = self.__cameraTilt * vec 94 | vec = self.__cameraPan * vec 95 | return self.object.parent.worldPosition + vec 96 | 97 | def __limitCameraRot(self): 98 | """Private function: Defines a rotation limit to the camera to avoid it 99 | from rotating too much (and gets upside down)""" 100 | xyz = self.__cameraTilt.to_euler() 101 | 102 | if xyz[0] > 1.4: 103 | xyz[0] = 1.4 104 | elif xyz[0] < -1.4: 105 | xyz[0] = -1.4 106 | 107 | self.__cameraTilt = xyz.to_matrix() 108 | 109 | def __getPlayerMovementStatus(self): 110 | """Private function: Verifies if the player is moving""" 111 | 112 | flag = False 113 | vec = self.__playerPos - self.object.parent.worldPosition.copy() 114 | if vec.length > 0.001: 115 | flag = True 116 | self.__playerPos = self.object.parent.worldPosition.copy() 117 | 118 | return flag 119 | 120 | def __applyCameraPosition(self): 121 | """Private function: Applies the camera position""" 122 | 123 | camPos = self.__getWorldCameraPos() 124 | 125 | if self.cameraCol: 126 | target = self.object.parent.worldPosition + \ 127 | Vector([0, 0, self.__cameraPos[2] * 0.5]) 128 | obHit, obPos, _ = self.object.rayCast(target, camPos, 0, 129 | self.cameraColProp, 1, 0, 0) 130 | if obHit != None: 131 | camPos = obPos 132 | self.object.worldPosition = camPos 133 | 134 | align = self.__cameraTilt * Vector([0, 1, 0]) 135 | align = self.__cameraPan * align 136 | 137 | self.object.alignAxisToVect([0, 0, 1], 1, 1) 138 | self.object.alignAxisToVect(align * (-1), 2, 1) 139 | 140 | 141 | ####--####################################################### 142 | 143 | def setCameraAlign(self, type): 144 | """Public function to change the camera alignment.""" 145 | 146 | self.__camAlign = {"Never": [0, 0], 147 | "On Player Movement": [0, 1], 148 | "Always": [1, 1]}[type] 149 | 150 | def setCameraPos(self, x, y, z): 151 | """Public function to change the camera position.""" 152 | self.__cameraPos = Vector([x,y,z]) 153 | 154 | def mouselook(self): 155 | """Mouselook function: Makes the mouse look at where you move your 156 | mouse.""" 157 | wSize = Vector([bge.render.getWindowWidth(), 158 | bge.render.getWindowHeight()]) 159 | 160 | wCenter = Vector([int(wSize[0] * 0.5), int(wSize[1] * 0.5)]) 161 | 162 | mPos = Vector(bge.logic.mouse.position) 163 | mPos[0] = int(mPos[0] * wSize[0]) 164 | mPos[1] = int(mPos[1] * wSize[1]) 165 | 166 | bge.render.setMousePosition(int(wCenter[0]), int(wCenter[1])) 167 | 168 | mDisp = mPos - wCenter 169 | mDisp *= self.mouseSens 170 | 171 | # Invert Mouselook 172 | mDisp[0] *= self.invertX 173 | mDisp[1] *= self.invertY 174 | 175 | self.__pan(mDisp[0]) 176 | self.__tilt(mDisp[1]) 177 | 178 | self.__limitCameraRot() 179 | 180 | def alignPlayerToView(self): 181 | """Aligns the player to the Camera view""" 182 | vec = self.getCameraView() 183 | self.object.parent.alignAxisToVect(vec, 1, 1.0 - self.camAlignSmooth) 184 | self.object.parent.alignAxisToVect([0,0,1], 2, 1) 185 | 186 | def getCameraView(self): 187 | """Returns the camera view direction""" 188 | return self.__cameraPan * Vector([0,1,0]) 189 | 190 | def update(self): 191 | """Update Function""" 192 | if self.active and not self.__error: 193 | self.mouselook() 194 | 195 | if self.__camAlign[self.__getPlayerMovementStatus()]: 196 | self.alignPlayerToView() 197 | 198 | self.__applyCameraPosition() -------------------------------------------------------------------------------- /Source/textures/Grid-Level2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Source/textures/Grid-Level2.png -------------------------------------------------------------------------------- /Source/textures/PaperboxGuy_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Source/textures/PaperboxGuy_d.png -------------------------------------------------------------------------------- /Source/textures/PaperboxGuy_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnidayStudio/UPBGE-CharacterController/73480778f42cfe21300de4d87372e59d88424c09/Source/textures/PaperboxGuy_n.png --------------------------------------------------------------------------------