flyx.org

Personal homepage of Felix Krause

In-depth Review of Skyrim's Gameplay

Preface

This is a review of The Elder Scrolls V: Skyrim. Or, perhaps more accurately, a review of current / “modern” action-adventure video games in general. I write this as someone who likes JRPGs, old-school video games in general, and particularly the Zelda series. I also do tabletop roleplaying.

I bought Skyrim because it was on sale on Steam, everyone seemed to like it and I was searching for a bit of distraction. I didn’t play any of its predecessors. And I stopped playing it after two days, so I guess I didn’t make it far into the storyline. My response to anyone who’ll tell me that I’m being unfair because the game gets better later will be “well, it should be good from the beginning”.

Starting a new Game

Some characters get introduced, factions are mentioned, you get to see some of the scenery. I have to say, the thing I like about this game most is the scenery: It is really a nice and not too repetitive walk through nature. Now I get to designing my character. Everything is customizable. I feel like installing Linux.

I quickly get the feeling that all this customizability is just an illusion. There are plenty of predefined presets for characters, but they all look grim and dirty. Now perhaps the storyline’s setting is one that requires grim and dirty characters, but this just isn’t my style. If I’m given the option to customize my character, I want there to be the possibility not to look grim and dirty.

This is a psychological thing, you know. Define a character for a game without any customizability and everything’s fine. Give the player customizability options, and the player has to live with the fact that the appearance of his character was his choice. If I cannot customize the character the way I want, I’ll be less satisfied than I would be without customizability.

Anyway, this is just a minor issue compared with the others I will come to. So after having chosen a race, name and appearance for my character, I soon get the ability to move freely, equip a weapon and some magic. I can choose whether to walk around in some light armor or in a mage robe. That’s nice, I guess. Well, as it turns out, it doesn’t make much of a difference.

Fighting Mechanic

So, what’s the difference between fighting with magic and fighting with a sword? The answer seems to be, not much. I have to equip the sword / fire thingy, target the opponent, and activate it. The sword delivers a strike, while magic is an ongoing action that just continues to deal damage. Fighting with a sword requires stamina, spellcasting requires Magicka. So being mage seems to boil down to still just hitting your opponents until they go down, but you’re exhausted more quickly and have shitty armor.

The only way that gives you a real choice regarding combat style is to use a bow. I like the way you can sneak upon enemies and just do double damage as long as they don’t notice you. Unfortunately, once they do notice you, you’ll have a hard time with your bow and need to switch to meelee weaponry.

This is another time where I get the feeling that the game just gives me the illusion of a choice. I cannot be a mage that does not primarily do direct damage, nor can I be a pure sniper. Especially boss fights usually require meelee attacks and you cannot do much about it.

Fights do not tend to allow much tactics either. The enemies just run towards you and try to hit you. Compare this to the enemies in the Zelda series: They often require a certain move or equipment to kill them. In my opinion, this delivers a richer and more diverse gameplay. In Skyrim, you can choose whether you want to kill your enemies primarily with your bow, or your sword, or your magic. But once you’ve chosen, you’ll just do it over and over again.

Of course, you just have to build up some meelee skill because you often wander around alone in Skyrim. This is imposed by the overall game design and I don’t see a way to do it better. Other people don’t seem to mind, so it is possibly more an opinion than critism.

Overworld

The overworld is just gorgeous and probably my favorite thing about this game. I know how much work it is to craft such a vast and diverse world, and it has been done really well. It even provides some interactions with all the plants you can pick.

The map is nice and the option to fast-travel to places I have been before avoids repetitive travelling. There are signs at every junction so I don’t get lost as long as I stay on the tracks.

Dungeons and Enemies

Okay now seriously, this is the worst part. I think this whole dungeon thing is a massive fuckup by game designers. Action-adventure games in the early days had this simple structure where you have this overworld thingy that gave you access to the dungeons, which were the “levels” of the game.

Roleplaying games have a similar structure, but the difference is that you travel with multiple characters with different abilities, and this works very well in combat situations. In Skyrim, it does not work, and you know why? Because it tries to be an action-adventure where you’re controlling one guy (or gal) who explores these dungeons, but at the same time it tries to be a roleplaying game where you should be able to shape your character.

By having dungeons which you typically have to fight through alone, the game requires you to be a killing machine, because that’s how you survive the dungeons. You can of course specialize in the speech skill so you can buy weapons for less money, but you still need to wield them yourself.

Another massive failure is that the dungeons are just not interesting. Okay, so I only played the storyline until I reached High Hrothgar, so it might get better later, but what I saw was just… caves. With zombies. And then more caves, with more zombies. Even the city of Whiterun has catacombs, with skeleton zombies. Okay, to be fair, there are also som spiders, and in some cases thiefs, but that’s it.

Again, compare this to the Zelda games, or RPGs like Final Fantasy: The diversity of enemies is much higher, as is the diversity of settings and dungeon theming. Now this has an obvious reason: Skyrim tries to deliver much more realistic consistence within the world it describes. Remember infiltrating the Shinra headquaters in Final Fantasy VII? There are flying enemies composed of razor blades or something strolling around. Does this make sense? Rather not. Is it fun? Sure!

Skyrim castrates itself by maintaining a high level of in-game realism. There just cannot be some random creatures strolling in the dungeons. If you’re exploring ancient ruins, there will be zombies. If you’re digging into caves, there will be zombies. And if you’re entering a haunted barrow, there will of course be zombies.

A lot of people will say that this is a good thing, because the more consistent the in-game world is, the more you can dive into it. This is true and it works really well for the quests available in towns, but it makes it really difficult to design interesting dungeons. That’s why I said earlier that dungeons are not really necessary in this game.

Levelling

Levelling up gives you the option to raise one of your basic values - Magicka, Health, or Stamina. Additionally, you can buy one additional skill from a huge set of skills that are divided in quite some categories. So it does give you the option of shaping your character more like you want him or her, but as I mentioned earlier, you can hardly get away without buying some combat skills.

A good thing is that you cannot just specialize in just one skill category, because you have to actually train your proficiency in each category before you can by all of its skills. Again, this is very realistic, and in this case, I don’t see much bad side effects, other than you cannot say “I just want to be a warrior” and only buy direct combat skills.

Conclusion

Skyrim tries to be too many things at once. And action adventure needs more diversity in enemies and fancier dungeons to stay interesting and avoid being repetitive. A classic RPG needs more characters so that each of them can really specialize in one thing or the other. A realistic RPG does not really need dungeons, because you actually want to focus on the role you play, and the combat system is only a minor part of this way of playing.

Moreover, compared to tabletop roleplaying, realistic role playing in a video game is utterly limited. You can only choose the paths and only make the decisions the game creators designed for you. Therefore, I think that realistic role playing just does not belong on the PC. A video game should either be an action adventure with more unusual enemies and fancier dungeons like Zelda does it, or it should tell an interesting story with complex characters, as Final Fantasy (and many others) does it.

Of course, this is a very subjective point of view, I know that. Many folks seem to like Skyrim’s approach to role playing and gameplay. And Skyrim does have its bright sides, like the brillantly crafted overworld. But in the end, I got bored pretty quickly. I guess I’ll just stick to tabletop roleplaying for the realistic RPG experience.

Tags: gaming

Writing Ada Bindings for C Libraries, Part 3

Previously…

Void Pointers

C has no generics. So whenever a subprogram parameter may take differently typed values, a void pointer is used. Usually, a void pointer value will be used in one of these ways:

  • It will be passed on to another subprogram that will know its type, cast it appropriately and do stuff with it.
  • It will be used to return data to the caller, and he has to know what to do with it.

Here’s an example for the second case:

void * clGetExtensionFunctionAddress(const char * func_name);

Here, a void pointer is returned to the caller. The purpose of this function is to return a pointer to a subprogram specified with func_name. So there is a fixed set of accepted values for func_name, and for every value, the function may return a differently typed pointer to a subprogram.

There are several possibilities to wrap C functions taking void pointers in Ada:

Import it multiple times with different signatures

package C renames Interfaces.C;
type Func_Type1 is access function return C.int;
pragma Convention (C, Func_Type1);
type Func_Type2 is access function (Param : C.int) return C.double;
pragma Convention (C, Func_Type2);
function Get_Extension_Function_Address
  (Func_Name : C.Strings.chars_ptr) return Func_Type1;
function Get_Extension_Function_Address
  (Func_Name : C.Strings.chars_ptr) return Func_Type2;
pragma Import (Convention => C, Entity => Get_Extension_Function_Address,
               External_Name => "clGetExtensionFunctionAddress");

The Import pragma will be applied to all functions that match the given name. While this works, it does not give us type safety: If the user calls the wrong function, he gets a function reference back that will not work as expected.

Wrap the C function

function Backend (Func_Name : C.char_array) return System.Address;
pragma Import (Convention => C, Entity => Backend,
               External_Name => "clGetExtensionFunctionAddress");

generic
   type Return_Type is private;
   Function_Name : String;
function Get_Extension_Function_Address return Return_Type is
   function Convert is new Ada.Unchecked_Conversion (System.Address, Return_Type);
begin
   return Convert (Backend (C.To_C (Function_Name)));
end Get_Extension_Function_Address;
function Get_Func1 is new Get_Extension_Function_Address
  (Func_Type1, "func1");
function Get_Func2 is new Get_Extension_Function_Address
  (Func_Type2, "func2");

Obviously, you want to expose just the last two functions to the caller. As you cannot implement a declaration made in a package specification by a generic instantiation, you have to use renames to do that:

function Get_Func1_Public return Func_Type1 renames Get_Func1;

Provide a generic interface

… so the caller can define the type he wants to use. This is useful in cases like this:

void registerCallback(void(*callback)(void* user_data), void* user_data);

Here, the C procedure lets the caller register a callback that, when called, will be passed a pointer to some data the caller provides. This is a pattern that is often used with callbacks in C. You can wrap it like this:

procedure Backend (Callback_Raw, User_Data : System.Address);
pragma Import (Convention => C, Entity => Backend,
               External_Name => "registerCallback");
generic
   type User_Data_Type is private;
   type User_Data_Access is access User_Data_Type;
   type Callback is access procedure (User_Data : User_Data_Type);
procedure Register_Callback (Target : Callback; User_Data : User_Data_Access) is
   function Convert_User_Data is new Ada.Unchecked_Conversion
     (User_Data_Access, System.Address);
   function Convert_Callback is new Ada.Unchecked_Conversion
     (Callback, System.Address);
begin
   Backend (Convert_Callback (Target), Convert_User_Data (User_Data));
end Register_Callback;

You may want to convert this code to a generic package that can define the types User_Data_Access and Callback itself based on the parameter User_Data_Type, particularly if there are multiple similar callback registering functions.

Be aware that this wrapper leaves it to the caller to make sure his callback function has the correct convention (one can also use the pragma Convention on subprograms that are implemented in Ada if they will be called by C code).

If you want to make your wrapper even thicker, you can define your own User_Data_Type and callback function, and embed the reference to the caller’s function as well as the caller’s data in your User_Data_Type. Your callback function can then extract the subprogram reference and user data from your container and call the callback the caller provided. This way, the caller does not need to apply any pragmas in his code.

Conclusion

If you want to wrap a void pointer, you usually declare it as System.Address and use Ada.Unchecked_Conversion in your wrapper. The lesser the caller needs to take care about Convention pragmas, the easier your wrapper is to use.

Bitfields

Bitfields are usually declared as numeric type like int in C. Then, a number of constants is defined that can be combined with bitwise OR to build a value of the bitfield. Example:

typedef cl_ulong            cl_bitfield;
typedef cl_bitfield         cl_device_type;

/* cl_device_type - bitfield */
#define CL_DEVICE_TYPE_DEFAULT                      (1 << 0)
#define CL_DEVICE_TYPE_CPU                          (1 << 1)
#define CL_DEVICE_TYPE_GPU                          (1 << 2)
#define CL_DEVICE_TYPE_ACCELERATOR                  (1 << 3)

cl_int clGetDeviceIDs(cl_platform_id   /* platform */,
                      cl_device_type   /* device_type */, 
                      cl_uint          /* num_entries */, 
                      cl_device_id *   /* devices */, 
                      cl_uint *        /* num_devices */);

Of course, you could just copy the constants to Ada and provide the same interface. But you can also wrap it with a record:

type Device_Type is record
   Default     : Boolean := False;
   CPU         : Boolean := False;
   GPU         : Boolean := False;
   Accelerator : Boolean := False;
end record;
for Device_Type use record
  Default     at 0 range 0 .. 0;
  CPU         at 0 range 1 .. 1;
  GPU         at 0 range 2 .. 2;
  Accelerator at 0 range 3 .. 3;
end record;
for Device_Type'Size use ULong'Size;
pragma Convention (C_Pass_By_Copy, Device_Type);

This way, the possible values are directly linked to the type. If you just provide constants and a numeric type, there is no explicit link between them.

Tags: ada programming