Placeholder Image

字幕列表 影片播放

  • [MUSIC PLAYING]

  • COLTON OGDEN: All right.

  • Welcome back to GD50.

  • This is Lecture 8.

  • Today we're going to be diving into the world of Unity

  • for the first time, which I'm excited about.

  • Going to be a whirlwind tour, but I'll try to cover as much as possible.

  • Transitioning away from 2D and away from Lua and LOVE 2D into 3D and C#

  • in the context of Unity.

  • Today we'll be talking about Helicopter Game 3D.

  • So Helicopter Game is a 2D game that was really famous in the 2000s.

  • It was a web game.

  • I was sponsored on a bunch of websites.

  • Addictinggames.com still has it on there,

  • and a few other websites have it.

  • It was a flash game.

  • But I remember playing it a lot.

  • It was the old precursor to Flappy Bird, which

  • was mentioned on the Wikipedia page.

  • There's a reference to it there.

  • And this is what the game play looked like.

  • It was very similar to Flappy Bird-- a little bit different

  • in that, instead of trying to avoid pipes, you're in a cave,

  • and you're trying to avoid the ceiling and the ground of the level.

  • And there were these little obstacles that would spawn in the middle,

  • as well.

  • So you'd have to navigate that.

  • But it was the same exact mechanic-- the sort of like, click to go up.

  • And then, when you didn't click, your helicopter

  • would just sink down via gravity.

  • Today we'll be talking about a bunch of brand new topics, things like Unity,

  • first and foremost-- the ecosystem with which we'll be doing

  • a lot of the things we'll be doing.

  • C# is the primary language we'll be using.

  • So we're going to take a step away from dynamic languages and move towards

  • statically-typed languages--

  • languages like C#, and Java, and the like.

  • Blender is a program we'll look at briefly today,

  • just because, in the context of 3D development,

  • you're going to want to have a tool that will let you create models.

  • And so the 3D software that I like to advocate for the most, especially

  • for folks that are just starting out, is Blender, because it's free,

  • and open source, and it has much the same feature

  • set as any commercial software, like 3D Studio Max, and Cinema 4D,

  • and the like.

  • We'll talk about what components are-- entities and components, how

  • they relate in this model that Unity has adopted for all of its programming.

  • Components are little pieces of behavior that you can then

  • combine to form a whole, that will then drive the behavior of whatever object

  • in your scene you want, rather than having

  • to customize its behavior via a long chain of inheritance and instantiation.

  • Colliders and triggers are important in 3D--

  • and 2D.

  • But today we'll be talking about colliders

  • and triggers-- things like the helicopter

  • colliding with coins, and buildings, and other planes that are flying.

  • Each of those has to have a collider.

  • And certain things have to be considered triggers in order to trigger

  • certain behavior with other entities.

  • Prefabs and spawning-- prefabs is a huge concept in Unity.

  • So prefabs are basically prefabricated objects

  • that you can customize as you want to-- lay them out in the editor,

  • rather than having to necessarily code all the details.

  • And then you can instantiate them in the actual scene,

  • via code programmatically, in a way that fits the model you're striving for.

  • Texture scrolling is something we'll look at briefly, because it's

  • the way that we accomplish the infinite scrolling aesthetic or behavior.

  • And we'll look at how we can do that in a different way

  • than we've done before, using u-v coordinates,

  • and specifically looking at materials, and modifying

  • certain attributes of those materials.

  • And lastly, to tie everything together as we've done before,

  • we'll look at audio-- things like audio listeners and audio sources--

  • what the difference is between them, and how

  • to add them easily to our game project.

  • But first, a demo-- if there would be anybody

  • willing to come up and take a look and play the 3D helicopter game that I

  • put together, that would be awesome.

  • Anybody?

  • Steven?

  • Awesome.

  • Thank you so much.

  • Let me go ahead and actually get it--

  • so I've pre-built it.

  • So let me go ahead and.

  • So the nice thing about Unity is, it exports to multiple platforms.

  • And right out of the gate, you can get just a--

  • I didn't put an icon for it.

  • But you can create just a native application very easily.

  • And so whenever you're ready, go ahead and hit Play,

  • and Up and Down will move your helicopter.

  • So this is the 3D helicopter game.

  • And I don't think we have sound live, but there should be audio.

  • Oh, I might have actually-- here we go.

  • [MUSIC PLAYING]

  • There we go.

  • That was my bad.

  • So there is music playing.

  • There's sound effects.

  • So notice that we have a 3D model.

  • This is what's called a 2.5D game.

  • So even though everything is in 3D--

  • the models and so forth--

  • the actual axes upon which we're bound are just two.

  • We're just bound to, I believe, the x and the y.

  • Could be the z and the x.

  • I don't recall offhand.

  • But we're bound to just simply two axes of movement.

  • But all the models, as we can see by the camera,

  • are in 3D, including our helicopter.

  • So we have a few things going on.

  • We have skyscrapers that are scrolling by.

  • We have coins that are also going by at the same speed as the skyscrapers.

  • We have a background that is infinitely scrolling.

  • We have, of course, our helicopter which has a rotating set of blades.

  • And when we collide with a coin, notice that we get a little--

  • it might be hard to see in house, but we have a little particle

  • effect that plays.

  • There's airplanes that are flying up top,

  • so we're instantiating those, as well, to fly past us

  • to provide another layer of obstacle.

  • And if we collide with an airplane, notice that we get de-spawned,

  • and then we trigger another particle effect to imitate an explosion.

  • And then, notice we also have a couple of other elements.

  • We have a GUI.

  • We have two GUI elements-- a coin total at the top right,

  • and then a game over here in the middle of the screen, which

  • only shows up once we have died.

  • And the explosive behavior-- if you want to collide with a building,

  • you'll see that.

  • It also triggers when you collide with a building.

  • So there's two things looking for these explosions-- the airplanes up top,

  • and the buildings below.

  • Those are our two obstacles.

  • But when they collide with the coins, we should increment our coin total,

  • and then display a different particle effect.

  • And then this goes on ad infinitum.

  • You can press Space to restart.

  • So we have keyboard input that's based on what

  • we press, different things happen.

  • And so that's effectively the demo that I've put together today.

  • So thanks, Steven.

  • I appreciate you coming up to demo it.

  • So that's the 3D helicopter game.

  • It's got most of the same mechanics as the web version from before--

  • I would say, maybe even more features just to illustrate a few new concepts.

  • But that's effectively what we're going with today.

  • We're just a pretty simple, Flappy Bird esque differently-themed game,

  • based on the same principles.

  • Fly forever, avoid obstacles.

  • And in this case, even get little collectibles.

  • And so notice that there are also effectively two states

  • in our game, which are just the playing state, and then the game over state.

  • The two are almost effectively the same.

  • The only real difference is that one doesn't have the helicopter present,

  • and displays a different GUI element in the middle of the screen.

  • If you haven't downloaded Unity already, there's two links here.

  • So the top link is just the catchall download link.

  • And then the second link is the beta link.

  • So we're actually using the beta in this course,

  • because Unity has started transitioning away

  • from a numerical system for their releases,

  • and is now going yearly with their releases.

  • So the last long-term release candidate was 2017's version.

  • But now that we're almost halfway through 2018,

  • the newest beta is the 2018 version.

  • It has a bunch of new features.

  • So go ahead and check that out.

  • And everything's been well tested, and runs very well--

  • very smoothly on Windows and Mac--

  • with the new beta.

  • So what is Unity?

  • The difference between what we've done so far and what we're doing today is,

  • now we're actually using a full-fledged game engine--

  • this system.

  • It's got a built in editor and all this awesome, cool

  • functionality that we really didn't get with Love2D before.

  • What we were doing before was using a framework,

  • and just implementing everything purely in code.

  • And as we'll see today, everything that you want to do that's customizable

  • effectively is--

  • or can be-- done via code.

  • But there are a lot of more efficient and more user-friendly

  • ways to accomplish the same thing, which we'll take a look at.

  • So Unity has a tremendous market share right now.

  • I forget.

  • I think in 2016 it had like 43% of all games released were done in Unity.

  • I don't know what the current numbers are.

  • I couldn't find them.

  • But there are other engines that are also very well used--

  • Unreal being among them.

  • And Unreal actually may have more market share now,

  • because of games like Fortnite, and because it's

  • been improved a lot over the last couple of years, and really marketed well.

  • But Godot, CryEngine-- there are a lot of game engines

  • that are similar to this that provide you

  • this all-encompassing way of dealing with your game scene

  • and with all your game data.

  • But Unity is a very easy engine to start getting used to,

  • and cranking things out, and being productive with,

  • without the tremendous learning curve that some

  • of the other engines like Unreal might have.

  • Unreal does have a more user-friendly way of doing things.

  • But if you want to get down into the nitty gritty with Unreal,

  • you're coding in semi-arcane C++.

  • So for folks who aren't used to it and aren't used to 3D game development,

  • it can be kind of a large board to get onto.

  • The nice thing about Unity, aside from the fact

  • that it's fairly easy to get started with, is that it's free.

  • And you can use it completely with all of its features

  • until you start making over $100,000 in gross revenue releasing Unity products.

  • And then there are other tiers.

  • The next tier above that is if you start making $200,000,

  • and you get new features with these other tiers.

  • But if you want to just start up a new company,

  • and use Unity, and take something to market--

  • completely free to do so.

  • And once you get over $100,000, that's a good problem to have.

  • It's not necessarily too much to ask to start

  • paying Unity to use it as a means of getting

  • onto the market in the first place.

  • And especially in mobile and VR, Unity's sort of like the forefront.

  • It's got even higher percentage.

  • It's like 60-something or 70% market share on mobile.

  • And then VR-- from the beginning, it's marketed itself very strongly

  • towards the use of VR.

  • And we'll actually use VR in the next lecture.

  • And the way in which we will accomplish all of the programmatic aspect

  • of this-- getting things actually implemented in code--

  • is via C#, which is very different than what we've used so far,

  • which we've used Lua, which is a dynamic scripting language,

  • very much like JavaScript.

  • So C# is very similar to Java, in which things actually have types.

  • And so here's a couple of screenshots of what the Unity editor looks like.

  • So the nice thing about the Unity editor, actually--

  • which we can see right off the gate here--

  • is that it's very customizable.

  • So on the top, that's the default view.

  • You have a bottom panel that shows you all your resources,

  • all your assets, things like scripts, and shaders, and models,

  • and textures, and sounds.

  • You have a nice file browser there on the left-hand side, which allows

  • you to easily navigate your project.

  • You don't have to go looking through your Windows or Mac computer, using

  • your finder, or whatnot, or your File Explorer,

  • and actually look through all your files.

  • You get a nice view there, so that you stay integrated

  • within the Unity ecosystem right there.

  • On the right-hand side, you can see all the behavior for whatever objects

  • you might have in your scene.

  • You get a nice big scene view.

  • So rather than having to run your game live, and look

  • at the behavior that way, you can actually

  • see in advance what your scene looks like, and analyze

  • your game objects that way, and their appearances,

  • and whatever you want to do with them.

  • And lay them out perfectly, rather than have

  • to programmatically figure things out.

  • And on the left-hand side there, you can see

  • a full list of all the game objects.

  • And then on the bottom right screen shot,

  • you can see that the editor is heavily customizable.

  • So you can lay things out however you want

  • to to fit your style of development.

  • I often like to have a panel on top, which shows me my scene view,

  • but then have a panel right below that, that shows me the game view.

  • So if I'm actually running a game and seeing it happen live,

  • I can also see it in the scene, and rotate around in this god mode

  • that you can get in a lot of games.

  • And actually analyze and see things that way,

  • and analyze their behavior frame by frame,

  • even if I want to, in a way that's not possible with however

  • I might have coded the camera in the actual game itself.

  • So C#.

  • So has anybody in here used a static language,

  • or is familiar with statically-typed languages

  • versus dynamically-typed languages?

  • [INAUDIBLE], Steven.

  • So what languages have you guys used, just out of curiosity?

  • Steven.

  • STEVEN: Java, C, C++.

  • COLTON OGDEN: Java, C, C++.

  • Donny?

  • DONNY: [INAUDIBLE]

  • COLTON OGDEN: C and C++.

  • OK.

  • So to you guys, this will be pretty similar.

  • So C# is very similar to Java.

  • Almost looks identical at first glance.

  • There are some features that are different.

  • But it was Microsoft's initiative to compete with Java.

  • It's a Microsoft language, first and foremost.

  • But there are ways to run C# code on other platforms,

  • primarily with the use of what's called Mono.

  • So Mono is an open source implementation of what's called the CLR--

  • the common language runtime--

  • which is how Microsoft allows several of its languages, like F#, and C#,

  • and Visual C++, Visual Basic.

  • They all compile to this intermediary format,

  • like how Java compiles to bytecode.

  • And you can run any of those languages' code--

  • their version of the bytecode-- with the CLR.

  • Mono-- what Mono is, and what Unity relies on

  • to allow its use across multiple operating systems,

  • is a CLR that's not just Windows specific.

  • It actually runs on Mac and on Linux machines, as well.

  • And you'll actually see evidence of its existence via the nomenclature used

  • for Mono behavior.

  • So within Unity, there's this thing called a Mono

  • behavior, which we'll take a look at.

  • From the beginning, actually, Unity was developed for Mac platforms

  • exclusively.

  • But in 2012 or 2011 they ended up making it usable across all major desktop

  • operating systems.

  • But C#-- suffice to say, very similar Java.

  • We'll look at its syntax.

  • But it's different from Lua in that, with Lua, you could just say,

  • oh, x equals 10, or object equals-- and then create

  • a table with whatever you want.

  • C# is a lot less flexible in this sense, in that you have to actually tell it

  • in advance what each data type is that you're trying to make.

  • So you can't just create a table or a integer.

  • You actually have to say, int my_int, or x equals, and then 10,

  • and then semi-colon.

  • So there's also more syntax to be conscious of.

  • And so really quickly, I can show you just what some C# looks like.

  • We'll take a look at this again going forward.

  • But as you can see here, we have public.

  • So that's another thing about C#.

  • In Java, if you're familiar with Java, they're

  • object-oriented programming languages.

  • And as such, there are objects that are public and private,

  • and you have access specifiers for different variables and classes

  • that will tell other variables and classes whether or not

  • they can communicate effectively.

  • So public class just means effectively anybody can see this class.

  • Other classes can see this.

  • But then you have--

  • see if I have one of these that has a private variable.

  • I know there's at least one.

  • Here we go.

  • So private, Text, text here, for example.

  • So first of all, notice that these are variable declarations

  • like we've seen before.

  • We're saying, this is a variable that I'm going to use.

  • In this case, public GameObject helicopter.

  • So I'm just defining it.

  • Or I'm declaring it.

  • I'm not defining it.

  • It doesn't equal anything yet.

  • But I'm saying, this is going to exist, right?

  • You can say equals something, and then define it on the same line.

  • But in this case, we are just declaring in advance what different variables

  • we're going to need later on.

  • Like for example, here.

  • We see, in our start method.

  • And we'll go over what all these methods are and how they're relevant.

  • But we can see here, text equals--

  • and then we're calling some function called GetComponent.

  • And then we have some funky syntax here--

  • less than, Text, greater than--

  • and then function call syntax--

  • two parentheses.

  • So we can see that there's a lot more syntax.

  • But all of the same principles still hold true.

  • It's still much the same way to think about programming.

  • It's just, there's a little bit more to stay conscious of.

  • And we trade off this dynamism for more performance.

  • So that's ultimately what it comes down to-- static versus dynamic languages,

  • you're trading flexibility for performance, in this case.

  • Though C# obviously is a lot faster than Lua.

  • We can do a lot more with it.

  • And actually, Unity isn't itself programmed in C#.

  • Unity itself is programmed in C++.

  • But it allows us to program any behavior that we want to with C#.

  • All right.

  • So that's a look at C# so we can see also what I was alluding to up here.

  • We have our private, public, and we do things with them.

  • And then, depending on what we've declared public or private,

  • these things also transfer into the editor.

  • We'll see how all of that comes together soon.

  • But suffice to say, that's a look at C#-- what it looks like.

  • And we'll use it, and we'll get comfortable with it.

  • But it's a little bit different than what we've done thus far.

  • Away from programming, we'll a very brief look at what Blender is.

  • So I have it installed here.

  • And this isn't required for the course.

  • I will provide you with all the models and all the assets

  • that you need to get working.

  • But this is what Blender is.

  • So Blender almost looks, at first glance, very similar to Unity.

  • The big difference is, this is meant to create new assets.

  • And technically, Blender does have a built-in game engine,

  • but it's not used for that purpose very much.

  • But the sole purpose that we would be using it in this course

  • would be just to create new 3D models.

  • So as you can see here, I can click on these vertices, and move them around.

  • And I'm not a guru by any stretch of the imagination when it comes to Blender.

  • But the helicopter, and jet, and skyscrapers,

  • and those kind of simple models, I made all of those in Blender.

  • And those are bundled with the project, if you

  • want to take a look at how those are implemented.

  • And you can download Blender at the--

  • I forget what the exact link is, actually.

  • Let's take a look.

  • Blender dot org.

  • And then, if we just go down here, download Blender,

  • 2.79 looks like the most recent version.

  • Blender 2.8-- I guess it's in development.

  • But there's nothing concrete about that on the front page.

  • But yeah.

  • If you want to model 3D assets, and you're curious,

  • and you want a free piece of really awesome software,

  • can't recommend Blender enough.

  • But like I said, I'm going to spend too much time on it, because it's not

  • something that will actually be required in the course

  • to implement 3D models, to create 3D models,

  • since I'll be providing all of that to you in the project in the GitHub repo.

  • And so here is a screenshot of basically what we just saw.

  • So the fundamental part of Unity.

  • So first of all, let's take a look at the Unity editor.

  • So we can see here much of what we saw in the screenshots.

  • But here, I am live looking at my scene, right?

  • I have a helicopter.

  • I have this background back here.

  • I have a camera--

  • that's right here.

  • You can see that it's a camera, because it's got the little camera icon.

  • But it's effectively just an invisible game object.

  • It's not actually going to be rendered to the screen ever.

  • And notice that there are no skyscrapers, no coins, no jets,

  • or anything like that.

  • Those are all instantiated dynamically.

  • But this is the beginning of our scene.

  • And it's all laid out in advance.

  • And we can see here with this camera preview--

  • which is kind of hard to see because it's really small--

  • but here's a larger view of what it looks like.

  • And then we've got a GUI element here, we've got coins here.

  • We have our helicopter.

  • But all of this is a marked difference from what we've gotten

  • used to, which is, program everything, and sort of load it,

  • and, oh, did I put this in the right position?

  • No.

  • I didn't.

  • So I got to.

  • If I wanted to come over here and just move

  • the helicopter a little bit which--

  • like that.

  • Whoops.

  • I just triggered it.

  • If you click on something, by the way, it

  • will play any particle systems that are associated with it.

  • So you can actually get a preview for those, as well.

  • But I can easily just move it here, to the right,

  • or to the left, before the game actually starts.

  • And if I click on the camera, we can see it there.

  • It's a little bit shifted to the left, as opposed to what it was before.

  • But if I just press Command-Z couple of times, take it back to normal.

  • So game object is an important concept.

  • So a game object is basically everything in Unity.

  • So the camera is a game object.

  • The helicopter, which is right here, is a game object.

  • Everything, by the way, that you want to see in your scene in Unity

  • is on this left-hand hierarchy right here.

  • So helicopter, the game object.

  • There's got to be a light source in a 3D scene in order

  • for you to actually see things.

  • So directional light-- that's a game object.

  • Background skyscraper spawner-- we don't see it anywhere.

  • But that's also a game object.

  • It's just an invisible one.

  • A coin spawner.

  • There's something called a canvas, which if we double-click that,

  • we can see we have this obscenely large GUI here that, paradoxically,

  • isn't on top of our scene.

  • Unity does a very interesting thing with all of its GUI elements,

  • in that it basically maps the canvas to one pixel

  • being equal to one Unity unit, which is usually equivalent to a meter.

  • And it draws this in a different draw call than it

  • draws all of the actual 3D stuff--

  • all the stuff that's down here, basically-- in World Space.

  • So when you create a canvas and you see it's gigantic,

  • that's just Unity's way of doing GUI.

  • It's strange.

  • But apparently they do it for performance reasons,

  • because they don't have to calculate like fractional points of where

  • the UI is supposed to be in World Space, which can get really small.

  • But the canvas is a game object.

  • Notice that helicopter has children.

  • It's got blades, a body, and then a blades collider, which actually could

  • have been part of the blades object.

  • Canvas itself has two children-- coin text and game over.

  • And those are the text on the top right, and "game over" in the center.

  • So those are just two text objects, which

  • are labels, that we can just populate with whatever text data we want.

  • The event system is an object.

  • That just gets added to your scene automatically when you add a canvas.

  • Airplane spawner, explosion particles, and explosion sound.

  • So we have all these different objects.

  • They're all part of our scene, and they all do different things.

  • But at the core, they're just game objects.

  • That's the core-- like the bottom type.

  • Yes?

  • AUDIENCE: So I would imagine you're not actually using

  • a helicopter [INAUDIBLE].

  • COLTON OGDEN: Correct.

  • AUDIENCE: So should you actually reset the position

  • of the background [INAUDIBLE]?

  • COLTON OGDEN: So the question was, since we are maintaining

  • a consistent position with the--

  • I'm going to try and scroll in, there we go.

  • The nice about Unity 2 is, if you right click,

  • you can use W-A-S-D to move around the scene,

  • which is super easy and convenient.

  • But the helicopter here stays in one spot.

  • And the camera always stays in one spot forever.

  • So do we move the background image, like we

  • do in our Love2D implementation of Flappy Bird,

  • where we reset its position?

  • And the answer is--

  • I will play it right now in scene view, and let you decide for yourself.

  • So let's go to Window.

  • I'm gonna go to Layouts.

  • So I like the two by three, like I said before,

  • so you can see the scene and the game running live at the same time.

  • I'm going to move my camera in the scene so that I'm

  • looking at this from back here.

  • And then I'm going to play the game.

  • [MUSIC PLAYING]

  • But so.

  • Here's where we-- I'm just going to hold it still.

  • So what do we think is happening?

  • Does it look like our thing is moving?

  • Or is it staying still?

  • It is staying still.

  • Any guesses as to how we're achieving this?

  • Changing the texture.

  • We are scrolling its texture.

  • So when you take a texture and apply it to a 3D surface,

  • you map the texture coordinates to the 3D coordinates of the object

  • that you want to render.

  • It's called UV Mapping.

  • They call it UV, because usually, when you're dealing with 3D objects,

  • you have your x, y, z.

  • And then you want to think of your texture separately from the 3D objects.

  • So you use u, v instead of x, y, since you already have x, y, z

  • allocated for your 3D object.

  • Basically, there's an offset part of the texture--

  • the material that holds the texture--

  • there's an offset field that will reposition the UV mapping

  • of that texture onto the 3D surface.

  • And when you offset it, it will just sort of wrap itself around,

  • if that makes any sense.

  • So what we do is we--

  • and I can actually show you in the code where this is.

  • This is going to be mentioned at the end of the lecture.

  • But we have the scrolling background component here.

  • And there's this thing on each material called a texture offset,

  • which basically shifts the UV mapping of your texture to your 3D object.

  • And so, by setting that texture offset to some value offset,

  • which is very similar to what we've done before.

  • Notice that offset is just scroll speed times time--

  • Time.time, in this case.

  • And I don't mean to overwhelm with, necessarily, all the details here.

  • But the bigger picture is that we're taking time--

  • the amount of time since the beginning of the game is started--

  • and we have some scroll speed that we've pre-determined.

  • And we can just scroll the texture offset here.

  • We're just placing it here at the x.

  • So this new Vector2, offset 0--

  • just think of these two as an x and y--

  • a vector 2, vector 3.

  • Vectors are just combinations of numbers.

  • So 1, 2, 3, 4-- however many you want.

  • A Vector 2 offset in 0.

  • So we don't want to scroll on the y-axis.

  • We just want to scroll left to right.

  • So we're going to apply no texture offset to our y-axis,

  • but we want to apply a texture offset to our x-axis.

  • So that's what their offset there is.

  • And I included it also here--

  • notice there's a Bump Map texture offset.

  • There's no Bump Map actually on our texture.

  • So a Bump Map is effectively--

  • I can maybe find a good Google image for it,

  • if you're not familiar with a Bump Map.

  • Let me go in here.

  • I'm going to hope that there's no shady images on Google Images, but a Bump

  • Map--

  • can we see it?

  • Well, it's kind of hard to tell.

  • But in a lot of 3D games, you'll see like bumpiness and shininess

  • on surfaces.

  • And that's accomplished the majority of the time using Bump Mapping.

  • Because obviously, if you were to model every single tiny little bump or model

  • in a surface, it would get extremely difficult to render large objects.

  • Because you're looking at thousands of polygons for a surface

  • that was actually this detailed in a game.

  • So what you do is, you take what's called a Bump Map,

  • and the Bump Map gets--

  • during lighting, it will effectively skew the surface normals of a 3D mesh.

  • So effectively, it will pretend as if your 3D mesh has a bunch of bumps in it

  • for the lighting.

  • Only for the lighting.

  • And so when it gets lit, and it gets shaded, it looks as if it's bumpy,

  • but you're still dealing with a completely flat, completely

  • simple cubic mesh in this case, or whatever mesh you want.

  • And so the long story short, here, if you

  • wanted to include a Bump Map on whatever surface you want to scroll,

  • or if you want to edit the Bump Map, this

  • is here just because you can also manipulate the Bump Map texture offset.

  • It's an included field in a material.

  • So any questions as to how this works at large?

  • We still have a lot to cover before we go into a lot of the specifics.

  • But conceptually, do we understand how this sort of works?

  • OK?

  • Cool.

  • So let's go back to the slides.

  • Right.

  • So we were talking about game objects.

  • So all of those things that we saw before were just game objects.

  • So let's go back to the default layout, and then we can look back at our scene.

  • So this is our scene.

  • All of those things were all of our game objects.

  • Those are ultimately containers.

  • I talked about this in a prior lecture.

  • But this is the beginning of an entity component system, in which case,

  • these game objects are our entities.

  • They are the entity class--

  • container class-- for what drives behavior.

  • And the thing that allows us to actually get interesting behavior out

  • of this sort of abstraction is the use of components.

  • And we'll talk about that.

  • So components are all of the things on the right-hand side,

  • by default of the Unity editor.

  • So if we're looking at our camera, for example,

  • and we take a look at all these things on the right,

  • we can see that we have something called a transform,

  • we have something called a camera.

  • A GUI layer deprecated, so that's by default.

  • It was imported from a prior version of Unity,

  • so you could actually delete this.

  • A flare layer, which we're not using.

  • An audio listener-- I believe that comes with all cameras by default,

  • though, in case you do want to use it.

  • An audio listener and an audio source.

  • So all of these different pieces are components.

  • They are what drive the behavior of this game object.

  • We can create just a brand new, empty game object-- oops,

  • I created it as a child.

  • We can create a brand new, empty game object here.

  • It's just a game object in our scene.

  • And it's right where our mouse is.

  • Well, it's right here.

  • And it does nothing.

  • It has one component.

  • And that component is the transform.

  • Anybody guess what a transform is?

  • Yeah.

  • AUDIENCE: Is that position, rotation, scale, et cetera?

  • COLTON OGDEN: Yes.

  • It is the position, rotation, and scale of a game object.

  • Something that was before put into six fields--

  • or in this case, nine fields, potentially, or maybe

  • three fields with children, is now an object that we can modify at will.

  • Well, it's a component that we can modify at will.

  • So if I do this-- notice, I'm just scrolling the x.

  • It's just moving that transform, which is just the position of that object.

  • And Unity knows to look at the transform component,

  • and render our object based upon where its transform lies,

  • rather than us having to manually set all these things, which we still

  • can do in the code.

  • We can just modify them in the Unity editor

  • directly via a graphical interface.

  • It's just an interesting, helpful layer of abstraction.

  • So a transform is a component-- a camera, in this case.

  • It's just a component.

  • We can attach this to anything we want to, and it becomes a camera.

  • And then we can set it to be either the default camera or not.

  • The camera has a bunch of different things.

  • And I can't claim to know all of the fields of every object

  • in Unity, because there's just a tremendous number of things.

  • But clear flags, background, culling mask--

  • all these things that are relevant to how the camera does its job--

  • they are all things that we have complete access

  • to here in the Unity editor.

  • We don't even need to touch code, really, for a lot of things.

  • An interesting fun thing we can take a look at

  • is if we go to Layouts-- go back to two by three, and take a look at our game

  • again.

  • So we have our camera currently looking at the screen here.

  • It's just always in one spot.

  • And it has a way of projecting the scene.

  • It can do one of two things.

  • It can be perspective projection or orthographic projection.

  • Does anybody know what the difference is between the two, at least visually?

  • So perspective projection-- what that does

  • is it models how real cameras, real lenses, the human eye, et cetera,

  • behave in real life.

  • Things distort a little bit.

  • And so you see things, depending on where you're looking at them,

  • they look wider or skewed, and not completely geometrical,

  • like you would if you were just to draw them at a certain distance.

  • Things have vanishing points, et cetera.

  • Orthographic-- things look a lot different.

  • So notice instantly, so we can see here, perspective

  • doesn't really change a whole lot visually with our helicopter.

  • Things do change a little bit with the background,

  • because the view angle is different between the two--

  • the perspective and the orthographic camera.

  • But if we make this a little bit larger.

  • So I'm going to increase the size like that.

  • And I play.

  • One thing we'll notice--

  • what's the first thing that folks are noticing?

  • You can actually see the difference between the top and the bottom.

  • Whoop.

  • How, in particular, I'll put your attention towards the buildings.

  • How do the buildings differ?

  • AUDIENCE: You can't see the side.

  • COLTON OGDEN: You can't see the side anymore.

  • The lens sort of curves a bit.

  • And so I'm not entirely familiar with all the math involved.

  • But at the end of the day, what it boils down to is, with a perspective camera,

  • you can actually gauge things as distance

  • relative to each other a little bit better.

  • With a orthographic camera, everything is completely flat,

  • and no matter how far away from you it is

  • on the access that's completely perpendicular to you,

  • it's going to look the exact same.

  • So these buildings are always going to look the exact same, no matter how far

  • away they are, no matter which position they are.

  • Because everything is just looked at completely flat.

  • And the view matrix is calculated in a different way.

  • And so, just with a simple button, you can

  • change how your game looks completely.

  • You've probably seen a lot of games.

  • I believe Crossy Road does everything with a orthographic camera,

  • versus a perspective camera.

  • It has a very distinctive look, especially

  • once you skew things a little bit.

  • But that's just one thing that you can change just

  • without having to touch a single line of code--

  • how your game looks via just the camera's implementation.

  • Let's take things back to how it was.

  • And there's a lot of things like the field of view.

  • So if you want to see more of the game world.

  • So things like in Quake and other shooters,

  • you can change this view, usually in your menu settings,

  • just so you can see more around you.

  • And it looks a little like a fisheye lens a little bit,

  • depending on how you do it.

  • And that's just more distortion of the perspective projection.

  • Yeah.

  • All of these things, they all have--

  • I don't know necessarily what every single one of them does,

  • just because there's just a lot involved.

  • And generally, you don't have to mess with it too much.

  • It just ultimately depends on what you are trying to accomplish in your game.

  • And you'll end up finding specific areas of the editor

  • and specific areas of the code base and documentation

  • that you dive deeply into.

  • But this essentially is what a component affords you the ability to do.

  • When you create a component and you attach it to a game object,

  • you can easily not only combine different things

  • to create this emergent new behavior and combination

  • of behaviors for your game objects.

  • But Unity allows you, via different ways of programming the components,

  • to modify them, depending on how customizable you want them to be,

  • in the editor itself.

  • So you don't have to touch any of this in code.

  • You can just go into the editor and quickly

  • whip things up because of the way that you've modeled all your components.

  • And so we see other things like the audio listener.

  • An audio listener is something that will literally listen for audio,

  • and play it back like a microphone to the game player.

  • And then an audio source is an actual source of audio.

  • And so it will play that audio at the location

  • of whatever that game object is.

  • So we've combined all these things.

  • So now we have a camera that is projecting everything

  • with perspective projection.

  • And it's an audio listener, so it's going to listen for audio in our scene.

  • And it's also going to play an audio source and listen to itself.

  • And that's just the beginning.

  • We have a whole bunch of other components

  • here, which we can take a look just really quickly.

  • So the directional light has a light component.

  • And so a light, you can do all kinds of things.

  • You can make a spotlight, directional light, point light.

  • All these look a little bit differently.

  • You can make it have a specific color.

  • So maybe if you wanted a really dark scene, you could have a dark light.

  • Or you could even emulate dawn or dusk with an orange light.

  • That's sort of how you'd go about doing that.

  • You can bake your lighting, which means that you just pre-compute it,

  • so that you don't have to render it in real time

  • when you actually play it on hardware.

  • A lot of different things, a lot of different settings.

  • We don't have time necessarily to go into all of them.

  • But looking through all of these, you can

  • see all these game objects are just combinations of these components.

  • And those combinations of components are responsible for the behavior

  • that we get in our game.

  • That's the ultimate essence of what Unity is, and how it does what it does.

  • And to illustrate the difference, we've talked about this before.

  • This is an inheritance chain, where Monster

  • inherits from Creature, which then goes to Goblin, Goblin Chief, Elite Goblin

  • Chief.

  • In Unity, you could create a game object,

  • and then give it a creature component, a goblin component, a patrol component--

  • right?

  • So maybe you want different things to patrol.

  • In Unity, it would be kind of complicated.

  • But you could create basically a pathway,

  • and then have your entity follow that pathway at a specific time.

  • And then, depending on how you've programmed it,

  • you could specify all these different characteristics in the editor.

  • So you could just say on the editor, OK, I

  • want you to go patrol from this range to this range.

  • And I want you to do it with this length of time, pause for this long--

  • right?

  • And then you can give that to anything in your scene.

  • You could make a camera patrol if you wanted to,

  • and then have that be demo code for the beginning of your game.

  • The flexibility is just insane.

  • Elite component.

  • Right?

  • If you want, maybe, certain enemies or certain objects, if they're elite,

  • maybe they shine bright.

  • And maybe they drop more experience or something.

  • A chief component maybe has better weaponry than a non-chief component.

  • And then a toxic component, maybe, is an enemy that, when it dies,

  • it sprays toxic gas or toxic liquid all over the place.

  • But the flexibility is there.

  • You can ascribe any component to any object to get any sort of behavior

  • that you want to.

  • It's on you, of course, to implement all these components, right?

  • You're not going to get all these for free.

  • You're going to get a lot for free.

  • Unity gives you a ton of components-- really good components.

  • But you're ultimately going to need to program your game logic.

  • And so you'll have to implement all these things-- elite,

  • goblin, what it means to be all these things.

  • But thankfully, it's not terribly painful.

  • The way at which we go about doing all of this-- and by the way,

  • are there any questions so far as to how Unity's model works,

  • the difference between composition versus inheritance,

  • anything we've talked about this far?

  • All right.

  • The way at which we actually go about making our own components,

  • which is probably what you'll spend the most of your time doing,

  • at least in the beginning, when you're bootstrapping your game,

  • is programming what's called a MonoBehaviour.

  • And so we talked about Mono before.

  • So Mono is the open source implementation of the CLR.

  • And MonoBehaviour, I imagine--

  • I tried to look this up and see where it derives from.

  • I imagine it's because it was originally a Mac exclusive project, Unity.

  • And so because everything was implemented in Mono,

  • and because this is way it's scripted, MonoBehaviour became the name.

  • But a MonoBehaviour is what a component is.

  • They are effectively one and the same.

  • The difference being that a MonoBehaviour

  • is how it's illustrated in code.

  • In C#, if you want to program something to be a component that you can then

  • attach to a game object, it needs to be inherited from MonoBehaviour.

  • So if we go to coin spawner, here.

  • And you can see the difference between what's built in to Unity

  • and what's your own via--

  • it'll say script here.

  • So in this case, I have a coin spawner object, right?

  • It's just an empty object, at least in terms of what it displays.

  • It's got a transform, which is irrelevant.

  • It could be literally anywhere in our entire scene.

  • The only difference between this and the empty game

  • object that we saw before is that it has a coin spawner.

  • And this coin spawner has this thing here.

  • All components that you add would have this script field here,

  • just to show you that that's the script that it's

  • getting it's code behavior from.

  • But also, we have this field here called prefabs, which has a size of 1.

  • And it's actually calculated based on how many elements

  • the list that it's responsible for has.

  • And we have one element, as it says, here.

  • And it's got a coin pre-fab, which is here.

  • And notice that I clicked on it.

  • It took me right to it in my assets.

  • And so prefabs and all of that-- what that means and how to get

  • that all working-- we'll talk about that.

  • But a coin spawner script has to exist in our code base

  • before we can actually add it to any game objects, right?

  • So in my project I do have--

  • usually, you have assets, a folder called Assets in your Unity Project.

  • And then I typically create a Resources folder.

  • It's not mandatory that you do so, but just for organization, I'll

  • create a Resources.

  • And then I'll have a bunch of folders for all sorts of different things

  • like we've done before.

  • Where in the Love2D project, at the parent level,

  • you have like a Fonts, a Music or Sounds, and then a source directory.

  • In this case we have Fonts, Materials, Models,

  • all the different assets that we will need in our game,

  • but also a Scripts folder.

  • And all the C# scripts here that we'll use amongst all of our components--

  • all of our game objects.

  • And so you could easily just go to any of these,

  • rather than have to go into your operating system, open up a new window,

  • and find your file, and navigate through all the files

  • that Unity generates for you, which can be kind of a pain.

  • You can just double-click on a coin script, for example.

  • And it'll open in your default editor.

  • And so one thing that I'll just mention here

  • is, if you go to your Unity preferences--

  • on Mac, it's Unity and then Preferences--

  • you can go to External Tools.

  • So by default, starting in 2018, Unity is transitioning away

  • from its prior IDE, which was called MonoDevelop, which was a good IDE.

  • There's transitioning away from that, and they're

  • making it focused on Visual Studio as the default IDE.

  • I don't use Visual Studio.

  • I like VS Code.

  • So what I did was-- and what you can do is--

  • you can choose your external script editor here,

  • if you prefer a different script editor-- like Atom,

  • or VS Code, or Sublime Text.

  • And then just browse for it on your file system,

  • whether you're on a Windows, Linux, or Mac machine.

  • And then you can easily just choose which file you want.

  • In this case, I chose VS Code.

  • And so that effect that that has is, whenever

  • I double-click on a script within Unity, it

  • will actually open the IDE or text editor

  • that I have assigned to it in Unity's preferences.

  • And so here is the Coin component that I have created for this example.

  • And notice that every component, first of all, is a class.

  • And actually, everything effectively in C#, just as in Java, is a class.

  • It's a public class.

  • And notice that I call it Coin, and I got this colon, and then MonoBehaviour.

  • So this colon-- anybody know, anybody take a guess what the colon means?

  • Tony?

  • TONY: Extends.

  • COLTON OGDEN: Extends, yes.

  • Extends or inherits from MonoBehaviour.

  • So anytime you have a component that you want

  • to create, you want to add it to Unity, and allow you to see it in the editor,

  • and allow you to actually drive behavior of an object,

  • you need to inherit from MonoBehaviour.

  • Like it says here on my VS Code, since I have the extension for OmniSharp,

  • it will actually tell me, MonoBehaviour is the base class

  • from which every Unity script derives.

  • There's a couple of things here.

  • So notice that we have a few methods.

  • So void Start, void Update, and void OnTriggerEnter.

  • So anybody take a guess as to what--

  • ignoring OnTriggerEnter-- does anybody have

  • a guess as to what Start and Update do?

  • Yeah.

  • AUDIENCE: Could it be just like that and [INAUDIBLE] update [INAUDIBLE]??

  • COLTON OGDEN: Sorry, say it one more time.

  • AUDIENCE: Would it just [INAUDIBLE] an update or [INAUDIBLE]??

  • COLTON OGDEN: Exactly.

  • It would be just like we did before when we created objects

  • in our Love2D projects, where we had an init function assigned to every class,

  • and an update function assigned to every class.

  • And we would call those ourselves.

  • Every MonoBehaviour can have a Start method.

  • In this case, it's empty, because every time you

  • create a new script by the Unity editor, it will automatically-- first of all,

  • it will automatically create a brand new file for you.

  • So let's just say I want to create a new script.

  • So if I'm in the Unity editor, and I go to my assets--

  • I right-click Create.

  • And then I want to create a new C# script.

  • It'll create it there.

  • I can say, maybe I want this to be--

  • I don't know-- Test Component, right?

  • So this should have the effect of, when I open it in my VS Code,

  • this Test Component-- this was auto generated for us.

  • We didn't have to write any of this boilerplate.

  • But every script that you create, by default,

  • is a MonoBehaviour, which it strictly does not have to be.

  • You could create a class of your own, that maybe you call behind the scenes,

  • or maybe it just represents a data structure or something.

  • But by default, anytime you create a new script in Unity,

  • it'll save you the hassle of typing out all this stuff by hand.

  • And it'll give you an empty Start method and an empty Update method.

  • And so, like Tony said, the Start method gets called as soon as the object

  • that it's assigned to gets instantiated.

  • Well, as soon as this component gets instantiated, rather.

  • Which often is the case that it gets instantiated

  • at the same time as a game object.

  • Not always.

  • You can create game components on the fly.

  • But this Start method will get called.

  • Anything that you put in here will get called right away.

  • And then this Update method, Unity will-- every frame,

  • go through every component on every live game object--

  • and will call the code that's contained within this Update function.

  • And you don't have to call this anywhere yourself.

  • Just by assigning this component to a game object in your scene,

  • you get the update, and start, and all this other functionality

  • given to you for free.

  • And MonoBehaviours are actually much more complex

  • than just Start and Update.

  • So if you go to MonoBehaviour here, we can see that--

  • by the way, I want to shout out Unity's documentation.

  • Unity has amazing documentation--

  • docs.unity3d.com.

  • You will go through pretty much everything

  • you possibly could ever want with every object implemented in Unity, in quite

  • a good detail, with a lot of examples.

  • But here we can see, just in the description,

  • it will tell us, the MonoBehaviour will get start, update, fixed update, late

  • update, on GUI-- all of these functions called for us, assuming

  • that they're implemented.

  • And you don't have to implement them if you don't want to.

  • If they're not implemented, they just won't happen.

  • If there's no start method, then that means

  • there's no start logic for this component.

  • So it doesn't need to execute.

  • If there's no update, maybe it just needs to start at the very beginning,

  • but never update after that, don't implement update.

  • And update will not be called.

  • All of these are optional.

  • That's just a small chunk of MonoBehaviour,

  • though, because you also have messages.

  • So messages are functions that you override.

  • As you can see, there's a whole lot of them.

  • And all of these messages get called, depending on certain things that

  • happen in the game scene.

  • So OnTriggerEnter, for example, if our object is a trigger, and we enter it,

  • then the behavior will get called in that function, which

  • will allow us to do things like-- if our helicopter enters a building,

  • we can call that helicopter's explode method,

  • which is literally what we do in this project, thanks to this function.

  • And all we really need to do is just a couple of lines of code.

  • It's very simple.

  • OnPostRender, OnPreRender-- all these different things

  • are called at different times.

  • You can look into all of these, if you want to, just by clicking on them

  • and looking at their documentation.

  • LateUpdate is called after all update functions have been called.

  • This can be relevant for certain physics calculations.

  • But there's a whole lot of different things you can do.

  • You don't necessarily need to do a lot of things yourself,

  • if Unity provides you with the function that gives you

  • the effect that you're looking for.

  • And so if you're curious, MonoBehaviour, and then the docs at large.

  • This is just all the documentation-- just an insane amount of documentation.

  • And that's just in the classes.

  • There's all these other little side areas, as well.

  • And so that's sort of what MonoBehaviour is.

  • It's a component.

  • It gets a lot of these functions called for you.

  • You don't have to implement them.

  • But if you do, you get a lot of functionality for free, basically.

  • Any questions as to what a MonoBehaviour is, how it works, and so forth?

  • Or any of the syntax we've seen thus far for creating a MonoBehaviour?

  • All right.

  • And so, here in our coin, for example, which we were looking at before,

  • notice that I'm referencing something called a transform.

  • Transform.position.x is less than negative 25, destroy a game object.

  • If this is the coin, what do we think that this is accomplishing?

  • AUDIENCE: It goes off the screen [INAUDIBLE]??

  • COLTON OGDEN: Yep.

  • So the transform, recall, was our component

  • that encapsulates our rotation, scale, and position.

  • So we can just say, if transform.position.x is less

  • than negative 25, destroy game object.

  • Because we're spawning the coins dynamically.

  • When they get off screen-- just like we did with the pipes, if you remember.

  • And I had a screenshot, actually, in that slide

  • that showcased what that looked like.

  • We can see that exact thing live here.

  • If I go to Layouts, two by three, and we play the game again,

  • and then I'll just die at some point.

  • But if we do this, we're seeing our scene live.

  • So we're following this-- yep.

  • See how the coin-- as soon as the building, and the plane,

  • and the coins all get to this point--

  • this is negative 25 in Unity units, not in pixels.

  • Everything in Unity is based on Unity units, which, by default,

  • 1 is equal to one meter, as opposed to-- we've been thus far using just pixels.

  • But pixels aren't viable in 3D.

  • [MUSIC PLAYING]

  • But that's effectively what that component does.

  • It's checking, in our Update function-- void Update--

  • if transform.position.x less than negative 25.

  • And notice that we don't actually have to declare transform

  • anywhere, which is interesting.

  • That's because MonoBehaviour, by default, gives you

  • access to its game objects transform just by default.

  • That's just something you get for free.

  • And then, notice gameObject-- we also haven't declared gameObject anywhere.

  • Because by default, we have access to gameObject.

  • It's the game object that this script belongs to.

  • That this component belongs to.

  • AUDIENCE: [INAUDIBLE]

  • COLTON OGDEN: This would be the actual--

  • so the interesting thing about the classes

  • is, you don't have to explicitly say this.

  • Because if you had int myNumber equals 10, and you just say, myNumber--

  • the difference between Lua and C# and Java is that,

  • it will already know what myNumber is, this is myNumber.

  • It will know its this objects.

  • AUDIENCE: Yeah.

  • COLTON OGDEN: You don't have to do that.

  • Sort of a nice little thing to save you a little bit of time.

  • Because you could then just say, myNumber plus equals 10.

  • Another nice thing about C#, by the way, which we didn't get in Lua--

  • you can do compound assignment operators.

  • So you can do plus equals, minus equals, times equals.

  • That's one of my little pet peeves I have with--

  • oh.

  • And notice that also, the nice thing about the fact that I

  • have OmniSharp-- it'll underline.

  • And this is just a trait of most IDEs, honestly.

  • So you get this with a lot of places.

  • But it'll tell you myNumber is not assigned, with the underlining,

  • and also just by hovering over it.

  • The named myNumber does not exist in the current context.

  • But I can just say, int myNumber.

  • And then it's gone.

  • A little bit more that we have to worry about,

  • and a little bit less that we have to worry about.

  • A little give and take.

  • But still, I think, overall, C# is going to be a lot more syntax-heavy.

  • You do have to worry about things like braces, and semi-colons,

  • and putting everything in a boilerplate.

  • And then, obviously, type declaration, and return type declaration.

  • It can be a little bit more than we've gotten used to so far.

  • But it's honestly not too bad, just given that you can use IDEs.

  • First of all, the fact that you're statically typing everything,

  • you can detect a lot more errors in advance.

  • If you're trying to do something with some type

  • that you're not supposed to do, like some function accepts

  • some object of some type, but you're passing in some other object,

  • you'll catch that in advance.

  • And so that's a really nice thing.

  • But yeah.

  • It is a give and take.

  • And also, IDEs will give you, like I said,

  • a lot of the functionality-- a lot of the autocomplete-- that

  • makes a lot of this more sustainable.

  • And it's not to say, of course, that Lua and Love2D don't have their own IDE.

  • Like ZeroBrane Studio is popular, from what I understand.

  • Haven't used it yet.

  • But particularly when you venture into Java, and C#, and compiled languages,

  • static languages-- having those features does save you a lot more time,

  • relatively speaking, than when you're in a dynamic language.

  • So that's what MonoBehaviours are.

  • Every component of our game effectively has-- like the helicopter

  • has its own script, a Heli Controller.

  • The coin has its own script-- the Coin Spawner, the Skyscraper Spawner,

  • the Skyscraper--

  • they all have their own scripts that drive their game behavior.

  • But they also have scripts and components that are part of Unity core,

  • as well.

  • Including Colliders and Triggers.

  • So if I'm looking in Unity--

  • so I'm going to go to--

  • and this will actually be the last thing we look at before we take a break.

  • But if I'm in Unity, and I'm going to go back to my default layout,

  • just because it's a little cramped on a 720p.

  • Go to my helicopter.

  • Helicopter's got a couple of pieces to it.

  • So let's actually go to where I can see it here.

  • So one of the awesome things I love about Unity

  • is, it gives you a lot of this editor magic-- this editor sort of sugar.

  • It shows you visually where a lot of the things are in your game world.

  • So the green rectangles-- anybody know what those are, or can guess?

  • AUDIENCE: Hit boxes?

  • COLTON OGDEN: Yep.

  • Hit boxes.

  • Exactly.

  • These are colliders.

  • So the blades-- it doesn't have a collider.

  • That blades collider is actually separate.

  • The box collider here is this collider right here.

  • And so a collider is literally just something

  • that collides with something else.

  • And you can set it to be a trigger or not.

  • If it's a trigger, then it actually won't trigger the OnTriggerCallback

  • function that we briefly saw earlier.

  • So the things that you want to activate triggers,

  • you should make those not triggers.

  • And then triggers, all you have to do is just click this little Trigger button.

  • So what a trigger is is just a region that you've determined--

  • or some object that you've determined-- should cause behavior

  • when it gets triggered.

  • So in this case, I've assigned--

  • actually, I'll just play it.

  • But the airplane has a trigger, right?

  • All of these things have colliders, whether they're triggers or not.

  • The helicopter-- its blades and its body--

  • there are two colliders, two boxes.

  • The coins all have colliders.

  • The skyscrapers will have colliders, and the airplanes have colliders.

  • The difference is that-- oh, and by the way, another cool thing.

  • It's easy to get very sidetracked, just because there's

  • so many cool things to talk about.

  • You can pause the game while it's running.

  • So it's paused right now, but it's in the exact state that I left it.

  • So there's three coins there.

  • I got my helicopter there.

  • I can freely go about the scene.

  • I can analyze things.

  • I can actually modify this in real time.

  • So I can change its rotation, if I want to.

  • Probably don't want to do that.

  • But I could if I want to.

  • I can change its position, right?

  • So I can move it left to right, like that.

  • And notice that I actually have just the body selected,

  • so the blades are kind of separate.

  • I don't even know what kind of crazy stuff

  • would happen if I just messed with this and just let it run.

  • I haven't tested it that crazily.

  • But you are allowed to step through.

  • Oh, OK.

  • It looks like they both just go.

  • Oh, yeah, because they're parented to the helicopter object.

  • So they're both going to align.

  • They're going to move at the exact same rate,

  • regardless of how far apart they are.

  • The individual objects.

  • But you can just step through your game's execution frame by frame,

  • and get a sense of--

  • if you're trying to pinpoint a bug, maybe, that's

  • position-based or something that's tricky

  • and you just haven't been able to find exactly what's going on.

  • You can look through your whole scene and all

  • of the fields of every object in step time,

  • just by stepping through-- this button here--

  • assuming that you're in pause mode.

  • And then just looking at all the components here on the right side.

  • Because those will all still update.

  • All these fields will update any time you perform

  • any changes in the actual scene.

  • So super helpful for debugging.

  • I know I've used it a bunch.

  • And then if I start again, or if I stop it,

  • the nice thing is, any of the changes that you

  • make while it's running to your object, they

  • don't get applied when you actually go back to the game.

  • So notice that I messed with the two pieces, I separated them.

  • And then I stopped the game.

  • It ended up going right back to where it was in the very beginning.

  • So all this gets just basically saved like a snapshot.

  • You can do whatever you want during your game, and then come back to it,

  • and it will all get reverted back to where it was.

  • Yeah.

  • TONY: So [INAUDIBLE] if you hit single player,

  • and I kept making changes while I'm not involved [INAUDIBLE]..

  • COLTON OGDEN: Yes.

  • So, yeah.

  • Tony just basically echoed what I just said, which was,

  • if you make changes in your game while it's running,

  • and you're trying to maybe tweak them such that it's perfect when you're

  • actually done, they don't get saved.

  • So you have to actually make a conscious effort to remember what you've done,

  • how you've changed different fields, to fix any bugs, if they do exist.

  • But it is something that can be disheartening or frustrating, if you

  • finally fix something, and then you forget what it is you changed.

  • And then you have to mess with it a whole bunch.

  • So remember all the changes you make, if they're

  • pertinent to your actual game's execution

  • and the debugging of your game.

  • And then there's also a console, as well, which is nice.

  • If you want to output things like JavaScript style on the web,

  • you can do console.log.

  • You can do the same thing with a debug call here in Unity.

  • I don't have any of those calls in the actual game.

  • But it is something that we'll look at going forward.

  • And it is something that can be very helpful

  • if you want to measure something or output something that you can't

  • necessarily look at in the inspector.

  • In the case of this game, most of anything that we want to take a look at

  • is visible in the inspector.

  • But if you have an algorithm, maybe you have a generator that's not visual,

  • like a level generator.

  • And you want to make sure that the data structure representing your level

  • is generating things properly, you can output everything maybe via hashmarks

  • or something in your console, and actually

  • see it that way when you're running the code,

  • rather than having to run the actual game and look through it that way.

  • Some things just are hard to model in the editor,

  • and still need console output.

  • So the console is there to help you.

  • And so debug.log is the function, I believe,

  • that you need to see all that stuff.

  • Right.

  • Colliders and triggers.

  • So we had a little bit of a tangent there,

  • but the helicopter is comprised of these three things.

  • But the blades collider should have ultimately

  • been merged with the blades object here.

  • But we can think of it as the blades and the body.

  • The reason they're separated-- well, there's two reasons they're separated.

  • One core reason they're separated is they're just different sizes.

  • So the blades are longer than the body.

  • And so we have, for that reason, two separate colliders.

  • So we're looking at the helicopter here.

  • We can see that the collider for the blades

  • extends a little bit farther than the body.

  • We don't want to create a box collider for the entire helicopter,

  • because a box collider right that goes out here, for example,

  • might not be super fair if we're just coming right

  • over a building, and our body--

  • maybe the building's right here, and we just miss it.

  • We don't want that to be collided with.

  • So sometimes you'll need to combine colliders

  • to accomplish the collision detection behavior you're looking at.

  • The nice thing about box colliders, which we're using here,

  • is that they're not very expensive, because they're just boxes.

  • It's easy to compute 3D box collision with other things that are 3D boxes.

  • And that's actually a topic in its own is,

  • taking a complicated model or object, and then breaking it down,

  • not in terms of what it looks like to be collided with,

  • but how you can simplify the collision detection of something,

  • like a character, by just modeling its arms, its body, its legs, as cylinders

  • or boxes, rather than complicated geometry.

  • You usually don't want to do peer geometry collision for anything,

  • because that gets really expensive.

  • You want to try and aim for simple shapes to be your colliders.

  • And Unity gives you a lot of simple shapes

  • by default. If you look at, for example, in our helicopter--

  • if you wanted to add a component-- which, by the way,

  • you can easily do here, just in the component inspector.

  • If you're looking at the bottom right, there's an Add Component button.

  • So you can add any of the components that you've written,

  • and also all the components that Unity gives you by default.

  • So if we look at it by collider, we can see

  • there's a lot of different colliders that Unity gives us for free.

  • Polygon collider, mesh collider, box collider, capsule collider.

  • I try and strive for boxes as much as I can.

  • Spheres aren't bad.

  • Spheres are usually fairly easy to calculate, as well.

  • But boxes are the easiest.

  • Capsules are pretty easy, too.

  • A lot of players will be capsule colliders, because characters usually

  • have rounded heads.

  • So emulating the collision for their head with a capsule makes more sense.

  • And we'll look at that, actually, next week, when we use a first-person

  • component that we can get for free.

  • The default collider for it is a capsule.

  • But the blades and the body both have their own collider.

  • They're not triggers.

  • But if we look at our prefabs--

  • which we'll take a look at what a prefab is shortly--

  • a skyscraper has a box collider which is a trigger, as we can see here.

  • And the coin is a trigger.

  • And the airplane is a trigger.

  • So these things all trigger, except other colliders that aren't triggers.

  • And if it detects a collision with a collider that's not a trigger,

  • it will call the OnTriggerEnter function,

  • which we saw is something that you can get with MonoBehaviour.

  • Yeah.

  • AUDIENCE: Could you have two values if you want to call the special

  • function two at the same time, you'd have the regular [INAUDIBLE]..

  • Is there an easier way to do that?

  • COLTON OGDEN: Can you give me a specific example?

  • AUDIENCE: Well, if I want to have a pinball game or something,

  • I'd want to model functioning when my ball hits something.

  • But at the same time, I just don't it to bounce off of stuff.

  • COLTON OGDEN: Oh, yeah.

  • AUDIENCE: So you could do that?

  • COLTON OGDEN: So the question was, can I implement such that, for example,

  • in a pinball game, if I have a ball that collides with something

  • and causes a trigger to occur, but also have it bounce off of something.

  • And you would do that with a rigid body.

  • So you give it a rigid body.

  • In this case, I actually gave a rigid body to the helicopter

  • because the original goal of mine was to have it be affected by gravity.

  • But it turned out that I actually liked it a little bit better without gravity.

  • So this rigid body isn't strictly necessary.

  • But it has a gravity component that you can

  • assign to it that will actually, then, calculate gravity, and pull it

  • down wherever-- you have a global gravity definition that's

  • in your Unity settings that will affect it that way.

  • And so what you would do is, you would give your ball a rigid body.

  • And other things that you want it to bounce off of those,

  • those also have rigid bodies.

  • And they can be kinematic or not, basically,

  • which we talked about before.

  • Kinematic can move, but not be affected by other objects.

  • And so the object that's a trigger, you still trigger code, it does something.

  • So your pinball machine, when it collides with something,

  • and it triggers that thing, it'll still trigger that code.

  • So whether that's increment your score, or cause something

  • to flash, a part of it will flash, or play a sound,

  • it'll do that in your OnTriggerEnter.

  • But your rigid body will also perform its work,

  • and bounce off of whatever it--

  • assuming that you don't destroy it with that OnTriggerEnter function,

  • it will bounce off of that surface, and behave in the way

  • that you are alluding to.

  • Does that make sense?

  • All right.

  • Cool.

  • So all these things have triggers.

  • The IsTrigger flag--

  • I mean, that's as easy as we need it to be.

  • So you just give a box collider.

  • And the nice thing about box collider, too,

  • is if your mesh is a box, or square, or rectangular,

  • it will usually just fit it perfectly to whatever you're trying to assign it to.

  • So in this case, I remember adding the box colliders for these skyscrapers.

  • It just-- because they're all rectangular, or cubical--

  • I don't know.

  • What's the rectangular 3D?

  • I forget the term.

  • What is it?

  • AUDIENCE: Rectangular prism.

  • COLTON OGDEN: Rectangular prism, yes.

  • Because they're all rectangular prisms, the box collector fits them.

  • It'll scale the right way.

  • When you have differently weird shaped objects,

  • it'll just basically be as big as it needs

  • to be to completely encapsulate it.

  • But like I said before, because the helicopter has got some weird geometry,

  • if we were to do that, it would by default just be this entire box here,

  • because this is the farthest it goes out on this particular axis.

  • So we want to combine meshes to produce the collision

  • behavior that we're looking for.

  • And the trigger enter bit is relevant because, if your game object is

  • a trigger, then once we collide with that object,

  • we want-- and by default, when you do OnTriggerEnter,

  • it will look for the collider on this object.

  • It's going to take in a collider other, right?

  • And what that is is the other collider-- the thing

  • that's colliding with this object, with this coin.

  • So the helicopter, because it's the only other object in our scene

  • that has a non-trigger collider.

  • OnTriggerEnter-- this is going to be a helicopter.

  • We can just say other.transform.parent, because our helicopter has children,

  • which have the actual colliders, and a parent has the helicopter controller

  • component.

  • We're going to GetComponent the HeliController.

  • So we're going to go through our that objects, that other

  • that collided with us.

  • We're going to get its transform parent.

  • We're going to do GetComponent, which is a function that will just look

  • through all of its list of components.

  • We specify which component we want using this identifier here.

  • So of class HeliController.

  • And these angle brackets are the generic type specifier

  • syntax, which basically looks for it.

  • You could pass in any type here, effectively,

  • and GetComponent will look at whatever type is in here,

  • and get the component of that type, specifically.

  • So you do have some sort of type flexibility in C#,

  • but you have to go the extra mile and specify it,

  • and implement functions that take generic arguments like this.

  • Which is similar to Java, and C++, and other languages that do it that way.

  • And then once we've gotten that component of type HeliController,

  • with this function call, we execute PickupCoin,

  • which is a function that's part of the HeliController.

  • Any guesses as to what PickupCoin does?

  • AUDIENCE: [INAUDIBLE]

  • COLTON OGDEN: Sorry?

  • AUDIENCE: Increments the coin counter and tells the coin to disappear.

  • COLTON OGDEN: Increments the coin counter, calls the coin to disappear.

  • Well, it actually doesn't call the coin to disappear, because we do that here.

  • AUDIENCE: Oh.

  • COLTON OGDEN: Destroy game object--

  • game object is going to be this coin that's executing this function.

  • There's one more thing, too.

  • Do you remember what happens when we pick up a coin?

  • Exactly.

  • The particle effect.

  • It triggers a particle effect.

  • We can go to the HeliController class and take a look at that.

  • PickupCoin.

  • Here, coinTotal plus equals 1.

  • GetComponent AudioSource.

  • Play.

  • So it's a little bit weird.

  • So with audio source, you can have multiple audio files.

  • So GetComponents AudioSource is what we need to actually get

  • the audio attached to this object.

  • And at index 0--

  • because there's only going to be one audio source anyway.

  • And then we just call Play, so that'll play the coin sound.

  • And then GetComponent, because there's only

  • going to be one particle system associated with this helicopter object.

  • We're going to get the particle system object.

  • And then we're just going to call Play on that, which will actually trigger

  • an emission one time of its particles.

  • That's in a nutshell what colliders and triggers are.

  • It's a lot of syntax at once, because Unity is really big.

  • But it's pretty simple.

  • Just make sure that you give the right shapes to your objects.

  • Make the right things triggers that you want

  • to cause behavior to happen when non-trigger things touch them.

  • And then implement OnTriggerEnter with the behavior-- the game logic--

  • that you need.

  • In the case of the skyscraper, for example, if we look at the skyscraper.

  • So I have a skyscraper component here.

  • And then if I OnTriggerEnter here, it effectively

  • does the same thing that the airplane does,

  • which is just go through the others, transform parent game object,

  • HeliController, and then call Explode.

  • And so Explode-- similar thing to what PickupCoin does,

  • and it triggers a particle effect.

  • But it also destroys the helicopter, and it tweaks the Game Over text

  • to turn on at that point.

  • So any questions as to how triggers work, or any of the syntax we've

  • talked about thus far?

  • I know that it's a lot.

  • It's kind of a fast tour.

  • But moving right along.

  • All right.

  • Oh, and here's a screenshot I took just to illustrate the green lines--

  • the box-- and then the orange is the actual mesh of the helicopter.

  • So we're going take a break.

  • And then when we get back, we're gonna talk about prefabs

  • and spawning them, and dive a little bit deeper

  • into some of the other parts of the project.

  • All right.

  • Welcome back to GD50 Lecture 8.

  • This is Helicopter Game 3D.

  • So before the break, we were talking about colliders and triggers,

  • and also just getting our hands used to using MonoBehaviours, and the editor,

  • and all sorts of things.

  • But we haven't really taken a look at prefabs,

  • which is a major part of this game.

  • So the coins, the skyscrapers, the airplanes--

  • those are all prefabricated assets that we have gotten

  • ready for spawning into our scene.

  • And we're going to take a look at how that actually works.

  • So here's a list of--

  • in our editor view-- all the different prefabs that we have.

  • So we can see airplane, blades, and body are prefabs.

  • The coin, all the skyscrapers.

  • There are three different kinds of skyscrapers.

  • And then the helicopter.

  • The helicopter itself we're not using as a prefab,

  • because I've already created it in the scene,

  • and it never gets instantiated dynamically.

  • But we could easily make it so, because I have

  • created a prefab with the helicopter.

  • So when we're looking at the scene here, we

  • notice that we have just the helicopter, and we have the background.

  • But there's no skyscrapers, there's no coins, and there's no airplanes.

  • Well, we do have our skyscraper spawner, the coin spawner, and the airplane

  • spawner.

  • And the three of those are just empty game objects,

  • but they have associated with them these spawner scripts.

  • And the spawner scripts, we've written ourself.

  • And then, once we've written them, we just simply add component here,

  • and then chosen the appropriate spawner.

  • And so what those do--

  • if we look in our code actually, and we go, for example, to the airplane

  • spawner, we have a Start method.

  • We have an Update method.

  • Notice that the Update method doesn't do anything, paradoxically,

  • because the airplane spawner is running over time.

  • We are spawning airplanes over time.

  • But the way that we do things over time asynchronously in Unity

  • is a little bit different than anything that we've done so far.

  • So this Start method here, we see that we're calling

  • this function called StartCoroutine.

  • And that function takes in a function called SpawnAirplanes.

  • And so at a high level, a coroutine is effectively a function

  • that yields control every time it's called.

  • So it'll run, and then, rather than go through its entire body of code,

  • and then end or return some value, and have that just be the end of it,

  • it'll actually yield, when you use the keyword Yield.

  • And depending on what you've done with that Yield,

  • it'll pause for some length of time, or do something for some length of time,

  • rather than end that function call.

  • So this SpawnAirplanes function here--

  • notice that it has a while true.

  • And so while true is not usually something that you want in a game,

  • because that's just going to lock your game forever.

  • But in the case of a coroutine, a coroutine is going to run,

  • but then it's going to yield control back

  • to whatever called it for some length of time,

  • or depending on how we've programmed it.

  • In this case, you can see here--

  • yield return new WaitForSeconds Random.Range 3, 10.

  • Does anybody have an idea of what that does?

  • Yeah.

  • AUDIENCE: The logic [INAUDIBLE] waits for between 3 and 10 seconds,

  • and continues.

  • COLTON OGDEN: It does.

  • It yields for 3 to 10 seconds, and then continues its execution.

  • That's exactly right.

  • Did you have a question?

  • AUDIENCE: I was thinking, why do you have three different skyscrapers set

  • up?

  • Couldn't you just have one skyscraper and change [INAUDIBLE]??

  • COLTON OGDEN: You could do that.

  • Yeah.

  • I guess for the sake of demonstrating prefabs.

  • And also, it's a little bit complex more complicated to get

  • into dynamic material setting, and changing materials in code.

  • But yeah.

  • You could effectively just create one skyscraper with one base color,

  • and then dynamically change its material.

  • But also to illustrate having multiple different prefabs

  • assigned to an object, that you can then choose at random

  • to generate in the game scene.

  • They might not necessarily be skyscrapers

  • that will have the exact same color.

  • It could be a goblin, or a orc, or a troll, or something.

  • And they're all different prefabs that we've created.

  • But you can put them in a list, and then dynamically choose

  • which one we want at random.

  • So a few different reasons.

  • But yeah.

  • In a situation where everything is just effectively differentiated

  • by a single color and a material change, you

  • could dynamically swap that as needed.

  • This code routine that we've started-- this function that

  • effectively can pause itself, even though it's being called while true

  • is in play.

  • It yields control with this Yield keyword.

  • And what it does is, it allows us to call something over and over again,

  • but then pause for some length of time.

  • Right?

  • We don't have to maintain a counter for spawn time in here.

  • Which we could do.

  • We could do float spawn time equals 0, right?

  • And then float spawn increment equals like 1,000.

  • And then while spawn time is less than spawn increment--

  • we could say in Update, if spawn increment.

  • Or we would do spawn time plus equals time.delta time first,

  • to get the length of time that's passed since the last frame.

  • And then we would say, if spawn time is greater

  • than spawn increment, instantiate a new airplane.

  • And then set spawn time back to 0.

  • And then just rinse and repeat this over and over again.

  • But coroutines take out this need, much like timer

  • did, for having anything that you keep track of with a specific timer.

  • You can do a whole bunch of different things with coroutines.

  • One of them, in this case that we're doing,

  • is just pausing for some length of time to do something.

  • In this case, we want to wait for some time

  • in between each instantiation of an airplane.

  • So we'll just yield control back to our game,

  • because we want to break out of this while true.

  • But we don't want to break out of it and completely break out of this function.

  • We want this to keep going over and over again.

  • So we see here, this Instantiate function is

  • where the actual instantiation happens.

  • And Instantiate takes in a prefab--

  • so a game object.

  • So in this case, we have a prefabs list here.

  • GameObject public, GameObject array called prefabs.

  • So this can take in any number of prefabs that we want it to.

  • We're going to return a range between 0 and length.

  • And then also, we want it to spawn.

  • We give it a position that we want to spawn in.

  • So Instantiate-- it takes in an object, it takes in a position,

  • and it takes in a rotation.

  • In this case, we want to give it this position.

  • So it's a Vector3--

  • it's an x, y, and a z--

  • at 26.

  • So off the right edge of the screen 7 to 10.

  • So within a range of verticality up towards the top.

  • And then at 11 on the z-axis, which is aligned with our helicopter

  • and the skyscrapers.

  • And then this Quaternion.Euler--

  • so 3D rotation, the math behind it is pretty complex.

  • And I don't know it well at all.

  • But Quaternion.Euler allows us to think in degrees,

  • and actually perform a 3D rotation on something fairly easily.

  • So if you want to rotate something in 3D, Quaternion.Euler.

  • Negative 90 degrees on the, I believe it's z, x, y.

  • In this case, the way that the default rotation of the airplane is,

  • we want to set it to negative 90, negative 90, 0.

  • You could also set up your prefab in a way such

  • that you don't need to pass this in.

  • You could do Quaternion.Identity in that case.

  • But this is just to illustrate how, if you want to rotate something

  • in code, how you would do it.

  • So Quaternion.Euler is the way that you rotate something in 3D.

  • So Instantiate-- global function that you

  • can call anywhere you want to, as long as you just

  • give it a actual object or prefab that it knows how to instantiate.

  • And then WaitForSeconds-- another global object,

  • which is a asynchronous object that will allow

  • us to yield for this length of time.

  • You pass in the length of time here.

  • It takes it in seconds, because WaitForSeconds.

  • And then, as soon as this length of time has passed,

  • it's going to come back up here into this while true instruction,

  • and then instantiate again.

  • We don't have to keep track of a time or anything-- any weird boilerplate.

  • We just have to call StartCoroutine here.

  • And notice that we explicitly have to call StartCoroutine routine in order

  • to trigger a coroutine function.

  • And to do that, it returns what's called an IEnumerator.

  • And so a lot of these details, you don't need to necessarily know

  • the low-level details of.

  • I certainly don't know all the low-level details of everything

  • that happens in Unity.

  • But just know if you want asynchronous behavior,

  • your function needs to return an IEnumerator,

  • which C# knows is a generator function which will yield control

  • at predetermined times.

  • And it will manage it for you.

  • And to trigger it, you need to StartCoroutine,

  • which is a function that Unity has defined for you.

  • So any questions as to how this works at a high level, at least?

  • AUDIENCE: I'm a little confused on the syntax for Yield.

  • Does the Yield return--

  • COLTON OGDEN: Yes.

  • Yield takes in something, and you get a new to instantiate a new WaitForSecond.

  • It's just the way the syntax is for yielding an object.

  • Yield--

  • AUDIENCE: What is it returning back?

  • COLTON OGDEN: It's returning this to the Yield instruction.

  • And the Yield instruction is then yielding that.

  • It is a little bit weird, though.

  • But you only really have to--

  • this is a common pattern that you'll see.

  • And so you'll just get used to it.

  • And honestly, this is probably one of the more syntactically difficult things

  • about Unity.

  • So I mean, as soon as you understand this,

  • pretty much anything else is fairly easy at that point.

  • I would say this is one of the weirder sides.

  • Because in Lua, and Love2D, and those kinds of environments,

  • it's a little bit easier to do this sort of thing with as much syntax.

  • C#, being a statically-typed environment, to get the same behavior,

  • you have to go a little bit further.

  • Do things their way.

  • But that's getting asynchronous behavior to work in C#.

  • And in Unity, typically, in order to avoid messy state management,

  • you will use coroutines, which are IEnumerators, often

  • with the WaitForSeconds object.

  • And just make sure to trigger them with StartCoroutine.

  • And then call that function as if it were an object.

  • Just like that.

  • And the behavior for the airplane spawner, the coin spawner,

  • and so forth, they're all fairly similar.

  • So StartCoroutine SpawnCoins.

  • And then while true.

  • So coins-- this row just spawns a random number of coins vertically.

  • So you can have one, or you can maybe have a couple.

  • And then for each of those, instantiate them

  • at a random position on the y-axis.

  • Here, random negative 10 to 10.

  • In this case, Quaternion.Identity, because we don't want

  • to rotate them any differently than they already are, which I alluded to before.

  • So Quaternion.Identity just means no rotation applied to the object.

  • So whatever its base rotation is as a prefab.

  • So it'll be whatever it is when you see it in the editor,

  • and when you create a new one, that's going to be its default rotation.

  • So basically the equivalent of, don't rotate this by anything.

  • And then, same as we saw before, yields a new WaitForSeconds.

  • In this case, 1 to 5, so a little bit more frequently than the airplane,

  • on average.

  • More coins.

  • As the airplane, I believe, was 3 to 10, so every 3 to 10 seconds.

  • And that's basically that.

  • And then we StartCoroutine, just like we did before with the airplane spawner.

  • And then lastly, the skyscraper spawner.

  • In this case, we only have one coin prefab,

  • we only have one airplane prefab, but we have three skyscraper prefabs.

  • And so the actual Random.Range here comes into play,

  • because that's how we get the different color skyscrapers spawning at random.

  • And like I said before, this can be extrapolated

  • to whatever sort of game environment you want,

  • where you have multiple types of objects that

  • are more complicated than just a color swap,

  • and you want to choose them at random.

  • We're doing all the same things that we did before.

  • And also, here, we can see that they have a random chance

  • 1 in 4, so a 25% chance of increasing the speed.

  • So skyscraper spawner actually has-- and this is relevant to the assignment--

  • skyscraper spawner has a float called speed here, which is, by default, 10.

  • And this drives the scroll speed of the skyscrapers, the coins,

  • and the airplanes.

  • And so when we call it static, do we know what that means?

  • Yeah?

  • AUDIENCE: Well, it's the same for all [INAUDIBLE] objects [INAUDIBLE]..

  • COLTON OGDEN: Yes.

  • So no matter how many skyscraper spawners we create,

  • they're all going to share this field speed,

  • and it's always going to be equal to the same amount across all instances.

  • So static means that it's part of the class.

  • It belongs to the SkyscraperSpawner class.

  • It does not belong to a SkyscraperSpawner object.

  • And so we don't ever really want to change that ourselves in this game.

  • But the airplane, for example, notice that in our Update function

  • here, if the transform.position.x is less than negative 25,

  • we should destroy the airplane.

  • So when it goes off screen, we should destroy it.

  • But if not, then we want to translate this object

  • on the x-axis by negative SkyscraperSpawner.speed times 2 times

  • Time.deltaTime.

  • So we're translating it on the x-axis, and we're

  • using the SkyscraperSpawner.speed as our core multiplier.

  • And then if we look at coin--

  • same thing.

  • I should ask, what is one design consideration

  • if we're looking at this--

  • all these components?

  • And let's say we're trying to get as decoupled as we

  • can, and well-engineered as we can.

  • What is a consideration that we can, noticing that, for example,

  • this exists in coin, and this exists in airplane?

  • AUDIENCE: What's the question again?

  • COLTON OGDEN: So how could we better engineer this?

  • Notice that we have--

  • and taking into consideration this component model in Unity--

  • what could we do with, for example, this?

  • AUDIENCE: So that Unity better [INAUDIBLE]..

  • COLTON OGDEN: Well, I'm thinking that, because the airplane and the coin--

  • well, first of all, it wouldn't belong in the spawner,

  • because the spawner's job is to spawn coins.

  • But the coins and the airplanes-- they have their behavior, right?

  • They have their update behavior.

  • But they're all doing kind of the same thing, which is scrolling to the left,

  • and then de-spawning at negative 25.

  • So a design consideration here would be, maybe we have a scroll component,

  • right?

  • Or a scroll and destroy component.

  • Or just a scroll component with an optional destroy flag.

  • And then we just put this on anything that we want to automatically scroll

  • to the left screen and de-spawn, rather than having

  • to code this in every single class.

  • So if you're ever looking through your code,

  • and you're seeing, for example, this and this being the exact same, especially

  • in Unity, because of how flexible components are,

  • try and think about how you can make it into its own component.

  • AUDIENCE: So where would you put the coins [INAUDIBLE]??

  • COLTON OGDEN: You make it yourself.

  • So if we went into here, into our Scripts thing, and just

  • made a new script.

  • And then we called it Scrollable.

  • And then we double-click it, it'll open up in our editor

  • that we've chosen in our preferences.

  • By default, it's going be Visual Studio with the newer versions.

  • But now we have a new scrollable behavior.

  • And then all we need to do--

  • we would say, oops--

  • not that.

  • If-- once again, I can't type.

  • Transform.position.x is less than negative 25,

  • destroy game object, right?

  • And so this is the exact same thing as all that behavior that we saw before.

  • So now we can take all of that out of coin--

  • we can take this out, and we can take this out here, and then

  • just add to the airplane and to the coin prefabs,

  • we'd just add this new Scrollable prefab,

  • and it'll take care of that for us.

  • Yeah.

  • AUDIENCE: How would you add that?

  • COLTON OGDEN: So if we're going back to Unity--

  • so the question is, how would you add the component to the prefab?

  • So in our prefabs, note that we have airplane, we have coin,

  • and we have all the skyscrapers.

  • So these are all prefabs.

  • Oh, and I haven't actually detailed how to create

  • a prefab, which I apologize for.

  • So a prefab-- all you need to do is create an object in your scene.

  • For example, I did it with this helicopter.

  • So I'm going to delete the helicopter, because this is just the helicopter

  • prefab.

  • It's not going to actually delete the helicopter from our scene.

  • But if I take this helicopter here, and then I drag it in here,

  • notice that now we have a prefab here.

  • We could take this helicopter out, and just add a new one right there.

  • So now I have two helicopters.

  • That's all a prefab is.

  • It's just something that you've made in your scene

  • that you want to replicate or duplicate in the future, ideally through code.

  • And notice it's showing here in our hierarchy view.

  • But obviously you don't want that.

  • But that's how you create a prefab is, you find something

  • that you've configured in your game scene, and then in your code,

  • you're allowed to then instantiate that prefab,

  • assuming that you've made that prefab a member of whatever class

  • instantiates it.

  • So in this case, the airplane spawner.

  • So notice that we call Instantiate here, like we saw, before on line 24.

  • But it's looking at prefabs.

  • It's a list, and it's looking at a random range between 0

  • and the length of the prefabs.

  • You have to declare a public GameObject list prefabs here.

  • And then, once we do that, and we go to our airplane spawner here,

  • notice that we have this prefabs list here.

  • So we actually get a editor view of that code, that data structure.

  • And we can just add prefabs that we want to.

  • And if we change the size to 2, then we can add a second prefab.

  • Or if we make the size 10, then we can put 10 different airplanes there.

  • But we only have one airplane that I modeled, so I was going to make it 1.

  • But this is the same thing.

  • This GameObject list is that same list, only

  • that's the view we have of it in the editor.

  • And it's just a different way of looking at the data.

  • And that's the nice thing about Unity is that, if you make

  • any of your fields public, like this.

  • If I did public int someNumber, right?

  • And I save that, and I go back here, my airplane spawner

  • should get a someNumber right here.

  • And then I can just set someNumber to whatever I want.

  • It doesn't do anything right now, because someNumber

  • doesn't affect the script.

  • But this is a nice way to take the burden from you out of the code.

  • You're not experimenting with code.

  • You're not tweaking some variable in your script.

  • SomeNumber 10, run me, OK-- someNumber 12, run it.

  • You can just assign it here in your actual editor view,

  • and then just tweak it as you want.

  • So I can just scroll through it.

  • I can start the game and I can pause the game.

  • AUDIENCE: So is it that you just make it public?

  • COLTON OGDEN: You have to make it public.

  • Exactly.

  • AUDIENCE: Oh, then that shows in.

  • COLTON OGDEN: Yep.

  • And so I can just do this.

  • And then I should be able to--

  • I can just change this now, if I wanted to, to 67.

  • And so if that actually had some sort of effect on my game world--

  • if my game relied on that number being meaningful--

  • this would update the scene.

  • And so you can make whatever-- you can debug this way,

  • or you can customize your components more this way.

  • But it's just a tremendous amount of flexibility.

  • And so that's how we're instantiating this from this list of prefabs.

  • It's a list of game objects.

  • We've made it explicit to Unity.

  • The editor's going to know, OK, these are game objects.

  • And so prefabs are game objects.

  • So we can just put any of these into this field

  • here, this element 0 field, or however many fields we have.

  • And it will, in the code, randomly choose one of those,

  • and then instantiate it.

  • So any questions as to how prefabs work, or instantiation,

  • or anything like that?

  • OK.

  • AUDIENCE: Does each prefab have a unique number or not?

  • COLTON OGDEN: Does each prefab have a unique number?

  • Not a unique number.

  • They just are all assets in your folder here.

  • They have some kind of identifier to Unity that's hidden from you.

  • It might be visible from you if you look.

  • They might have some sort of generated ID or something like that, so

  • that it knows, underneath the scenes.

  • But it's not something that you worry about in your actual code.

  • We've created a list here that's just--

  • it will expect some number.

  • It doesn't know how many.

  • And then, in the editor, you can actually tell it how many.

  • Because every list that you make visible in

  • the editor will have this size field.

  • And that's just part of the editor's abstraction.

  • The editor has abstractions for a lot of different objects.

  • You can create a lot of different objects in your MonoBehaviours,

  • and make them public, and Unity will have a different view for them.

  • Things like color pickers, and stuff like that.

  • I don't know the exact object offhand, but if you make a public color

  • picker in your MonoBehaviour or something like that,

  • you'll actually get a color picker here that you can choose a color for,

  • which is pretty awesome.

  • And there's a lot of different customized views.

  • I think, actually, the directional light might be an example.

  • See this color here?

  • This is somewhere in code.

  • There's a public color something.

  • And then in the editor, when you're actually editing it,

  • you can choose the color.

  • And Unity gives you the option to make it wherever you want.

  • And notice it's actually affecting the background there.

  • One of the really big strengths of Unity,

  • and a reason to really make your components customizable,

  • is this one-to-one mapping between your data, your code,

  • and your editor view of the data.

  • Take the burden away from you programming it,

  • which you're more prone to make mistakes, too,

  • and also waste a lot of time.

  • And then just be able to modify.

  • How much faster is it for me to just say,

  • I want this red color, rather than color.r equals something,

  • and then color.g equals something, color.b equals something.

  • And then assign it that way.

  • So that's just a simple example to illustrate it.

  • But there's a tremendous amount of potential involved

  • in making your components customizable.

  • And so that's what I did here.

  • Because I wanted the skyscraper spawner to spawn not just one skyscraper

  • but several skyscrapers, I created this public prefabs list,

  • that I can then populate from Unity here.

  • And say, OK, this object, this object, and this object.

  • Put that into the skyscraper spawner here, in these slots.

  • And then my code knows to instantiate it, randomly picking one of those.

  • Yeah.

  • AUDIENCE: In the example earlier, how would you

  • actually go about making something scrollable now that you've made that?

  • Would you just add component to the coin?

  • COLTON OGDEN: How would you go about making something scrollable now that

  • you've--

  • AUDIENCE: You declared that Scrollable class, right?

  • COLTON OGDEN: Oh, yeah.

  • So I have a Scrollable script here.

  • Oh, do you mean the infinite scrolling background, or the scrollable object?

  • AUDIENCE: The one where you define it as being destroyed when it's [INAUDIBLE]..

  • COLTON OGDEN: Oh, yeah.

  • So in order to do that-- so we've made the Scrollable script here.

  • So you would just go to-- in this case, since we're

  • instantiating all of the coins, and skyscrapers, and airplanes as prefabs,

  • you actually edit the prefab itself.

  • So I'd go to this coin prefab.

  • I'd add component here at the bottom.

  • And I would add Scrollable there, which is a script.

  • And you can tell it's yours, because it says script there.

  • Just like coin here is a script, and rotate script is a script.

  • That's actually an old script that I didn't use anymore,

  • because I just made it part of the coin itself.

  • But this is another part of what makes the coin class, by the way,

  • is this transform.rotate x, y, and z, either relative

  • to itself or to the world space.

  • In this case, it shows the world space, because I want

  • it to rotate on the world's y-axis.

  • But if you rotate an object, its own x, y, z still exist.

  • And so you can have something rotate about its own x, y,

  • and z, regardless of its rotation in the world.

  • And so I want the coin to always rotate.

  • So 5 degrees on the y-axis here.

  • So this is another part that you could take and make it its own script--

  • just a rotatable script.

  • Auto rotate script.

  • And then just make these public fields, like public x, public y, public z.

  • And then you could change those in the editor,

  • and then actually customize your objects without having to go into the code

  • itself.

  • AUDIENCE: So you don't have to do any collisions, actually, yourself.

  • You just click the button and type in the Collide function.

  • COLTON OGDEN: Yeah.

  • You don't have to do any of the collision yourself,

  • because Unity comes with a 3D physics engine.

  • You have to tell it what to do when the collisions happen.

  • But you don't have to actually code the 3D physics engine yourself, which

  • is a tremendous amount of work.

  • One of the big selling points, definitely,

  • for doing any 3D things in Unity.

  • I mean, a lot of engines now have 3D engines for free.

  • There was a time when it was less prevalent.

  • AUDIENCE: How would you know what you collided with?

  • COLTON OGDEN: Because there is a collider that it gets.

  • Where is it again?

  • Is it the skyscraper, for example?

  • So every skyscraper, when it collides with something,

  • this OnTriggerEnter gets passed in this collider other.

  • And so that's going to be the object that you collided with.

  • You can assign a name to an object, and then get its name, as well, in code,

  • if you needed to do that.

  • In this case, we wanted to know whether it's the helicopter or not.

  • So we got the HeliController from it.

  • But in our game, it's kind of a special case,

  • because there's only one type of object that

  • has a collider that's a non-trigger, and that's the helicopter.

  • So we can assume that this exists when we do get a collision.

  • But otherwise, we probably want to say, get component of HeliController,

  • and then test it for equal to null.

  • And if it's equal to null, then we want to not do something.

  • If we tried to call explode on a null object,

  • that's going to be a null pointer exception.

  • And then you're going to crash your game.

  • But in this case, we're guaranteed for that not to happen.

  • Because, like I said, the only collider that

  • can collide with this that's not a trigger is the helicopter object.

  • So does that makes sense?

  • AUDIENCE: Sounds like a physics problem with an exploding element.

  • COLTON OGDEN: Oh, yeah.

  • Yeah.

  • That's all too real.

  • In this case, though, all explode is is, thankfully, just destroy game object

  • and then start a particle effect.

  • So any more questions as to how instantiation,

  • prefabs, using the Unity editor, interacting with your model

  • behaviors, public variables?

  • Yeah.

  • AUDIENCE: You get processes, you first enter the data in that to there,

  • and then it runs the code to load the data,

  • or can you also put value data in the editor?

  • COLTON OGDEN: You cannot define--

  • so the question is, do you first create the data in the editor,

  • and then the code, and then tie them together that way?

  • In order to get a new component, you will have to create the class first--

  • the model behavior that you want--

  • and give it the public fields that it needs for the editor to actually read.

  • So the editor reads your script, looks through all the public fields,

  • and then will create the necessary GUI elements in the editor

  • to interact with the code.

  • But this needs to exist.

  • All this code needs to exist.

  • Anything that has these public variables that you want accessible in the editor

  • needs to exist in code first, and then you

  • have the power to use it in the editor.

  • And then it's customizable at that point.

  • Any more questions?

  • All right.

  • So that's the prefabs.

  • So we looked at texture scrolling before.

  • And we're going to go kind of quickly through this.

  • But all texture showing was, if we recall,

  • was I have a scrolling background class here.

  • It's got a scroll speed.

  • So if we wanted to, since it's public, we can edit it in the editor

  • and make it faster or slower.

  • But if we maintain a reference to our renderer.

  • Every object has a renderer-- mesh renderer, typically, for 3D objects.

  • And the renderer has a material associated with it.

  • Everything in Unity has a material.

  • And material doesn't necessarily have a texture.

  • But in this case, our background does have a texture,

  • and it's going to be called MainTex by default.

  • That's the main texture, if we want to assign a texture

  • to see visually on a material.

  • So material can just be a color often, but it can also

  • be a color and also a texture.

  • So in this case, the background is a texture.

  • It does have a texture assigned to it.

  • And notice that if we go to the background,

  • and then here is where we actually create.

  • So this is a material here.

  • It's got a shader.

  • So we're not going to talk too much about what shaders are.

  • We've talked about them briefly.

  • In Unity, you can go quite a bit farther with them.

  • Well, technically speaking, I guess you could go equally far with Love2D--

  • 2D shaders and 3D shaders, at least in vertex and fragment shaders.

  • But for all intents and purposes, we're not

  • going to go into detail on shaders today.

  • But they're very similar.

  • In this case, every material has a shader in Unity,

  • in order for it to render.

  • Because anything that renders needs to be shaded in Unity.

  • But notice that here we have a texture.

  • So this is that main texture thing that we saw before.

  • And then this is the bump map here.

  • We don't have a bump map, because our texture isn't bumpy.

  • It doesn't have any contour detail or anything like that.

  • But you can just select a texture here.

  • So I could give it that texture if I wanted to.

  • Or I have another couple of textures here.

  • This texture here.

  • It doesn't matter which texture you choose, but you can choose a texture.

  • And then that material will then, it will calculate,

  • based on the mesh, how to draw that texture best to it.

  • And you can set every material's texture offset via its SetTextureOffset

  • function on the x and the y-axis.

  • And that'll shift it, effectively accomplishing

  • what a scrolling background needs.

  • It'll draw it, and then it'll basically wrap around to the other side.

  • So that's how we get the infinite scrolling texture.

  • We just set its texture offset.

  • We specific call SetTextureOffset on the main texture,

  • and then we pass in a Vector 2.

  • So just two numbers--

  • offset and 0.

  • Because we don't want to touch the y-axis, so it was 0, and then offset

  • is just some value that increases over time.

  • And then Time.time, recall--

  • time since the beginning of the game has started.

  • Time.delta time-- time since the last frame.

  • And so we can just do one calculation in here and update every time,

  • and then multiply it times scroll speed to get

  • the scroll amount that we want here.

  • And I've set it to 0.1 just for a semi-slow effect.

  • But in the editor, if we look at our scrolling background, which is here.

  • And then I've made it public, so I could just easily make this 1.

  • And I don't know how fast it's going to be, but 10 times faster.

  • So probably pretty fast.

  • Yep.

  • So it's hauling.

  • But yeah.

  • All that is is a shifting how it maps the texture onto the 3D surface.

  • And this 3D surface, to be clear, is just a plane.

  • So just a plane.

  • And notice that it doesn't get rendered from the back.

  • So anything which is a polygon has one side that gets shaded

  • and one side that does not get shaded.

  • So if you see invisibility on one side, and not invisibility on one side,

  • that's not a bug.

  • That's a feature.

  • That's the texture scrolling side of things.

  • Anybody have any questions as to how that works?

  • We won't really need it for much going forward.

  • It's not part of the assignment.

  • But it ties in to what we've done before.

  • So I thought it would be interesting to cover.

  • Last thing we'll look at is audio.

  • Audio is pretty easy.

  • All you need in Unity to do audio is an audio source and an audio listener.

  • So a source plays audio.

  • So James, if you are an audio source, I'd be an audio listener.

  • And I'm listening for audio and playing it back

  • to the speakers when I hear any sort of source of audio in the game world.

  • So you can have infinite audio sources, but ideally one audio listener.

  • And it's usually whatever main camera that you're drawing with.

  • And then sounds in Unity can be either 2D or 3D.

  • In this case, all the sounds that I've put into this game are 2D.

  • And that's just this spatial blend here.

  • Notice that there's a 2D to 3D slide.

  • And so that'll calculate, based on where the sound is,

  • how it sounds in the game space.

  • And it's based on how far away it is, and what side of you it is.

  • And so the main camera has the audio source for the music.

  • And so it just starts the music right away.

  • Notice that we didn't have to code any of this.

  • I just attached an audio source to the camera.

  • And then there's a Play On Awake checkbox, and a Loop checkbox.

  • So normally we've done like--

  • at the start of our game code, set looping to true, and then

  • play the source.

  • In this case, we'd have to do none of those things.

  • This is just given to us by Unity by default.

  • AUDIENCE: You put the audio on the camera?

  • COLTON OGDEN: Yep.

  • AUDIENCE: You could put it on anything.

  • COLTON OGDEN: You could put it on anything you want to.

  • AUDIENCE: The sound is relative to that object.

  • COLTON OGDEN: The sound is relative to that object, yeah.

  • That's audio.

  • It's pretty simple.

  • I mean, it's an art form when it comes to how

  • you want to position things in your game world,

  • and how you want them to sound, and whatnot.

  • And you can get more complicated with it.

  • But that's how you would do basic 2D, in this case, audio.

  • But you could easily also do 3D audio just

  • by making this set to 0 from 0 to 1.

  • And you can make it more or less 3D, too.

  • If it's not all the way to 1, it'll somewhat be 2D.

  • So you'll hear some of it no matter what, and some of it will be 3D.

  • And then setting it to 1, it'll be purely 3D.

  • So if it gets far enough away, you won't hear it at all.

  • The explosion sound, as well, gets triggered.

  • And so this happens in the--

  • I forget exactly offhand where it gets triggered.

  • I believe it's in the HelicopterExplode function?

  • Oh, yes.

  • I created an object in the scene that has an audio source for the explosion

  • sound.

  • And because I made this public, I can just put it on this object.

  • And so there's a reference to that audio source at all times.

  • And so I trigger explosionSound.play whenever we call this Explode method

  • on the HeliController.

  • For the reason that I didn't put it on this object, because we destroy

  • this object as soon as we get into a collision.

  • And so I didn't want to put the explosion sound

  • audio source on this object, because it would get destroyed in there.

  • It would not play audio, effectively, as soon as we destroy it.

  • So we have to have it somewhere else on the scene.

  • I could have also just made the object invisible, but I didn't.

  • I destroyed it.

  • And so, yeah.

  • That's this explosion sound object which, if we look at the helicopter,

  • in the HeliController, which is all the way down here.

  • Notice that it has a reference to an explosion particle

  • system and an explosion sound.

  • And we just have to click and drag the object effectively from here to here

  • to pair the two of them.

  • And then the script that this is referring

  • to will always have a reference to those, as long as these

  • are set to something.

  • And notice that we also have coin total we've made public.

  • So I guess I could experiment with it.

  • And then speed-- we made this public so that you can make the helicopter

  • faster, if you wanted to, or slower.

  • And so you'll find it often useful to make a lot of these fields public

  • just for you to experiment with.

  • And then there's never really harm in doing so,

  • because folks aren't going to be able to actually see this in the editor

  • when they download your game.

  • So make as much public as you want for debugging purposes,

  • and just have fun with it.

  • But that's audio.

  • Audio sources produce the sound.

  • Audio listeners listen to the sound.

  • And actually make it go through your computer speakers

  • or whatever your default audio system is.

  • And here's just a showcase of the two on the camera.

  • And that's audio.

  • So any questions as to how the audio works at all?

  • All right.

  • A couple of last things before we close out here.

  • So the Asset Store is probably one of the biggest things about Unity

  • that sells it.

  • So oftentimes, it's a lot easier just to get something,

  • whether it's a library, if you're programming, or something else,

  • get something that's implemented for you,

  • so that you don't have to implement it yourself.

  • In this case, there's an RPG Systems for Rapid Development

  • something, Visual Scripting system, looks similar to Unreal.

  • There's a ton of things in the Asset Store-- models, scripts, audio, editor

  • tool kits.

  • A lot of cool things.

  • A lot of it's free, if you want to experiment.

  • So a lot of the assets that I got to mess with in this game here

  • are free, like the coin and some of the other stuff that's bundled with it.

  • There's a lot of really cool things and really cool systems

  • to help you for $10 or $20.

  • You can spend a little bit of money, and then

  • get like 25% of your game done right away.

  • And then you can actually get closer to shipping it.

  • So I think it's hugely valuable to be looking at the Asset Store

  • and thinking about how you can save your time that way,

  • if you're trying to actually ship a game and get to market.

  • So definitely look through the Asset Store.

  • See if there's anything that will make your life easier

  • when it comes to developing games.

  • Because I know that I've used it several times, and I rarely regret doing so.

  • So Assignment 8 is going to be pretty simple.

  • So for the second part, there's a bug in the game

  • where, if you die over and over again, the scroll speed never resets.

  • So it is keeping faster, and faster, and faster, and faster forever.

  • But it's a one line fix.

  • And I want you guys to find where to fix it, given that we know,

  • given what we've seen today.

  • And there's a hint here about static variables.

  • So it will also be detailed in the spec as well.

  • But this is an easy part.

  • This part's fairly easy, too, because all it really is

  • is taking the kind of the same stuff we've looked at--

  • the coin generator and coins--

  • and then making gems that spawn in addition to the coins at a rarer rate.

  • And these gems should be worth five coins.

  • And so when you collide with the gems, just add 5 to the coin total.

  • And if you collide with a coin, add 1 to the coin total.

  • And that's basically it for Assignment 8.

  • So next time, we're going to dive into first-person games in Unity.

  • So we'll look at some of the features that we get for free.

  • We're going to implement a simple maze-like game where you can navigate.

  • And it will be dark and kind of scary, similar

  • to a game called Dreadhalls, which is a mobile VR game.

  • And we'll actually test out Unity's VR, and try and get a VR headset in here

  • so people can check that out.

  • Cool.

  • Thanks a lot.

  • I'll see you guys next time.

[MUSIC PLAYING]

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

Unity/C#教程 | 直升機遊戲3D--CS50的遊戲開發介紹。 (Unity / C# Tutorial | Helicopter Game 3D - CS50's Intro to Game Development)

  • 6 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字