Uh mazing: 01 An introduction to 3d game development

For this post, we’ll cover a what goes into building a 3d game. Fun, right? Maybe not super exciting but there are some important concepts we need to cover. Without further ado, getting started designing a video game for newbs.

If you’re interested in building video games, you’ve probably played a video game or two in your time. I know I’ve wasted way to many hours… When you’re playing any 3d game, you’re perspective of the world is usually either 1st person (most shooters) or 3rd person (most RPG’s) Or, you might observing from above (most an RTS). It turns out, these don’t make much difference once you get further into building the world. What you probably don’t think about, is the fact that you, or your avatar, are not moving. That’s right – in a 3d game: You don’t move – the rest of the world does.

In a 3d game: You don’t move, the rest of the world does

Graphics Rendering, a summarized explanation

Now, let me explain how graphics work, in a nutshell.

  1. You have a flat 2d screen.
  2. The screen has pixels!
  3. Just like a painter, you are tricking the human eye into seeing 3d
  4. Graphics rendering starts with a single pixel, moves to a line, then a triangle.
  5. The entire world is made of triangles
  6. Every detail added on top of that, is just enhancing groups of triangles.

Pretty simple right? Drawing triangles? I tend to think so. The real magic with the triangles – comes with positioning, and movement rules. Positioning is when things start to get a bit more complicated.

I took a video game design course in college. Some of the best take-aways from that course were which libraries to use, and, how to use the positioning. The easiest way I can explain positioning, or coordinates, is to start with a square. Now, draw two lines, one from top to bottom, the other from left to right. You now have two axis. Usually, the top to bottom one is the Y axis, and the left to right one is the X axis. So – if you move the Y axis along the X axis (draw the top to bottom bar a little to the right from where it started) You’ll have moved to a new coordinate along the X axis. The way we represent these positions in software are with Vectors. vector(1.0f, 2.0f) would mean a position of 1 on the x and 2 on the y axis.

Rendering

Now, lets go 3d! Remember the square, and drawing the lines across it? Do that with a cube. Yes, you’ve suddenly got a third line going from front to back. This is the z-axis. A vector can represent this as well. vector(1.0f, 2.0f, 1.0f) where the 3rd item is the position on the z-axis.

Now, it’s great to travel on axis – but if we can only look one direction, how do we turn, or look up? This is where rotation comes into play. And here’s where the programming gets tricky – because now, instead of just moving the world around your perspective – you need to rotate the world around your perspective, yet not rotate everything in the world at the same time! Fortunately, this is where other’s work can be helpful. Rendering engines take care of a lot of the complicated math using well known, powerful algorithms. If you’re still curious about using a camera approach, here is an example of a camera, and render method using C# and OpenGL

Textures, Sounds, and Networking

It isn’t enough to simply draw lines, squares, or donuts. For a game to be immersive it will need the donut to look like it got home from the bakery, the line will need to go ‘zap’ as it flashes by, and the kid across town is going to need to know where the square is. These auxiliary pieces to a game are crucial to some of the best games out there. To some extent, these can be overlooked in 3d world development. At the same time, the can’t be missed.

These will be covered a bit later on – there are tutorials out there already. And it is possible to develop the piece in some isolation. Well, textures might belong to the engine. that said, there are lots of opportunities to cover them.

Summary

This introduction covers some of the different high level aspects of the 3d world. Because 3d development is such a large item. Having a plan on what to attack next is useful to avoid scope creep and other difficulties getting anything working. As this guide is built out – it will somewhat follow my own approach to a development plan.

Advertisement

Chatbot 3: Long Term Memory using a file

In the previous post, we covered Golang Slices used for short term memory for the chatbot. As we noted – this only lasts as long as the program is running. Now, let’s see if we can keep information when the chatbot goes offline. That is – long term memory. There are a few different approaches for long term memory. Writing to a file or to a database are the most common. For this example, we’re going to use a file. For future development, most use a database. This allows the database to handle the i/o (input/output) and maintain performance. This is because a database is designed specifically for the use case of storing and retrieving data.

Step 1: Breaking it down

Conceptually, File I/O is actually much more straightforward then previous concepts. The program needs a destination file, a payload, and permission when writing to a file. It needs a source file, destination and permission when reading from a file. It will also need to know how to interpret or parse the data when reading. Thus, it also needs to store the payload in a consistent format. There are a large number of guides in any given programming language for writing to / reading from a file. I’m not going to recommend one currently – and will focus on the actual implementation.

Step 2: File I/O in golang

The first example we have, is creating a file. Here is how to create ‘test.txt’ and avoid a memory leak. A memory leak is when something is reference by the program, but the code flow of the program no longer has context for that reference.

// Get a read/write file reference by creating it if it doesn't exist, or appending to it if it does exist.
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) // This provides a reference to the file 'f', or an error if it cannot. 

// check for the error. 
if err != nil { // a 'nil' error means no error occurred. 
  fmt.Println(err) // print the error
  return // stop processing the context.
}

// ##IMPORTANT## Always close the file reference to prevent a memory leak!
defer func() { // defer is an instruction that runs when the 'context' closes.
	_ = file.Close() // close the reference!, the underscore is a way to ignore the value returned by Close()
}()

Reading from an open file reference

// Reading each line in a file into an array
var lines []string // declare the slice to hold the data
scanner := bufio.NewScanner(file) // use the buffer input output package to start a file scanner
for scanner.Scan() { // loop through the file scanner
  lines = append(lines, scanner.Text()) // add the scanned line to the lines slice.
}

Writing to an open file reference

text := "Coming to a file near you" // string to write to file
_, err = fmt.Fprintln(file, text) // write the data to the file
if err != nil { // error handling if file I/O fails.
   fmt.Println(err)
}

\\ or alternatively
linesWritten, err := f.WriteString(text)

Step 3: Date storage format

There are many ways data can be stored within a file. If you’ve messed with files in the past – you’ll know a bit about text .txt files. Maybe you’ve seen markdown .md files. There are many different types of files – and these all serve different purposes. markdown, text, and even .csv files are all Human Readable Formats. If you open any of these in a text editor, you’ll be able to make sense of them quickly. However – they aren’t exactly the best for storing data from a program. For this particular example, we are going to simply use a txt file to place human readable data into the file.

Step 4: Chatbot Enhanced Memory

  1. launch program
  2. check if history exists (create it if it doesn’t exist)
    1. load the history into the history slice
  3. send a friendly greeting and store it to history
  4. send a simple instruction and store it to history
  5. Have the user put something in and store it to history
  6. echo what the user put in and store it to history
  7. Check if the user typed ‘history’
    1. Store the request to history
    2. don’t repeat the history in the history file, just count the number of lines.
    3. Loop through the history slice
    4. print the key and the value
  8. repeat 4-7 until the user says ‘bye’

Step 5: Putting it all together

Now that we’ve covered the details, we can run the program. We’ll find that it isn’t much different from running the program during lesson 2. However – when we say ‘bye’ and start the program again, that’s where the changes are noticeable.

  1. run the program
  2. type hello
  3. type history
  4. type bye
  5. run the program again
  6. type history

Example output

Here is the final example used above. It is also available in github in chatbot, lesson 3

func main() {
	f, err := os.OpenFile("history.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

	// check for the error.
	if err != nil { // a 'nil' error means no error occurred.
		fmt.Println(err) // print the error
		return           // stop processing the context.
	}

	defer func() {
		_ = f.Close()
	}()

	// string for storing input
	var input string

	// Slice for storing the history of the world.
	var history []string
	scanner := bufio.NewScanner(f) // use the buffer input output package to start a file scanner
	for scanner.Scan() {           // loop through the file scanner
		history = append(history, scanner.Text()) // add the scanned line to the lines slice.
	}

	fmt.Println("Hello, World!")
	fmt.Println("I am Echo! Please tell me something to say by typing it in, and pressing enter!")
	_, _ = f.WriteString("Hello, World! \n")
	_, _ = f.WriteString("I am Echo! Please tell me something to say by typing it in, and pressing enter! \n")
	// Stop when we see "bye"
	for input != "bye" {

		fmt.Print("You: ")
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			input = scanner.Text()
			break
		}
		_, _ = f.WriteString("You: " + input + "\n")
		// Add the new input to the history
		history = append(history, input)
		fmt.Print("Echo: " + input + "\n")
		_, _ = f.WriteString("Echo: " + input + "\n")
		// Check if the input was the string 'history'
		if input == "history" {
			// We won't add the history to the history, instead we can add how many lines of history exist.
			_, _ = f.WriteString("<Truncated> " + strconv.Itoa(len(history)) + " Lines of history repetition.")
			// iterate (loop) through the history
			for k, v := range history {
				// print out the Key and the Value
				fmt.Println(k, v)
			}
		}
	}
}

I don’t know about you – but that code is starting to look like my pantry after my kids try finding the peanut butter. In our next lesson – we will start to look at organizing code a little bit better.

Chatbot 2: Improve the chatbot with a memory

Many people can recount their first memory. We don’t really know how we do it, or why it is a first memory. It’s just there. We can also remember things more recently – or – remember information that is imaginary. For this lesson, we will be covering memory in terms of programming. Because a chatbot without a history, is like playing Memory and just choosing random cards each turn. Lesson examples can be found on Github

Step 1: Understanding the Lesson

Before we dig into code – let’s see if we can understand a little more about storing things in program memory. For most languages a developer can write, there are a few different ways to store data. In previous lessons, we’ve covered what a variable is – including a few types of variables. In this lesson, we are diving more deeply into the array type of variable.

One way to understand an array, is to think of series of mailboxes. They all have a number on them so the delivery person can put the mail into the correct slot. When a large neighborhood is built, there are only so many houses – and – there are only so many mailboxes. Depending on how this neighborhood is setup, any new houses will either be an easy addition (or) for a grouped mailbox setup – a new group would eventually need to be created. Arrays are like the grouped mailbox setup.

But what is an array, really? An array is a declared list of keys and values. From the mailbox example, the mailbox number is the key, and the things inside the mailbox are the value. At a more physical layer – arrays are blocks of ram reserved for storing sets of specific types of variables. In different programming languages, there are different mechanics for using arrays. In c++, one needs to program what happens if you try to add more keys to an array then the original requested amount. In golang – the language itself handles this for you using slices – so you don’t have to program the tedious memory management. Most modern languages handle the array management operations in some similar fashion.

Step 2: Using an Array in Golang

Now that we’ve briefly covered the definition of an array, let’s look at a few array operations. The Golang Tour provides an example of arrays here: A Tour of Go (golang.org).

Example from the golang tour with notes:

func main() {
  // creating a new array
  var a [2]string

  // adding to an array using a key
  a[0] = "Hello"
  a[1] = "World"

  // looking up something in an array
  fmt.Println(a[0], a[1]) // Prints Hello World

  // Creating an array with values
  primes := [6]int{2, 3, 5, 7, 11, 13}
  fmt.Println(primes) // Print [2 3 5 7 11 13]

  // removing the item with key 3 from the array
  var slice []int                                // create a 
  slice - notice how it doesn't have a length specified?
  slice = append(slice, 1, 2, 3, 4, 5, 6) // add values 
  to the array
  removalKey := 3                                       // set a key for removal

  // remove an item by adding the first portion (before the key, and the second half after the key together) 
  slice = append(slice[:removalKey], 
  slice[removalKey+1:]...)
  fmt.Println(slice) // Print [1 2 3 5 6]        // notice how the '4' is gone?

  // iterate (loop) through the slice and print the key and the value
  for key, value := range slice {
    fmt.Println(k, v)
  } 
}

Step 3: Chatbot wants a slice

With only a few lines of change, we are going to have chatbot keep a short term memory of the conversation. Here is a breakdown of the logic we’re going to use for chatbot:

  1. launch program
  2. send a friendly greeting
  3. send a simple instruction
  4. Have the user put something in
  5. echo what the user put in
  6. Check if the user typed ‘history’
    1. Loop through the history slice
    2. print the key and the value
  7. repeat 4-6 until the user says ‘bye’

To start, we will initialize a new slice.

// Slice for storing the history of the world.
var history []string

Next, whenever we get new input, we are going to store it in the history

// Add the new input to the history
history = append(history, input)

Finally, chatbot is going to check for a ‘prompt’ that it will use to print the history.

// Check if the input was the string 'history'
if input == "history" {
	// iterate (loop) through the history
	for k, v := range history {
		// print out the Key and the Value
		fmt.Println(k, v)
	}
}

Step 4: Putting it all together

The completed above code can be found in this github repository. There is a main.go file (the code) and a README.md file (the directions).

To get the following output, run the program, and enter the following lines

  1. type test
  2. type cool
  3. type history
Program Output

Well, we’ve now got a chatbot that is still a copycat. It can now recount all the things it has copied. This ‘history’ only lasts while the program is running. In future lessons, we can explore more permanent memory, command logic, and making the code organized.

Introduction to Programming 000

Quite some time ago, I went to college for Computer Science degree. I knew that I wanted to provide for a family, that I didn’t want to perform a job with a lot of manual labor, and that doctors and lawyers would fit the bill. But I also knew that I was good with computers, I’d hacked a few games after all. I’d even written a few programs. In fact, the first one I ever “wrote” I can still provide from memory.

10 print Hello World
20 goto 10

But let me get into what this post is really about. An introduction to the world of programming. Currently, I’m teaching my son 6th grade math. It’s introducing him to Algebra. Algebra is cool. It makes math useful, repeatable even. It’s kind of what programming does. The first things you learn in school are definitions, terminology, the words, and what they mean. That way you can communicate with everyone else that learns about the same subject. Without further ado, let me introduce you to some common concepts of programming.

Operators

Just like 6th grade math, you need operators to do anything. Consider 2+2. It equals 4. That’s what your 1st grade teacher taught you. The ‘+‘ sign is the most important piece here. It is the operator. It tells you what to do with the things around it. There are lots of operators out there. Most of the ones you learned in Math apply. And there are few other special operators – but we need to cover other items before we introduce them.

Variables

Leaning back on algebra, variables are representational. They can vary in what they represent. For example, if we say x is a variable, and we have an equation x+1=2, we know that the x variable is 1. Or, if we say, the total is x+1, we can set x to a certain number, say 2, and know that the total is 3. Variables are a huge part of programming. Without them, we wouldn’t be able to record the result of operations. Now, speaking of operations, one of the special operators, is the assignment operator.

In many languages, the assignment operator is notated by the = sign. In other languages, it is a combined => symbol. An example of an assignment operation, would be as follow.

x = 10+2

if we were to print the value of x, it would say 12.

Types

Unlike Algebra, programming variables can be different types. This is because, different things need to be tracked in a computer. In algebra, you’re just tracking numbers. Examples of different types are:

  • integer
  • boolean
  • string
  • object
  • float

An analogy would be something like measurements. If you try to measure flour for a cookie recipe, and you use 2 tablespoons instead of cups – or – you used 2 grams instead of 2 cups – you end up with goop (I know from experience, goop doesn’t taste good). And for this reason, programming languages use types. Keeping track of a of number is an integer. Keeping track of someone’s name, well, that’s a string, and keeping track of your next door neighbor and their email address (is creepy) but that group of types, that’s an object.

Differences between Programming languages

I’m not going to cover a lot on different programming languages at this time. Just know they are out there. A lot of the core principles are the same, or similar, even if the terminology is different. This is because the C programming language became an effective way for programmers to convey ideas. If you’re into language history, think of it as the ‘Latin’ of the programming world in that a lot of other languages stem from it.

Next step

At this point, I’m going to recommend downloading Golang and finding a text editor or IDE. Because eventually, we’ll get started on using some of these concepts in an actual application. Once you’ve gotten an IDE – head on over to Program 1: Build a simple text bot!

A day in the life of a Computer Science Major

If you’re wondering what declaring a C.S. major will look like – read onward. Turns out, you’ve already figured out you want to work on computers, not toilets. No offense to toilet fixers, but it’s just not my cup of tea. The same goes for doctors – it’s great they save lives, but, there’s just too much trauma involved. Well, you’ve made it to school, college, university, pick your name of choice. And here is what a day in the life of a computer science major really looks like.

You go to class, like everyone else. And if you’re at a liberal arts college, you got to the same classes as a everyone else. There’s only one or two classes a semester you really care about – the other classes are all just ‘there’ as part of the program. So we sit through the superfluous classed, get down enough information to pass whatever test it is, then hold out for the interesting class that involves some kind of coding. Pretty similar to anyone going to a credit filler class.

Now, it’s time for a real class. Professor gets up, starts off with a crazy story about how his commute was <insert generic story here>. 5 minutes later, everyone has straggled into the classroom. The talk now shifts to a Q/A session – quickly followed by a round of projected code – then 20 minutes of the class trying to do what the professor asked. Homework handed out, and that’s it.

Now, some C.S. majors take the down time for doing the assignment. Most skip and hangout, watch trending and relevant stuff – or play games. The gamer stereotype has slowly been fading – but it is still quite prevalent. In other words – a C.S. major has pretty much the same college life as any college student – it’s just the extra curricular activities not covered here that are truly different.