Yu-Gi-Oh GO

Emil Villefrance – emvil15

Harald Nielsen – harni15

Introduction

The idea of the project is to create a game, based around the augmented reality concept of Pokémon GO, but with the game mechanics of Yu-Gi-Oh. The players should be able to navigate their characters in the game, by moving themselves in the real world. The in-game world would have AI duelists for the player to battle.

The importance of this application lies in its ability to entertain users, and to make them go outside and get some light exercise, rather than sitting still while playing videogames.

In order to fully grasp the subjects of this report, it is important that the reader has a basic understanding of common engineering methods, and an intermediate understanding of programming, particularly in SWIFT, and at least some notion of the concept of both Pokémon GO and Yu-Gi-Oh.

The main problem, which the project needs to solve, is that currently the only option for an outdoor gaming application is Pokémon GO, which has dull gameplay, and requires very little strategical thought. The problem is that people are not sufficiently motivated by a monotonous grind, as evident by the enormous drop-off in Pokémon Go’s player base.

By exploiting the proven concepts of Pokémon GO, the need to travel around the real world, coupled with a franchise that is well known among the young adults, and getting rid of the gameplay mechanics that make the game repetitive and boring, through the strategic and more complex system of the Yu-Gi-Oh card game, the project aims to entice the people who need a game that is challenging, and which has depth and complexity to it.

 

Methods and Materials

When coming up with an idea for the application, the first thought was to look at the market and see where it would be possible to obtain a user base. The process started by brainstorming and discussing which areas might have potential gaps. After finding which area of the market could have potential users, a concept was created. Requirements for the application were specified, making it easier to understand what the application would need in order to succeed. To get a better understanding of how to make the application intuitive and easy to use, a basic prototype was developed, containing only navigation between the different views, very little functionality, and no actual gameplay. The layout of the application is based on simplicity, and only adding detail to elements that the user can interact with. This prototype was tested on a smartphone, rather than a computer, to ensure that navigating the app would be easy through the touch interface, and not only with a mouse and keyboard. The main test subjects were the developers, as it is easier to load different versions on to the smartphone, test them, and then make changes and improvements immediately. When the prototype reached a point where the most important views were finalized in terms of design, it was presented to a small group of potential users, who gave feedback on the design and layout. Based on the feedback from the potential users, the requirements were tweaked, and the prototype was changed further. This process was repeated in order to improve the prototype, and to make sure that the changes that were implemented were improving the experience of using the application.

Results

  •         Results of Each Method
  •         Code (with Crayon Syntax Highlighter) Mainly Important Code
  •         Link to Code Library
  •         YouTube Video Demonstration Link

User testing

While researching the market for similar apps, we found that Pokemon GO, Yu-Gi-Oh! Duel Links and Ingress combined contained almost all the elements of functionality we were going for. This of course meant that we could get inspiration from these apps to see which elements worked and which didn’t. Furthermore we could look more into the users of these apps to get an idea of the market we’re trying to target ourselves. We found that our target users are:

  • Mostly young adults and kids.
  • Mainly men.

Regarding the test of the prototype, we started out with prototyping on paper to test the overall layout of the app. The very first designs looked as follows:

The feedback we got from this was as follows:

  • The horizontal arrangement is clumsy.
  • More interesting and stylized design would be enjoyable.
  • Top-down map seems bland and boring, would prefer 2.5D
  • Use elements from the franchise, for example as the map markers

We made some alternative designs based on this feedback from the users. We would like to implement the 2.5D view (like Pokemon GO), but we found it to be too complex to implement for this simple prototype. We did take the other suggestions into account though. The new prototype looked as follows:

We also generated some simple user demands based on the interviews we’ve had:

  • Less battery usage than Pokemon GO.
  • Easy-to-use UI.
  • Reliable security.
  • Tutorial and easily accessible game concepts.
  • It should not be possible to purchase unbeatable benefits.

 

Relevant code

The first important view that we have, is a map view. This is where the user is able to move around the world to find duelists, battle stops and so on.

 

class MapVC: UIViewController, CLLocationManagerDelegate {



   @IBOutlet weak var map: MKMapView!

   

   var manager = CLLocationManager()

   

   override func viewDidLoad() {

       super.viewDidLoad()

       

       // Getting the users location.

       manager.delegate = self

       manager.desiredAccuracy = kCLLocationAccuracyBest

       manager.requestWhenInUseAuthorization()

       manager.startUpdatingLocation()

       

        map.delegate = self

       

       if let newCoordinate = manager.location?.coordinate {

           let duelist = Duelist(title: "Kaiba", coordinate: CLLocationCoordinate2DMake(newCoordinate.latitude.advanced(by: 0.005), newCoordinate.longitude))

           map.addAnnotation(duelist)

       }



   }

 

 

First off this viewcontroller is implementing the CLLocationManagerDelegate-protocole, meaning that this class has to adhere to the this protocole (by implementing the methods required). This also means that we get access to the CLLocationManager-features. The on we’re interested in in this project is the ability to get the users location. So we initiate a CLLocationManager to a variable. This variable is then used in the viewDidLoad()-method, were we set up the delegate (which is the viewController itself, which is why we implemented the CLLocationManagerDelegate-protocol in the first place). We also the desired accuracy we want when getting the users location, and then asks the user for permission to use their location and finally start getting their location with the startUpdatingLocation()-method.

We altso set the map.delegate to self, since this viewcontroller will be controlling the mapView as well via the outlet at the top of the class.

 

After all this is set up, we add a duelist as an annotation 0.005 latitude-points north of the users current location. This is just done to be able to test the prototype.

 

The Duelist-class looks as follows:

class Duelist: NSObject, MKAnnotation {

   let title: String?

   let coordinate: CLLocationCoordinate2D

   

   init(title: String, coordinate: CLLocationCoordinate2D) {

       self.title = title

       self.coordinate = coordinate

       

       super.init()

   }

   

}

 

 

This class implements NSObject and MKAnnotation and we made it to be able to custimize the annotation to our needs further on in the development process.

   

Next up, we needed to implement a way to segue to another view (the “Duel view”) when a duelist-annotation is pressed on the mapped. This is handled in the following way:

 

extension MapVC: MKMapViewDelegate {

   // 1

   func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

       // 2

       guard let annotation = annotation as? Duelist else { return nil }

       // 3

       let identifier = "marker"

       var view: MKMarkerAnnotationView

       // 4

       if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)

           as? MKMarkerAnnotationView {

           dequeuedView.annotation = annotation

           view = dequeuedView

       } else {

           // 5

           view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)

           view.canShowCallout = true

           view.calloutOffset = CGPoint(x: -5, y: 5)

           view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)

       }

       return view

   }

   

   func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {

       performSegue(withIdentifier: "toDuelistView", sender: view)

   }

}

 

 

So, we’ve added an extension to our Map-viewcontroller which implements MKMapViewDelegate. This enables us to access the annotation on the map and make an AnnotationView which can then be used to show a CallOut (see picture below) which we can then click to perform a segue to the duelistView.

The next important view is the Duelist-view, which has the Duelist-viewcontroller. This viewController implements UICollectionViewDelegate, UICollectionViewDataSource to be able to control and feed data to the collection views we’re using to show the cards on the table. The 2 collectionViews are actually controlled in the same viewController, since they’re located on the same view. So, for example, the cellForItemAt-method below, shows how we’re determining whether we need to return a PlayerCell or a DuelistCell:

 

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

       if collectionView == self.duelistCV {

           

           if let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: "DuelistCell", for: indexPath) as? DuelistCardCell {

               // Set up cell

               cellA.photo = DataService.instance.getDuelistCards()[indexPath.row].image

               print("DuelistCell.")

               return cellA

           

           } else {

               print("StandardCell (duelist)")

               return UICollectionViewCell()

           }



       }

           

       else {

           if let cellB = collectionView.dequeueReusableCell(withReuseIdentifier: "PlayerCell", for: indexPath) as? PlayerCardCell {

               print("PlayerCell.")

               cellB.photo = DataService.instance.getPlayerCards()[indexPath.row].image

               return cellB

           } else {

               print("StandardCell (player)")

               return UICollectionViewCell()

           }

           

       }

   }

 

 

So, we simply check if the collectionView is a Duelist-collectionView and then returns a DuelistCardCell if it is. If it’s not, we know it must be the Player-collectionView that’s calling the function and we therefore return a PlayerCardCell.

 

The last chunk of we code we want to show here, is the way we handle the user interaction on the duelistView. When the user clicks a cell (a card) in one of the collectionViews, the method below is called:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

       if collectionView == self.playerCV {

           if let cell = playerCV.cellForItem(at: indexPath) as? PlayerCardCell {

               if DataService.instance.getPlayer().cardsOnBoard[indexPath.row].attack == 0 {

                   // Generate and set playercard

                   let generatedPlayerCard = GameService.instance.generateCard()

                   cell.photo = generatedPlayerCard.image

                   DataService.instance.getPlayer().setCardsOnBoard(index: indexPath.row, card: generatedPlayerCard)

                   

                   if let duelistCell = duelistCV.cellForItem(at: indexPath) as? DuelistCardCell {

                   // Generate and set duelistcard

                       let generatedDuelistCard = GameService.instance.generateCard()

                       duelistCell.photo = generatedDuelistCard.image

                       DataService.instance.getDuelistAI().setCardsOnBoard(index: indexPath.row, card: generatedDuelistCard)

                   }

               } else {

                   DataService.instance.getPlayer().setSelectedCard(index: indexPath.row)

                   print("Selected player card: \(DataService.instance.getPlayer().selectedCard)")

               }

           }

           print(indexPath.row)

       } else {

           if DataService.instance.getPlayer().cardsOnBoard[indexPath.row].attack != 0 && DataService.instance.getPlayer().selectedCard != -1 {

                   DataService.instance.getDuelistAI().setSelectedCard(index: indexPath.row)

               

                   // Determine winner

                   var damage = GameService.instance.determineWinner(card1: DataService.instance.getPlayer().cardsOnBoard[DataService.instance.getPlayer().selectedCard], card2: DataService.instance.getDuelistAI().cardsOnBoard[DataService.instance.getDuelistAI().selectedCard])

                   if damage < 0 {

                       DataService.instance.getPlayer().deductLifepoints(damage: damage)

                       self.playerLP.text = String(DataService.instance.getPlayer().lifePoints)

                       if DataService.instance.getPlayer().lifePoints <= 0 {

                           winnerLabel.text = "Duelist wins!"

                           winnerLabel.isHidden = false

                           

                           duelistCV.isHidden = true

                           playerCV.isHidden = true

                       }

                   } else {

                       DataService.instance.getDuelistAI().deductLifepoints(damage: damage)

                       self.duelistLP.text = String(DataService.instance.getDuelistAI().lifePoints)

                       if DataService.instance.getDuelistAI().lifePoints <= 0 {

                           winnerLabel.isHidden = false

                           

                           duelistCV.isHidden = true

                           playerCV.isHidden = true

                       }

                   }

                   DataService.instance.getPlayer().setSelectedCard(index: -1)

                   DataService.instance.getDuelistAI().setSelectedCard(index: -1)

               

                   print("Selected duelist card: \(DataService.instance.getDuelistAI().selectedCard)")

           }

       }

   }

 

 

As you can see, this method ended up being pretty long, since we’re basically handling all user interaction in it. Let’s break it down. First off we check if the user clicked a player-card. If he did, we try to cast the cell to a PlayerCardCell. If this succeeds, we check if the card is facing down, by checking if it has 0 attack points. If it is, we will generate a card to the player and show it in the cell. The same thing is done (at the same time) for the duelist, since no real AI is implemented yet. So the duelist will turn a card at the same location as you. If however there already is a card on the location the player is pressing, we set the players selectedCard to the index of the card that’s pressed. This then enables the player to select a card from the duelist that he want to battle against. So when both a player- and a duelistCard has been seleceted, a battle will be triggered and a winner will be determined. The calculation is done in our GameService-class, but for the purposes of this project is pretty simple:

card1.attack card2.defense

If the result of this calculation is less than 0, we know that the Player has taken damage, and we deduct his lifePoints accordingly. If on the other hand the result of the damage-calculation is more than 0, the Player hos won and lifePoints is deducted from the Duelist instead.

If the Player or the Duelists lifePoints hits 0 (or below) it means that they have lost, and a Label is shown indicating who won the battle. Furthermore to the 2 card-CollectionViews is hidden to prevent further interaction from the user. The user can then return to the map by clicking the “Back to map”-button.

 

Link to code on bitbucket.org

https://bitbucket.org/iosprogrammingsdu/yu-gi-oh-go (you need to be a part of the iosprogrammingsdu-team to view the repository, since it is private)

Note: If the program is run on a computer in xCode, the location of the enemy duelist will only show up if you change back and forth between ‘city run’ and no location. This bug only appears on the computer, and is not a problem when the app runs on an iPhone.

Link to YouTube Demonstrational Video

https://youtu.be/pvtkuZ3j_Pg

 

Discussion

As the game is not by any means finished, it currently is not completing any of its goals. The game lacks in complexity, one of the key values of the initial idea, and a main reason for users to want to play it.

With more time and effort, it should be possible for the game to accomplish its aims, namely to make people play videogames outdoors, to fill out the gap in the current market by bringing together the best elements from all its competitors, and to keep users entertained through a longer period of time.

The evaluations suggest that the game has large potential, and that the design and layout is reasonably user friendly and easy to use.

The major hurdle to overcome in order to make the game interesting, is to strike a balance between the game being a monotonous grind, and an overly complex system, with a learning curve that is too steep.

This means that the game needs more complexity than it currently has. There needs to be a larger variety in cards, and the battling needs to be more resembling of the original Yu-Gi-Oh game. However, the game may need to be more simple than the original Yu-Gi-Oh game, which has a lot of cards with special effects, and the overall complexity of the game makes it very difficult for anyone to pick up.

 

The game still needs a lot of functionality to be finished. These include the following:

Better graphics and potentially animations, making it more appealing.

A larger roster of cards, and the ability to gain new cards as you progress.

The ability to set up your deck as you want to, allowing for unique playstyles.

A shop, where you can buy things, which will create the game’s main revenue.

Connection to a server with a database, which can keep track of all the users.

Possibly multiplayer, allowing players to battle each other.

Possibly connection to Google Maps’ API, in order to import special locations.

Special in-game locations that the user can interact with.

 

These are currently the apparent shortcomings of the application, and should be taken into consideration if development of the game was to continue.

 

Compared to the main rivals, Yu-Gi-Oh Duel Link and Pokémon GO, Yu-Gi-Oh GO, in its finished version, would be more complex than Pokémon GO, and more engaging than Yu-Gi-Oh Duel Link.

As mentioned, the game should be able to retain its player base better than Pókemon GO, but the Pókemon franchise is better known, and it may be harder for a game based on Yu-Gi-Oh to build up a large player base. This especially holds true because the complexity of Yu-Gi-Oh could deter some potential users from trying it beyond their first duel.

 

So in order for the game to do well, be enjoyable, and create value, it needs a lot of work and balancing. The important thing is to be able to hold on to users after they start playing, avoiding that the game turns stale and boring, while at the same time ensuring that it is easy enough for most people to pick it up and start playing.

The shop needs to be alluring, but the game can not be ‘pay to win’, as it would ruin the experience for those players who do not wish to pay a lot.

 

Leave a Reply