Skip to content

Commit b764a66

Browse files
committed
Rename starter game files to demo game and added comments
1 parent 8b04423 commit b764a66

5 files changed

Lines changed: 242 additions & 176 deletions

File tree

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,109 +8,137 @@ import simd
88
import SwiftUI
99
import UntoldEngine
1010

11+
// BallState represents the different states a ball can be in during gameplay.
12+
// This makes it easier to manage transitions like idle → kick → moving → decelerating.
1113
enum BallState: Codable {
1214
case idle
1315
case kick
1416
case moving
1517
case decelerating
1618
}
1719

20+
// BallComponent is a custom ECS component that stores the ball's state and motion data.
21+
// Every entity with this component will behave like a ball in the game.
1822
public class BallComponent: Component, Codable {
19-
var motionAccumulator: simd_float3 = .zero
20-
var state: BallState = .idle
21-
var velocity: simd_float3 = .zero
23+
var motionAccumulator: simd_float3 = .zero // Used to accumulate velocity for smoother force application
24+
var state: BallState = .idle // Current state of the ball (idle, moving, etc.)
25+
var velocity: simd_float3 = .zero // Current velocity of the ball
2226
public required init() {}
2327
}
2428

29+
// ballSystemUpdate runs once per frame and updates all entities that have a BallComponent.
30+
// This is where we apply physics, update states, and define how the ball behaves.
2531
public func ballSystemUpdate(deltaTime: Float) {
32+
// Get the ID of the BallComponent so we can query entities that use it
2633
let customId = getComponentId(for: BallComponent.self)
2734
let entities = queryEntitiesWithComponentIds([customId], in: scene)
2835

2936
for entity in entities {
3037
guard let ballComponent = scene.get(component: BallComponent.self, for: entity) else { continue }
3138

39+
// Apply drag to simulate resistance as the ball moves
3240
setLinearDragCoefficient(entityId: entity, coefficients: simd_float2(0.7, 0.0))
3341
setAngularDragCoefficient(entityId: entity, coefficients: simd_float2(0.07, 0.0))
3442

43+
// Update the ball based on its current state
3544
switch ballComponent.state {
3645
case .idle:
46+
// Do nothing, the ball is at rest
3747
break
3848
case .kick:
49+
// Transition to moving when the ball is kicked
3950
ballComponent.state = .moving
4051
applyVelocity(finalVelocity: ballComponent.velocity * 5.0, deltaTime: deltaTime, ball: entity)
4152
case .moving:
42-
53+
// If the velocity drops below a threshold, start decelerating
4354
if simd_length(getVelocity(entityId: entity)) <= 0.1 {
4455
ballComponent.state = .decelerating
4556
}
4657
case .decelerating:
58+
// Gradually slow down the ball
4759
decelerate(deltaTime: deltaTime, ball: entity)
48-
if simd_length(getVelocity(entityId: entity)) < 0.1 {}
60+
if simd_length(getVelocity(entityId: entity)) < 0.1 {
61+
// You could transition back to .idle here if desired
62+
}
4963
}
5064
}
5165
}
5266

67+
// Apply a force to the ball to simulate a kick or strong push.
68+
// Uses an accumulator to smooth motion and applies both linear and angular forces.
5369
func applyVelocity(finalVelocity: simd_float3, deltaTime: Float, ball: EntityID) {
5470
guard let customComponent = scene.get(component: BallComponent.self, for: ball) else { return }
5571

5672
let mass: Float = getMass(entityId: ball)
5773
let ballDim = getDimension(entityId: ball)
74+
75+
// Blend previous motion with new input for smoother physics
5876
let bias: Float = 0.4
5977
let vComp: simd_float3 = finalVelocity * (1.0 - bias)
6078
customComponent.motionAccumulator = customComponent.motionAccumulator * bias + vComp
6179

80+
// Apply linear force based on mass and deltaTime
6281
var force: simd_float3 = (customComponent.motionAccumulator * mass) / deltaTime
6382
applyForce(entityId: ball, force: force)
6483

84+
// Apply angular force so the ball spins as it moves
6585
let upAxis = simd_float3(0.0, ballDim.depth / 2.0, 0.0)
66-
6786
force *= 0.25
68-
6987
applyMoment(entityId: ball, force: force, at: upAxis)
7088

89+
// Reset velocity so physics is only applied through forces
7190
clearVelocity(entityId: ball)
7291
clearAngularVelocity(entityId: ball)
7392
}
7493

94+
// Gradually slow down the ball by applying counter-forces.
95+
// Works similarly to applyVelocity, but reduces motion instead of adding it.
7596
func decelerate(deltaTime: Float, ball: EntityID) {
7697
guard let customComponent = scene.get(component: BallComponent.self, for: ball) else { return }
7798

7899
let ballDim = getDimension(entityId: ball)
79100
let velocity: simd_float3 = getVelocity(entityId: ball)
101+
102+
// Blend down velocity for smoother deceleration
80103
let bias: Float = 0.5
81104
let vComp: simd_float3 = velocity * (1.0 - bias)
82105
customComponent.motionAccumulator = customComponent.motionAccumulator * bias + vComp
83106

107+
// Apply counter-force to slow down
84108
var force: simd_float3 = (customComponent.motionAccumulator * getMass(entityId: ball)) / deltaTime
85109
force *= 0.15
86110
applyForce(entityId: ball, force: force)
87111

112+
// Apply spin reduction
88113
let upAxis = simd_float3(0.0, ballDim.depth / 2.0, 0.0)
89-
90114
force *= 0.25
91-
92115
applyMoment(entityId: ball, force: force, at: upAxis)
93116

117+
// Clear velocity so deceleration is handled through applied forces
94118
clearVelocity(entityId: ball)
95119
clearAngularVelocity(entityId: ball)
96120
}
97121

122+
// BallComponent_Editor integrates the BallComponent with the Editor.
123+
// This makes the component visible and editable in the UI.
98124
var BallComponent_Editor: ComponentOption_Editor = .init(
99125
id: getComponentId(for: BallComponent.self),
100126
name: "Ball Component",
101127
type: BallComponent.self,
102128
view: makeEditorView(fields: [
103-
.text(label: "Ball Component",
104-
placeholder: "Entity name",
105-
get: { entityId in
106-
getEntityName(entityId: entityId) ?? "None"
107-
},
108-
set: { entityId, targetName in
109-
setEntityName(entityId: entityId, name: targetName)
110-
}),
111-
129+
.text(
130+
label: "Ball Component",
131+
placeholder: "Entity name",
132+
get: { entityId in
133+
getEntityName(entityId: entityId) ?? "None"
134+
},
135+
set: { entityId, targetName in
136+
setEntityName(entityId: entityId, name: targetName)
137+
}
138+
),
112139
]),
113140
onAdd: { entityId in
141+
// When a BallComponent is added in the Editor, register it with the entity
114142
registerComponent(entityId: entityId, componentType: BallComponent.self)
115143
}
116144
)
Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,68 @@ import simd
1010
import SwiftUI
1111
import UntoldEngine
1212

13+
// CameraFollowComponent defines how a camera should follow a target entity.
14+
// It stores the name of the target and the offset from that target.
15+
// This makes it possible to create a third-person or top-down camera easily.
1316
public class CameraFollowComponent: Component, Codable {
14-
var targetName: String = "nil"
15-
var offset: simd_float3 = .zero
17+
var targetName: String = "nil" // The name of the entity the camera should follow
18+
var offset: simd_float3 = .zero // The offset position relative to the target
1619
public required init() {}
1720
}
1821

22+
// -----------------------------------------------------------------------------
23+
// Helper functions for accessing and modifying CameraFollowComponent values
24+
// -----------------------------------------------------------------------------
25+
1926
func getOffsetTarget(entityId: EntityID) -> simd_float3 {
2027
guard let cameraFollowComponent = scene.get(component: CameraFollowComponent.self, for: entityId) else { return .zero }
21-
2228
return cameraFollowComponent.offset
2329
}
2430

2531
func setOffsetTarget(entityId: EntityID, offset: simd_float3) {
2632
guard let cameraFollowComponent = scene.get(component: CameraFollowComponent.self, for: entityId) else { return }
27-
2833
cameraFollowComponent.offset = offset
2934
}
3035

3136
func getTargetName(entityId: EntityID) -> String? {
3237
guard let cameraFollowComponent = scene.get(component: CameraFollowComponent.self, for: entityId) else { return nil }
33-
3438
return cameraFollowComponent.targetName
3539
}
3640

3741
func setTargetName(entityId: EntityID, name: String) {
3842
guard let cameraFollowComponent = scene.get(component: CameraFollowComponent.self, for: entityId) else { return }
39-
4043
cameraFollowComponent.targetName = name
4144
}
4245

46+
// -----------------------------------------------------------------------------
47+
// Camera Follow System
48+
// Updates all entities with a CameraFollowComponent once per frame.
49+
// Calls the cameraFollow function to smoothly track the target entity.
50+
// -----------------------------------------------------------------------------
51+
4352
public func cameraFollowUpdate(deltaTime _: Float) {
4453
let customId = getComponentId(for: CameraFollowComponent.self)
4554
let entities = queryEntitiesWithComponentIds([customId], in: scene)
4655

4756
for entity in entities {
4857
guard let cameraFollowComponent = scene.get(component: CameraFollowComponent.self, for: entity) else { continue }
4958

50-
cameraFollow(entityId: entity, targetName: cameraFollowComponent.targetName, offset: cameraFollowComponent.offset, smoothSpeed: 0.5, alignWithOrientation: false)
59+
cameraFollow(
60+
entityId: entity,
61+
targetName: cameraFollowComponent.targetName,
62+
offset: cameraFollowComponent.offset,
63+
smoothSpeed: 0.5,
64+
alignWithOrientation: false
65+
)
5166
}
5267
}
5368

69+
// -----------------------------------------------------------------------------
70+
// Core camera follow logic
71+
// Moves the camera smoothly to follow the target and optionally aligns its
72+
// orientation with the target’s forward direction.
73+
// -----------------------------------------------------------------------------
74+
5475
func cameraFollow(
5576
entityId: EntityID,
5677
targetName: String,
@@ -61,78 +82,78 @@ func cameraFollow(
6182
lockZAxis: Bool = false,
6283
alignWithOrientation: Bool = true
6384
) {
85+
// Ensure the entity has a CameraComponent
6486
guard let cameraComponent = scene.get(component: CameraComponent.self, for: entityId) else {
6587
return
6688
}
6789

90+
// Find the target entity by name
6891
guard let targetId: EntityID = findEntity(name: targetName) else {
6992
return
7093
}
7194

7295
// Get the target's position and orientation
7396
var targetPosition: simd_float3 = UntoldEngine.getPosition(entityId: targetId)
7497
let targetOrientation: simd_float3x3 = UntoldEngine.getOrientation(entityId: targetId) // Rotation matrix
75-
7698
var cameraPosition = cameraComponent.localPosition
7799

78-
// Apply axis locking to the target position
100+
// Apply axis locking (keeps the camera fixed on chosen axes)
79101
if lockXAxis { targetPosition.x = cameraPosition.x }
80102
if lockYAxis { targetPosition.y = cameraPosition.y }
81103
if lockZAxis { targetPosition.z = cameraPosition.z }
82104

83-
// Calculate the offset relative to the entity's orientation
105+
// Adjust offset based on orientation if requested
84106
var adjustedOffset = offset
85107
if alignWithOrientation {
86-
// Rotate the offset vector using the 3x3 rotation matrix
108+
// Rotate the offset by the target's orientation matrix
87109
adjustedOffset = targetOrientation * offset
88110
}
89111

90-
// Calculate the desired position by applying the adjusted offset
112+
// Calculate where the camera *wants* to be
91113
let desiredPosition: simd_float3 = targetPosition + adjustedOffset
92114

93-
// Smoothly interpolate the camera position towards the desired position
115+
// Smoothly interpolate from the current camera position to the desired position
94116
cameraPosition = lerp(start: cameraPosition, end: desiredPosition, t: smoothSpeed)
95117

96-
// Move the camera to the new position
118+
// Move the camera to its new position
97119
moveCameraTo(entityId: entityId, cameraPosition.x, cameraPosition.y, cameraPosition.z)
98120

99-
// Align the camera's view direction using lookAt
121+
// Orient the camera towards the target if enabled
100122
if alignWithOrientation {
101-
// Calculate the forward direction of the entity
102-
let _: simd_float3 = targetOrientation * simd_float3(0, 0, 1) // Forward direction in local space
123+
// Forward direction of the target
124+
let _: simd_float3 = targetOrientation * simd_float3(0, 0, 1)
103125

104-
// Determine the "up" vector (you might need to adjust this based on your engine's coordinate system)
105-
let up: simd_float3 = targetOrientation * simd_float3(0, 1, 0) // Transform the local up vector by the orientation
126+
// Up vector (adjust depending on coordinate system)
127+
let up: simd_float3 = targetOrientation * simd_float3(0, 1, 0)
106128

107-
// Use lookAt to orient the camera towards the target's forward direction
129+
// Look at the target from the camera’s position
108130
cameraLookAt(entityId: entityId, eye: cameraPosition, target: targetPosition, up: up)
109131
}
110132
}
111133

134+
// -----------------------------------------------------------------------------
135+
// Editor Integration
136+
// Makes CameraFollowComponent visible in the Editor (Inspector panel).
137+
// -----------------------------------------------------------------------------
138+
112139
var CameraFollowComponent_Editor: ComponentOption_Editor = .init(
113140
id: getComponentId(for: CameraFollowComponent.self),
114141
name: "Camera Follow",
115142
type: CameraFollowComponent.self,
116143
view: makeEditorView(fields: [
144+
// Editable field for selecting the target entity by name
117145
.text(label: "Target Name",
118146
placeholder: "Entity name",
119-
get: { entityId in
120-
getTargetName(entityId: entityId) ?? "None"
121-
},
122-
set: { entityId, targetName in
123-
setTargetName(entityId: entityId, name: targetName)
124-
}),
147+
get: { entityId in getTargetName(entityId: entityId) ?? "None" },
148+
set: { entityId, targetName in setTargetName(entityId: entityId, name: targetName) }),
125149

150+
// Editable field for the follow offset
126151
.vector3(label: "Offset",
127-
get: { entityId in
128-
getOffsetTarget(entityId: entityId)
129-
},
130-
set: { entityId, newOffset in
131-
setOffsetTarget(entityId: entityId, offset: newOffset)
132-
}),
133-
152+
get: { entityId in getOffsetTarget(entityId: entityId) },
153+
set: { entityId, newOffset in setOffsetTarget(entityId: entityId, offset: newOffset) }),
134154
]),
135155
onAdd: { entityId in
156+
// When added in the Editor, register this component with the entity
136157
registerComponent(entityId: entityId, componentType: CameraFollowComponent.self)
137158
}
138159
)

0 commit comments

Comments
 (0)