Advanced Game Design
Camel Code Review
Here are examples of the “Camel” game. Our goal here is to do code reviews on this code.
Before the code review, think about:
First, list what are the goals of the code review.
Look for common mistakes. Keep a to-do list.
Can a person drink more water than is in the canteen?
Do we mis-calculate how far back the people are?
Can the chasing people skip past the person and miss seeing them?
Can we both win and lose the game at the same time? Or otherwise get conflicting messages?
Quantify effectiveness of your code review. (Bugs found, changes made, etc.)
Code reviews often include work on unit-tests. We aren’t doing that here but keep it in mind.
Code reviews should be less than 400 lines and 60 minutes.
Camel Version 1
1using System;
2
3namespace CamelGame
4{
5 class Program
6 {
7 private static void Choices()
8 {
9 Console.WriteLine(" ");
10 Console.WriteLine("A. Drink from your canteen.");
11 Console.WriteLine("B. Ahead moderate speed.");
12 Console.WriteLine("C. Ahead full speed.");
13 Console.WriteLine("D. Stop and rest.");
14 Console.WriteLine("E. Status check.");
15 Console.WriteLine("Q. Quit.");
16 Console.WriteLine(" ");
17 }
18 private static void DistanceTraveled(int camelMovement)
19 {
20 Console.WriteLine(" ");
21 Console.WriteLine("You traveled " + camelMovement + " miles.");
22 }
23 private static void ChoiceA(ref int thirst, ref int drinks)
24 {
25 if (drinks > 0)
26 {
27 drinks -= 1;
28 thirst = 0;
29 }
30 else Console.WriteLine("You are out of water.");
31 }
32 private static void ChoiceB(ref int milesTraveled, ref int thirst, ref int camelTiredness, ref int drinks, ref int nativeDistance, Random random)
33 {
34 int camelMovement = random.Next(5, 12);
35 int nativeMovement = random.Next(7, 14);
36 milesTraveled += camelMovement;
37 thirst += 1;
38 camelTiredness += 1;
39 nativeDistance += nativeMovement;
40 int oasisFound = random.Next(1, 20);
41 if (oasisFound == 1)
42 {
43 Oasis(out thirst, out camelTiredness, out drinks);
44 }
45 DistanceTraveled(camelMovement);
46 }
47 private static void ChoiceC(ref int milesTraveled, ref int thirst, ref int camelTiredness, ref int drinks, ref int nativeDistance, Random random)
48 {
49 int camelMovement = random.Next(10, 20);
50 int nativeMovement = random.Next(7, 14);
51 int addTiredness = random.Next(1, 3);
52 milesTraveled += camelMovement;
53 thirst += 1;
54 camelTiredness += addTiredness;
55 nativeDistance += nativeMovement;
56 int oasisFound = random.Next(1, 20);
57 if (oasisFound == 1)
58 {
59 Oasis(out thirst, out camelTiredness, out drinks);
60 }
61 DistanceTraveled(camelMovement);
62 }
63 private static void ChoiceD(ref int nativeDistance, ref int camelTiredness, Random random)
64 {
65 Console.WriteLine("The Camel is happy");
66 int nativeMovement = random.Next(7, 14);
67 nativeDistance += nativeMovement;
68 camelTiredness = 0;
69 }
70 private static void ChoiceE(int milesTraveled, int drinks, int nativeDistance)
71 {
72 Console.WriteLine("Miles traveled: " + milesTraveled);
73 Console.WriteLine("Drinks in canteen: " + drinks);
74 Console.WriteLine("The natives are " + (milesTraveled - nativeDistance) + " miles behind you.");
75 }
76 private static void Oasis(out int thirst, out int camelTiredness, out int drinks)
77 {
78 Console.WriteLine("You found an oasis!");
79 Console.WriteLine("The camel is rested and your thirst and canteen are replenished.");
80 thirst = 0;
81 drinks = 3;
82 camelTiredness = 0;
83 }
84 static void Main(string[] args)
85 {
86 int milesTraveled = 0;
87 int thirst = 0;
88 int camelTiredness = 0;
89 int drinks = 3;
90 int nativeDistance = -20;
91 string choice;
92 string playAgain;
93 bool done = false;
94 Random random = new Random();
95
96 Console.WriteLine("Welcome to Camel!");
97 Console.WriteLine("You have stolen a camel to make your way across the great Mobi desert.");
98 Console.WriteLine("The natives want their camel back and are chasing you down! Survive your desert trek and out run the natives.");
99
100 while (!done)
101 {
102 Choices();
103 Console.Write("Enter Choice: ");
104 choice = Console.ReadLine();
105 if (string.Equals("Q", choice.ToUpper()))
106 {
107 done = true;
108 }
109
110 else if (string.Equals("A", choice.ToUpper()))
111 {
112 ChoiceA(ref thirst, ref drinks);
113 }
114
115 else if (string.Equals("B", choice.ToUpper()))
116 {
117 ChoiceB(ref milesTraveled, ref thirst, ref camelTiredness, ref drinks, ref nativeDistance, random);
118 }
119
120 else if (string.Equals("C", choice.ToUpper()))
121 {
122 ChoiceC(ref milesTraveled, ref thirst, ref camelTiredness, ref drinks, ref nativeDistance, random);
123 }
124
125 else if (string.Equals("D", choice.ToUpper()))
126 {
127 ChoiceD(ref nativeDistance, ref camelTiredness, random);
128 }
129
130 else if (string.Equals("E", choice.ToUpper()))
131 {
132 ChoiceE(milesTraveled, drinks, nativeDistance);
133 }
134
135 if (thirst > 6)
136 {
137 Console.WriteLine("You died of thirst.");
138 done = true;
139 }
140
141 else if (thirst > 4)
142 {
143 Console.WriteLine("You are thirsty.");
144 }
145
146 if (camelTiredness > 8 & !done)
147 {
148 Console.WriteLine("Your camel is dead.");
149 done = true;
150 }
151
152 else if (camelTiredness > 5)
153 {
154 Console.WriteLine("Your camel is getting tired.");
155 }
156
157 if (milesTraveled - nativeDistance <= 0 & !done)
158 {
159 Console.WriteLine("The natives caught you!");
160 done = true;
161 }
162
163 else if (milesTraveled - nativeDistance <= 15)
164 {
165 Console.WriteLine("The natives are getting close!");
166 }
167
168 if (milesTraveled >= 200 & !done)
169 {
170 Console.WriteLine("You escaped the natives!");
171 done = true;
172 }
173
174 if (done)
175 {
176 Console.Write("Play Again? (Y/N) ");
177 playAgain = Console.ReadLine();
178 if (string.Equals("Y", playAgain.ToUpper()))
179 {
180 done = false;
181 }
182 else if (string.Equals("N", playAgain.ToUpper()))
183 {
184 Console.WriteLine("Thanks for playing!");
185 }
186 }
187 }
188 }
189 }
190}
Camel Version 2
1using System;
2using System.Collections;
3using System.Collections.Generic;
4
5namespace Camel
6{
7 class Program
8 {
9 // Initialize variables for use throughout the entire program
10 static int playerPosition;
11 static int hadesPosition;
12 static int gameLength;
13 static bool done;
14 static int energy;
15 static int maxEnergy = 20;
16 static int drachmas;
17 static int maxShops = 4;
18 static int hadesMovement;
19 static int turnCounter;
20 static int positionDifference;
21 static string distanceStatement;
22 static string energyStatement;
23 static int[] shops;
24 static int movementModifier;
25 static int hadesMovementModifier;
26 static Dictionary<string, int> shopItems;
27 static int speedBoostTurns;
28 static int maxSpeedBoost = 5;
29 static int roadBlockTurns;
30 static int maxRoadBlock = 5;
31 static bool purchaseMade;
32 static bool moved;
33 static bool exitShop;
34 static string lineBreak = "-------------------------------------------------------------------------------------";
35
36 static void Main(string[] args)
37 {
38 // ----------------------------------------- SETUP FUNCTIONS FOR GAME USE -----------------------------------------
39 void InitializeGame()
40 {
41 // Initialize game variables and general setup.
42 hadesPosition = 0;
43 // Set player position relative to Hades at the beginning of the game.
44 playerPosition = SetPlayerPosition(hadesPosition);
45 // Randomize the length that the player must travel.
46 SetGameLength();
47 // Get our list of shop locations.
48 shops = DisperseShops();
49 energy = maxEnergy;
50 turnCounter = 0;
51 // This is used to keep our game running until the player wins or is caught by Hades.
52 done = false;
53 hadesMovementModifier = 0;
54 movementModifier = 0;
55 shopItems = new Dictionary<string, int>();
56 InitializeShop();
57
58 string gameStartTutorial;
59 gameStartTutorial = "You are in ancient Greece and have just completed an undercover recon mission for Zeus. Hades has discovered " +
60 "what you have done and is now chasing you while you make your way back to Olympus! Get back to Olympus before Hades makes you pay " +
61 "the price!";
62
63 Console.WriteLine(gameStartTutorial);
64 Console.WriteLine(lineBreak);
65 }
66
67 // This function will set the Game's length every time it is started up.
68 int SetGameLength()
69 {
70 gameLength = RandomNumber(45, 60);
71 return gameLength;
72 }
73
74 // Function for generating random numbers within the game.
75 int RandomNumber(int min, int max)
76 {
77 Random random = new Random();
78 return random.Next(min, max);
79 }
80
81 // This function sets the players initial starting position with respect to the enemy's position.
82 int SetPlayerPosition(int hadesLocation)
83 {
84 int playerLocation;
85 playerLocation = hadesLocation + RandomNumber(1, 6);
86 return playerLocation;
87 }
88
89 int SetHadesPosition()
90 {
91 hadesMovement = RandomNumber(2, 5);
92 if (hadesMovement - hadesMovementModifier < 0)
93 {
94 hadesMovement = 0;
95 }
96 else
97 {
98 hadesMovement = hadesMovement - hadesMovementModifier;
99 }
100 hadesPosition += hadesMovement;
101 return hadesPosition;
102 }
103
104 int[] DisperseShops()
105 {
106 int previousShop;
107 int interval;
108 int[] shopLocations = new int[maxShops];
109
110 previousShop = 0;
111
112 for (int i = 0; i < maxShops; i++)
113 {
114 interval = RandomNumber(4, 10);
115 if (previousShop + 4 < gameLength && (previousShop + interval) < gameLength)
116 {
117 shopLocations[i] = previousShop + interval;
118 previousShop = shopLocations[i];
119 }
120 }
121
122 return shopLocations;
123 }
124
125 string GetEnergyStatement(int energy)
126 {
127 if (energy <= 3)
128 {
129 return "You are exhausted...";
130 }
131 else if (energy > 3 && energy <= 10)
132 {
133 return "You are starting to get tired...";
134 }
135 else if (energy > 10 && energy <= 15)
136 {
137 return "You still feel moderately energetic.";
138 }
139 else
140 {
141 return "You are pulsing with energy!";
142 }
143 }
144
145 void CheckForAncientRuins()
146 {
147 int random;
148 int drachmasGained;
149 random = RandomNumber(0, 26);
150 drachmasGained = RandomNumber(5, 15);
151 if (random == 12)
152 {
153 Console.WriteLine("You have stumbled upon some ancient ruins. You have found " + drachmasGained + " drachmas and your energy has been restored!");
154 drachmas += drachmasGained;
155 energy = maxEnergy;
156 }
157 }
158
159 void Purchase(string itemName, int price)
160 {
161 string shopStatement = "";
162
163 if (itemName.Equals("Energy Drink") && drachmas >= price)
164 {
165 energy = maxEnergy;
166 shopStatement = "Your energy has been replenished!";
167 shopItems.Remove("Energy Drink");
168 drachmas -= price;
169 purchaseMade = true;
170 }
171 else if (itemName.Equals("Speed Boost") && drachmas >= price)
172 {
173 movementModifier = RandomNumber(2, 4);
174 speedBoostTurns = maxSpeedBoost;
175
176 shopStatement = "Your movement speed has been boosted by " + movementModifier +
177 " for " + speedBoostTurns + " turns!";
178 shopItems.Remove("Speed Boost");
179 drachmas -= price;
180 purchaseMade = true;
181 }
182 else if (itemName.Equals("Road Block") && drachmas >= price)
183 {
184 hadesMovementModifier = RandomNumber(1, 3);
185 roadBlockTurns = maxRoadBlock;
186 shopStatement = "You have injured Hades and have restricted his movement by " +
187 hadesMovementModifier + " for " + roadBlockTurns + " turns!";
188 shopItems.Remove("Road Block");
189 drachmas -= price;
190 purchaseMade = true;
191 }
192 else if (drachmas < price)
193 {
194 Console.WriteLine("You cannot afford that!");
195 purchaseMade = false;
196 }
197 else
198 {
199 Console.WriteLine("You have decided to leave.");
200 exitShop = true;
201 }
202 if (purchaseMade)
203 {
204 Console.WriteLine("You have purchased one " + itemName);
205 Console.WriteLine(shopStatement);
206 }
207 }
208
209 void OpenShop()
210 {
211 string shopStatement = "You have stumbled upon a traveling merchant!";
212 int i = 1;
213 Console.WriteLine(shopStatement);
214 Dictionary<int, string> itemList = new Dictionary<int, string>();
215
216 foreach (KeyValuePair<string, int> item in shopItems)
217 {
218 string displayItem = i + ". " + item.Key + " -------- " + item.Value;
219 Console.WriteLine(displayItem);
220 itemList.Add(i, item.Key);
221 i += 1;
222 }
223
224 string leave = i + ". Leave";
225 itemList.Add(i, "Leave");
226 string playerDrachmas = "Your current held drachmas: " + drachmas;
227 string buy = "Would you like to make a purchase?";
228 Console.WriteLine(leave);
229 Console.WriteLine(playerDrachmas);
230 Console.WriteLine(buy);
231
232 purchaseMade = false;
233 exitShop = false;
234
235 while (!purchaseMade && !exitShop)
236 {
237 string userInput = Console.ReadLine();
238
239 try
240 {
241 string itemName;
242 int price;
243
244 itemList.TryGetValue(Convert.ToInt32(userInput), out itemName);
245 shopItems.TryGetValue(itemName, out price);
246 Purchase(itemName, price);
247 exitShop = true;
248 }
249 catch
250 {
251 Console.WriteLine("Please enter a valid number.");
252 }
253 }
254 }
255
256 void InitializeShop()
257 {
258 shopItems.Add("Energy Drink", 10);
259 shopItems.Add("Speed Boost", 25);
260 shopItems.Add("Road Block", 15);
261 }
262
263 void IncrementItemDuration()
264 {
265 if (speedBoostTurns > 0)
266 {
267 speedBoostTurns -= 1;
268 }
269 if (roadBlockTurns > 0)
270 {
271 roadBlockTurns -= 1;
272 }
273 }
274
275 void GetUserDecision()
276 {
277 string slow;
278 string medium;
279 string fast;
280 string rest;
281 string input;
282 string status;
283 string quit;
284 bool decided;
285 int drachmasGained = 0;
286
287 decided = false;
288
289 slow = "1. Slow and Steady...";
290 medium = "2. Keep a moderate pace.";
291 fast = "3. Full steam ahead!!!";
292 rest = "4. Stop and take a rest...";
293 status = "5. Journey Status.";
294 quit = "6. Quit Game.";
295
296 if (positionDifference >= 10)
297 {
298 Console.WriteLine("Hades is very far away...");
299 }
300 else if (positionDifference >= 6)
301 {
302 Console.WriteLine("Hades is getting closer.");
303 }
304 else
305 {
306 Console.WriteLine("Hades is right on your tail!!!");
307 }
308 Console.WriteLine();
309 Console.WriteLine(slow + "\n" + medium + "\n" + fast + "\n" + rest + "\n" + status + "\n" + quit);
310 Console.WriteLine("What would you like to do?");
311
312 while (!decided)
313 {
314 // Get user input and set up if statements to evaluate user input.
315 input = Console.ReadLine();
316
317 int distanceTraveled;
318 int energyUsed;
319
320 // If player has decided to take it slow...
321 if (input.Equals("1"))
322 {
323 distanceTraveled = (RandomNumber(1, 3) + movementModifier);
324 energyUsed = RandomNumber(-3, -1);
325 playerPosition += distanceTraveled;
326 distanceStatement = "You have decided to play it safe, and have traveled " + distanceTraveled + " miles. ";
327 energyStatement = GetEnergyStatement(energy);
328 drachmasGained = RandomNumber(1, 3);
329
330 // Make sure we are not surpassing the maximum energy cap.
331 if (energy - energyUsed < maxEnergy - energyUsed)
332 {
333 energy -= energyUsed;
334 }
335 else
336 {
337 energy = maxEnergy;
338 }
339
340 IncrementItemDuration();
341
342 decided = true;
343 }
344 // If player has decided on medium travel speed...
345 else if (input.Equals("2") && energy >= 4)
346 {
347 distanceTraveled = (RandomNumber(3, 5) + movementModifier);
348 energyUsed = RandomNumber(2, 4);
349 playerPosition += distanceTraveled;
350 energy -= energyUsed;
351 distanceStatement = "You have decided to move at a steady pace, and have traveled " + distanceTraveled + " miles. ";
352 energyStatement = GetEnergyStatement(energy);
353 drachmasGained = RandomNumber(2, 4);
354 drachmas += drachmasGained;
355 IncrementItemDuration();
356
357 decided = true;
358 }
359 // If player has decided to go full speed...
360 else if (input.Equals("3") && energy >= 8)
361 {
362 distanceTraveled = (RandomNumber(4, 8) + movementModifier);
363 energyUsed = RandomNumber(6, 8);
364 drachmasGained =
365 playerPosition += distanceTraveled;
366 energy -= energyUsed;
367 distanceStatement = "You have decided to travel at full speed, and have traveled " + distanceTraveled + " miles. ";
368 energyStatement = GetEnergyStatement(energy);
369 drachmasGained = RandomNumber(3, 6);
370 IncrementItemDuration();
371
372 decided = true;
373 }
374 // If player has decided to rest...
375 else if (input.Equals("4"))
376 {
377 energyUsed = RandomNumber(-10, -6);
378 energy -= energyUsed;
379 distanceStatement = "You have decided to take a rest and recover your energy. ";
380 energyStatement = GetEnergyStatement(energy);
381 IncrementItemDuration();
382
383 decided = true;
384 }
385 else if (input.Equals("5"))
386 {
387 Console.WriteLine("----------------- STATUS REPORT -----------------\nEnergy: " + GetEnergyStatement(energy) +
388 "\nDrachmas: " + drachmas + "\nHades is " + positionDifference + " miles behind you.");
389 decided = false;
390 }
391 else if (input.Equals("6"))
392 {
393 bool decisionMade = false;
394 Console.WriteLine("Are you sure you would like to quit?\n1. Yes\n2. No");
395 string choice;
396
397 while (!decisionMade)
398 {
399 choice = Console.ReadLine();
400
401 if (choice.Equals("1"))
402 {
403 Console.WriteLine("You have exited the game.");
404 done = true;
405 decided = true;
406 decisionMade = true;
407 }
408 else if (choice.Equals("2"))
409 {
410 decisionMade = true;
411 }
412 else
413 {
414 Console.WriteLine("Please enter either 1 or 2.");
415 }
416 }
417 }
418 // If player has entered anything that is not above or does not have enough energy...
419 else
420 {
421 if ((input.Equals("2") && energy < 4) || (input.Equals("3") && energy < 8))
422 {
423 Console.WriteLine("You do not have enough energy for that!");
424 }
425 else
426 {
427 Console.WriteLine("That is not an option, please enter a number between 1 and 4.");
428 }
429 // Keep loop running.
430 decided = false;
431 }
432 }
433
434 // Update Hades position and print out the game status.
435 if (!done)
436 {
437 SetHadesPosition();
438 Console.WriteLine(distanceStatement + energyStatement);
439 Console.WriteLine("While traveling you found " + drachmasGained + " drachmas!");
440 drachmas += drachmasGained;
441 turnCounter += 1;
442 }
443
444 for (int i = 0; i < shops.Length; i++)
445 {
446 if (playerPosition == shops[i])
447 {
448 OpenShop();
449 }
450 }
451 }
452 // -----------------------------------------------------------------------------------------------------------------------
453
454 // ----------------------------------------------------MAIN GAME SETUP----------------------------------------------------
455 InitializeGame();
456
457 // Run this loop while the game is not over.
458 while (!done)
459 {
460 string input;
461 bool playAgainDecision;
462 if (playerPosition < gameLength)
463 {
464 if (playerPosition <= hadesPosition)
465 {
466 Console.WriteLine("You were caught! Game Over!");
467 playAgainDecision = false;
468 Console.WriteLine("Would you like to try again?\n1. Yes\n2. No");
469
470 while (!playAgainDecision)
471 {
472 input = Console.ReadLine();
473
474 if (input.Equals("1"))
475 {
476 InitializeGame();
477 playAgainDecision = true;
478 done = false;
479 }
480 else if (input.Equals("2"))
481 {
482 playAgainDecision = true;
483 done = true;
484 }
485 else
486 {
487 Console.WriteLine("Invalid Input. Please enter either 1 or 2.");
488 playAgainDecision = false;
489 }
490 }
491 }
492 else
493 {
494 CheckForAncientRuins();
495 GetUserDecision();
496 positionDifference = playerPosition - hadesPosition;
497 Console.WriteLine(lineBreak);
498 }
499 }
500 else
501 {
502 Console.WriteLine("You win!");
503 playAgainDecision = false;
504 while (!playAgainDecision)
505 {
506 input = Console.ReadLine();
507
508 if (input.Equals("1"))
509 {
510 InitializeGame();
511 playAgainDecision = true;
512 done = false;
513 }
514 else if (input.Equals("2"))
515 {
516 playAgainDecision = true;
517 done = true;
518 }
519 else
520 {
521 Console.WriteLine("Invalid Input. Please enter either 1 or 2.");
522 playAgainDecision = false;
523 }
524 }
525 }
526 }
527 }
528 // -----------------------------------------------------------------------------------------------------------------------
529 }
530}
Camel Version 3
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Threading.Tasks;
6
7namespace CamelGame
8{
9 class Program
10 {
11 private static readonly int MILES_TO_HIDEOUT = 200;
12
13 private static bool done;
14 private static bool win;
15 private static bool quit;
16 private static int milesTraveled;
17 private static int fillupsLeft;
18 private static int policeMilesTraveled;
19 private static int gasTankLeft;
20 private static char userInput;
21 private static bool validInput;
22 private static readonly Random rand = new Random();
23
24 static bool FoundOasis(int findingNumber)
25 {
26 if (findingNumber == 15)
27 {
28 return true;
29 }
30 else
31 {
32 return false;
33 }
34 }
35
36 static void Main()
37 {
38 bool playAgain = true;
39
40 while (playAgain)
41 {
42 Console.WriteLine("Welcome to Bank Heist!\n" +
43 "You have stolen one-million dollars from a bank and must escape to your secret hide out.\n" +
44 "The police are hot on your tail and will stop at nothing to catch you!\n" +
45 "Out run the cops and escape to your hideout to keep your freedom.\n");
46
47 done = false;
48 win = false;
49 validInput = true;
50
51 milesTraveled = 0;
52 gasTankLeft = 0;
53 fillupsLeft = 3;
54
55 policeMilesTraveled = -20;
56
57
58 while (!done)
59 {
60 Console.WriteLine();
61 Console.WriteLine("A. Ahead moderate speed.\n" +
62 "B. Ahead full speed.\n" +
63 "C. Stop to fill up the gas tank.\n" +
64 "D. Status check.\n" +
65 "Q. Quit.\n");
66
67 Console.Write("What is your choice? ");
68 userInput = Console.ReadKey().KeyChar;
69 Console.WriteLine("\n");
70
71 validInput = true;
72
73 // The user chooses to quit the game.
74 if (char.ToUpper(userInput) == 'Q')
75 {
76 QuitGame();
77 }
78 // The user chooses to check their status.
79 else if (char.ToUpper(userInput) == 'D')
80 {
81 CheckStatus();
82 }
83 // The user chooses to hide for the night.
84 else if (char.ToUpper(userInput) == 'C')
85 {
86 StopToFillGas();
87 }
88 // The user chooses to move ahead full speed.
89 else if (char.ToUpper(userInput) == 'B')
90 {
91 MoveAhead(false);
92 }
93 // The user chooses to move ahead slowly.
94 else if (char.ToUpper(userInput) == 'A')
95 {
96 MoveAhead(true);
97 }
98 // The user input was invalid.
99 else
100 {
101 Console.WriteLine("You input was invalid.");
102 validInput = false;
103 }
104
105 CheckIfCaught();
106
107 }
108
109 if (win)
110 {
111 Console.WriteLine("\nCongratulations! You've escaped the police and won the game!");
112 }
113 else if (!win && !quit)
114 {
115 Console.WriteLine("\nYou have lost the game.");
116 }
117 else
118 {
119 Console.WriteLine("\nThanks for playing.");
120 }
121
122 Console.Write("Would you like to play again? (Y/N) ");
123 userInput = Console.ReadKey().KeyChar;
124 Console.WriteLine("\n");
125
126 if (char.ToUpper(userInput) == 'Y')
127 {
128 playAgain = true;
129 }
130 else if (char.ToUpper(userInput) == 'N')
131 {
132 playAgain = false;
133 }
134 else
135 {
136 Console.WriteLine("You have entered an invalid value and the game will now close.\n");
137 playAgain = false;
138 }
139 }
140
141 Console.WriteLine("Thank you for playing. Press any key to exit.");
142 _ = Console.ReadKey();
143 }
144
145 private static void CheckIfCaught()
146 {
147 if (char.ToUpper(userInput) != 'Q' && validInput)
148 {
149 if (gasTankLeft > 8 && !done)
150 {
151 Console.WriteLine("Your car ran out of gas and you got caught.");
152 done = true;
153 }
154 else if (gasTankLeft > 5)
155 {
156 Console.WriteLine("Your gas is getting low.");
157 }
158
159 if ((milesTraveled - policeMilesTraveled) <= 0 && !done)
160 {
161 Console.WriteLine("The police caught you.");
162 done = true;
163 }
164 else if ((milesTraveled - policeMilesTraveled) <= 15)
165 {
166 Console.WriteLine("The police are getting close!");
167 }
168
169 if (milesTraveled >= MILES_TO_HIDEOUT && !done)
170 {
171 done = true;
172 win = true;
173 }
174 }
175 }
176
177 static void QuitGame()
178 {
179 done = true;
180 quit = true;
181 }
182
183 static void CheckStatus()
184 {
185 Console.WriteLine("Miles traveled: " + milesTraveled);
186 Console.WriteLine("Gas fill-ups remaining: " + fillupsLeft);
187 Console.WriteLine("The police are " + (milesTraveled - policeMilesTraveled) +
188 " miles behind you.");
189 }
190
191 static void StopToFillGas()
192 {
193 gasTankLeft = 0;
194 fillupsLeft -= 1;
195 policeMilesTraveled += rand.Next(7, 15);
196
197 if (policeMilesTraveled < milesTraveled)
198 {
199 Console.WriteLine("Your gas tank is full.");
200 }
201 }
202
203 static void MoveAhead(bool slow)
204 {
205 int currentMilesTraveled;
206 if (!slow)
207 {
208 currentMilesTraveled = rand.Next(10, 21);
209 gasTankLeft += rand.Next(1, 4);
210 }
211 else
212 {
213 currentMilesTraveled = rand.Next(5, 13);
214 gasTankLeft++;
215 }
216 milesTraveled += currentMilesTraveled;
217 policeMilesTraveled += rand.Next(7, 15);
218 Console.WriteLine("You traveled " + currentMilesTraveled + " miles.");
219
220 int findingAHideout = rand.Next(19);
221
222 if (FoundOasis(findingAHideout) && milesTraveled < MILES_TO_HIDEOUT)
223 {
224 Console.WriteLine("You found an abandoned hideout!");
225 fillupsLeft = 3;
226 gasTankLeft = 0;
227 }
228 }
229
230 }
231}
Blender to Unity
Our goal is to learn to create simple, low-poly 3D items in Blender. Color them. Then import to Unity.

Note
Don’t forget to Blender scale interface up before showing this tutorial so people can see.
Set Up Unity
Create a new 3D project in Unity
Create a 20x20 dark green plain for the ground
Create 3D Items in Blender
Open Blender
Notice window and hierarchy, like Unity
Navigation
Click-left to select
Middle click to rotate around focused object (Unity is alt-left click)
Number pad . to change focus (Unity is F key)
Shift-Middle button pans (unity is just left click if you are in the ‘hand tool’ mode)
Show axis thing in upper right to select side views. Also show num pad
Delete everything. We don’t want to import a camera or light.
We will be creating one file for each object.
Select item in hierarchy or screen, then delete key
Add->Mesh->Cylinder
Lower left, expand out window and select 0.5 meters and 12 verts
Edit object
Explain object mode, and edit mode. Use tab to switch
Show vert, edge, face select tools
Show alt-click to get circle
Show ‘E’ to extend.
Show ‘S’ to scale.
Show ‘G’ to move.
Show xyz to select axis
Make pine tree. Show how to scale to zero.
Materials in Blender
Materials
Show how to create a material for leaves
Assign it.
Can’t see it! Show select material view. And other views.
Create new material for trunk
Now need to assign. Show wireframe, face select, hidden faces.
Import in Unity
More Practice
Weekend Assignment
Out of class, work through Chapter 1 and Chapter 2. You can skip the last object modifiers item in Chapter 2.
Mixamo to Unity
This covers how to get a 3D character into your scene, using Mixamo character assets and animations.

Mixamo has a few character assets (not its primary purpose) and a lot of animations for characters (its primary purpose).
Setup
Our goal here is to create a landscape for our character to walk around. We’ll add a plane and have a few cubes to help with a sense of distance and perspective.
Create a new Unity 3D project
Add a plane, name it “Ground”
Scale x/z to 10x10
Create a grass material color
Add material to plane
Create a cube
Create a different material and add to cube
Add rigid body physics to cube. Test.
Duplicate a few cubes
Position the camera
Don’t forget to save

Download the Character from Mixamo
Go to Mixamo.com
Log in. You’ll log in with an Adobe id or some SSO choice they have.
Get the Character
Once there, go to the “Character” tab and find a character you like. I’m using Claire.

Select your character
Hit “Download”
You want “FBX for Unity”. You do not want the generic FBX it defaults to.
Make sure T-Pose is selected
Download
Get the Animations
Now we need our idle and walking animations.

Switch to “Animations”
Search on “Idle”
Select an idle animation. If you don’t see it play with your character hit “refresh” on the browser. You can adjust the animation. For example, widen out the hands so they don’t clip through the characer.
Click “Download”
Select FBX for Unity. (Again, the default FBX doesn’t work.)
Select “Without Skin” because we already downloaded that.
Next, repeat for a walking animation. You’ll get an extra check-box for in-place which you must check. This will keep the animation from moving the character forward, while the code thinks the character is in the same location.
Warning
You must select “In-Place” checkbox for any moving animation
Add Mixamo Characters and Animations to Project
Now we want to get the character to appear in our project.
Create a folder for your character. In this case, I used “Claire”.
Create subfolders for “Materials” and “Textures”
Drag the character and two animations from your ‘downloads’ to the folder you created.
Drag the character from the assets to your scene. It will be white, as no textures ore materials have been applied yet.
Next click on your character in Assets.
Select Materials in the Inspector panel.
Click “Extract Textures” and put them in the Textures folder we created.
Click “Extract Materials” and put them in the Materials folder we created.
If you get a message like this, just go ahead and fix.
Now your character should look good.
Get Character to Move
Now we need to get the character to move around. We are going to use a character controller. It is more complex than rigid body physics, but offers more control.
Add Character Controller
Add Character Script
Make the camera a ‘child’ of the player and position behind the player.
Add this character script:
1using System.Collections;
2using System.Collections.Generic;
3using UnityEngine;
4
5public class CharacterScript : MonoBehaviour
6{
7 [SerializeField] Transform playerCamera = null;
8 [SerializeField] float mouseSensitivity = 3.5f;
9 [SerializeField] float walkSpeed = 6.0f;
10 [SerializeField] float gravity = -13.0f;
11 [SerializeField] [Range(0.0f, 0.5f)] float moveSmoothTime = 0.3f;
12 [SerializeField] [Range(0.0f, 0.5f)] float mouseSmoothTime = 0.03f;
13
14 [SerializeField] bool lockCursor = true;
15
16 float cameraPitch = 0.0f;
17 float velocityY = 0.0f;
18 CharacterController controller = null;
19
20 Vector2 currentDir = Vector2.zero;
21 Vector2 currentDirVelocity = Vector2.zero;
22
23 Vector2 currentMouseDelta = Vector2.zero;
24 Vector2 currentMouseDeltaVelocity = Vector2.zero;
25
26 void Start()
27 {
28 controller = GetComponent<CharacterController>();
29 if (lockCursor)
30 {
31 Cursor.lockState = CursorLockMode.Locked;
32 Cursor.visible = false;
33 }
34 }
35
36 void Update()
37 {
38 UpdateMouseLook();
39 UpdateMovement();
40 }
41
42 void UpdateMouseLook()
43 {
44 Vector2 targetMouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
45
46 currentMouseDelta = Vector2.SmoothDamp(currentMouseDelta, targetMouseDelta, ref currentMouseDeltaVelocity, mouseSmoothTime);
47
48 cameraPitch -= currentMouseDelta.y * mouseSensitivity;
49 cameraPitch = Mathf.Clamp(cameraPitch, -90.0f, 90.0f);
50
51 playerCamera.localEulerAngles = Vector3.right * cameraPitch;
52 transform.Rotate(Vector3.up * currentMouseDelta.x * mouseSensitivity);
53 }
54
55 void UpdateMovement()
56 {
57 Vector2 targetDir = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
58 targetDir.Normalize();
59
60 currentDir = Vector2.SmoothDamp(currentDir, targetDir, ref currentDirVelocity, moveSmoothTime);
61
62 if (controller.isGrounded)
63 velocityY = 0.0f;
64
65 velocityY += gravity * Time.deltaTime;
66
67 Vector3 velocity = (transform.forward * currentDir.y + transform.right * currentDir.x) * walkSpeed + Vector3.up * velocityY;
68
69 controller.Move(velocity * Time.deltaTime);
70
71 }
72}
While the character does not animate yet, it should be able to move with mouse and WASD keys.
Animate
Add Armature Rigs
Select your character in the assets folder.
In the “Inspector” tab, select “Rig”.
Select “Humanoid”
Select “Create From This Model”.
Select “Apply”
Select the “Idle” animation.
In the “Inspector” tab, select “Rig”.
Select “Humanoid”
Select “Copy From Other Avatar”.
Double-click on “Source” and select the avatar you just created
Select “Apply”
Repeat for the “Walk” animation.
There may be warnings. That’s ok.
Add Idle Animation
Add Speed Parameter
We will need to transition from idle to walking based on speed. We need to update our character controller to spit this out. Here’s our updates:
1using System.Collections;
2using System.Collections.Generic;
3using UnityEngine;
4
5public class CharacterScript : MonoBehaviour
6{
7 [SerializeField] Transform playerCamera = null;
8 [SerializeField] float mouseSensitivity = 3.5f;
9 [SerializeField] float walkSpeed = 6.0f;
10 [SerializeField] float gravity = -13.0f;
11 [SerializeField] [Range(0.0f, 0.5f)] float moveSmoothTime = 0.3f;
12 [SerializeField] [Range(0.0f, 0.5f)] float mouseSmoothTime = 0.03f;
13 Animator _animator;
14
15 [SerializeField] bool lockCursor = true;
16
17 float cameraPitch = 0.0f;
18 float velocityY = 0.0f;
19 Vector3 velocity = Vector3.zero;
20
21 CharacterController controller = null;
22
23 Vector2 currentDir = Vector2.zero;
24 Vector2 currentDirVelocity = Vector2.zero;
25
26 Vector2 currentMouseDelta = Vector2.zero;
27 Vector2 currentMouseDeltaVelocity = Vector2.zero;
28
29 void Start()
30 {
31 _animator = GetComponentInChildren<Animator>();
32 controller = GetComponent<CharacterController>();
33 if (lockCursor)
34 {
35 Cursor.lockState = CursorLockMode.Locked;
36 Cursor.visible = false;
37 }
38 }
39
40 void Update()
41 {
42 UpdateMouseLook();
43 UpdateMovement();
44 float speedPercent = velocity.magnitude / walkSpeed;
45 _animator.SetFloat("speed", speedPercent);
46 }
47
48 void UpdateMouseLook()
49 {
50 Vector2 targetMouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
51
52 currentMouseDelta = Vector2.SmoothDamp(currentMouseDelta, targetMouseDelta, ref currentMouseDeltaVelocity, mouseSmoothTime);
53
54 cameraPitch -= currentMouseDelta.y * mouseSensitivity;
55 cameraPitch = Mathf.Clamp(cameraPitch, -90.0f, 90.0f);
56
57 playerCamera.localEulerAngles = Vector3.right * cameraPitch;
58 transform.Rotate(Vector3.up * currentMouseDelta.x * mouseSensitivity);
59 }
60
61 void UpdateMovement()
62 {
63 Vector2 targetDir = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
64 targetDir.Normalize();
65
66 currentDir = Vector2.SmoothDamp(currentDir, targetDir, ref currentDirVelocity, moveSmoothTime);
67
68 if (controller.isGrounded)
69 velocityY = 0.0f;
70
71 velocityY += gravity * Time.deltaTime;
72
73 velocity = (transform.forward * currentDir.y + transform.right * currentDir.x) * walkSpeed + Vector3.up * velocityY;
74
75 controller.Move(velocity * Time.deltaTime);
76
77 }
78}
Now in the Animator, we should be able to add speed
:

Then we can add in our “walk” animation. Add transitions, and make it based on speed. Greater than 0.3, we animate. Less than 0.3, we idle.

Right now, the animations will only run once. Double-click between both animations and make sure that “Loop Time” box is checked for both animations.

Also, the animations won’t transition until they are done. Flip between both animations and uncheck “Has Exit Time.”

Uncheck Root Motion
Depending on your animation, the animation can move the character. Typically it works best if it is just an animation. Select your character, and in the “Animator” section, uncheck root motion:

References
Acacia Developer. First Person Controller. Sep 10, 2020
Acacia Developer. Unity FPS Controller code. Sep 10, 2020
Niklas Bergstrand. Adding walk and run animation in Unity. May 19, 2021
Texture Objects
We want to put an image on an object, rather than just have a solid color.
Texture Types
There are several types of textures.
Diffuse/Albedo map - Color for object. The is the basics of what you need. Although the image can look “flat.” Think bricks. Shouldn’t look flat, but will be with just a diffuse map.
Bump maps - Create illusion of depth via grayscale data. Shade of gray is height. These are grayscale images.
Normal maps - Better than bump maps, uses RGB for more info. This can give us x, y, and z. Allows for angle and more realistic looks. These maps tend to look blue.
Displacement/Height map - This map is used to actually changes surface they are on.
Specular/Metallic - Maps out what part of the image is shiny.
Here are some samples from Texturise, their “Tilable Wood Planks Texture”.

Texture

Normal

Specular

Displacement

Albido/Texture image/Color

Normal

Displacement

Specular

Everything
Here they are, in action on Blender.
Texture Websites
Where can you get textures?
Poliigon (Paid)
Very Simple Textures
Create a new project.
Add a 10x10 plane.
Create a folder called “Textures”
Toss the images there.
Create a material in that folder.
Toss onto the plane.
Put images into texture
Toss ‘texture’ to Albedo.
Toss ‘specular’ to ‘metalic’. Change to ‘Albedo Alpha’ and turn smoothness down to about 0.1. You can use this for occlusion instead.
Toss ‘normal’ to ‘normal map’
Toss ‘displacement’ to ‘Height map’

You can change the ‘tiling’ to control how many times it repeats on the surface.
UV Mapping
Take some road textures:

Road texture

Road texture normal

Road texture specular
Create a road texture. I used specular for occlusion. Apply to a new cube.

Looks ok. But what if we scale the cube?

We need to change the geometry, and not scale the item. Then do a “UV Unwrap”.
Go to blender. Create a cube. Go into edit mode and not object mode. Change the cube dimensions.

Change the bottom view to UV. Do a smart UV unwrap:

Delete camera and light. Save into your Assets folder. Toss cube onto scene. Apply material. See how it maps?
Change mapping. Save. See results.


2D Unity Part 1
Contents
Create sample sprites and add to Unity
Clone the base Unity project: https://github.com/pvcraven/2022_Class_2D_Project
Create sprites in Aseprite.
Use NES palette
Create a 16x16 character.
Create a 16x32 tree. (Or some other size, keeping in mind 16x16 is the character size.)
Save to
Assets/Sprites/Trees
orAssets/Sprites/Characters
folder.Call your character
tree_name
orcharacter_name
. Obviously, use your first and/or last name, not “name”.Export your sprite as a
.png
in that same folder.
Open in Unity, confirm the assets are there.
Do a git add, commit, push and pull to sync with the whole class.
Warning
Be careful of .meta files
Unity adds a .meta
file that tags a GUID for each file. If you create
or move a file into a Unity project, let unity create a .meta
for it
before check in! This includes the exported .png
. Failure to do this
will cause a lot of merge headaches.
Change sprite settings
Create your own scene. Call it
scene_name
.Drag character onto the screen.
Way too small. Unity defaults to 100 pixels to one ‘unit’ which is 1 meter. Change from 100 to 16.
Great. Now the character is blurry. Change the filtering to ‘point’.
Character might be blotchy. Turn off compression.
Should be able to run the scene and see character properly.
Repeat these steps for your sprites. Don’t do this for other people’s sprites.
Sync with GitHub.
Make sprites solid
Add a rigid body 2d. Run the game. Character should now fall.
Zero out the gravity.
Add to your character, the MyCharacterController script that is already in the project under the scripts folder. Examine the script and see how it works.
Should be able to move character with WSAD. Can adjust speed as needed.
Add your tree.
Try running. No collision.
Add colliders to the character and tree.
There are circle colliders, capsule colliders, box colliders. Pick the best one.
You might not want to make a collider around everything for a more 3D look.
Try running. Character spins!
Freeze rotation.
Character may or may not appear behind/ahead of the tree properly. You can use sort mode in project settings to fix:
Add in score
Add in a sprite to increase your score.
You’ll need a collider. Make the collider a “trigger”.
You’ll need to add in the ScoreScript. Examine this script and the character controller together to see how they work.
Set the points for the score script.
Test.
You can also have items that make the score go down by putting in a negative number for points.
Add in scene change
Create a sprite that will will cause you to go to the next level.
You’ll need a collider. Make the collider a “trigger”.
You’ll need to add in the SceneChangeScript. Examine this script and the character controller together to see how they work.
Your scene must appear in File…Build Settings. This is where you determine the order of levels. As this is a common area, only one person can edit at a time. So let the instructor do this in class.
Summary
This should step you through most of what you need to complete 2D Assignment 1. Expand what you’ve learned to create an explorable level. Don’t worry about the background image yet, we’ll get to that with tiles.
2D Unity Part 2
Create tile set
Tiles will be 16x16. We’ll make multiple tiles at a time. Make a 16*3 and 16*4 image:

Keep in mind in Aseprite you can:
Use things like 16*3 in the sprite dimensions, no need to multiply itself.
You can show the grid overlay
Import and split tile set
When you import the sprite, we need to set our standard three changes, and then set it to multiple sprites. Then we click on the nearly-hidden sprite editor button and slice it up.

Commit and push.
Create tile map and palette
Create a new rectangular tile map for your scene.
Open the tile palette.
Create your own tile palette with your own name

Create a new palette. Create a new folder for it “Tile Palette”.

Select your sprites. Move to palette. Create folder for “Tile images”.

Order is weird. Somehow there’s a way to import better I think, but I don’t know it. To change order, click ‘Edit’ button and then alternate between S and M keys to move tiles to where you’d like.

Paint with the tiles.
Change your rendering order so tiles appear below your sprites. Use layers, or ordering in layers.

Show how to do layers
Show how to do a tile collider 2d
2D Animation
Contents
In this tutorial we’ll work on animating sprites.
Create a time-based animation in Aseprite
Create a folder for your animation.
Follow one of these tutorials:
Source: SadFace-RL Fire Animations
Source: SadFace-RL Water Animations
Work on using:
Keyboard shortcuts
Select tool
Frames
Export a sprite sheet.
File->Export Sprite Sheet
Output->Output file
Import a sprite sheet in Unity
Import a sprite sheet and slice it like we did before.

Drag the first image onto your scene.
Click Window…Animation
Click your object, you should see an option to create an animation and controller from it.
Drag images onto the timeline
Too fast.
Drag out the frames, slow it down

Create animated character frames in Aseprite


Get character working with idle animation in Unity
Here’s a video that covers what we are doing:
First, go ahead an import your character animations, then slice up the images.
If you want to replace a character you already have with the animated sprites, (recommended) you can replace the character’s texture by dragging the sprite image to the proper location.

Make sure your program still works ok.
Create an idle animation for your character like we did before.

Make sure that works. Now we need a clip for walking/running. Add a new clip from the Animator tab:

Show how to play clip, and change clip.
See how both clips show up in Animator.
Add a parameter, and transitions:

Update code:
1using System.Collections;
2using System.Collections.Generic;
3using UnityEngine;
4using UnityEngine.SceneManagement;
5
6
7public class MyAnimatedCharacterController : MonoBehaviour
8{
9 public int score = 0;
10
11 Rigidbody2D body;
12
13
14 float horizontal;
15 float vertical;
16 float moveLimiter = 0.7f;
17
18 public float runSpeed = 5.0f;
19
20 public AudioSource sound;
21 public AudioSource scoreIncreaseSound;
22 public AudioSource scoreDecreaseSound;
23
24 private SpriteRenderer spriteRenderer;
25 private Animator animator;
26
27 void Start()
28 {
29 // Get the rigid body component for the player character.
30 // (required to have one)
31 body = GetComponent<Rigidbody2D>();
32 spriteRenderer = GetComponent<SpriteRenderer>();
33 animator = GetComponent<Animator>();
34 }
35
36 void Update()
37 {
38 // Get our axis values
39 horizontal = Input.GetAxisRaw("Horizontal");
40 vertical = Input.GetAxisRaw("Vertical");
41 }
42
43 void FixedUpdate()
44 {
45
46 // If player is running diagonally, we don't want them to move extra-fast.
47 if (horizontal != 0 && vertical != 0) // Check for diagonal movement
48 {
49 // limit movement speed diagonally, so you move at 70% speed
50 horizontal *= moveLimiter;
51 vertical *= moveLimiter;
52 }
53
54 if (horizontal > 0.1)
55 spriteRenderer.flipX = false;
56 else
57 spriteRenderer.flipX = true;
58
59 // Set player velocity
60 body.velocity = new Vector2(horizontal * runSpeed, vertical * runSpeed);
61 animator.SetFloat("HorizontalSpeed", Mathf.Abs(horizontal));
62 }
63
64 void OnTriggerEnter2D(Collider2D colliderEvent)
65 {
66 // Did we run into an object that will affect our score?
67 ScoreScript scoreObject = colliderEvent.gameObject.GetComponent(typeof(ScoreScript))
68 as ScoreScript;
69
70 if (scoreObject != null)
71 {
72 // Yes, change the score
73 score += scoreObject.points;
74
75
76 // Destroy the object
77 Destroy(colliderEvent.gameObject);
78 }
79
80 // Did we run into an object that will cause a scene change?
81 SceneChangeScript sceneChangeObject = colliderEvent.gameObject.GetComponent(typeof(SceneChangeScript))
82 as SceneChangeScript;
83 if (sceneChangeObject != null) {
84 // Yes, get our current scene index
85 int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
86 // Load up the scene accourding to the sceneChange value
87 UnityEngine.SceneManagement.SceneManager.LoadScene(currentSceneIndex + sceneChangeObject.sceneChange);
88 }
89 }
90 void OnGUI()
91 {
92 // Dispaly our score
93 GUIStyle guiStyle = new GUIStyle(GUI.skin.label);
94 guiStyle.fontSize = 32; //modify the font height
95 GUI.Label(new Rect(10, 10, 250, 50), "Score: " + score, guiStyle);
96 }
97}
2D Shooting
Contents
There are three main ways to shoot things in Unity.
For laser-types of things where you insta-hit, you can use ray-casting.
You can move sprites and check by distance.
You can move sprites, and use colliders. This is how we are going to demo things here.
Make a sprites in Aseprite
We need a target and a projectile.
Make a bullet, laser, heart, whatever projectile you want to shoot.
Make a target to hit.
Export, and import into Unity changing the normal three things. (pixels per unit, compression, filter)

Detect mouse down events
Now, we will use the mouse button to shoot. First, we need to detect mouse-down events.
In our Update
method on cour controller (not FixedUpdate
, doesn’t seem to work well), we can detect a mouse-down
event with Input.GetMouseButtonDown(0)
. The 0 is for our left mouse button. An implementation might look like:
// Has the mouse been pressed?
if (Input.GetMouseButtonDown(0))
{
Debug.Log("Mouse down");
}
Code and confirm it works.
Create a bullet
Now we need something to shoot.
Create a bullet prefab.
Add a box collider so we can detect collisions. Set the collider to be a trigger, as we don’t want it bumping into things.
Add a rigidbody so we can move it via physics.

Go to your character controller cand add a public variable for the prefab. Code would look like:
public GameObject bulletPrefab;
Then drag the prefab into the new blank spot in your character.

Update code to fire the bullet:
// Mouse pressed?
if (Input.GetMouseButtonDown(0))
{
// Make a bullet
var bullet = Instantiate(bulletPrefab, body.position, Quaternion.identity);
// Get the body of the bullet
var bulletbody = bullet.GetComponent<Rigidbody2D>();
// Move the bullet to the right
bulletbody.velocity = new Vector2(4, 0);
}
It would be better code if you make the speed a public variable rather than hard-code it. And we’ll get to aiming in a bit.
Create targets
Now we need something to shoot. Create targets. Add a collider. Add a tag for “Destroyable”.

Add a bullet script to destroy
This bullet script will destroy itself after moving 8 units, or it will destroy an object tagged ‘destroyable’.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletScript : MonoBehaviour
{
Vector3 _origin;
public float maxDistance = 8.0f;
// Start is called before the first frame update
void Start()
{
// Get position we started at, so we can see how far the bullet traveled.
_origin = transform.position;
}
public void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("Trigger");
if (collision.tag == "Destroyable")
{
Debug.Log("Destroyable");
// Destroy item we hit
Destroy(collision.gameObject);
// Cause bullet to destroy itself
// Put this outside the if to get deleted when hitting non-destroyable objects
Destroy(gameObject);
}
}
// Update is called once per frame
void Update()
{
// How far has the bullet gone?
float distance = Vector2.Distance(_origin, transform.position);
// If too far, then remove ourselves from the game.
if (distance > maxDistance)
{
// Cause bullet to destroy itself
Destroy(gameObject);
}
}
}
Calculate angles
Next, if we want to fire in a particular direction, we need to do some math. Here’s the code with comments.
1 // Get the angle of a vector
2 public float GetYRotFromVec(Vector2 v1)
3 {
4 float _r = Mathf.Atan2(v1.y, v1.x);
5 float _d = (_r / Mathf.PI) * 180;
6
7 return _d;
8 }
9
10 void Update()
11 {
12 // Get our axis values
13 horizontal = Input.GetAxisRaw("Horizontal");
14 vertical = Input.GetAxisRaw("Vertical");
15
16 // Has the mouse been pressed?
17 if (Input.GetMouseButtonDown(0))
18 {
19 // -- Fire a bullet
20
21 // Create the bullet
22 var bullet = Instantiate(bulletPrefab, body.position, Quaternion.identity);
23 // Get a reference to the bullet's rigid body
24 var bulletbody = bullet.GetComponent<Rigidbody2D>();
25 // Where is the mouse on the screen?
26 var mousePosition = Input.mousePosition;
27 // Where is the mouse in the world?
28 Vector3 target3 = Camera.main.ScreenToWorldPoint(mousePosition);
29 // Set the z value of this vector 3
30 target3.z = 0;
31 // What is the normalized vector from the player to the mouse?
32 Vector2 direction = (target3 - transform.position).normalized;
33 // What is the angle in degrees?
34 float angle = GetYRotFromVec(direction);
35 // Rotate the bullet
36 bulletbody.rotation = angle;
37 // Give the bullet speed
38 bulletbody.velocity = direction * bulletSpeed;
39 }
40 }
Adding a Bloom Effect
Contents
Step 1 - Add the post processing package to your project
Go to Window -> Package Manager and then install the “Post Processing” package. This is project-wide so this only needs to happen once for an entire project.

Step 2 - Enable HDR for the project

Step 3 - Add a post-processing layer to the camera
Select the camera. Add a post process layer component to the camera.

Select the ‘Bloom’ layer. You may need to create this layer if it does not yet exist for your project.

Step 4 - Create a post processing profile
Find/create a directory for post processors.
Create a post processor:

Add a bloom effect:

Step 5 - Create a post processing volume
Go to your project, add an empty. Call it “post-process bloom” or something like that.
Add a “Process Volume” component to it.
Drag in the post processor to the proper field.

This makes everything glow, fine if you are doing some neon geometry wars thing. But what about just one thing?
Step 6 - Make one thing glow
Set post-processing intensity to 1. Zero turns it off, we don’t want that. Above 1 will make everything glow. Don’t want that.
Create a new material called “Glow”.
Give it the following properties:

You have to specify the color, it doesn’t pick it up from the image.

2D Particle System
Contents
Let’s make particles! For a YouTube video that covers this, see: https://www.youtube.com/watch?v=_z68_OoC_0o
Create a white sprite particle
Use Aseprite
Add a particle system
In Unity, select GameObject -> Effects -> Particle System. You should now have a new particle system in your game throwing off fuzzy dots.

The rotation of the default system has the particles flying up. Take out the -90 rotation on the particle game object and the particles fly towards the camera. Experiment with it.
Experiment with shape of emitter.

Add gravity to make the particles fly down.
Make the particles sprites

Scale the particles

Color the particles

Amount of particles
Adjust “rate over time”
Particle trails
Try adding trails, as shown in the video.
Make things blow up when hit
Update your code so that your bullet script will create a “burst” prefab when you hit an item. You’ll need to have the prefab be created with a script that will destroy itself over time.
Note
This example just shows the important parts. It doesn’t show the needed “make the bullet disappear after a while.” We showed that earlier. You’ll need to combine your scripts.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BurstBulletScript : MonoBehaviour
{
public GameObject burstPrefab;
Rigidbody2D body;
// Start is called before the first frame update
void Start()
{
body = GetComponent<Rigidbody2D>();
}
public void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Destroyable")
{
// Destroy the item
Destroy(collision.gameObject);
// Create the 'burst' effect
var burst = Instantiate(burstPrefab, body.position, Quaternion.identity);
}
}
}
2D Attacks
This section based off: https://www.youtube.com/watch?v=1QfxdUpVh5I
Add time-limited trigger for attacks
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 public class CravenAttackScript : MonoBehaviour
6 {
7 // How frequently can we attack?
8 public float attackTimeLimit = 0.5f;
9
10 // Countdown timer for attacks
11 private float attackCountdownTimer = 0;
12
13 void Update()
14 {
15 // See if we can attack, via timer.
16 if (attackCountdownTimer <= 0)
17 {
18 // We can attack. See if user hit space bar.
19 if (Input.GetKey(KeyCode.Space))
20 {
21 Debug.Log("Attack");
22 attackCountdownTimer = attackTimeLimit;
23 }
24 }
25 else
26 {
27 // Attack timer needs count-down
28 attackCountdownTimer -= Time.deltaTime;
29 }
30 }
31 }
Do damage
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 public class CravenAttackScript : MonoBehaviour
6 {
7 // How frequently can we attack?
8 public float attackTimeLimit = 0.5f;
9
10 // Countdown timer for attacks
11 private float attackCountdownTimer = 0;
12
13 // An empty parented that says where to attack
14 public Transform attackPos;
15 // Radius of attack circle
16 public float attackRange;
17 // What layer will the enemies be on?
18 public LayerMask enemyLayer;
19 // How much damage to deal
20 public int damage = 3;
21
22 void Update()
23 {
24 // See if we can attack, via timer.
25 if (attackCountdownTimer <= 0)
26 {
27 // We can attack. See if user hit space bar.
28 if (Input.GetKey(KeyCode.Space))
29 {
30 Debug.Log("Attack");
31 // Reset the countdown timer
32 attackCountdownTimer = attackTimeLimit;
33 // What enemies did we hit?
34 Collider2D[] enemiesToDamage = Physics2D.OverlapCircleAll(attackPos.position, attackRange, enemyLayer);
35 // Loop through each enemy we hit
36 for(int i=0; i < enemiesToDamage.Length; i++)
37 {
38 // Get the enemy script attached to this object
39 CravenEnemyScript enemyScript = enemiesToDamage[i].GetComponent<CravenEnemyScript>();
40 // If there is an enemy script
41 if (enemyScript)
42 {
43 // Damage
44 enemiesToDamage[i].GetComponent<CravenEnemyScript>().health -= damage;
45 // Print health levels
46 Debug.Log(enemiesToDamage[i].GetComponent<CravenEnemyScript>().health);
47
48 // --- ToDo: destroy enemy here when health <= 0
49 }
50 else
51 {
52 // We hit an enemy, but there's no script attached to it.
53 Debug.Log("Enemy Script not present");
54 }
55 }
56 }
57 }
58 else
59 {
60 // Attack timer needs count-down
61 attackCountdownTimer -= Time.deltaTime;
62 }
63 }
64 // Used to draw a circle when we are selecting the player in the scene view
65 void OnDrawGizmosSelected()
66 {
67 Gizmos.color = Color.red;
68 Gizmos.DrawWireSphere(attackPos.position, attackRange);
69 }
70 }
Note
You’ll need: * An enemy script * Turn on gizmos in the scene view * An enemy layer * Program a change to the attackPos when user changes direction.
Camel in C#
If you had me for CMSC 150, you likely remember the Camel game. Your task for this assignment is to code the Camel game in C#.
Here is the link for the description of the Camel game:
https://arcade-book.readthedocs.io/en/latest/labs/lab_04_camel/camel.html
Use Visual Studio. It is free to install. You can download it from here: https://visualstudio.microsoft.com/downloads/
Create a new console app project, and call it Camel
:

This open with a “Hello World” program. Run the program. It will appear in a separate console window as opposed to a window in the IDE.
Here’s some code to get started:
1 using System;
2
3 namespace Camel
4 {
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 // Introductory message
10 Console.WriteLine("Welcome to Camel!");
11
12 // Main game loop
13 bool done = false;
14 while (!done)
15 {
16 // Print commands
17 Console.WriteLine();
18 Console.WriteLine("A. Drink from your canteen.");
19 Console.WriteLine("B. Ahead moderate speed.");
20 Console.WriteLine("C. Ahead full speed.");
21 Console.WriteLine("D. Stop and rest.");
22 Console.WriteLine("E. Status check.");
23 Console.WriteLine("Q. Quit.");
24
25 // Get user command
26 Console.Write("What is your command? ");
27 string userCommand = Console.ReadLine();
28 Console.WriteLine();
29
30 // Process user command
31 if (userCommand == "a")
32 {
33 Console.WriteLine("You drank from the canteen.");
34 } else
35 {
36 Console.WriteLine("Unknown command.");
37 }
38 }
39 }
40 }
41 }
Part of this task is practicing how to quickly search up answers. I’m not going to step through how to code in C#, you have enough talent to get started on your own.
We will review some of programs together so we can get ideas from each other.
Today, make sure you have created a project that can print “Hello World.” By the time you come to class Thursday, have a start to the main game loop.
While it is possible to code the program in one function and loop, see if you can use good design and break the parts into functions.
Feel free to change the theme and add features.
If you change the theme, you must still have a number line you are traveling across, some kind of resource you can run out of, and “something” that can catch you.
Be ready to present your work on Thursday and your final project on Tuesday.
To turn in, upload GitHub URL to your project.
Roll-a-Ball
Follow the Unity tutorial for roll-a-ball: https://learn.unity.com/project/roll-a-ball
Put into git, and use this .gitignore file: https://github.com/github/gitignore/blob/main/Unity.gitignore
Upload to GitHub
Create a readme and include a screenshot
Tuesday next week will be workday
Bring in something mostly working.
Get help with github
Get help with readme and image
Thursday we’ll demo our games
Turn in github url
Custom Roll-a-Ball
Start with your prior roll-a-ball assignment.
Create at least two objects in Blender, and add them as obstacles in your game.
Obstacles must be at least as complex as the trees we created.
Obstacles must not be the same trees we created. Do something different.
Obstacles must have at least two different materials on them.
Update the collectable to have a custom shape you create in blender.
Feel free to turn off the rotation if it doesn’t work for your collectable.
Update your playing field with a custom playing field created in Blender.
Grading will be a somewhat subjective. Impress me, don’t shoot for the minimum.
Update the image in your read-me.
Turn in a Git-Hub link to your project.
Team 3D Game Work
For this project (and the coming weeks) you’ll break into groups and develop a 3D game. Your goal is to improve this game, week-by-week.
Starting the Project
Start with one of your roll-a-ball games as a working base.
Invite other team members to that project.
You must cite any 3d models, textures, sounds that you use from another source.
Get this started by creating a section in your readme to hold this info.
Figure out how you want to stay in contact. Exchange info. (E-mail, slack, discord, etc.)
Brainstorm Ideas
Theme?
Color scheme? See Adobe Kuler and create a swatch?
Create one or more tasks for everyone. Backup tasks are a good idea. See “Some Ideas On Tasks”
Each Week on Thursday
Pick goals/tasks for each person to get done that week.
Enter each task as an issue in GitHub.
Assign it to the proper person.
If you notice bugs, enter them in GitHub and assign to the proper person.
Each Week on Tuesday
Make sure everyone’s work has been merged into GitHub.
Test your application to make sure it is working.
Help each other with the tasks and bugs.
Some Ideas on Tasks
Object modeling
Create an interesting playing field
Add more objects, add more detail to objects
Materials
Learn some of the options for materials, and make things shiny, etc.
Note: Learn these in Unity, not Blender. Blender to Unity material transfer is limited.
Textures
Map an image onto an item (UV Mapping) Can do in Blender or Unity.
Learn to use normal maps
Create water
Shaders
Work with shaders to create better looking materials
Lighting
Instead of one generic light, add better lighting. Spot lights, lamps, etc.
Skybox
Learn to add a skybox. Warning: Be very careful about how hi-res of an image you download. These can be huge and blow up your project if you download something too big.
Sound
Add sound for pickups
Add sound when bouncing into objects
Add sound for movement
Create level system
Go to new level if all items are picked up
Go to new level if player gets to a goal point
Enemies
Create items that reset the user to the start if you bump into them
Create have player lose a life when hitting enemy
Support ‘game over’ when player loses all lives
Have enemy move towards player
Investigate path finding to have enemy move around objects
Particles
Create liquids, smoke, clouds, flames, magic effects
Shooting
Be able to shoot things. Enemies, collectables, walls.
UI
Create intro/instruction screens
Allow game restart
Show lives left
Add background/panel to UI
Add dialog system (encounter NPC, have popup dialog)
Multiplayer
Add networking
Animation
Animate obstacles
Make moving platforms
Create switches that trigger events
Create a 3d car instead of a ball to move around
Create a 3d walking character rather than a rolling ball. Use Mixamo.
Player
Add ability to jump
Add ability to run
Important Notes
Do not add assets into a folder without using Unity. This will lead to merge errors that will lose you a lot of time.
If working on a challenging item, have a back-up goal. You’ve got to get something done, so you don’t want to be stuck if things are more complex than expected.
Everyone must be on the same version of Unity. Do not upgrade your Unity. That will force everyone to upgrade, or you’ll just end up losing your work.
Your work must be integrated. For example, if your task is designing a tree, don’t spend all your time making a beautiful tree in Blender and never get it into the game. Create a cylinder in Blender. Get it into the game. Fancy it up with some branches. Get that in the game. Add materials. Get that into the game. If something isn’t in the game, it might as well not exist.
Commit early. Commit often. If you only commit during one day this week, it won’t look like you’ve done much work at all.
The fancy materials and modifiers you use in Blender are not likely to show up in Unity. Keep it simple. Make sure things work in Unity before sinking a lot of time into them.
Turn In
Turn in a report.
Summarize what you finished this week.
Link to the GitHub project.
Link to the issue that has the item(s) you worked on.
Link to your commits. It will look something like: https://github.com/pythonarcade/arcade/commits?author=pvcraven
Include an image of what you did, and show it working in the game.
Grading
I’ll grade the way I evaluated the work of my employees back when I worked IT.
Integration with the project. When I hit ‘play’ on the game, can I see what you did? If so, that will help give you a good grade. Don’t make the mistake of adding a model, sound, material, or some other component, but not make it part of gameplay. If I hit ‘play’ and can’t see your work, then it serves no purpose. When adding items, start with a simple version. For example, a cube, a beep, code that just prints “hello world” at the right trigger. You have something working. Go back and add detail. Always keep it in the playable game.
Frequency of commits. Do you have commits spread across three or more days? This shows ongoing work and integration with the whole project. In the workplace, I’d expect commits every day. Or hour or two. If you are doing something that might break the project, do it in a separate branch, then merge. Ask if you’d like help learning to do this.
Quantity/complexity of work. Did you do some scripting? Or add a detailed model? Or add a lot of different low-poly models?
Documentation. Did you include links to your project and your commits? Did you detail what you did that works in words? Include screenshots? Did you make it so simple to see what you did, I don’t even need to clone the game? Did you see me in class and show off your work there? Did you use the issue tracking? As a manager, I’m looking at that more than diving into your code. You don’t want managers diving into the code, make it easy for them to track progress.
Citations.
2D Assignment 1
In this assignment, we’ll get started with 2D.
Requirements
Turn in a report detailing and showing (with screenshots) your completion of:
Item |
Points |
---|---|
Sprite 1 |
10 |
Sprite 2 |
10 |
Sprite 3 |
10 |
Sprite 4 |
10 |
Sprite 5 |
10 |
Scene |
10 |
Proper collision |
10 |
Implement scored items |
10 |
Implement next level transition |
10 |
Get something other than player moving |
10 |
Scoring:
0 pts - not implemented
1-5 pts - buggy
6-7 pts - meets minimum requirements. i.e., it works.
8-9 pts - Expanded beyond minimum requirements
10 pts - Expanded into something that looks like an actual game.
Directions
To get started, clone our project. Get invited as a collaborator.
https://github.com/pvcraven/2022_Class_2D_Project
All item names must include yours so we can identify them.
Then most of what you need to get started is at: 2D Unity Part 1.
The main thing not covered is getting some objects to move via scripts. I’m leaving that up to you to figure out.
Sample Items to Create
Outdoors
Tree
Rock
Fence
Grass
Flowers
House or some building
Icons
Pencil
Hand
Hand-held Items
Wand
Sword
Staff
Gems
Coin
Potion
Clothing
Boot
Shoe
Shirt
Vest
Hat
Helmet
Food
Fruit
Pumpkin
Apple
Pear
Orange
Grapes
Pineapple
Raspberry
Watermelon
Strawberry
Cherries
Bananas
Other food
Mushroom
Ice cream code
Donut
Cookie
Pizza
2D Assignment 2
In this assignment, we’ll get continue work on our 2D level. This assignment will concentrate on:
2D Tile maps
Adding sound effects
Requirements
Turn in a report detailing and showing (with screenshots) your completion of:
Item |
Points |
Scoring |
---|---|---|
Basic Tiles Created |
15 |
Set of 12 background tiles, as shown in class. Must have some detail to get full 15 points. |
Additional tiles created |
15 |
Create at least three additional tiles. I’d suggest something that would be an obstruction, or additional background tiles. |
Background tile map layer |
20 |
Background tile layer. Must be reasonably extensive for the full 20 points. |
Collision tile map layer |
15 |
Add things to run into as part of tile tile map. |
Sound effects added |
20 |
Sound effect for points up, points down, and level up/down. |
Unity background music |
15 |
Play some background music. Don’t forget to turn it off when changing levels. |
Directions
Most of what you need to get started with the tiles can be found in 2D Unity Part 2.
The sound effects and background music should not be too hard. I’m leaving it up to your web-search skills to figure out how to add that.
2D Animation Assignment
Like earlier assignments, please create a doc that points to the work that you did. Get practice documenting and showing off your work, as this is going to be very important for getting promoted in your career.
Item |
Points |
Scoring |
---|---|---|
Create animated time-based sprite frames 1 |
10 |
Create at least 8 frames of a time-based sprite. This can be from the fire or water sprite created in class during 2D Animation. To turn in, take a screenshot and show the resulting images. |
Create animated time-based sprite in Unity 1 |
5 |
Get the animated sprite from above working in Unity. Slow down the keyframes so it doesn’t run full speed. |
Create animated time-based sprite frames 2 |
10 |
Create at least 8 frames of a time-based sprite. |
Create animated time-based sprite in Unity 2 |
5 |
Get the sprite working. |
Create animated time-based character sprite frames |
10 |
Create “idle” and “walking” animations in Aseprite. Show the images created. |
Create animated time-based character sprite in Unity |
10 |
Idle animation must play. When character moves, the walking animation must start. When character is done walking, go back to idle. Select left/right based on direction character is moving. |
Create animated time-based NPC sprite frames |
10 |
Create one more moving animation, this time for an NPC. Must have two different animations. (Idle/walk) |
Create animated time-based NPC sprite in Unity |
10 |
Get both animations and transition to work for NPC character. |
Expand level |
10 |
Take the level that you have, and make it bigger. I suggest showing the original level, and the expansion. You’ll probably want at least two screens worth of additional layout. |
Total |
100 |
2D Final Assignment
Like earlier assignments, please create a doc that points to the work that you did. Get practice documenting and showing off your work, as this is going to be very important for getting promoted in your career.
Item |
Points |
Scoring |
---|---|---|
Add the ability to fire projectiles |
20 |
Some kind of projectile needs to be fired. |
Create a destroyable target |
15 |
Have an item that is destroyed when hit by the projectile |
Create something that glows |
15 |
At least one item needs a glow effect |
Create a particle system |
20 |
For full points, the particle effect needs to be triggered. For example, an explosion when an item is hit. Stand-alone particle systems that just emit are worth about 16 points. Scoring will be somewhat dependent on amount of particle features used. |
Create an ‘attack’ system. |
20 |
? |
Demo your level. |
10 |
Be in class for our last class, and show off your level. |
Total |
100 |