Jump to content
  • Advertisement

Leaderboard


Popular Content

Showing content with the highest reputation since 11/07/18 in all areas

  1. 1 point
    The past few days I've been working on figuring out how the "peer-to-peer" bits of my game network are going to work. For now I'm testing with MQTT, because it's light and fast and reliable, but you could pretty much replace MQTT with ZeroMQ or just some raw TCP/UDP sockets code or anything else that fits the bill. In order to build the most robust gaming environment I can I want to provide the capabilities to the player/community to help build out the games network infrastructure. Allowing a player to opt-into hosting a relay node, or simply enabling the built in relay features in their clients. In that spirit, I'm going for something of a distributed mesh type topology with some rudimentary "routing" code built into the nodes themselves. For MQTT, I'm planning to use specific subscription topics that are universal throughout the system like node names or etc, so that the "Relays" or "Routers" can determine which channel(s) to put the data on based on latency/etc. Obviously, if I'm going to use MQTT, there aren't really any out of the box solutions for what I'm trying to achieve. Additionally, I'll be wanting to authenticate the MQTT network based on data in my game server cluster(probably via Redis), so it'll be a custom code solution using somebody else's brilliant netcode. Below you will see a screenshot of my first performance tests of my base functional MQTTnet server & a simple mqtt client hammering away at it. The server is .net core running on a linux VM on my local machine. The client is using MQTTnet as well, running on the windows client. The client here is sending batches of 10000 messages to the server, it's also subscribed to the same topic, so it waits until it gets all the sent messages back and then outputs the time it took in ms. The messages are set to a topic of "LoadTestinAllDayLong" and the message is just a random GUID. So, it looks like right now it's pushing about 2000 messages per second, running over TCP, with reliability(on a 4core 4gb virt essentially over a loopback).. Not too bad at all, can't wait to test it over a real network though. Here's a quick sketch of the idea as it stands, broad strokes again.. Keep in mind now, this won't be the primary game network, it will be used for localized simulations, direct peer-to-peer activities, or alternative channels for transient data to be moved around the network. Server Source: using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using MQTTnet; using MQTTnet.Server; using MQTTnet.Protocol; using MQTTnet.Diagnostics; namespace UWMqtt { class Program { public static async Task Main(string[] args) { MqttServerOptions options = new MqttServerOptions(); options.DefaultEndpointOptions.ConnectionBacklog = 10000; options.DefaultEndpointOptions.Port = 1883; options.DefaultCommunicationTimeout = new TimeSpan(0,0,5); options.ConnectionValidator = c => { if (c.ClientId.Length < 10) { c.ReturnCode = MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; return; } if (c.Username != "yummyusername") { c.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; return; } if (c.Password != "chewypasswordstring") { c.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; return; } c.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; }; MqttNetGlobalLogger.LogMessagePublished += (s, e) => { if (e.TraceMessage.Level == MqttNetLogLevel.Info || e.TraceMessage.Level == MqttNetLogLevel.Error || e.TraceMessage.Level == MqttNetLogLevel.Warning) { String trace = $">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"; if (e.TraceMessage.Exception != null) { trace += Environment.NewLine + e.TraceMessage.Exception.ToString(); } Console.WriteLine(trace); } }; IMqttServer mqttServer = new MqttFactory().CreateMqttServer(); await mqttServer.StartAsync(options); Console.WriteLine("Press any key to exit."); Console.ReadLine(); await mqttServer.StopAsync(); } } } Client Source: using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using MQTTnet; using MQTTnet.Client; namespace mqttClientHammer { class Program { public static bool running = true; static long mrec = 0; static long msen = 0; public static void Main(string[] args) { MqttFactory factory = new MqttFactory(); IMqttClient mqttClient = factory.CreateMqttClient(); IMqttClientOptions options = new MqttClientOptionsBuilder() .WithClientId(Guid.NewGuid().ToString()) .WithTcpServer("ser.ver.ip.add", 1883) .WithCredentials("yummyusername","chewypasswordstring") .Build(); mqttClient.Disconnected += async (s, e) => { Console.WriteLine("### DISCONNECTED FROM SERVER ###"); await Task.Delay(TimeSpan.FromSeconds(1)); try { await mqttClient.ConnectAsync(options); } catch { Console.WriteLine("### RECONNECTING FAILED ###"); } }; mqttClient.Connected += async (s, e) => { Console.WriteLine("### CONNECTED WITH SERVER ###"); // Subscribe to a topic await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("LoadTestinAllDayLong").Build()); Console.WriteLine("### SUBSCRIBED ###"); await Task.Factory.StartNew(() => { publisher(mqttClient); }); }; mqttClient.ApplicationMessageReceived += (s, e) => { //Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); //Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); //Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); //Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); //Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); //Console.WriteLine(); mrec++; }; // StartAsync returns immediately, as it starts a new thread using Task.Run, // and so the calling thread needs to wait. mqttClient.ConnectAsync(options); Console.ReadKey(); } static void publisher(IMqttClient mc) { long lmr = 0; Stopwatch sw = new Stopwatch(); while (running) { if (mc.IsConnected) { sw.Start(); Parallel.For(0, 10000, i => //for (int i = 0; i < 1000; i++) { try { mc.PublishAsync("LoadTestinAllDayLong", Guid.NewGuid().ToString()); msen++; } catch (Exception) { } }); } else { Console.WriteLine("Load Test Waiting on Reconnect..."); } if (mrec != msen) { lmr = mrec; Thread.Sleep(100); while(mrec != lmr) { lmr = mrec; Thread.Sleep(100); } } Console.WriteLine("MRec: " + mrec.ToString() + " MSen: " + msen + " T: " + (sw.ElapsedMilliseconds).ToString()); sw.Stop(); sw.Reset(); Thread.Sleep(1); } } } }
  2. 1 point
    Yes, the device context state also encapsulates all device context state, not just which API target you're using. You swap to an empty one when you enter your code, set all of your state, then swap back to the app's before leaving.
  3. 1 point
    Probably not the best example, I've used a skewed orthogonal matrix to achieve a 3d effect for a Bounty Bob strikes back remake. In 3d space all the platforms are plain boxes.
  4. 1 point
    You have to be careful to split the responsabilities well. Here I'm thinking of the player as the object that will represent what a player can or can not do. Thinking on the messages/methods a player could receive I thinking of: Shoot() Jump() Crouch() TakeHit(double damage) RotateAim(double verticalAngle, double horizontalAngle) So, in this way you can ensure that the state of the player will always be valid through those methods. As an example, on CounterStrike the player can't shoot while jumping, right? So, the object that will guarantee this is the player, the implementation options are many, you can do something like: public boolean shoot() { if(this.state == State.JUMPING) { return false; } else { this.state = State.SHOOTING; // do some logic like decrease ammo or something return true; } } This way, you are protecting the player state, if the player press the shoot button while jumping, the player will return false, indicating the player could not do the action. Ok, so I'm saying this just to be clear about the responsibility of the player class, it basically encapsulates the player state management. Those concepts are related to Object Oriented Programming, but getting back to the original problem... As @frob said, ideally most of your code should be the same besides being played singleplayer or multiplayer. That's also related to Object Oriented Programming principles, you must try to do your architecture in a way that you reuse the code that is common. Ok, but how to do that? I'll make a kind of pseudo-code implementation trying to illustrate. First let's think a single player game, than we expand the idea. So, you will need an object that will know all the players and will handle the interactions with each other, let's call this class GameWorld. Also let's imagine a class responsible to update the screen, the PlayerRenderer class. And finally we will have a class that knows how to listen to the keyboard (let's imagine that shooting and aiming is also done through Keyboard and that we have multiple players on the same keyboard), we can name this class as PlayerInput. Ok, the responsibilities are there, but how tie them together? First of all, the PlayerInput must know the Player in order to command him to shoot, jump or crouch when the proper key are pressed. So to do that we can just pass the player instance and the keys on the keyboard on PlayerInput constructor and trust that the PlayerInput instance will listen to the keyboard and call the proper player proper methods. Something like that: public class PlayerInput { private Player player; private String shootKey; public PlayerInput(Player player, String shootKey) { this.player = player; this.shootKey = shootKey; } // This method may be called on gameloop update or something public void checkKeys() { if (Keyboard.isPressed(shootKey)) { this.player.shoot(); } } } // Usage PlayerInput p1Input = new PlayerInput(player1, "CTRL"); PlayerInput p2Input = new PlayerInput(player2, "ALT"); while(true) { p1Input.checkKeys(); p2Input.checkKeys(); sleep(1/60); } Ok, so our player is shooting now, but no one is being hit, neither the shooting animation are being played. The only one who know about all players is the GameWorld, so it would be a good idea to warn him too so he can check if any player was hit, we can do the same approach of making the player receive GameWorld on constructor and making him call a method on GameWorld like (playerShooting(player, direction)), but think that the PlayerRenderer will also need that information to play the animation, this means that the player would have to know PlayerRenderer and GameWorld. As multiple objects are interested on player events we can create a interface named PlayerObserver which will have the method shot(Player player, float direction). So now, GameWorld and PlayerRender will implement this interface and Player will broadcast the shoot to all interested objects. The implementation would be something like this: public interface PlayerObserver { public void shoot(Player player, double direction); } public class Player { private double aimDirection; private List<PlayerObserver> observers; public Player(/* receive attributes */) { // assign those attributes (health, player name, etc) } public void notify(PlayerObserver observer) { this.observers.add(observer); } public void shoot() { if (this.state == State.JUMPING) { // do nothing } else { for(int i = 0; i < observers.getLength(); i++) { observers.get(i).shoot(this, aimDirection); } } } } public class GameWorld implements PlayerObserver { public void shoot(Player player, double direction) { Player damagedPlayer = this.findPlayerOnDirection(direction); if(damagedPlayer != null) { damagedPlayer.beingHit(); } } } public class PlayerRenderer implements PlayerObserver { public void shoot(Player player, double direction) { this.renderShootingAnimation(direction); } } // Usage Player player1 = new Player(); Player player2 = new Player(); PlayerInput p1Input = new PlayerInput(player1); PlayerInput p2Input = new PlayerInput(player2); GameWorld world = new GameWorld(player1, player2); PlayerRenderer p1Renderer = new PlayerRenderer(); PlayerRenderer p2Renderer = new PlayerRenderer(); player1.notify(world); player2.notify(world); player1.notify(p1Renderer); player2.notify(p2Renderer); while(true) { p1Input.checkKeys(); p2Input.checkKeys(); gameWorld.update(); sleep(1/60); } This is some kind of java pseudo-code that I wrote from my head just to illustrate it probably will not compile in java (It has been a while since I stop working with java). But the main idea is that player is not tied with PlayerRenderer nor GameWorld. What we gain from that? Now we can have other kinds of listeners, like GameServer, who will listen a Player shoot and will send to the server, now talking about a multiplayer game. And about the input on a multiplayer game? You will not need the player2 PlayerInput anymore, instead you will have a NetworkPlayerInput, which will listen to the server (instead of listening to the Keyboard) and will call the shoot(), jump() and the other methods. So as you can seen, just thinking a little bit on the architecture we can switch to a multiplayer game reusing almost all the code we have done except for the network specifics. But this example are not considering network delay, this would work if the two GameWorlds on the two computers are always synced. That not the case at all, so instead of the server just forward the player inputs, it will have a GameWorld instance that will be used as source of truth, the GameWorld on the player computer will have to obey what the server GameWorld is saying as the truth. This will add a new way that the GameWorld on the client will be updated because the server state will override the client state. I would adapt the client game world to receive state sync events which would basically override the current GameWorld state to the new one (sent by the game server). This way the NetworkPlayerInput will not be needed anymore. The book Development and Deployment of Multiplayer Online Games explain in details the problems and most common techniques to deal with multiplayer online games. The tutorial I know that explain in a simpler way is this one: http://buildnewgames.com/real-time-multiplayer/ This is a complicated topic, I suggest you start by a simpler game like a pacman online on a local network so you can learn how to propagate the state from server to the clients. After that you can try to put on a real server it will feel really laggy, then you use the book techniques to improve it. After understanding the techniques it will be much easier to create your FPS. PS.: I'm not an expert on the subject, I just work with web applications for a long time and I'm studying game servers specific techniques right now. hehe Hope it helped
  5. 1 point
    You can also have multiple eclipse java projects and have some projects reliant on other ones, which can be a good way of structuring things as well.
  6. 1 point
    Assuming you have your Java source files in nested folders that correspond to the respective packages (as declared in the package statement in each file) what you can organize is the location of these package folders: Eclipse looks for them in the Eclipse project source directories, which are set in the project properties. You can add multiple locations (for example, one for the main code and one for tests) and exclusions.
  7. 1 point
    This is a concept for the layout of the equipment room. In the centre will be a reclining, operating chair with a spotlight about it. This is where the player will sit to get their spacesuit repaired. Lasers will shoot out of the light attachment from above and repair the suit with its futuristic technology. There are shelves at the sides of the room holding general space equipment: helmets, space suits, boots, and oxygen tanks. This room will have the same background music as the game, and the laser beams may have a sound effect. - Kristen
  8. 1 point
    The problem of difficulty in games has been debated to great depths for a long time. Various alternatives to the traditional approach with different difficulty modes at the beginning of a particular game have been proposed, analyzed and implemented. And yet, as much as they patch up the errors of the traditional approach, within them arise numerous inherent problems and difficulties. As such, I would like to propose another alternative–not so much a mechanical solution that requires implementation, but rather a different approach to difficulty design. One thing I’d like to stress is that, this has been applied in various games quite successfully before, and I’ll mention them later on, but not to the extent to which it can deservedly become a central design philosophy, in my opinion. This I presume is due to a lack of a rather clear and deliberate approach to difficulty design. But first, let me attempt to briefly summarize a few popular criticisms of the traditional difficulty modes approach and its alternative. Problems with Difficulty Modes Picture yourself coming into a brand new game, only to be asked to choose a difficulty mode that’s suitable for yourself, and presented with a number of different menu options. And frankly, they don’t do that good of a job at giving you sufficient information to make such an important decision. This is how many games in our history have done difficulty, and it continues to be fairly prevalent among modern games. Here are its common criticisms: Asking the player to make such a decision right at the beginning is not exactly a good idea. To select a difficulty mode before the game even starts is to make a major commitment based on very little information available (e.g. a short description). Once the player has selected a difficulty, they are probably going to live with it for the entire playthrough. Even if the game allows the player to change the difficulty mode later on, it is, in itself, still not a very good idea. For one, explicitly selecting a difficulty mode in a menu-based manner is certainly not an interesting choice that games strive to offer their players. They do not have to weigh anything against anything. They do not have to analyze the risks and rewards coming as a result of each option. And generally speaking, players are not going to be good at weighting short-term convenience against long-term enjoyment. They just do not know the game enough. Such approach would defeat the entire point of progression through unlocking higher and better tools to enhance and assist with gameplay. It would go against the intended gameplay experience from the game designer. And most importantly, it would make the player feel judged for not choosing a higher difficulty. There have been several solutions to negate these issues, of which Mark Brown has gone into depths in one of his videos. However, not one of them was able to solve them all and still maintain immersion. Dynamic Difficulty Adjustment The idea of Dynamic Difficulty Adjustment (or DDA) hinges on the theory of the player’s Flow State, in which the player is completely immersed, and the game’s difficulty feels just right. Any more difficulty will cause frustration and break immersion. Any less difficulty and the player will quickly find boredom, and you guessed it, lose immersion. Therefore, as designer Andrew Glassner put it in his book Interactive Storytelling, games “should not ask players to select a difficulty level. Games should adapt themselves during gameplay to offer the player a consistent degree of challenge based on his changing abilities at different tasks.” Or in other words, games should be implemented with a performance evaluation system as well as a dynamic difficulty adjustment system in order to adjust itself to accommodate the infinitely different and ever-changing characteristics of players. More on the technical details of DDA can be found in Robin Hunicke’s 2005 paper The Case for Dynamic Difficulty Adjustment in Games. However, while the Flow State theory admittedly has its merits, the DDA approach doesn’t go without its numerous downsides: Some players, when they find out about DDA, hate it. Especially when DDA cannot be turned off, the player ends up feeling patronized, and not respected by the game as an adult, capable of taking on challenges and improving him/herself. Players can, and will, learn to exploit DDA by pretending to be worse at playing than they actually are. And oftentimes, a DDA system will require some sort of break time in order to avoid revealing itself to the player, thus not able to quickly adapt itself to the player’s ostensible skill level. DDA inhibits the player’s ability to learn and improve. As soon as the player improves, the difficulty ramps up to match their skill level, thus eliminating the possibility of positive results. If the player cannot see some sort of feedback from the game regarding their performance, they cannot know whether any changes in their approach to gameplay were effective. DDA may create absurdities. One of the popular example of DDA going awry is the rubber-band effect in racing games, where opponents speed up and slow down seemingly for no reason in order to adapt to the player’s performance. DDA is incompatible with some forms of challenge. If the challenge in question is numerically-based, then DDA can work easily. However, when the challenge is symbolical, with pre-designed elements that are nakedly visible to the player, often having only one or a few intended solutions, then DDA cannot work. There are many interesting and nuanced approaches to DDA that I won’t mention since that’s beyond the scope of this segment. While I imagine there are going to be a lot of way to make DDA functional and sufficiently inscrutable through clever algorithms and implementation, I am rather discussing the fundamentals. Organic Difficulty in Games There seems to be a number of different terms to address this approach, but just for this article I’m going to use the term “Organic Difficulty.” This is something that has been tossed around in the last decade or so. The basic idea of Organic Difficulty is that the game does not ask the players to select or adjust their preferred difficulty via GUI-based commands, nor does it automatically adapt itself to match with the player’s performance and progress. But rather, the game allows the player to interact with it in certain ways to make it easier, or harder, for themselves. These take the form of tools, approaches, strategies, input sequences or methods, etc. which should often come with some sort of trade-off. This is something that has been implemented in a number of games including From Software’s Dark Souls, which Extra Credits has dedicated an entire episode to, and which everyone should take a look. In Metal Gear Solid V, for every mission the player has completed, there’s a score rating system which provides a rough overview of the player’s performance based on a number of factors such as stealth, lethality, accuracy, completion speed, whether the player has completed any mission tasks, and what tools they used. While the player does get minus points for mistakes such as getting detected, raising enemy alert, taking hits, etc. some other factors are not as clear-cut as to how they constitute minus points aside from narrative reasons. The player can always go on a lethal rampage, tossing grenades at everybody in sight, or calling a support helicopter to airstrike the entire enemy base. The player is provided the tools to do exactly all of those, and they’re always just a few buttons away, and the worst they get is a C rank, provided they completed the mission, and a slight dip in their earnings. Another example of this can be found XCOM: Enemy Within. There's a "cheesy" tactic in the game that can almost ensure victory, which is to have a unit with the Mimetic Skin ability to safely spot the enemies, thus enabling a squadsight-sniper from across the entire map to pick them off one-by-one safely without any real repercussion. This strategy is extremely effective in virtually every mechanical aspect of combat, with the only risk being that the spotter must not be flanked for they would instantly lose invisibility. The actual problem with this strategy is that it’s incredibly boring: your snipers just simply shoot every turn, and you can only take a few shots every turn, not to mention reloading. This strategy is best suited for beginners and people who have made mistakes and want to get out of the downward spiral. While on the other end of the spectrum, there are players who understand how the game and the AI of every alien unit in the game work, so they are more confident about moving up close and personal with enemies with minimal armor. Because for them, it's not about defending against the enemies, but about manipulating, "nudging" the enemies into behaving the way these players want them to (e.g. nobody needs armor when enemies are only going to attack the tank; nobody needs to take good cover when enemies are too scared to move to flank in front of an Opportunist-overwatch unit; etc.) The above examples seem to imply a few important points regarding difficulty: Difficulty should not only be designed around the mechanics of a game. It should also take into account the aesthetics or elegance of those very mechanics. Punishment does not always have to be tangible or significant, as long as it is enough to indicate to players that they are straying off the intended experience. A good analogy would be physical pain. The pain itself is not what’s causing harm to your body. The physical wound is. Pain is merely a bodily signal to let you know that what’s happening right now is pretty bad and you probably shouldn’t let what just happened happen again. But remember, the choice is ultimately yours! It may not be a good idea to put people on the linear graph of "gaming skill" where some people are simply "softcore, not-so-good at video games" and some other are "hardcore and always challenge-seeking." The idea alone is absurd, because players on such a graph would move up and down constantly, even during a single playthrough. Some people pick things up faster than a game can predict with its tutorials' pacing. Some people due to real life reasons have to abandon the game for some time, and they lose a bit of their touch when they come back to it. Instead of judging the player’s skill and trying to accommodate every possibility, games should be judging player interactions instead, using a spectrum between Effectiveness and Aesthetics of Play (or what I shall humbly name Ludoaesthetics). The Effectiveness-Ludoaesthetics Spectrum (ELS) On the Effectiveness-Ludoaesthetics Spectrum (ELS), difficulty exists only at the lowest technical level. Each end of the ELS represents what each player wants at a certain point in the game with certain conditions. On this spectrum, games are designed with the player’s interactions, approaches and strategies in mind, each with its own degree of effectiveness and ludoaesthetics. These are not solely defined by mechanics or the player’s skill level, but rather the way in which they are experienced and perceived by the player. Effectiveness refers to how well the player can progress and achieve their goals in a game using the set of tools they’re given and the strategies they’re allowed to formulate. How easy those tools are to use, and how good they are at helping the player progress towards the game’s intended goals, primarily constitute Effectiveness. Players who aim towards and stay on this end primarily look for the most effective ways to achieve the intended goals of the game (which of course include playing the game the easy way). Ludoaesthetics refers to the perceivable aesthetic appeals of the aforementioned set of tools and strategies given to the players. Players who aim towards this end do not necessarily look for the most effective ways to achieve the intended goals. But rather they tend to look for the added intrinsic benefits derived from unconventional play. These benefits include: Superficial Attractiveness: Visual and auditory appeal of using the subject matter or the subject matter itself. It can be represented by any entity the player can recognize in the game such as a character with great visual design, a badass-looking weapon with satisfying visual and sound effects, etc. Competitiveness: a.k.a. bragging rights. This is rather self-explanatory. There is always that portion of players who keep seeking greater and greater challenges to prove themselves to the world. They may even go as far as handicapping themselves with arbitrary limitations to heighten the challenge. Greater sense of satisfaction derived from greater challenges that may go beyond the goals intended by the game. People who have been through heights of overwhelming odds know about, and may expect, the immense amount of satisfaction that comes with them. Narrative Fantasy: Players may look for things that may not be effective or productive in terms of gameplay because they would align with the narrative better (in games that understandably contain some degree of ludonarrative dissonance), or they would add an extra layer of depth and intensity to the narrative and thereby enhancing it. Essentially, they’re sacrificing gameplay optimality to elevate their narrative fantasy. Design for Ludoaesthetics The point of designing for ludoaesthetics is NOT to create increasingly harder challenges in order to accommodate the player’s increasing skills (though that is not to say such approach has no merits whatsoever). But rather, it is actually to encourage players to strive for aesthetics in their gameplay and to lean more towards the right side of the spectrum. Here are a few suggestions on how to go about it. Creating more depth Depth refers to the amount of space the player is allowed to make interesting choices using the set of tools they’re given by a game. For a more detailed explanation of what Depth is in comparison to Complexity, you can take a look at Extra Credits’ episode on Depth vs. Complexity. Essentially, Complexity is the amount of constituent elements that make up a game, and Depth is the degree of interactivity between those elements. The very nature of ludoaesthetics has to do with the deviation from the default, intended approach (a.k.a. Playing “by-the-book.”) Therefore, the more those elements “talk” to one another, the better chance it is for ludoaesthetics to emerge, because then the player will be able to find more different ways to control or manipulate each element. [Also read: Design for Theorycrafting] Depth is pretty much the prerequisite for ludoaesthetics even as a concept to exist. Without a lot of depth, the window of opportunities for ludoaesthetics get significantly lower or completely non-existent. Creating patterns suggesting the possibility of gameplay aesthetics Adding more depth is not only about simply adding more stuff in a game and making them as obscure as they possibly can be. It is also about leaving breadcrumbs to suggest that there is more than meets the eye, therefore encouraging players to explore further possibilities. What kind of depth to even add? And how does one go about communicating it? Below is a conceptual representation of a set of challenges typically found in video games. Each challenge is represented by a window of failure and a window of success. These windows can be spatial, temporal, symbolic, strategic, or a combination of all. They are the spaces in which the player enters by behaving in a certain expected way. Secondly, the black line represents the player’s interactive maneuvers: where to get across and which direction to turn to next, in order to overcome the set of challenges without stumbling into the windows of failure. For example, say we have a situation in a 3D platformer game where the player is facing a pit, and across the pit leaning towards the right side there is a narrow platform. In such a scenario, we can assume that the window of failure includes any and all sets of behaviors that lead the player plummeting down the pit, and the window of failure includes those that lead the player to landing on the platform across the pit safely. Now consider the same representation of challenge above, but this time with a slight deliberate arrangement. As you can see, the sizes of the windows of failure and the windows of success stay exactly the same, but the positions of the windows of success have been altered so that they align somewhat (but not exactly aligned to the point of being too obvious). You can see that nested within the windows of success is a narrower window where the amount of the player’s maneuvers stays extremely minimal. Stepping into this window offers the opportunity for a non-disrupted gameplay flow, where a deliberate and guided set of behaviors will let the player “breeze” through the challenges seemingly almost with ease. This window is where ludoaesthetics occur. Of course, the downsides of it are aplenty: it can be extremely difficult to realize such a window exists in a real scenario. And in order to stay inside such a narrow window, the player has to be extremely precise and/or smart in their gameplay. You can think of this window of non-disrupted flow as an intended “weak point” of the challenge, where a single and concentrated attack will break the whole thing apart in one fell swoop. But the process of identifying such a weak point, and delivering the finishing blow with great accuracy may require a lot of trials and errors, and can be extremely tedious and/or difficult. An Example from Master Spy A common manifestation of ludoaesthetics comes in the form of speedrunning. Finishing with speed is, for the majority of games, not the primary intended goal. Games are rarely ever designed to be speedrun, and most players do not have to finish any games at high speed in order to not miss anything. So speedrunning has always been a sort of arbitrary self-imposed challenge by those who seek greater sense of enjoyment from their favorite games. However, there are a few exceptions. And you can find the above mentioned window of non-disrupted flow in levels like this one from Master Spy by Kris Truitt. In this game you play the role of the Master Spy, to infiltrate ridiculously well-guarded buildings, palaces and fortresses with a huge number of different enemies, hazards and contraptions standing in your way. And you are given no tools whatsoever but an invisibility cloak that can help you sneak past the eyesight of certain enemies while halving your movement speed. In the example above, your goal is to retrieve the keycard on the other side of the wall slightly to the right of your starting point, and then to escape through the white door right above your starting point safely. And while your cloak can get you past the eyesight of the guards, it is of no use whatsoever against the dogs, who can smell you even when you’re cloaked and will sprint forwards to attack you at horrendous speed as soon as you’re on the same ground as them. So what you have to do as a sequence of actions in this level is first to cloak yourself, then drop down from the first ledge past the the first guard, then quickly decloak to regain speed as the cloak is useless against the incoming dogs. Then before the first dog reaches you, move forward to the right, then quickly jump up. Keep jumping to retrieve the keycard while avoiding the second and third dog. Cloak up, then get on the ledge with the three moving guards. Finally, jump to the left to reach your destination. However, as you can see from the footage above (courtesy of a speedrunner nicknamed Obidobi), as soon as the player reaches the ledge with the three moving guards on the right, the guards turn to the other side and begin moving away from where the player is, effectively freeing the player from having to cloak and having their movement speed halved. And then right before the player reaches for the white door, the guard on the far right is about to touch the wall and thereby turning back to the left. This is such a tiny window of success that should the player not have begun moving right after they start the level and stayed uncloaked at the end, they would have failed. The level is designed in such a way that it can be completely solved without wasting any moment and action. Is it significantly more difficult to play this way? Yes. Was this arrangement absolutely necessary? Not really. But the designer made the level with the expectation that people are going to speedrun the game and will be looking to optimize their timing with each level. Thus, the levels in Master Spy are designed so that should the player start looking to speedrun the game, they will easily recognize that sweet, sweet window of non-disrupted flow. It is an immensely satisfying experience to discover it. Ensure Usability As usual, it is easy to get too extremely logical about design and forget all about the equilibrium, which is almost always what design is about. In this case, it is important that designers must ensure that whatever tools they’re making for their players to achieve ludoaesthetics, MUST have at least some sort of usability, even if it’s incredibly niche or extremely difficult to pull off. Things that serve nothing and mean nothing are NOT aesthetic. Say you have an RPG, and one of your players goes out of their way in order to build an unconventional character because they see some sort of future potential from this build, only to find out later that when they’re finished with the build, the meta of the game has changed and the window of opportunity for such a build has long passed. This means that the entire amount of depth you added, and the ludoaesthetics you might have intended by allowing that player to go in such away, is utterly useless and entirely wasted. So always remember to ensure usability for everything you add in your game. Conclusion Organic Difficulty and the ELS are not only, and not necessarily, an alternative solution to the whole difficulty problem. But rather, they represent an entire paradigm shift away from the idea that games should find more and more complex ways to serve players with different skill levels, and towards a design philosophy where players are given integrated tools within the context of games to set their own difficulty at any point without breaking immersion and perhaps the extra baggage of shame. It is not enough to have your players stay at the same level of difficulty throughout the game, or dynamically adjust the difficulty on the fly to suit them. It is best, in my opinion, to let your players cook to their palate. Just make sure that the process of cooking and the game itself are one and the same. References The Designer's Notebook: Difficulty Modes and Dynamic Difficulty Adjustment (2008) by Earnest Adams. Retrieved at https://www.gamasutra.com/view/feature/132061/the_designers_notebook_.php The case for dynamic difficulty adjustment in games (2005) by Robin Hunicke Cognitive Flow: The Psychology of Great Game Design (2012) by Sean Baron. Retrieved at http://www.gamasutra.com/view/feature/166972/cognitive_flow_the_psychology_of_.php Depth vs. Complexity (2013) by Extra Credits. Available at https://www.youtube.com/watch?v=jVL4st0blGU The True Genius of Dark Souls II (2014) by Extra Credits. Available at https://www.youtube.com/watch?v=MM2dDF4B9a4 What Makes Celeste's Assist Mode Special | Game Maker's Toolkit (2018) by Mark Brown. Available at https://www.youtube.com/watch?v=NInNVEHj_G4
  9. 1 point
    For one of the upcoming projects (which will follow in some of the following posts), and as it had to fit the lore of the game, a black hole was necessary. Before going forward - let me add an image of what result I want to achieve: Artist conception of black hole - NASA/JPL-Caltech While the image is similar to the final effect I wanted to achieve, I did some changes on the effect to be more colorful and bright - but the original idea was from this image. The effect is actually separated in 3 major parts - Disk, Streaks and Post processing.Of course there is also the core of the black hole (which is just a small sphere, with black color). Disk The disk around black hole is actually just matter that rotates around the core. Depending on the distance from the core the average density will increase near the event horizon and decrease further from it. Near the core the density can be so high that it may eventually have temperature close to star - therefore there might be high emissive energy - and therefore light. Also, due to the time dilation (and therefore light having hard time escaping near the event horizon), the emissivity is getting lower very close near the event horizon. Anything beyond event horizon is invisible from the outside, because gravity there is so strong, not even photons can escape. At least that is what I understand from the topic from physics point of view. This actually explained what can be seen on the image and what is going on graphically, that is: The disk rotates around the core The density of disk decreases further from the core The emissive light decreases further from the core, and therefore some (outer) parts of the disk will be lit by inner part ... although inner part around the core has to be somehow darker Which can be solved with simple texturing and some basic lighting of the result. Using whirl-like texture as a basis proved to be a good start for me. I started off by creating a whirl-like texture that would define density in various parts of the disk, which resulted in this: Generating a normal map for lighting from this is actually quite straight forward (and easy in Substance Designer F.e.) - and after short time, I also had a normal map: Putting just these together with basic diffuse lighting (standard N.L) from the center (slightly above the plane) gives us some basic results: Next thing is defining emissivity. This is done simply by using 1D gradient texture for which the coordinate will be distance from the center. The gradient I came up with is: Notice the left part - which is near the event horizon.will give us similar feeling to the image as we're not jumping straight to bright value. Applying emissive value (as both - multiplier for the color, and as emission) gives us this look: Which looks good enough already - I personally played a bit with values (mainly playing with contrast and other multiplication factors - F.e. for alpha channel/transparency), and ended up with this result: Resulting pixel shader is as simple as: fixed4 frag (v2f i) : SV_Target { // Calculate texture coordinate for gradient float2 centric = i.uv * 2.0f - 1.0f; float dist = min(sqrt(centric.x * centric.x + centric.y * centric.y), 1.0f); // Lookup gradient float3 gradient = tex2D(_GradientTex, float2(dist, 0.0f)).xyz; // Light direction (hack - simulates light approx. in the middle, slightly pushed up) float3 lightDir = normalize(float3(centric.x, -centric.y, -0.5f)); // Use normals from normal map float3 normals = normalize(tex2D(_NormalsTex, i.uv).xyz * 2.0f - 1.0f); // Simple N.L is enough for lighting float bump = max(dot(-lightDir, normals), 0.0f); // Alpha texture float alpha = tex2D(_AlphaTex, i.uv).x; // Mix colors (note. contrast increase required for both - lighting and alpha) return fixed4((gradient * bump * bump * bump + gradient) * 0.75f, min(alpha * alpha * 6.0f, 1.0f)); } Streaks There are 2 streaks, directing upwards and downwards from the core. My intention was to make them bright compared to the core and blue-ish - to keep the background more colorful in the end. Each streak is composed from 2 objects, a very bright white sphere (which will take advantage of used post processing effects to feel bright), and a geometry for the streaks (instead of using particles). The geometry is quite simple - looks a bit like rotated and cut hyperbole, notice the UV map on the left (it is important for understanding the next part): This geometry is there 4 times for each direction of the streak, rotated around the origin by 90, 180 and 270 degrees. The actual idea for streaks was simple - have a simple geometry of cut surface, and roll a texture over it. Multiplying with correct color and distance from the beginning of the streak adds color effect that nicely fades into the background. To create a particles-like texture that varies in intensity I used Substance Designer again and come up with: By simply applying this texture as alpha, and moving the X-texture coordinate the streak is animated, like: Multiplying by wanted color gives us: And multiplying by factor given by distance from the origin of the streak results in: Which is actually quite acceptable for me. For the sake of completeness, here is the full pixel shader: fixed4 frag (v2f i) : SV_Target { // Texture coordinates, offset based on external value (animates streaks) float2 uv = i.uv.xy + float2(_Offset, 0.0f); // Alpha texture for streaks fixed alpha = tex2D(_AlphaTex, uv); // Distance from origin factor (calculated from texture coordinates of streaks) float factor = pow(1.0f - i.uv.x, 4.0f); // Multiplication factor (to 'overbright' the effect - so that it 'blooms properly' when applying post-process) float exposure = 6.0f; // Apply resulting color return fixed4(exposure * 51.0 / 255.0, exposure * 110.0 / 255.0, exposure * 150.0 / 255.0, alpha * factor); } Putting the effects together ends up in: Post Processing By using simple bloom effect, we can achieve the resulting final effect as shown in video, which improves this kind of effect a lot. I've added lens dirt texture to bloom. We need to be careful with the actual core - as that needs to stay black (I intentionally let it stay black even through the bloom). You can do this either by using floating-point render target before the bloom and write some low value instead of black (careful with tone mapping though - yet you might want to go even for negative numbers), or just render the core after the bloom effect. The resulting effect looks like: And as promised - a video showing the effect:
  10. -1 points
    On any platform you could control the rate of update and render with this simple algorithm   float dt; //delta time in seconds float clock; //last time sample in seconds float render_timer; //time control for rendering   dt = 0.0; render_timer = 0.0; //init the render timer clock = getTime(); //API callback to get the current time in seconds   while(true) //game loop {    dt = getTime() - clock; //get the current delta time for this frame    clock = getTime(); //updates the clock to check the next delta time      update(); //update stuff;      if(render_timer >= (1.0f/60.0f)) //checks if the frame is ready to render    {       render(); //render your stuff       render_timer -= (1.0f/60.0f); //do not set to zero, remove the accumulated frame time to avoid skipping    }      render_time += dt; //updates the render timer   }
  • Advertisement
×

Important Information

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

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!