• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.

evanofsky

Members
  • Content count

    487
  • Joined

  • Last visited

Community Reputation

2913 Excellent

About evanofsky

  • Rank
    GDNet+

Personal Information

  1. I think we're in agreement; we just have different definitions of what a singleton is. To me, a singleton is defined as a function that lazily initializes a single global instance of an object. Without that lazy check, it's just... a regular global. I am totally cool with grouping global variables into a structure or object to "logically split them out and divy up code and responsibilities". I do it all the time. I only take issue when you put a singleton in front of it.
  2. Alone on a Friday night, in need of some inspiration, you decide to relive some of your past programming conquests. The old archive hard drive slowly spins up, and the source code of the glory days scrolls by... Oh no. This is not at all what you expected. Were things really this bad? Why did no one tell you? Why were you like this? Is it even possible to have that many gotos in a single function? You quickly close the project. For a brief second, you consider deleting it and scrubbing the hard drive. What follows is a compilation of lessons, snippets, and words of warning salvaged from my own excursion into the past. Names have not been changed, to expose the guilty. 2004 I was thirteen. The project was called Red Moon -- a wildly ambitious third-person jet combat game. The few bits of code that were not copied verbatim out of Developing Games in Java were patently atrocious. Let's look at an example. I wanted to give the player multiple weapons to switch between. The plan was to rotate the weapon model down inside the player model, swap it out for the next weapon, then rotate it back. Here's the animation code. Don't think about it too hard.public void updateAnimation(long eTime) { if(group.getGroup("gun") == null) { group.addGroup((PolygonGroup)gun.clone()); } changeTime -= eTime; if(changing && changeTime = 0; i = Math.Min(this.bindings.Count - 1, i - 1)) this.bindings.OnChanged(this); } }} Every single field in the game, down to the last boolean, had an unwieldy dynamically allocated array attached to it. Take a look at the loop that notifies the bindings of a property change to get an idea of the issues I ran into with this paradigm. It has to iterate through the binding list backward, since a binding could actually add or delete UI elements, causing the binding list to change. Still, I loved data binding so much that I built the entire game on top of it. I broke down objects into components and bound their properties together. Things quickly got out of hand.jump.Add(new Binding(jump.Crouched, player.Character.Crouched));jump.Add(new TwoWayBinding(player.Character.IsSupported, jump.IsSupported));jump.Add(new TwoWayBinding(player.Character.HasTraction, jump.HasTraction));jump.Add(new TwoWayBinding(player.Character.LinearVelocity, jump.LinearVelocity));jump.Add(new TwoWayBinding(jump.SupportEntity, player.Character.SupportEntity));jump.Add(new TwoWayBinding(jump.SupportVelocity, player.Character.SupportVelocity));jump.Add(new Binding(jump.AbsoluteMovementDirection, player.Character.MovementDirection));jump.Add(new Binding(jump.WallRunState, wallRun.CurrentState));jump.Add(new Binding(jump.Rotation, rotation.Rotation));jump.Add(new Binding(jump.Position, transform.Position));jump.Add(new Binding(jump.FloorPosition, floor));jump.Add(new Binding(jump.MaxSpeed, player.Character.MaxSpeed));jump.Add(new Binding(jump.JumpSpeed, player.Character.JumpSpeed));jump.Add(new Binding(jump.Mass, player.Character.Mass));jump.Add(new Binding(jump.LastRollKickEnded, rollKickSlide.LastRollKickEnded));jump.Add(new Binding(jump.WallRunMap, wallRun.WallRunVoxel));jump.Add(new Binding(jump.WallDirection, wallRun.WallDirection));jump.Add(new CommandBinding(jump.WalkedOn, footsteps.WalkedOn));jump.Add(new CommandBinding(jump.DeactivateWallRun, (Action)wallRun.Deactivate));jump.FallDamage = fallDamage;jump.Predictor = predictor;jump.Bind(model);jump.Add(new TwoWayBinding(wallRun.LastWallRunMap, jump.LastWallRunMap));jump.Add(new TwoWayBinding(wallRun.LastWallDirection, jump.LastWallDirection));jump.Add(new TwoWayBinding(rollKickSlide.CanKick, jump.CanKick));jump.Add(new TwoWayBinding(player.Character.LastSupportedSpeed, jump.LastSupportedSpeed));wallRun.Add(new Binding(wallRun.IsSwimming, player.Character.IsSwimming));wallRun.Add(new TwoWayBinding(player.Character.LinearVelocity, wallRun.LinearVelocity));wallRun.Add(new TwoWayBinding(transform.Position, wallRun.Position));wallRun.Add(new TwoWayBinding(player.Character.IsSupported, wallRun.IsSupported));wallRun.Add(new CommandBinding(wallRun.LockRotation, (Action)rotation.Lock));wallRun.Add(new CommandBinding(wallRun.UpdateLockedRotation, rotation.UpdateLockedRotation));vault.Add(new CommandBinding(wallRun.Vault, delegate() { vault.Go(true); }));wallRun.Predictor = predictor;wallRun.Add(new Binding(wallRun.Height, player.Character.Height));wallRun.Add(new Binding(wallRun.JumpSpeed, player.Character.JumpSpeed));wallRun.Add(new Binding(wallRun.MaxSpeed, player.Character.MaxSpeed));wallRun.Add(new TwoWayBinding(rotation.Rotation, wallRun.Rotation));wallRun.Add(new TwoWayBinding(player.Character.AllowUncrouch, wallRun.AllowUncrouch));wallRun.Add(new TwoWayBinding(player.Character.HasTraction, wallRun.HasTraction));wallRun.Add(new Binding(wallRun.LastWallJump, jump.LastWallJump));wallRun.Add(new Binding(player.Character.LastSupportedSpeed, wallRun.LastSupportedSpeed));player.Add(new Binding(player.Character.WallRunState, wallRun.CurrentState));input.Bind(rollKickSlide.RollKickButton, settings.RollKick);rollKickSlide.Add(new Binding(rollKickSlide.EnableCrouch, player.EnableCrouch));rollKickSlide.Add(new Binding(rollKickSlide.Rotation, rotation.Rotation));rollKickSlide.Add(new Binding(rollKickSlide.IsSwimming, player.Character.IsSwimming));rollKickSlide.Add(new Binding(rollKickSlide.IsSupported, player.Character.IsSupported));rollKickSlide.Add(new Binding(rollKickSlide.FloorPosition, floor));rollKickSlide.Add(new Binding(rollKickSlide.Height, player.Character.Height));rollKickSlide.Add(new Binding(rollKickSlide.MaxSpeed, player.Character.MaxSpeed));rollKickSlide.Add(new Binding(rollKickSlide.JumpSpeed, player.Character.JumpSpeed));rollKickSlide.Add(new Binding(rollKickSlide.SupportVelocity, player.Character.SupportVelocity));rollKickSlide.Add(new TwoWayBinding(wallRun.EnableEnhancedWallRun, rollKickSlide.EnableEnhancedRollSlide));rollKickSlide.Add(new TwoWayBinding(player.Character.AllowUncrouch, rollKickSlide.AllowUncrouch));rollKickSlide.Add(new TwoWayBinding(player.Character.Crouched, rollKickSlide.Crouched));rollKickSlide.Add(new TwoWayBinding(player.Character.EnableWalking, rollKickSlide.EnableWalking));rollKickSlide.Add(new TwoWayBinding(player.Character.LinearVelocity, rollKickSlide.LinearVelocity));rollKickSlide.Add(new TwoWayBinding(transform.Position, rollKickSlide.Position));rollKickSlide.Predictor = predictor;rollKickSlide.Bind(model);rollKickSlide.VoxelTools = voxelTools;rollKickSlide.Add(new CommandBinding(rollKickSlide.DeactivateWallRun, (Action)wallRun.Deactivate));rollKickSlide.Add(new CommandBinding(rollKickSlide.Footstep, footsteps.Footstep)); I ran into tons of problems. I created binding cycles that caused infinite loops. I found out that initialization order is often important, and initialization is a nightmare with data binding, with some properties getting initialized multiple times as bindings are added. When it came time to add animation, I found that data binding made it difficult and non-intuitive to animate between two states. And this isn't just me. Watch this Netflix talk which gushes about how great React is before explaining how they have to turn it off any time they run an animation. I too realized the power of turning a binding on or off, so I added a new field:class Binding{ public bool Enabled;} Unfortunately, this defeated the purpose of data binding. I wanted to get rid of UI state, and this code actually added some. How can I eliminate this state? I know! Data binding!class Binding{ public Property Enabled = new Property { Value = true };} Yes, I really did try this briefly. It was bindings all the way down. I soon realized how crazy it was. How can we improve on data binding? Try making your UI actually functional and stateless. dear imgui is a great example of this. Separate behavior and state as much as possible. Avoid techniques that make it easy to create state. It should be a pain for you to create state. Conclusion There are many, many more embarrassing mistakes to discuss. I discovered another "creative" method to avoid globals. For some time I was obsessed with closures. I designed an "entity" "component" "system" that was anything but. I tried to multithread a voxel engine by sprinkling locks everywhere. Here's the takeaway: Make decisions upfront instead of lazily leaving them to the computer. Separate behavior and state. Write pure functions. Write the client code first. Write boring code. That's my story. What horrors from your past are you willing to share? If you enjoyed this article, try these: The Poor Man's Voxel Engine The Poor Man's Character Controller One Weird Trick to Write Better Code
  3. Best explanation I've seen of quaternions, complete with Blender practical application tips https://t.co/MVxF1Pb5cE
  4. RT @MarkPuente: The best letter to the editor in today's @TB_Times. https://t.co/E60kou9BXw
  5. woah guys i actually kinda did an animation https://t.co/6Q7hoBIm1R
  6. RT @Oniropolis: Never tire of this. The video game as poetry. https://t.co/VsAX4olICo https://t.co/nFycOUPXp3
  7. YES! More awesome devs coming to the Midwest! We live cheap! And we don't even mind if you pick that state up north https://t.co/R1Dhjdq22V
  8. What percentage of a relationship would you say happens over text these days?
  9. Anyone planning on attending Handmade Con 2016? https://t.co/x3Rs6ruCdw
  10. Allow me to regale you with an exciting tale: the birth of a janky dialogue and voice system. I have a JSON file with all the localized strings in my game, like this:{ "danger": "Danger", "level": "Level %d", ...} A preprocessor takes this and generates a header file with integer constants for each string, like this:namespace strings{ const int danger = 0; const int level = 1; // ...} At runtime, it loads the JSON file and hooks up the integer IDs to localized strings. A function called "_" takes an integer ID and returns the corresponding localized string. I use it like this:draw_string(_(strings::danger), position); This all worked (and still works) pretty well for UI strings. Not so much for dialogue. To write dialogue, I had to come up with a unique ID for each line, then add it to the strings file, like this:{ "hello_penelope": "Hello! I am Penelope.", "nice_meet_you": "Nice to meet you.", ...} Yes, the preprocessor generated a new integer ID in the header file every time I added a line of dialogue. Gross. I construct dialogue trees in Dialogger. With this setup, I had to use IDs like "hello_penelope" rather than actual English strings. Also gross. A better way I keep the string system, but extend it to support "dynamic" strings loaded at runtime that do not have integer IDs in the header file. Now I can write plain English in the dialogue trees. The preprocessor goes through all of them and extracts the strings into a separate JSON file, using the SHA-1 hash of each string for its ID. Once everything is loaded, I discard all string IDs in favor of integer IDs. I couldn't find a simple straightforward SHA-1 implementation that worked on plain C strings, so here's one for you. The point of all this is: I now have a single JSON file containing all the dialogue in the game. Ripe for automation... Speak and spell Penelope is an AI character. I'm using text-to-speech for her voice, at least for now. I don't want to integrate a text-to-speech engine in the game; that's way too much work. And I don't want to manually export WAVs from a text-to-speech program. Also too much work. I create a free IBM Bluemix account. They have a dead simple text-to-speech API: make an HTTP request with basic HTTP authentication, get a WAV file back. I write an 82-line Python script that goes through all the dialogue strings and makes an HTTP request for each one. It keeps track of which strings have previously been voiced, to facilitate incremental updates. Now I have a folder of WAV files, each one named after a SHA-1 hash. I'm using Wwise for audio, so the next step requires a bit of manual involvement. I drag all the WAVs into the project and batch create events for them. Now when I display a dialogue string, I just have to look up the SHA-1 hash and play the audio event. Easy. Disaster strikes I don't hear anything. All signs indicate the audio is playing correctly, but nothing comes out of my speakers. I look at one of the audio files in Wwise. Looks like the file is corrupted. I play the WAV in a number of different programs. Some play it fine, others don't play it at all. I edit my text-to-speech script to use Python's wave library to load the WAV file after downloading it from IBM. Sure enough, the library doesn't know what to make of it. Too lazy to care, I edit the wave library in-place in my Python distribution. YOLO. After a bit of printf debugging, I pinpoint the issue. The WAV format is based on RIFF, a binary format which breaks the file into "chunks". According to Wikipedia, the format of each chunk is as follows: 4 bytes: an ASCII identifier for this chunk (examples are "fmt " and "data"; note the space in "fmt "). 4 bytes: an unsigned, little-endian 32-bit integer with the length of this chunk (except this field itself and the chunk identifier). variable-sized field: the chunk data itself, of the size given in the previous field. a pad byte, if the chunk's length is not even. Turns out, IBM's text-to-speech API generates streaming WAV files, which means it sets the "length" field to 0. Some WAV players can handle it, while others choke. Wwise falls in the latter category. Fortunately, I can easily figure out the chunk length based on the file size, modify it using the wave library, and write it back out to the WAV file. Like so. Problem solved. Wwise is happy. Next I set up some Wwise callbacks to detect the current volume of Penelope's voice, and when she's done speaking. Here's the result, along with some rope physics in the background being destroyed by the wonky framerate caused by my GIF recorder: If you want to hear it, check out the IBM text-to-speech demo here. Thanks for reading! Mirrored on my blog
  11. RT @ADAMATOMIC: @fasterthanlime "dada are you just randomly trying different sin() / cos() -1/1 variations until you get what you want" "..…
  12. It's not just cygwin, it's actual Linux binaries running on Windows. It intercepts and translates Linux syscalls. https://t.co/YlloZZTGwl
  13. nav mesh generation now under 30 seconds https://t.co/5SW9aPp5om