Jump to content
  • Advertisement
mrpeed

Python code review for Snake

Recommended Posts

I wrote Snake in Python 3 using Pygame and was wondering if anyone can do a code review of it? If this is the appropriate fourm to post such a thing?

Some things to mention:

1. I realize I could have used a dict in the place of my Segment class, but I decided to go with the class because it looked more clean to me.

2. I used recursion heavily, though I could have used a list instead. I decided to do it recursively for practice and fun (I don't use recursion often). 

3. I don't have doc strings for any of my functions.

4. I probably could have used my get_all_snake_segment_locations function to avoid recursion.

5. I set fps to 10 to limit the speed of the game. Is this a bad way to do such a thing?

6. I attached an input manager I created and unit tests for my game for completeness. Though, I'm only asking the actual game to be reviewed, if you want to look at those you can. Also, note the unit tests are not complete yet for several functions I changed.

7. I really appreciate anyone who takes the time to give me feedback of any kind. This fourm has been a huge help to me and I'm grateful for everyone's  insight!

import sys
import random
import itertools
import pygame
import inputmanager

class Segment:
	
	def __init__(self, rect, direction=None, parent=None, child=None):
		self.rect = rect
		self.direction = direction
		self.parent = parent
		self.child = child

class Game:

	def __init__(self): 
		pygame.init()
		self.fps_clock = pygame.time.Clock()
		self.fps = 10
		self.window_size = (640, 480)
		self.displaysurf = pygame.display.set_mode(self.window_size)
		pygame.display.set_caption("Snake")
		self.cell_size = (32, 32)
		self.start_location = (320, 224)
		self.head_segment = Segment(pygame.Rect(self.start_location, self.cell_size))
		self.up = "up"
		self.down = "down"
		self.left = "left"
		self.right = "right"
		self.black = (0, 0, 0)
		self.green = (0, 255, 0)
		self.red = (255, 0, 0)
		self.direction = None
		self.extender = None
		self.cell_locations = set(
			itertools.product(
				range(0, self.window_size[0], self.cell_size[0]), 
				range(0, self.window_size[1], self.cell_size[1])
			)
		)
		
	def main(self):
		while True:
			self.get_input()
			self.update()
			self.render()
			self.fps_clock.tick(self.fps)
			
	def get_input(self):
		inputmanager.InputManager.get_events()
		inputmanager.InputManager.check_for_quit_event()
		inputmanager.InputManager.update_keyboard_key_state()
		inputmanager.InputManager.get_keyboard_input()
		
	def update(self):
		self.handle_input()
		self.update_snake_direction(self.head_segment, self.direction)
		self.move_snake(self.head_segment)
		if self.extender is None:
			self.add_extender_to_board()
		if self.head_segment_collided_with_extender():
			self.extend_snake()
		if self.game_over():
			self.refresh()
			
	def handle_input(self):
		if inputmanager.InputManager.quit:
			self.terminate()
		if (inputmanager.InputManager.keyboard[pygame.K_UP] == inputmanager.InputManager.pressed
			and self.direction != self.down):
				self.direction = self.up
		elif (inputmanager.InputManager.keyboard[pygame.K_DOWN] == inputmanager.InputManager.pressed
			and self.direction != self.up):
				self.direction = self.down
		elif (inputmanager.InputManager.keyboard[pygame.K_LEFT] == inputmanager.InputManager.pressed
			and self.direction != self.right):
				self.direction = self.left
		elif (inputmanager.InputManager.keyboard[pygame.K_RIGHT] == inputmanager.InputManager.pressed
			and self.direction != self.left):
				self.direction = self.right
				
	def terminate(self):
		pygame.quit()
		sys.exit()
				
	def update_snake_direction(self, segment, parent_direction): ###TEST
		if segment.child is not None:
			self.update_snake_direction(segment.child, parent_direction)
		if segment.parent is None:
			segment.direction = parent_direction
		else:
			segment.direction = segment.parent.direction
			
	def move_snake(self, segment):
		self.move_segment(segment)
		if segment.child is not None:
			self.move_snake(segment.child)
			
	def move_segment(self, segment):
		if segment.direction == self.up:
			segment.rect.move_ip(0, -self.cell_size[1])
		elif segment.direction == self.down:
			segment.rect.move_ip(0, self.cell_size[1])
		elif segment.direction == self.left:
			segment.rect.move_ip(-self.cell_size[0], 0)
		elif segment.direction == self.right:
			segment.rect.move_ip(self.cell_size[0], 0)
			
	def add_extender_to_board(self):
		snake_segments_locations = set(self.get_all_snake_segment_locations(self.head_segment))
		location = random.choice(list(self.cell_locations-snake_segments_locations))
		self.extender = pygame.Rect(location, self.cell_size)
		
	def get_all_snake_segment_locations(self, segment):
		yield segment.rect.topleft
		if segment.child is not None:
			yield from self.get_all_snake_segment_locations(segment.child)
		
	def head_segment_collided_with_extender(self):
		return self.head_segment.rect.colliderect(self.extender)
	
	def extend_snake(self):
		self.extender = None
		self.add_segment_to_snake(self.head_segment)
	
	def add_segment_to_snake(self, segment):
		if segment.child is None:
			if segment.direction == self.up:
				topleft = (segment.rect.x, segment.rect.y+self.cell_size[1])
			elif segment.direction == self.down:
				topleft = (segment.rect.x, segment.rect.y-self.cell_size[1])		
			elif segment.direction == self.left:
				topleft = (segment.rect.x+self.cell_size[0], segment.rect.y)
			elif segment.direction == self.right:
				topleft = (segment.rect.x-self.cell_size[0], segment.rect.y)
			segment.child = Segment(
				pygame.Rect(topleft, self.cell_size),
				segment.direction,
				segment
			)
		else:
			self.add_segment_to_snake(segment.child)
			
	def game_over(self):
		return any([
			self.head_segment_collided_with_self(self.head_segment), 
			self.head_segment_out_of_bounds()
		])
		
	def head_segment_collided_with_self(self, segment):
		if segment.child is not None:
			if self.head_segment.rect.colliderect(segment.child.rect):
				return True
			else:
				return self.head_segment_collided_with_self(segment.child)
		return False
			
	def head_segment_out_of_bounds(self):
		if (self.head_segment.rect.x < 0 
			or self.head_segment.rect.y < 0 
			or self.head_segment.rect.x >= self.window_size[0] 
			or self.head_segment.rect.y >= self.window_size[1]):
				return True
		return False
		
	def refresh(self):
		self.head_segment = Segment(pygame.Rect(self.start_location, self.cell_size))
		self.direction = None
		self.extender = None
			
	def render(self):
		self.displaysurf.fill(self.black)
		self.draw_snake(self.head_segment)
		if self.extender is not None:
			self.draw_extender()
		pygame.display.update()
			
	def draw_snake(self, segment):
		pygame.draw.rect(self.displaysurf, self.green, segment.rect)
		if segment.child is not None:
			self.draw_snake(segment.child)
			
	def draw_extender(self):
		pygame.draw.rect(self.displaysurf, self.red, self.extender)
	
if __name__ == "__main__":
	game = Game()
	game.main()

 

test_game.py

inputmanager.py

 

game.py

Edited by mrpeed

Share this post


Link to post
Share on other sites
Advertisement

Copied the files, and added comment, start with the input manager, as I refer back to that from the game comments. (see bottom of the post for the files).

16 hours ago, mrpeed said:

I realize I could have used a dict in the place of my Segment class

I agree, dicts look like cheap classes, but accessing fields looks messy. Also, there is no useful place to document what variables exist, and what they do.

16 hours ago, mrpeed said:

I decided to do it recursively for practice and fun

Ah, ok.

I agree that recursion is only useful if you have recursive data structures, so running into it is indeed dependent on the kind of problems that you usually solve. In this case, it's mostly just obfuscating intention, so better remove it.

16 hours ago, mrpeed said:

doc strings for any of my functions

Function names are pretty good, just a bit long-ish.

Documenting data and data flow is at least as important. Allowed values of all variables, and their meaning, and input/output relations of functions is crucial in avoiding problems.

As you seem to favor heavily fragmented function (a zillion functions of a few lines), some way to document the global picture would be useful too, as there is no single one page function that shows that.

I think it would be useful to try having less functions. You're almost using a function as a one line comment description above a block of lines now.

16 hours ago, mrpeed said:

I set fps to 10 to limit the speed of the game. Is this a bad way to do such a thing?

No idea, it does give you that authentic feeling doesn't it? :P

Some stuff to ponder is the problem of deciding which key the user pressed last. Right now the result depends on your order of processing, rather than me pressing keys in some order, I think (but didn't quite check).

You can of course also consider faster updating of the game screen, which makes things move more smooth. Maybe something for a next game?

16 hours ago, mrpeed said:

unit tests for my game for completeness. ...  if you want to look at those you can

I am a non-believer in unit test code for anything you can easily find out in the first use, so I'll pass on this one.

game.py

inputmanager.py

Edited by Alberth
point to files at the top

Share this post


Link to post
Share on other sites
7 hours ago, Alberth said:

Copied the files, and added comment, start with the input manager, as I refer back to that from the game comments. (see bottom of the post for the files).

Thanks so much for the reply, I'm going to review your comments and I'll get back with any additional questions.

Share this post


Link to post
Share on other sites

I did a second draft of the game.py file if you want to take a look. I plan to redo my input manager this week.

Some questions:

1. I use lots of functions because I find reading something like draw_segment() easier than reading pygame.draw.rect(self.displaysurf, GREEN, segment.rect), even if the function is one line. Also, I find that smaller functions are easy to write tests for which I often do. And if I need to make a change in the future, I would know exactly where a change needs to go. Do you think that approach is overkill for this program or in general? 

2. Do you think putting all of my methods in the Game class for a game of this size is bad?

Again thanks for taking your time to look at my game! It has been super helpful.

#Removed refresh(), terminate(), extend_snake(), draw_extender(), draw_snake()
#Capitalized constants
#Added spaces
#Modified some methods
#Now uses lists

import sys
import random
import itertools
import pygame
import inputmanager

FPS = 10
WINDOW_SIZE = (640, 480)
CELL_SIZE = (32, 32)
START_LOCATION = (320, 224)
UP = "up"
DOWN = "down"
LEFT = "left"
RIGHT = "right"
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

class Segment:
	
	def __init__(self, rect, direction=None):
		self.rect = rect
		self.direction = direction

class Game:

	def __init__(self):
		self.direction = None
		self.extender = None
		self.cell_locations = self.cell_locations = set(
			itertools.product(
				range(0, WINDOW_SIZE[0], CELL_SIZE[0]), 
				range(0, WINDOW_SIZE[1], CELL_SIZE[1])
			)
		)
		self.snake = [Segment(pygame.Rect(START_LOCATION, CELL_SIZE))]
		
		pygame.init()
		self.fps_clock = pygame.time.Clock()
		self.displaysurf = pygame.display.set_mode(WINDOW_SIZE)
		pygame.display.set_caption("Snake")
		
	def main(self):
		while True:
			self.get_input()
			self.update()
			self.render()
			self.fps_clock.tick(FPS)
			
	def get_input(self):
		inputmanager.InputManager.get_events()
		inputmanager.InputManager.check_for_quit_event()
		inputmanager.InputManager.update_keyboard_key_state()
		inputmanager.InputManager.get_keyboard_input()
		
	def update(self):
		self.handle_input()
		self.update_snake_direction(self.direction)
		self.move_snake()

		if self.extender is None:
			snake_segments_locations = set(segment.rect.topleft for segment in self.snake)
			location = random.choice(list(self.cell_locations-snake_segments_locations)) ###Need to use set to subtract, and need to convert to list to use random.choice
			self.extender = pygame.Rect(location, CELL_SIZE)
			
		if self.head_segment_collided_with_extender():
			self.extender = None
			self.add_segment_to_snake()
			
		if self.game_over():
			self.snake = [Segment(pygame.Rect(START_LOCATION, CELL_SIZE))]
			self.direction = None
			self.extender = None
			
	def handle_input(self):
		if inputmanager.InputManager.quit:	
			pygame.quit()
			sys.exit()
			
		if (inputmanager.InputManager.keyboard[pygame.K_UP] == inputmanager.InputManager.pressed
			and self.direction != DOWN):
				self.direction = UP
		elif (inputmanager.InputManager.keyboard[pygame.K_DOWN] == inputmanager.InputManager.pressed
			and self.direction != UP):
				self.direction = DOWN
		elif (inputmanager.InputManager.keyboard[pygame.K_LEFT] == inputmanager.InputManager.pressed
			and self.direction != RIGHT):
				self.direction = LEFT
		elif (inputmanager.InputManager.keyboard[pygame.K_RIGHT] == inputmanager.InputManager.pressed
			and self.direction != LEFT):
				self.direction = RIGHT
				
	def update_snake_direction(self, head_direction):
		for index in reversed(range(len(self.snake))):
			self.snake[index].direction = self.snake[index-1].direction
		self.snake[0].direction = head_direction
			
	def move_snake(self):
		for segment in self.snake:
			self.move_segment(segment)
			
	def move_segment(self, segment):
		move_amount = {
			UP: (0, -CELL_SIZE[1]),
			DOWN: (0, CELL_SIZE[1]),
			LEFT: (-CELL_SIZE[0], 0),
			RIGHT: (CELL_SIZE[0], 0)
		}.get(segment.direction, (0, 0))
		segment.rect.move_ip(move_amount)
		
	def head_segment_collided_with_extender(self):
		return self.snake[0].rect.colliderect(self.extender)
	
	def add_segment_to_snake(self):
		topleft = {
			UP: (self.snake[-1].rect.x, self.snake[-1].rect.y+CELL_SIZE[1]),
			DOWN: (self.snake[-1].rect.x, self.snake[-1].rect.y-CELL_SIZE[1]),
			LEFT: (self.snake[-1].rect.x+CELL_SIZE[0], self.snake[-1].rect.y),
			RIGHT: (self.snake[-1].rect.x-CELL_SIZE[0], self.snake[-1].rect.y)
		}.get(self.snake[-1].direction, (0, 0))
		self.snake.append(Segment(pygame.Rect(topleft, CELL_SIZE), self.snake[-1].direction))
			
	def game_over(self):
		return (self.head_segment_collided_with_self() 
				or self.head_segment_out_of_bounds())
		
	def head_segment_collided_with_self(self):
		return any([self.snake[0].rect.colliderect(segment) for segment in self.snake[1:]])
			
	def head_segment_out_of_bounds(self):
		return (self.snake[0].rect.x < 0 
				or self.snake[0].rect.y < 0 
				or self.snake[0].rect.x >= WINDOW_SIZE[0] 
				or self.snake[0].rect.y >= WINDOW_SIZE[1])
		
	def render(self):
		self.displaysurf.fill(BLACK)
		for segment in self.snake:
			pygame.draw.rect(self.displaysurf, GREEN, segment.rect)
		if self.extender is not None:
			pygame.draw.rect(self.displaysurf, RED, self.extender)
		pygame.display.update()
	
if __name__ == "__main__":
	game = Game()
	game.main()

@Alberth

game.py

Edited by mrpeed

Share this post


Link to post
Share on other sites
12 hours ago, mrpeed said:

. I use lots of functions because I find reading something like draw_segment() easier than reading pygame.draw.rect(self.displaysurf, GREEN, segment.rect), even if the function is one line.

Like I said, you're using functions as description of a single block of code, ie you could also do

# Draw segment
pygame.draw.rect(self.displaysurf, GREEN, segment.rect)

 

12 hours ago, mrpeed said:

Also, I find that smaller functions are easy to write tests for which I often do

If you need tests to write good code, how do you write good tests then?

 

12 hours ago, mrpeed said:

Do you think that approach is overkill for this program or in general?

I think in 5-10 years from now, when you read an old program of yourself, you'll find that having to jump a lot between functions before even understanding what is being done and before you understand how variables change, is very counter-productive.

Let's just wait 10 years to find out if I am right.

 

12 hours ago, mrpeed said:

2. Do you think putting all of my methods in the Game class for a game of this size is bad?

I think some of them belong to the input manager at least, but you didn't do that yet.

Other than that, there is no  "bad". If the code works, it's good, there are just many sutbly different variations of "good". The optimum is different for everybody, as well as for the goal and context of the code.

If this works for you, it's ok. However, by all means don't be afraid to experiment. Try a different approach some times. You might stumble on great discoveries and improve your coding.

 

The biggest omission to your code is documentation of the variables first, and documentation of functions second. It may feel like a waste of time (and at the time of writing it, it is), but you're saving yourself hours and hours of reverse-engineering existing code not to mention days of debugging, because you missed that a variable had a special value for some case. I started documenting lots of years ago, and found I got more productive, despite spending time writing additional text.

Share this post


Link to post
Share on other sites
On 10/22/2017 at 11:23 AM, Alberth said:

I agree, dicts look like cheap classes, but accessing fields looks messy. Also, there is no useful place to document what variables exist, and what they do.

Objects in Python are actually implemented like "dictionaries" :D

Edited by matt77hias

Share this post


Link to post
Share on other sites

I am aware of that, and of course that makes fully sense.

What I intended to say was that

something.field

looks a lot better and reads nicer than the forced stringy keys, as in

somedict["field"]

 

Constant keys don't quite fit with dictionaries :)

Share this post


Link to post
Share on other sites

  • Advertisement
  • Advertisement
  • Popular Tags

  • Similar Content

    • By Alberto Muratore
      Hello, my name is Alberto Muratore and I'm a young game developer specialized in programming.

      Last summer I finished working on Abstract Arena, my first completed project published on Steam. During the development, lasted more than 1 year, I created every aspect of the game except for the audio sector. I previoulsy joined small competitions and had collaborations within the amateur scene, since I started having fun with game development when I was a kid. In the recent months I also started writing videogames reviews for a english-italian website about indie games.

      Abstract Arena Steam page: http://store.steampowered.com/app/678230/Abstract_Arena/
      Abstract Arena website: http://www.abstractarena.com/

          

      I'm currently offering my skills as freelancer in the role of programmer: what I'm searching for is a project that only lacks of the coding part. I'm very good at using the GameMaker: Studio engine, and I already own a license to export on Android platform. I can write code for any genre of 2D games, and I already have experience with the peculiarities of the Android platform (multiple touch controls, accelerometer). I will be able to start working full time only during September, so be aware of that.

      The plan is the following:
      1 - you have an idea about your next project (2D game of any kind, for Windows and/or Android platform),
      2 - you (or your team) create all of the graphics and audio resources,
      3 - I write the whole code for the game (gameplay, menus and anything within the game) putting everything togheter [during September],
      4 - if you like my work you can choose how much to pay for it, and you (or your team) keep the rights to sell the game without sharing any percentage with me.

      Thank you for reading my announcement
      If you have any question, please write me at the following address: albertomurat@gmail.com
    • By N Drew
      I am working on a 2D SideScroller game in my own made game engine using SFML and C++.I am searching for 2D artists,especially pixel artist for making and animating characters,backgrounds and other props that can be made in any Drawing Program.The artist will become part of the team of Hammer Studios and he got a part of the Revenue Sharing.If you are interested send me a mail at:ghiurcutaandrei@gmail.com .If you are not an artist but you want to be a part of our Team,Soon we will be recruiting an C++ AI programmers that worked in SFML/OpenGL.
      We work together using Discord.

    • By JoAndRoPo
      While going through a Game Design Document Template, I came across this heading - Core Game Loop & Core Mechanics Loop. 
      What's the difference? Can you provide some examples of an existing game?  Suppose if I am including these topics in a Game Design Document, how should I explain it so that my team can understand? 
    • By Ds ds
      Hi, my name is Andres, I'm a programmer with a technician degree and a Diploma in C#, looking for a project in Unity to start my career in game development. I don't do it for a paid but a recognition and start a portfolio, preferably a 2D game. Thanks for read, have a nice day. 
       
    • By Doommy
      If someone could assist me through this I would be really grateful. I'm using SharpDX/C#/WinForms but I think this could more apply to directx in general.
      I'm very new to graphics programming and I'm really just trying to do something as simple as displaying a rectangle to the screen.
      Here is my issue:
      I have the below code:
      ----------------------------------------------------------
       var desc = new SwapChainDescription()
                  {
                      BufferCount         = 1,
                      ModeDescription     = new ModeDescription(1024, 768, new Rational(60, 1), Format.R8G8B8A8_UNorm),
                      IsWindowed          = false,
                      OutputHandle        = form.Handle,
                      SampleDescription   = new SampleDescription(1, 0),
                      SwapEffect          = SwapEffect.Discard,
                      Usage               = Usage.RenderTargetOutput
                  };
      I'm not sure if the window is loading in full screen. Actually to make it go full screen I actually have to set the forms property to: this.WindowState        = FormWindowState.Maximized; but that only seems lke Im using a C# code to maximize the form. For instance if I don't set the form to maximize, the form loads at the original size if IsWindowed is set to false. I recall with directx programing using dx7, when I set full screen you could actually see what looked like a display resolution change. I'm not seeing that. It pretty much looks like the form is loaded at the same size as the screen and not the value I provide in modeDescription. This is not what I want as well because I want to set the display to 1024x768 to avoid stretching of my graphics in wide screens.
      Can someone help me make sense of this please.
    • By HomeBrewArcana
      Hey All,
      I'm looking to get into the gaming industry. I've skirted around the idea for a long time, always thinking that I couldn't do it. I've finally decided to take the plunge.
       
      My question is whether it's worth going to school for game design/coding etc. I've been writing content for paper games for a while, and have a good idea of story and some basic design. But I have next to no technical know how. 
       
      My instinct is that such things can be learned with a lot of practice, video tutorials, and more practice. I've also heard that a degree is not really that important, since you get hired based on your portfolio/prototypes. Why not just make the games?
      But won't a degree help with contacts and mentoring--I'm not a great networker.
       
      Of course, it'll plunge me into more debt, but...
      If anyone has advice, let me know. Also any idea of a program to start with: Game Maker, Unity, Godot, Construct, Stencyl--I've heard good things about them all, so much so that I don't know which would be best to start with!
       
      Thanks
    • By bartekm777
      Hello
      About me
      Lvl 28   Programmer (day job: non-gamedev-programmer, making games as a hobby for about 2 years) Some vector art experience - tried to make some assets on my own using vector software and scripts   Some design experience (designing my own games ) About game
      Turn-based fantasy rpg inspired by games like Heroes 3 (also WoG mod), NEO Scavenger, Battle Brothers I would like to create easy to use editor for creating custom scenarios (similar to the one from Heroes 3) World and story are clean slate, I did some drafts but I'm not good at it so it's possibly subject to change I decided to create graphics using vector software + scripts to make it faster (rpg's tend to have lots of assets), also it's more precise and easier to create tileable graphics (for example: rivers, paths) No sound/music work has been done yet Who do I look for?
      Definitely someone with 2d art skills  I would like to focus more on programming 2D animator (skletal animations are preferred) Additional programmer could make development faster Someone for creating sounds/music/both It's a hobby project, I work on it in my free time. In case the project make it to the finish line and get shipped  - I can offer rev-share  
      Below should be few screens of what I already did (about 2 months of work) - some graphics, editor prototype screenshot and game prototype screenshot




    • By Liquifire
      I'm making an small 2D engine using Kha and I have a timer class, which basically simply either waits a certain amount of time to call a function, or repeatedly calls a certain function after every x seconds. I simply want to know if I should have timers run on different threads. I'm aware that makes sense, but I might use many timers in a game for example, would that still be okay? Also I'm currently writing an animation components, which waits every x seconds to draw another image using the timer class. And in a normal 2D games, I would have many objects with animations on them, other than the other timers. So I just wanted to ask people who have more experience and knowledge than I have what I should do for timers: Either leave them on the same main thread, or make them run on different threads. Thanks in advance.
    • By this. games
      [Free]
      Looking For: 2d Artist and Animator.
      Requirements: Experience in 2d art and ability to commit to 1-2 years of work.
      Payment: Rev-Share (Think Free as the product may not end up being sold)
      Description: An 3/4 view 2d game with a Sci-fi style. The game is based on a man who was put into stasis for punishment for his crimes. He has woken up 100 years later and must now regain his position at the top of the criminal underworld by killing his opponents. There will be a "Hub World" city and then PCG gang hideouts where you fight enemies. The assets required will be tile sets for the different hideouts, assets for the city ie buildings streets etc. and characters both the player and a number of enemies.
       
      Send resume and portfolio to thisdotgames@gmail.com if you are interested.
    • By Liquifire
      So I'm making this basic 2D engine using Haxe Kha, and i just need to integrate some physics. Just basic collision detection and gravity on basic shapes, nothing too detailed. But i want to try not to write it myself, so i just want suggestions on what open source phyisics engines i could use, and that wont be impossible to integrate. Thanks in advance.
  • Advertisement
  • Popular Now

  • Forum Statistics

    • Total Topics
      631353
    • Total Posts
      2999488
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!