Skip to content

Instantly share code, notes, and snippets.

@DarrenHurst
Last active August 30, 2024 23:52
Show Gist options
  • Save DarrenHurst/5699108fcb6ed0a867a278147dcb91fe to your computer and use it in GitHub Desktop.
Save DarrenHurst/5699108fcb6ed0a867a278147dcb91fe to your computer and use it in GitHub Desktop.
BOO#1
//
// ContentView.swift
// BooAnimation
//
// Created by Darren Hurst on 2024-08-30.
//
import SwiftUI
import CoreMotion
struct ContentView: View {
var body: some View {
VStack {
BOOView()
}
.padding()
}
}
#Preview {
BOOView()
}
struct HauntedHouse: View {
var body: some View {
ZStack {
// Main Structure
Rectangle()
.fill(Color.gray)
.frame(width: 250, height: 200)
.offset(y: 50)
.overlay(
Rectangle()
.stroke(Color.black, lineWidth: 3)
)
Rectangle()
.frame(width:400,height: 150)
.offset(y:60)
.opacity(0.8)
// Left and Right Side Extensions
HStack(spacing: 10) {
Rectangle()
.fill(Color.gray)
.frame(width: 80, height: 180)
.overlay(
Rectangle()
.stroke(Color.black, lineWidth: 3)
)
Spacer()
Rectangle()
.fill(Color.gray)
.frame(width: 80, height: 180)
.overlay(
Rectangle()
.stroke(Color.black, lineWidth: 3)
)
}
.frame(width: 250)
.offset(y: 50)
// Roof
RoofShape()
.fill(Color.black.opacity(0.6))
.frame(width: 300, height: 100)
.offset(y: -40)
// Left and Right Roof Extensions
HStack(spacing: 80) {
RoofShape()
.fill(Color.gray)
.frame(width: 100, height: 50)
.offset(x: -110, y: -40)
RoofShape()
.fill(Color.gray)
.frame(width: 100, height: 50)
.offset(x: 110, y: -40)
}
// Door
Rectangle()
.fill(Color.brown)
.frame(width: 60, height: 100)
.offset(y: 130)
.overlay(
Rectangle()
.stroke(Color.black, lineWidth: 3)
)
// Windows
VStack(spacing: 10) {
HStack(spacing: 50) {
WindowShape()
WindowShape()
}
.offset(y: 10)
HStack(spacing: 50) {
WindowShape()
WindowShape()
}
.offset(y: 50)
}
ZStack {
Circle()
.fill(RadialGradient(colors: [.white, .gray, .black], center: .trailing, startRadius: 50, endRadius: 150))
.frame(width: 140)
.shadow(color: .white, radius: 10)
}.offset(x:-150, y:-250).opacity(0.3)
ZStack {
Circle()
.fill(RadialGradient(colors: [.white, .gray, .black], center: .trailing, startRadius: 50, endRadius: 150))
.frame(width: 140)
.shadow(color: .white, radius: 10)
Circle().frame(width: 40).shadow(color: .white, radius: 10)
}.offset(y:-200)
.scaleEffect(0.2)
.offset(y:-59)
.zIndex(3.0)
// Circular Window
Circle()
.fill(Color.white)
.frame(width: 40, height: 40)
.overlay(
Circle()
.stroke(Color.black, lineWidth: 3)
)
.offset(y: -100)
}
}
}
struct RoofShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.closeSubpath()
return path
}
}
struct WindowShape: View {
var body: some View {
RoundedRectangle(cornerRadius: 5)
.fill(Color.white)
.frame(width: 30, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 3)
)
}
}
struct GhostShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.width
let height = rect.height
let waveHeight: CGFloat = height * 0.1
let waveWidth: CGFloat = width / 4
// Start at the bottom-left corner
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
// Wavy bottom part
path.addCurve(to: CGPoint(x: waveWidth, y: rect.maxY - waveHeight),
control1: CGPoint(x: waveWidth / 2, y: rect.maxY),
control2: CGPoint(x: waveWidth / 2, y: rect.maxY - waveHeight))
path.addCurve(to: CGPoint(x: 2 * waveWidth, y: rect.maxY),
control1: CGPoint(x: waveWidth + waveWidth / 2, y: rect.maxY - waveHeight),
control2: CGPoint(x: waveWidth + waveWidth / 2, y: rect.maxY))
path.addCurve(to: CGPoint(x: 3 * waveWidth, y: rect.maxY - waveHeight),
control1: CGPoint(x: 2 * waveWidth + waveWidth / 2, y: rect.maxY),
control2: CGPoint(x: 2 * waveWidth + waveWidth / 2, y: rect.maxY - waveHeight))
path.addCurve(to: CGPoint(x: rect.maxX, y: rect.maxY),
control1: CGPoint(x: 3 * waveWidth + waveWidth / 2, y: rect.maxY - waveHeight),
control2: CGPoint(x: 3 * waveWidth + waveWidth / 2, y: rect.maxY))
// Right side straight up
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + height * 0.6))
// Rounded head
path.addArc(center: CGPoint(x: width / 2, y: rect.minY + height * 0.6),
radius: width / 2,
startAngle: .degrees(0),
endAngle: .degrees(180),
clockwise: true)
// Left side straight down
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + height * 0.6))
path.closeSubpath()
return path
}
}
struct GhostView: View {
@State var animate: Bool = false
var inColor: Color = .blue
@StateObject var boo: BOOSlideValues
var body: some View {
VStack {
let size = (100.0,150.0)
ZStack {
GhostShape().trim(from: 0, to: 10.0).fill(inColor.opacity(0.4)).shadow(radius: 10)
// .offset(x: animate ? -90 : 90).shadow(color: .white, radius: 10)
GhostShape().fill(.white.opacity(0.4)).shadow(radius: 10)
// .offset(x: animate ? -90 : 90)
Circle().fill(.white).frame(width: size.0 / 6, height: size.1 / 3)
.offset(x: 9)
Circle().fill(.white).frame(width: size.0 / 6, height: size.1 / 3).shadow(radius: 10)
Circle().fill(.black).frame(width: size.0 / 12, height: size.1 / 3)
.offset(x: 9)
Circle().fill(.black).frame(width: size.0 / 12, height: size.1 / 3).shadow(radius: 10)
// .offset(x: animate ? -90 : 90)
}.frame(width: size.0, height:size.1)
.animation(.smooth().speed(0.63).repeatForever(autoreverses: true), value: animate)
}
.position(x: boo.xpoint, y: boo.ypoint)
.onAppear() {
animate.toggle()
}
}
}
class BOOSlideValues : ObservableObject {
@Published var ypoint: Double = 150.0
@Published var xpoint: Double = 150.0
}
extension BOOSlideValues: Equatable {
static func == (lhs: BOOSlideValues, rhs: BOOSlideValues) -> Bool {
rhs.xpoint == lhs.xpoint
}
}
struct BOOView : View {
@StateObject var booValues: BOOSlideValues = BOOSlideValues()
@State var view: any View = EmptyView()
// Detect device shake
@State var motionManager:CMMotionManager = CMMotionManager()
fileprivate func ghosts(_ r: GeometryProxy, booValues: BOOSlideValues) -> some View {
return ZStack {
GhostView(inColor: .yellow, boo: booValues)
.scaleEffect(0.2)
GhostView( boo: booValues)
.offset(x:0,y:0)
.scaleEffect(0.6)
GhostView(inColor: .red, boo: booValues)
.offset(x:0,y:150)
.scaleEffect(0.9)
}
.frame(width: r.size.width, height:r.size.height)
}
var body: some View {
motionManager.startDeviceMotionUpdates(to: .main) { motion, error in
if let motionx = motion, motionx.rotationRate.x >= 0.5 {
print(motionx)
booValues.xpoint = booValues.xpoint - 5
if booValues.xpoint < 0 {
booValues.xpoint = 0
}
}
if let motiony = motion, motiony.rotationRate.y >= 0.5 {
print(motiony)
booValues.ypoint = booValues.ypoint - 5
if booValues.ypoint < 0 {
booValues.ypoint = 0
}
}
if let motionx = motion, motionx.rotationRate.x <= -0.5 {
print(motionx)
booValues.xpoint = booValues.xpoint + 5
if booValues.xpoint > 320 {
booValues.xpoint = 320
}
}
if let motiony = motion, motiony.rotationRate.y <= -0.5 {
print(motiony)
booValues.ypoint = booValues.ypoint + 5
if booValues.ypoint > 320 {
booValues.ypoint = 320
}
}
}
return VStack {
GeometryReader { r in
ZStack {
HauntedHouse()
.onAppear() {
view = ghosts(r, booValues: booValues)
}
}
.frame(width: r.size.width, height:r.size.height)
.background(LinearGradient(colors: [.black,.white.opacity(0.4),.clear], startPoint: .top, endPoint: .bottom))
.background(LinearGradient(colors: [.clear,.clear ,.green], startPoint: .top, endPoint: .bottom))
AnyView(view)
VStack {
Slider(value: $booValues.xpoint, in: 0...320, onEditingChanged: { _ in
print(booValues.xpoint)
view = EmptyView()
view = ghosts(r, booValues: booValues)
})//.hidden()
Slider(value: $booValues.ypoint, in: 0...320, onEditingChanged: { _ in
print(booValues.ypoint)
view = EmptyView()
view = ghosts(r, booValues: booValues)
})//.hidden()
}
}
}
}
}
struct GhostPreview : PreviewProvider {
static var previews: some View {
BOOView()
}
}
@DarrenHurst
Copy link
Author

Simulator.Screen.Recording.-.iPhone.15.-.2024-08-30.at.14.21.48.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment