Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collectibles #18

Merged
merged 13 commits into from Mar 22, 2024
5 changes: 5 additions & 0 deletions SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift
Expand Up @@ -38,6 +38,11 @@ extension GameScene: SDScene {
objectMap[object.node] = object
addChild(object.node)
}

public func removeObject(_ object: SDObject) {
objectMap[object.node] = nil
object.removeFromParent()
}
}

extension GameScene: SKPhysicsContactDelegate {
Expand Down
4 changes: 4 additions & 0 deletions SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift
Expand Up @@ -39,4 +39,8 @@ public class SDObject {
public var physicsBody: SDPhysicsBody? {
willSet { node.physicsBody = newValue?.body }
}

func removeFromParent() {
node.removeFromParent()
}
}
1 change: 1 addition & 0 deletions SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift
Expand Up @@ -5,4 +5,5 @@ public protocol SDScene {
var size: CGSize { get }

func addObject(_ object: SDObject)
func removeObject(_ object: SDObject)
}
8 changes: 8 additions & 0 deletions star-dash/star-dash.xcodeproj/project.pbxproj
Expand Up @@ -83,6 +83,8 @@
E64361132BA4C2CD003850FD /* PhysicsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610D2BA4C2CC003850FD /* PhysicsModule.swift */; };
E64361142BA4C2CD003850FD /* CreationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610E2BA4C2CC003850FD /* CreationModule.swift */; };
E64361152BA4C2CD003850FD /* GameBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610F2BA4C2CC003850FD /* GameBridge.swift */; };
E69EE9322BAC6CBB00033AB5 /* GameInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E69EE9312BAC6CBB00033AB5 /* GameInfo.swift */; };
E69EE9342BAC6CC300033AB5 /* OverlayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E69EE9332BAC6CC300033AB5 /* OverlayInfo.swift */; };
E6A011172BA5F4AD006904D9 /* EntitySyncInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A011162BA5F4AD006904D9 /* EntitySyncInterface.swift */; };
E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; };
E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; };
Expand Down Expand Up @@ -208,6 +210,8 @@
E643610D2BA4C2CC003850FD /* PhysicsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsModule.swift; sourceTree = "<group>"; };
E643610E2BA4C2CC003850FD /* CreationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreationModule.swift; sourceTree = "<group>"; };
E643610F2BA4C2CC003850FD /* GameBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameBridge.swift; sourceTree = "<group>"; };
E69EE9312BAC6CBB00033AB5 /* GameInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameInfo.swift; sourceTree = "<group>"; };
E69EE9332BAC6CC300033AB5 /* OverlayInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayInfo.swift; sourceTree = "<group>"; };
E6A011162BA5F4AD006904D9 /* EntitySyncInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntitySyncInterface.swift; sourceTree = "<group>"; };
E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = "<group>"; };
E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -342,6 +346,7 @@
4E630F282B9F7EC20008F887 /* Components */,
4E630F252B9F7E500008F887 /* Entities */,
46B8C0992BA328D900498705 /* GameEngine.swift */,
E69EE9312BAC6CBB00033AB5 /* GameInfo.swift */,
);
path = GameEngine;
sourceTree = "<group>";
Expand Down Expand Up @@ -527,6 +532,7 @@
E6A745102BA057040080C1BE /* Rendering */ = {
isa = PBXGroup;
children = (
E69EE9332BAC6CC300033AB5 /* OverlayInfo.swift */,
E6B5509F2BA15D2000DC7396 /* MTKRenderer */,
E6A745132BA057040080C1BE /* Renderer.swift */,
E6B0AAD02BAAE3DC009CB939 /* ViewDelegate.swift */,
Expand Down Expand Up @@ -719,6 +725,7 @@
files = (
46D418182BA5CD840091A38B /* Player+Collidable.swift in Sources */,
46D4181E2BA5D2620091A38B /* Monster+Collidable.swift in Sources */,
E69EE9342BAC6CC300033AB5 /* OverlayInfo.swift in Sources */,
E6B1DC902BA34A4800473563 /* SDPhysicsEngine in Sources */,
4E630F272B9F7E770008F887 /* Entity.swift in Sources */,
4E630F2C2B9F7F460008F887 /* Component.swift in Sources */,
Expand Down Expand Up @@ -762,6 +769,7 @@
E64361112BA4C2CD003850FD /* SyncModule.swift in Sources */,
E6A7451B2BA0C1890080C1BE /* PlayerView.swift in Sources */,
46D418282BA5D6800091A38B /* Floor+Collidable.swift in Sources */,
E69EE9322BAC6CBB00033AB5 /* GameInfo.swift in Sources */,
46D418222BA5D4E60091A38B /* Obstacle+Collidable.swift in Sources */,
E638B9CF2BAB3C5D00931CC2 /* TeleportEvent.swift in Sources */,
E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */,
Expand Down
14 changes: 12 additions & 2 deletions star-dash/star-dash/Constants/PhysicsConstants.swift
Expand Up @@ -21,15 +21,25 @@ struct PhysicsConstants {
}

struct CollisionMask {
static let player = CollisionCategory.max ^ CollisionCategory.player
static let player = CollisionCategory.max ^ CollisionCategory.player ^ CollisionCategory.collectible
static let monster = CollisionCategory.player | CollisionCategory.tool
static let collectible = CollisionCategory.player
static let collectible = CollisionCategory.none
static let obstacle = CollisionCategory.player | CollisionCategory.monster | CollisionMask.tool
static let tool = CollisionCategory.max ^ CollisionCategory.collectible ^ CollisionCategory.tool
static let wall = CollisionCategory.player | CollisionCategory.monster | CollisionCategory.tool
static let floor = CollisionCategory.player | CollisionCategory.monster | CollisionCategory.tool
}

struct ContactMask {
static let player = CollisionCategory.floor | CollisionCategory.collectible
static let monster = CollisionCategory.player
static let collectible = CollisionCategory.player
static let obstacle = CollisionCategory.none
static let tool = CollisionCategory.obstacle
static let wall = CollisionCategory.tool | CollisionCategory.player
static let floor = CollisionCategory.player
}

struct Dimensions {
// TODO: determine appropriate size for each
static let player = CGSize(width: 60, height: 60)
Expand Down
8 changes: 8 additions & 0 deletions star-dash/star-dash/GameBridge/GameBridge.swift
Expand Up @@ -88,5 +88,13 @@ class GameBridge {
}

private func removeObject(from entityId: EntityId) {
guard let object = entitiesMap[entityId] else {
return
}

entitiesMap[entityId] = nil
objectsMap[object.id] = nil

self.scene.removeObject(object)
}
}
Expand Up @@ -38,6 +38,7 @@ class PhysicsModule: SyncModule {
object.physicsBody = createRectanglePhysicsBody(physicsComponent: physicsComponent)
object.physicsBody?.restitution = physicsComponent.restitution
object.physicsBody?.isDynamic = physicsComponent.isDynamic
object.physicsBody?.affectedByGravity = physicsComponent.affectedByGravity
object.physicsBody?.categoryBitMask = physicsComponent.categoryBitMask
object.physicsBody?.contactTestMask = physicsComponent.contactTestMask
object.physicsBody?.collisionBitMask = physicsComponent.collisionBitMask
Expand Down
6 changes: 5 additions & 1 deletion star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift
Expand Up @@ -31,7 +31,11 @@ extension SpriteModule: CreationModule {
var newObject = SDObject()
if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity.id) {
let spriteObject = SDSpriteObject(imageNamed: "PlayerRedNose")
spriteObject.size = CGSize(width: 100, height: 140)

if let size = spriteComponent.size {
spriteObject.size = size
}

newObject = spriteObject
}

Expand Down
Expand Up @@ -12,16 +12,16 @@ class SpriteComponent: Component {
// for sprite set will need to discuss how to rep animation
var image: String
var textureAtlas: String?
var size: CGSize
var size: CGSize?

init(id: ComponentId, entityId: EntityId, image: String, textureAtlas: String?, size: CGSize) {
init(id: ComponentId, entityId: EntityId, image: String, textureAtlas: String?, size: CGSize?) {
self.image = image
self.size = size
self.textureAtlas = textureAtlas
super.init(id: id, entityId: entityId)
}

convenience init(entityId: EntityId, image: String, textureAtlas: String?, size: CGSize) {
convenience init(entityId: EntityId, image: String, textureAtlas: String?, size: CGSize?) {
self.init(id: UUID(), entityId: entityId, image: image, textureAtlas: textureAtlas, size: size)
}
}
Expand Up @@ -10,23 +10,39 @@ import Foundation
class Collectible: Entity {
let id: EntityId
private let position: CGPoint
private let sprite: String
private let points: Int
jasonqiu212 marked this conversation as resolved.
Show resolved Hide resolved
private let size: CGSize

init(id: EntityId, position: CGPoint) {
init(id: EntityId, position: CGPoint, sprite: String, points: Int, size: CGSize) {
self.id = id
self.position = position
self.sprite = sprite
self.points = points
self.size = size
}

convenience init(position: CGPoint) {
self.init(id: UUID(), position: position)
convenience init(position: CGPoint, sprite: String, points: Int, size: CGSize) {
self.init(id: UUID(), position: position, sprite: sprite, points: points, size: size)
}

func setUpAndAdd(to: EntityManager) {
let positionComponent = PositionComponent(entityId: self.id, position: self.position, rotation: .zero)
let physicsComponent = PhysicsComponent(entityId: self.id, size: PhysicsConstants.Dimensions.collectible)
let physicsComponent = PhysicsComponent(entityId: self.id, size: self.size)
physicsComponent.affectedByGravity = false
physicsComponent.isDynamic = false
physicsComponent.categoryBitMask = PhysicsConstants.CollisionCategory.collectible
physicsComponent.contactTestMask = PhysicsConstants.ContactMask.collectible
physicsComponent.collisionBitMask = PhysicsConstants.CollisionMask.collectible
let spriteComponent = SpriteComponent(entityId: self.id, image: sprite, textureAtlas: nil, size: size)

to.add(entity: self)
to.add(component: positionComponent)
to.add(component: physicsComponent)
to.add(component: spriteComponent)
}

static func createCoinCollectible(position: CGPoint) -> Collectible {
Collectible(position: position, sprite: "Coin", points: 10, size: CGSize(width: 50, height: 50))
jasonqiu212 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Up @@ -27,7 +27,7 @@ class Floor: Entity {
physicsComponent.restitution = 0.0
physicsComponent.isDynamic = false
physicsComponent.categoryBitMask = PhysicsConstants.CollisionCategory.floor
physicsComponent.contactTestMask = PhysicsConstants.CollisionCategory.player
physicsComponent.contactTestMask = PhysicsConstants.ContactMask.floor
physicsComponent.collisionBitMask = PhysicsConstants.CollisionMask.floor

to.add(entity: self)
Expand Down
Expand Up @@ -30,11 +30,16 @@ class Player: Entity {
let healthComponent = HealthComponent(entityId: self.id, health: GameConstants.InitialHealth.player)
let physicsComponent = PhysicsComponent(entityId: self.id, size: PhysicsConstants.Dimensions.player)
physicsComponent.categoryBitMask = PhysicsConstants.CollisionCategory.player
physicsComponent.contactTestMask = PhysicsConstants.CollisionCategory.floor
physicsComponent.contactTestMask = PhysicsConstants.ContactMask.player
physicsComponent.collisionBitMask = PhysicsConstants.CollisionMask.player
physicsComponent.affectedByGravity = true
physicsComponent.restitution = 0.0
let spriteComponent = SpriteComponent(entityId: self.id, image: "", textureAtlas: "", size: .zero)
let spriteComponent = SpriteComponent(
entityId: self.id,
image: "PlayerRedNose",
textureAtlas: "",
size: CGSize(width: 100, height: 140)
)
let scoreComponent = ScoreComponent(entityId: self.id, score: 0)

to.add(entity: self)
Expand Down
13 changes: 13 additions & 0 deletions star-dash/star-dash/GameEngine/GameEngine.swift
Expand Up @@ -20,6 +20,18 @@ class GameEngine {
setUpSystems()
}

func gameInfo() -> GameInfo? {
guard let scoreSystem = systemManager.system(ofType: ScoreSystem.self),
let playerEntityId = entityManager.playerEntityId(),
let score = scoreSystem.score(of: playerEntityId) else {
return nil
}

return GameInfo(
playerScore: score
)
}

func update(by deltaTime: TimeInterval) {
systemManager.update(by: deltaTime)
eventManager.executeAll(on: self)
Expand Down Expand Up @@ -57,6 +69,7 @@ class GameEngine {
private func setUpSystems() {
systemManager.add(PositionSystem(entityManager, dispatcher: self))
systemManager.add(PhysicsSystem(entityManager, dispatcher: self))
systemManager.add(ScoreSystem(entityManager, dispatcher: self))
}
}

Expand Down
3 changes: 3 additions & 0 deletions star-dash/star-dash/GameEngine/GameInfo.swift
@@ -0,0 +1,3 @@
struct GameInfo {
let playerScore: Int
}
8 changes: 8 additions & 0 deletions star-dash/star-dash/GameEngine/Systems/ScoreSystem.swift
Expand Up @@ -18,6 +18,14 @@ class ScoreSystem: System {
self.dispatcher = dispatcher
}

func score(of entityId: EntityId) -> Int? {
guard let scoreComponent = getScoreComponent(of: entityId) else {
return nil
}

return scoreComponent.score
}

func applyScoreChange(to entityId: EntityId, scoreChange: Int) {
guard let scoreComponent = getScoreComponent(of: entityId) else {
return
Expand Down
4 changes: 4 additions & 0 deletions star-dash/star-dash/Rendering/MTKRenderer/MTKRenderer.swift
Expand Up @@ -35,6 +35,10 @@ class MTKRenderer: NSObject, Renderer {
super.init()
}

func updateOverlay(overlayInfo: OverlayInfo) {
playerView?.updateOverlay(score: overlayInfo.score)
}

/// Set ups the views for a single player game.
func createSinglePlayerView(at superview: UIView) {
let playerView = PlayerView(superview: superview, device: self.device, drawDelegate: self)
Expand Down
9 changes: 7 additions & 2 deletions star-dash/star-dash/Rendering/MTKRenderer/OverlayView.swift
Expand Up @@ -11,16 +11,21 @@ class OverlayView: UIView {
let scoreLabel = UILabel()

func setupSubviews() {
scoreLabel.text = "Score: 0"
scoreLabel.numberOfLines = 1
scoreLabel.translatesAutoresizingMaskIntoConstraints = false
scoreLabel.textColor = .white
scoreLabel.textColor = .black
addSubview(scoreLabel)

NSLayoutConstraint.activate([
scoreLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: margin),
scoreLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -1 * margin),
scoreLabel.leadingAnchor.constraint(greaterThanOrEqualTo: self.leadingAnchor, constant: margin)
])

update(score: 0)
}

func update(score: Int) {
scoreLabel.text = "Score: \(score)"
}
}
4 changes: 4 additions & 0 deletions star-dash/star-dash/Rendering/MTKRenderer/PlayerView.swift
Expand Up @@ -35,4 +35,8 @@ class PlayerView {
func setControlViewDelegate(_ delegate: ControlViewDelegate) {
controlView.controlViewDelegate = delegate
}

func updateOverlay(score: Int) {
overlayView.update(score: score)
}
}
3 changes: 3 additions & 0 deletions star-dash/star-dash/Rendering/OverlayInfo.swift
@@ -0,0 +1,3 @@
struct OverlayInfo {
let score: Int
}
1 change: 1 addition & 0 deletions star-dash/star-dash/Rendering/Renderer.swift
Expand Up @@ -4,5 +4,6 @@ import UIKit
The `Renderer` protocol defines the requirements for an object responsible for rendering game objects onto a view.
*/
protocol Renderer {
func updateOverlay(overlayInfo: OverlayInfo)
func createSinglePlayerView(at rootView: UIView)
}
13 changes: 13 additions & 0 deletions star-dash/star-dash/ViewController.swift
Expand Up @@ -57,6 +57,11 @@ class ViewController: UIViewController {

let floor = Floor(position: CGPoint(x: scene.size.width / 2, y: scene.size.height / 2 - 400))
floor.setUpAndAdd(to: entityManager)

let collectible = Collectible.createCoinCollectible(
position: CGPoint(x: scene.size.width / 2 + 30, y: scene.size.height / 2 - 100)
)
collectible.setUpAndAdd(to: entityManager)
}
}

Expand All @@ -66,6 +71,14 @@ extension ViewController: SDSceneDelegate {
gameBridge?.syncToEntities()
gameEngine?.update(by: deltaTime)
gameBridge?.syncFromEntities()

guard let gameInfo = gameEngine?.gameInfo() else {
return
}

renderer?.updateOverlay(overlayInfo: OverlayInfo(
score: gameInfo.playerScore
))
}

func contactOccured(objectA: SDObject, objectB: SDObject, contactPoint: CGPoint) {
Expand Down