Skip to content

Commit

Permalink
added state attribute and drawing color based on state. additionally …
Browse files Browse the repository at this point in the history
…moved to using gesture recognizers to more easily see taps vs drags. updated the README quite a bit.
  • Loading branch information
mgrider committed Aug 10, 2022
1 parent 5148ac6 commit 4a0bce7
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 30 deletions.
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,56 @@

An example project using [SpriteKit](https://developer.apple.com/spritekit/) and [HexGrid](https://github.com/fananek/hex-grid) to draw hexagonal grids in various configurations.

> Please note that main is currently being used for development. For the most stable experience, you may want to download a release, or `git checkout` a release tag.
> Please note that main is currently (temporarily) being used for development. For the most stable experience, you may want to download a release, or `git checkout ` a release tag.

## How to use this project

1. Download this project
2. Open the project in Xcode
3. Pick a deployment target (an iOS simulator works nicely)
3. Pick a deployment target (any iOS simulator should work)
4. Run the project
5. To see different types of grid, you can change the configuration (by pressing the gear button)

## TODO list
5. Use the configuration button (gear icon in upper-right) to switch between different types of grid layout

* move from using `isHighlighted` to using a state `Int` instead and looping through some (configurable?) state colors

## History / Release Notes

### pre-v0.2.0

* draws coordinates on top of each grid cell (type is based on the configuration)
* fixed iPadOS & macOS targets which were crashing when opening the config menu
* moved from using `isHighlighted` to using a state `Int` instead, with some state colors defined in `Cell+State.swift`

### v0.1.0

* draws a grid based on a configurable state object, including square, hex, and triangle examples


## Backlog / ideas list

* Make additional things configurable
- state colors & scene background colors
- the coordinates for "custom" generation
- the frame of the hexagon(s)
- allow toggling on/off the secondary hexagon (currently a yellow background)
- or _maybe_ make an array of configuration objects, and allow any number of hexagons to be drawn
* Add more visual effects
- animate the border of a tapped cell
- screen shake
- growing and shrinking of cells
* Add the concept of a "current cell", and buttons to highlight various cells relative to that cell. (The idea here would be to expose more of the HexGrid API in the demo.)
- neighboring cells
- diagonally neighboring cells
- shortest path to some other cell
* Add a UINavigationController and additional types of examples.
- An example drawn with `UIKit`
- A fully functioning game example? ([Hex](https://en.wikipedia.org/wiki/Hex_(board_game)) maybe?)

### Some ideas more suitable for the main hex-grid project

* allow shifting of all grid coordinates
- 1 direction at a time
- moving 0,0,0 all the way to an edge of the grid
* remove `Cell` `isOpaque` and `isBlocked` properties in favor of attributes with the same names
- write optional "where" clauses for all the functions that use them instead. (This would allow the API to return all cells neighboring a cell _where_ an attribute is true, or to find a path of cells sharing an attribute, similar.)
* fix rectangle grid generation
4 changes: 4 additions & 0 deletions SKHexGrid.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
850E03D828A061BB00BC5934 /* Cell+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850E03D728A061BB00BC5934 /* Cell+State.swift */; };
85331842288C87230000DB75 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85331841288C87230000DB75 /* AppDelegate.swift */; };
85331844288C87230000DB75 /* GameScene.sks in Resources */ = {isa = PBXBuildFile; fileRef = 85331843288C87230000DB75 /* GameScene.sks */; };
85331846288C87230000DB75 /* Actions.sks in Resources */ = {isa = PBXBuildFile; fileRef = 85331845288C87230000DB75 /* Actions.sks */; };
Expand All @@ -24,6 +25,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
850E03D728A061BB00BC5934 /* Cell+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cell+State.swift"; sourceTree = "<group>"; };
8533183E288C87230000DB75 /* SKHexGrid.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SKHexGrid.app; sourceTree = BUILT_PRODUCTS_DIR; };
85331841288C87230000DB75 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
85331843288C87230000DB75 /* GameScene.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = GameScene.sks; sourceTree = "<group>"; };
Expand Down Expand Up @@ -82,6 +84,7 @@
85CD4FFB288C918D00B73468 /* Point+CGPoint.swift */,
85CD5001288D99F200B73468 /* HexSize+CGSize.swift */,
85CD4FFD288C920E00B73468 /* Cell+Highlight.swift */,
850E03D728A061BB00BC5934 /* Cell+State.swift */,
85331849288C87230000DB75 /* GameViewController.swift */,
85CD5004288F164B00B73468 /* ConfigurationView.swift */,
);
Expand Down Expand Up @@ -181,6 +184,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
850E03D828A061BB00BC5934 /* Cell+State.swift in Sources */,
85CD4FFE288C920E00B73468 /* Cell+Highlight.swift in Sources */,
85CD5005288F164B00B73468 /* ConfigurationView.swift in Sources */,
85331848288C87230000DB75 /* GameScene.swift in Sources */,
Expand Down
42 changes: 42 additions & 0 deletions SKHexGrid/Cell+State.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import HexGrid
import UIKit

extension Cell {

fileprivate static let kStateAttribute: String = "kStateAttribute-key"

public enum State: Int {
case empty
case tapped
case touchStarted
case touchContinued
case touchEnded

public var color: UIColor {
switch self {
case .empty:
return UIColor.lightGray
case .tapped:
return .systemOrange
case .touchStarted:
return UIColor(red: 59/256, green: 172/256, blue: 182/256, alpha: 1.0)
case .touchContinued:
return UIColor(red: 130/256, green: 219/256, blue: 216/256, alpha: 1.0)
case .touchEnded:
return UIColor(red: 179/256, green: 232/256, blue: 229/256, alpha: 1.0)
}
}
}

fileprivate var stateInt : Int {
return self.attributes[Cell.kStateAttribute]?.value as? Int ?? -1
}

var state: State {
return State(rawValue: stateInt) ?? .empty
}

func setState(to: State) {
self.attributes[Cell.kStateAttribute] = .init(to.rawValue)
}
}
109 changes: 85 additions & 24 deletions SKHexGrid/HexGridScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,8 @@ class HexGridScene: SKScene {
print("could not find cell")
return
}
if cell.attributes["isHighlighted"] == true {
cellColor = UIColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 1)
} else if cell.isBlocked {
cellColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1)
} else {
cellColor = .lightGray
}
let state = cell.state
cellColor = state.color
shapeNode.fillColor = cellColor
}

Expand Down Expand Up @@ -145,27 +140,58 @@ class HexGridScene: SKScene {
SKAction.fadeOut(withDuration: 0.5),
SKAction.removeFromParent()]))
}

// add some gesture recognizers
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:)))
view.gestureRecognizers = [tapGesture, dragGesture]
}

// MARK: handling input

/// Note: to support taps as well as drags, we're using gesture recognizers as opposed to

func tapped(atPoint pos: CGPoint) {
if let cell = try? grid.cellAt(pos.hexPoint) {
if cell.isBlocked {
print( "Cell x: \(cell.coordinates.x), y: \(cell.coordinates.y), z: \(cell.coordinates.z) is blocked!")
} else {
print( "Cell tapped - x: \(cell.coordinates.x), y: \(cell.coordinates.y), z: \(cell.coordinates.z)")
if cell.state == .tapped {
cell.setState(to: .empty)
} else {
cell.setState(to: .tapped)
}
updateCell(cell: cell)
}
}
}

func touchDown(atPoint pos : CGPoint) {
if let cell = try? grid.cellAt(pos.hexPoint) {
if cell.isBlocked {
print( "Cell x: \(cell.coordinates.x), y: \(cell.coordinates.y), z: \(cell.coordinates.z) is blocked!")
} else {
print( "Cell x: \(cell.coordinates.x), y: \(cell.coordinates.y), z: \(cell.coordinates.z)")
cell.toggleHighlight()
cell.setState(to: .touchStarted)
updateCell(cell: cell)
}
}
else if let n = self.spinnyNode?.copy() as! SKShapeNode? {
if let n = self.spinnyNode?.copy() as! SKShapeNode? {
n.position = pos
n.strokeColor = SKColor.green
self.addChild(n)
}
}

func touchMoved(toPoint pos : CGPoint) {
if let cell = try? grid.cellAt(pos.hexPoint),
!cell.isBlocked,
cell.state != .touchStarted
{
cell.setState(to: .touchContinued)
updateCell(cell: cell)
}
if let n = self.spinnyNode?.copy() as! SKShapeNode? {
n.position = pos
n.strokeColor = SKColor.blue
Expand All @@ -174,32 +200,67 @@ class HexGridScene: SKScene {
}

func touchUp(atPoint pos : CGPoint) {
if let cell = try? grid.cellAt(pos.hexPoint), !cell.isBlocked {
cell.setState(to: .touchEnded)
updateCell(cell: cell)
}
if let n = self.spinnyNode?.copy() as! SKShapeNode? {
n.position = pos
n.strokeColor = SKColor.red
self.addChild(n)
}
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// newer gesture-based input

for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
@objc func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
let point = sender.location(in: view)
let pointInScene = convertPoint(fromView: point)
tapped(atPoint: pointInScene)
}
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
@objc func handlePan(sender: UIPanGestureRecognizer) {
let viewPoint = sender.location(in: view)
let point = convertPoint(fromView: viewPoint)
if sender.state == .began {
touchDown(atPoint: point)
} else if sender.state == .changed {
touchMoved(toPoint: point)
} else if sender.state == .ended {
touchUp(atPoint: point)
}
}


override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
// old way, handling each touch individually

// override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// for t in touches { self.touchDown(atPoint: t.location(in: self)) }
// }
//
// override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
// }
//
// override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// for t in touches {
// if t.tapCount == 1 {
// self.tapped(atPoint: t.location(in: self))
// } else {
// self.touchUp(atPoint: t.location(in: self))
// }
// }
// }
//
// override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
// for t in touches { self.touchUp(atPoint: t.location(in: self)) }
// }

// MARK: update loop for realtime games

// override func update(_ currentTime: TimeInterval) {
// // Called before each frame is rendered
// }
}

0 comments on commit 4a0bce7

Please sign in to comment.