@rbxts/gravity-controller 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @rbxts/gravity-controller
2
+
3
+ Typescript bindings for [EgoMoose's Rbx-Gravity-Controller](https://github.com/EgoMoose/Rbx-Gravity-Controller) with
4
+ ground normal finding by [EmilyBendsSpace](https://x.com/EmilyBendsSpace)
5
+
6
+ ## Flamework setup
7
+
8
+ 1. Add `installGravityControllerClass` to the `onInit` of some `@Service`
9
+
10
+ ```typescript
11
+ import { OnInit, Service } from '@flamework/core'
12
+ import { installGravityControllerClass } from '@rbxts/gravity-controller'
13
+
14
+ @Service()
15
+ export class GravityService implements OnInit {
16
+ onInit() {
17
+ // Install EgoMoose's Rbx-Gravity-Controller https://github.com/EgoMoose/Rbx-Gravity-Controller
18
+ installGravityControllerClass()
19
+ }
20
+ }
21
+ ```
22
+
23
+ 2. Setup a `GravityController` in some `@Controller`
24
+
25
+ ```typescript
26
+ import { Controller, OnStart } from '@flamework/core'
27
+ import {
28
+ getGravityControllerUp,
29
+ GravityController,
30
+ GravityControllerClass,
31
+ installGravityControllerClass,
32
+ } from '@rbxts/gravity-controller'
33
+ import { Players } from '@rbxts/services'
34
+
35
+ @Controller({})
36
+ export class PlayerGravityController implements OnStart {
37
+ gravityControllerClass: GravityControllerClass | undefined
38
+ gravityController: GravityController | undefined
39
+
40
+ onStart() {
41
+ this.gravityControllerClass = installGravityControllerClass()
42
+ Players.LocalPlayer.CharacterAdded.Connect((_character) => {
43
+ this.disableGravityController()
44
+ this.enableGravityController()
45
+ })
46
+ if (Players.LocalPlayer.Character) this.enableGravityController()
47
+ }
48
+
49
+ disableGravityController() {
50
+ this.gravityController?.Destroy()
51
+ this.gravityController = undefined
52
+ }
53
+
54
+ enableGravityController() {
55
+ if (this.gravityController || !this.gravityControllerClass) return
56
+ const gravityController = new this.gravityControllerClass(Players.LocalPlayer)
57
+
58
+ // Use EmilyBendsSpace's getGroundNormal() to walk up walls
59
+ gravityController.GetGravityUp = getGravityControllerUp
60
+
61
+ this.gravityController = gravityController
62
+ }
63
+ }
64
+
65
+ ```
package/out/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /// <reference types="@rbxts/types" />
2
+ /// <reference types="@rbxts/types" />
3
+ /// <reference types="@rbxts/types" />
4
+ export interface GravityController extends Instance {
5
+ Player: Player;
6
+ Character: Model;
7
+ Humanoid: Humanoid;
8
+ HRP: BasePart;
9
+ Maid: {
10
+ Mark: (connection: RBXScriptConnection) => void;
11
+ };
12
+ GetFallHeight(): number;
13
+ GetGravityUp(self: GravityController, oldGravityUp: Vector3): Vector3;
14
+ ResetGravity(gravityDirection: Vector3): void;
15
+ }
16
+ export interface GravityControllerClass {
17
+ new (player: Player): GravityController;
18
+ }
19
+ export declare let gravityControllerClass: GravityControllerClass;
20
+ export declare function installGravityControllerClass(): GravityControllerClass;
21
+ export declare function getGroundNormal(cframe: CFrame, originOffset: Vector3, oldGravityUp: Vector3): Vector3;
22
+ export declare function getGravityControllerUp(gravityController: GravityController, oldGravityUp: Vector3): Vector3;
package/out/init.lua ADDED
@@ -0,0 +1,174 @@
1
+ -- Compiled with roblox-ts v2.3.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
5
+ local Players = _services.Players
6
+ local ReplicatedStorage = _services.ReplicatedStorage
7
+ local RunService = _services.RunService
8
+ local StarterPlayer = _services.StarterPlayer
9
+ local function installGravityControllerClass()
10
+ if exports.gravityControllerClass then
11
+ return exports.gravityControllerClass
12
+ end
13
+ if RunService:IsServer() then
14
+ local starterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts")
15
+ local starterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts")
16
+ local _parent = script.Parent
17
+ if _parent ~= nil then
18
+ _parent = _parent:WaitForChild("GravityController")
19
+ end
20
+ local parent = _parent
21
+ if not parent then
22
+ error("GravityController module not found")
23
+ end
24
+ local client = parent:WaitForChild("Client")
25
+ local replace = function(child, parent)
26
+ local found = parent:FindFirstChild(child.Name)
27
+ local _result = found
28
+ if _result ~= nil then
29
+ _result:Destroy()
30
+ end
31
+ child.Parent = parent
32
+ end
33
+ replace(client:WaitForChild("PlayerScriptsLoader"), starterPlayerScripts)
34
+ replace(client:WaitForChild("RbxCharacterSounds"), starterPlayerScripts)
35
+ replace(client:WaitForChild("Animate"), starterCharacterScripts)
36
+ parent:WaitForChild("GravityController").Parent = ReplicatedStorage
37
+ end
38
+ exports.gravityControllerClass = require(ReplicatedStorage:WaitForChild("GravityController"))
39
+ return exports.gravityControllerClass
40
+ end
41
+ local PI2 = math.pi * 2
42
+ local ZERO = Vector3.new(0, 0, 0)
43
+ local LOWER_RADIUS_OFFSET = 3
44
+ local NUM_DOWN_RAYS = 24
45
+ local ODD_DOWN_RAY_START_RADIUS = 3
46
+ local EVEN_DOWN_RAY_START_RADIUS = 2
47
+ local ODD_DOWN_RAY_END_RADIUS = 1.66666
48
+ local EVEN_DOWN_RAY_END_RADIUS = 1
49
+ local NUM_FEELER_RAYS = 9
50
+ local FEELER_LENGTH = 2
51
+ local FEELER_START_OFFSET = 2
52
+ local FEELER_RADIUS = 3.5
53
+ local FEELER_APEX_OFFSET = 1
54
+ local FEELER_WEIGHTING = 8
55
+ -- Thanks to EmilyBendsSpace for the new get normal function!
56
+ -- https://devforum.roblox.com/t/example-source-smooth-wall-walking-gravity-controller-from-club-raven/440229?u=egomoose
57
+ local function getGroundNormal(cframe, originOffset, oldGravityUp)
58
+ local ignoreList = {}
59
+ for _, player in Players:GetPlayers() do
60
+ if player.Character then
61
+ local _character = player.Character
62
+ table.insert(ignoreList, _character)
63
+ end
64
+ end
65
+ local _position = cframe.Position
66
+ local _originOffset = originOffset
67
+ local origin = _position + _originOffset
68
+ local radialVector = if math.abs(cframe.LookVector:Dot(oldGravityUp)) < 0.999 then cframe.LookVector:Cross(oldGravityUp) else cframe.RightVector:Cross(oldGravityUp)
69
+ local centerRayLength = 25
70
+ local _oldGravityUp = oldGravityUp
71
+ local _arg0 = -centerRayLength
72
+ local centerRay = Ray.new(origin, _oldGravityUp * _arg0)
73
+ local centerHit, _centerHitPoint, centerHitNormal = game.Workspace:FindPartOnRayWithIgnoreList(centerRay, ignoreList)
74
+ local mainDownNormal = if centerHit then centerHitNormal else ZERO
75
+ local centerRayHitCount = 0
76
+ local evenRayHitCount = 0
77
+ local oddRayHitCount = 0
78
+ local downHitCount = 0
79
+ local downRaySum = ZERO
80
+ do
81
+ local i = 0
82
+ local _shouldIncrement = false
83
+ while true do
84
+ if _shouldIncrement then
85
+ i += 1
86
+ else
87
+ _shouldIncrement = true
88
+ end
89
+ if not (i < NUM_DOWN_RAYS) then
90
+ break
91
+ end
92
+ local dtheta = PI2 * ((i - 1) / NUM_DOWN_RAYS)
93
+ local angleWeight = 0.25 + 0.75 * math.abs(math.cos(dtheta))
94
+ local isEvenRay = i % 2 == 0
95
+ local startRadius = if isEvenRay then EVEN_DOWN_RAY_START_RADIUS else ODD_DOWN_RAY_START_RADIUS
96
+ local endRadius = if isEvenRay then EVEN_DOWN_RAY_END_RADIUS else ODD_DOWN_RAY_END_RADIUS
97
+ local downRayLength = centerRayLength
98
+ local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
99
+ local _oldGravityUp_1 = oldGravityUp
100
+ local _arg0_1 = -LOWER_RADIUS_OFFSET
101
+ local _arg0_2 = endRadius - startRadius
102
+ local _arg0_3 = offset * _arg0_2
103
+ local dir = _oldGravityUp_1 * _arg0_1 + _arg0_3
104
+ local _arg0_4 = offset * startRadius
105
+ local ray = Ray.new(origin + _arg0_4, dir.Unit * downRayLength)
106
+ local hit, _hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
107
+ if hit then
108
+ local _downRaySum = downRaySum
109
+ local _arg0_5 = hitNormal * angleWeight
110
+ downRaySum = _downRaySum + _arg0_5
111
+ downHitCount = downHitCount + 1
112
+ if isEvenRay then
113
+ evenRayHitCount = evenRayHitCount + 1
114
+ else
115
+ oddRayHitCount = oddRayHitCount + 1
116
+ end
117
+ end
118
+ end
119
+ end
120
+ local feelerHitCount = 0
121
+ local feelerNormalSum = ZERO
122
+ do
123
+ local i = 0
124
+ local _shouldIncrement = false
125
+ while true do
126
+ if _shouldIncrement then
127
+ i += 1
128
+ else
129
+ _shouldIncrement = true
130
+ end
131
+ if not (i < NUM_FEELER_RAYS) then
132
+ break
133
+ end
134
+ local dtheta = 2 * math.pi * ((i - 1) / NUM_FEELER_RAYS)
135
+ local angleWeight = 0.25 + 0.75 * math.abs(math.cos(dtheta))
136
+ local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
137
+ local _exp = offset * FEELER_RADIUS
138
+ local _oldGravityUp_1 = oldGravityUp
139
+ local _arg0_1 = -LOWER_RADIUS_OFFSET
140
+ local dir = (_exp + (_oldGravityUp_1 * _arg0_1)).Unit
141
+ local _oldGravityUp_2 = oldGravityUp
142
+ local _arg0_2 = -FEELER_APEX_OFFSET
143
+ local _exp_1 = origin - (_oldGravityUp_2 * _arg0_2)
144
+ local _arg0_3 = dir * FEELER_START_OFFSET
145
+ local feelerOrigin = _exp_1 + _arg0_3
146
+ local ray = Ray.new(feelerOrigin, dir * FEELER_LENGTH)
147
+ local hit, _hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
148
+ if hit then
149
+ local _feelerNormalSum = feelerNormalSum
150
+ local _arg0_4 = FEELER_WEIGHTING * angleWeight
151
+ local _arg0_5 = hitNormal * _arg0_4
152
+ feelerNormalSum = _feelerNormalSum + _arg0_5
153
+ feelerHitCount = feelerHitCount + 1
154
+ end
155
+ end
156
+ end
157
+ if centerRayHitCount + downHitCount + feelerHitCount > 0 then
158
+ local _downRaySum = downRaySum
159
+ local _exp = mainDownNormal + _downRaySum
160
+ local _feelerNormalSum = feelerNormalSum
161
+ local normalSum = _exp + _feelerNormalSum
162
+ if normalSum ~= ZERO then
163
+ return normalSum.Unit
164
+ end
165
+ end
166
+ return oldGravityUp
167
+ end
168
+ local function getGravityControllerUp(gravityController, oldGravityUp)
169
+ return getGroundNormal(gravityController.HRP.CFrame, if gravityController.Humanoid.RigType == Enum.HumanoidRigType.R15 then ZERO else oldGravityUp * 0.35, oldGravityUp)
170
+ end
171
+ exports.installGravityControllerClass = installGravityControllerClass
172
+ exports.getGroundNormal = getGroundNormal
173
+ exports.getGravityControllerUp = getGravityControllerUp
174
+ return exports
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@rbxts/gravity-controller",
3
+ "version": "1.0.0",
4
+ "main": "out/init.lua",
5
+ "keywords": [
6
+ "gravity",
7
+ "rbxts"
8
+ ],
9
+ "author": "angleopera",
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/AngleOpera/gravity-controller.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/AngleOpera/gravity-controller/issues"
17
+ },
18
+ "types": "out/index.d.ts",
19
+ "files": [
20
+ "out",
21
+ "GravityController.rbxmx",
22
+ "!**/*.tsbuildinfo"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "rbxtsc",
29
+ "prepublishOnly": "rbxtsc"
30
+ },
31
+ "devDependencies": {
32
+ "@rbxts/compiler-types": "^2.3.0-types.0",
33
+ "@rbxts/types": "^1.0.760",
34
+ "@typescript-eslint/eslint-plugin": "^7.1.1",
35
+ "@typescript-eslint/parser": "^7.1.1",
36
+ "copyfiles": "^2.4.1",
37
+ "eslint": "^8.57.0",
38
+ "eslint-config-prettier": "^9.1.0",
39
+ "eslint-plugin-prettier": "^5.1.3",
40
+ "eslint-plugin-roblox-ts": "^0.0.36",
41
+ "eslint-plugin-simple-import-sort": "^12.0.0",
42
+ "eslint-plugin-unused-imports": "^3.1.0",
43
+ "prettier": "^3.2.5",
44
+ "roblox-ts": "^2.3.0-dev-3a89c93",
45
+ "typescript": "5.4.2"
46
+ },
47
+ "dependencies": {
48
+ "@rbxts/services": "^1.5.4"
49
+ }
50
+ }