#はじめに
パズルゲーム作れたら楽しいだろうなと思い作ってみました
パズルの仕様としてはパズドラを思い浮かべてもらえれば良いと思います。
参考 Swiftris
#ソース
https://github.com/konpeto000/tilepuzzle
#イメージ
*(タイルの配置はランダムですので起動後3色以上並んでいても消えません)
#反省
ノードの管理が未だによくわかっておりません。。
アクション実行中にタッチ操作を受け付けてしまうので
これをどうにかしたいのですがどなたかわかる方がいらっしゃったら教えてください。。
今回は強引にアクション実行中にタッチ操作を受け付けないようにしています
#解説
タイルの初期化
func randomColor()->UIColor{
//0:red,1:green,2:blue,3:yellow
let rnd = arc4random()%4
var color:UIColor!
switch rnd{
case 0:
color = UIColor.redColor()
case 1:
color = UIColor.greenColor()
case 2:
color = UIColor.blueColor()
case 3:
color = UIColor.yellowColor()
default:
break
}
return color
}
func initMakeTile(){
for(var i:Int = 0;i < NumColumns;i++){
for(var j:Int = 0;j < NumRows;j++){
let sprite = makeTileOne()
sprite.position = CGPointMake(CGFloat(i)*TileSize,-CGFloat(j)*TileSize)
tileArrayPos[i][j] = sprite.position
board.addChild(sprite)
}
}
}
func makeTileOne()->SKSpriteNode{
let sprite = SKSpriteNode()
sprite.anchorPoint = CGPointMake(0, 1.0)
sprite.alpha *= 0.8
sprite.color = randomColor()
sprite.size = CGSizeMake(TileSize-1, TileSize-1)
return sprite
}
この一連で描画するタイルを作成し
タイルの位置をtileArrayPos[][]
に保存する
後々nodeAtPoint
を使い位置からノードを参照できるようにするため
タッチ操作
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
touchedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(touchedNode == node as NSObject && !moveActionFlag){
touchedNode.alpha *= 0.5
}
}
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let passagedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(passagedNode == node as NSObject && touchedNode != passagedNode && !moveActionFlag){
moveTile(passagedNode)
}
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let endedNode = self.nodeAtPoint(location)
for node in self.board.children{
if(endedNode == node as NSObject && !moveActionFlag){
moveActionFlag = true
deleteAndDropTile()
touchedNode.alpha *= 2
}
}
}
}
began関数
ここで選択したノードをtouchedNode
に保存する
そしてそのノードのアルファ値を半分にするそうすることによって
選択したノードをわかりやすくした
moved関数
ここで選択されるノードをpassageNode
に保存し
それぞれの条件に当てはめるそしてmoveTile(passagedNode)
にとばす
ended関数
ここでタイルの消去と落ちる処理を行うdeleteAndDropTile()
にとばす
またtouchedNode
のアルファ値を元に戻す
どの関数にもアクション実行中は実行しないというmoveActionFlagを条件に持たせる
もっとうまいやり方があると思うのだが思いつかなかった。。。
タイルの移動
func moveTile(node:SKNode){
let touchedAction = SKAction.moveTo(node.position, duration: 0.08)
let passeagedAction = SKAction.moveTo(touchedNode.position, duration: 0.08)
moveActionFlag = true
touchedNode.runAction(touchedAction, completion: {self.moveActionFlag = false})
node.runAction(passeagedAction, completion: {self.moveActionFlag = false})
}
タッチしたタイルが他のタイルと接触したときに呼ばれ
そのノードの位置をアクションによって交換する関数
アクション実行中はmoveActionFlag
をtrue
にして
それぞれのtouch関数の処理を呼び出せないようにする
アクションが終了したらfalse
とする
タイルの消去と落ちる処理
func deleteAndDropTile(){
var deleteTileArray = Array(arrayLiteral:SKNode())
deleteTileArray.removeAll()
deleteColumns(&deleteTileArray)
deleteRows(&deleteTileArray)
if(deleteTileArray.isEmpty){
moveActionFlag = false
return
}
board.removeChildrenInArray(deleteTileArray)
dropTile(){
self.makeTileInEmpty(){
self.deleteAndDropTile()
}
}
}
消すタイルをそれぞれ行と列で分けdeleteTileArray
に入れる
もし入っていなければmoveActionFlag
を折り再帰を終了する
dropTile()
に飛ばしアクション終了後makeTileInEmpty()
とdeleteAndDropTile()
に飛ばす
列行の削除
func deleteColumns(inout array:[SKNode]){
var deleteColumnsArray = Array(arrayLiteral:SKNode())
for(var i:Int = 0;i < NumColumns;i++){
var color = searchNode(tileArrayPos[i][0]).color
var count = 0
for(var j:Int = 0;j < NumRows;j++){
var node = searchNode(tileArrayPos[i][j])
if(color == node.color){
deleteColumnsArray.append(node)
count++
}else{
if(count >= 3){
insertArray(&array, arrayB: deleteColumnsArray)
}
deleteColumnsArray.removeAll()
deleteColumnsArray.append(node)
color = node.color
count = 1
}
}
if(count >= 3){
insertArray(&array, arrayB: deleteColumnsArray)
}
deleteColumnsArray.removeAll()
}
}
(行は列の処理とほぼかわらないので割愛)
選択したノードを原点とし探索を開始する
次のノードの色が原点と同じならばカウント(同じ色の数)を増加しdeleteColumnsArray
に入れる
またそうでなく同じ色の数が3以上ならばその配列をarray
に入れるinoutなのでdeleteTileArray
に入れると同じこと
そして同じ色の数が3未満ならばdeleteColumnsArray
の要素をすべて捨てる
ノードの探索
func searchNode(pos:CGPoint)->SKSpriteNode{
let node = self.nodeAtPoint(CGPointMake(pos.x+TileSize,pos.y-4*TileSize))
return node as SKSpriteNode
}
指定した場所のノードをSKSpriteNode
として返す
nodeAtPoint
でやるのだがいまいちわかっておらず
xにタイル1つ分yにタイル4つ分ずらさないとうまく行かない
タイルの落ちる処理
func dropTile(completion:()->()){
for(var i:Int = 0;i < NumColumns;i++){
for(var j:Int = 0;j < NumRows;j++){
let k = searchEmpty(searchNode(tileArrayPos[i][j]))
if(k != 0){
let sprite = searchNode(tileArrayPos[i][j])
let action = SKAction.moveTo(tileArrayPos[i][j+k], duration: 0.1)
sprite.runAction(action)
}
}
}
runAction(SKAction.waitForDuration(0.2), completion: completion)
}
とあるノードの下にある空白の個数をsearchEmpty
で探し
その個数分だけそのノードをアクションで下げる
空白の探索
func searchEmpty(node:SKSpriteNode)-> Int{
var count = 0
if(node.name != "board"){
for(var k:Int = Int(-1*node.position.y/TileSize);k < NumRows;k++){
let sprite = searchNode(tileArrayPos[Int(node.position.x/TileSize)][k])
if(sprite.name == "board"){
count++
}
}
}
return count
}
とあるノードの下の空白の個数を数えるのだが
Int(-1*node.position.y/TileSize)
はそのノードのj番目という意味である
Int(-1*node.position.x/TileSize)
はi番目
空白とはいわゆるboard
自身なのでそのノードの名前がboard
つまり空白のときカウントしその数を返す
空白にタイルを入れる
func makeTileInEmpty(completion:()->()){
var emptyList = Array(count:NumColumns,repeatedValue:0)
for(var i:Int = 0;i < NumColumns;i++){
var count = 0
for(var j:Int = 0;j < NumRows;j++){
if(searchNode(tileArrayPos[i][j]).name == "board"){
count++
scorePoint += 100
}
}
emptyList[i] = count
}
var i = 0
for k in emptyList{
for(var j:Int = 0;j < k;j++){
let sprite = makeTileOne()
board.addChild(sprite)
let action = SKAction.moveTo(tileArrayPos[i][j], duration: 0.1)
sprite.runAction(action)
}
i++
}
runAction(SKAction.waitForDuration(0.2), completion: completion)
}
タイルがすべて下に落ちているので
その列の空白を数え各列ごとにemptyList
に保存し
要素分だけタイルを作成しboard
に追加していく