Jump to content

Strategy game data modifiers - Please help


Thunderfist
 Share

Recommended Posts

I think it's very valuable in situations like this to not try and get Phaser to manage this for you with a physics system, but instead make Phaser the view of an existing model that runs the simulation for you.

I don't know how you've coded it so far, but suppose that all your units are represented in a simulation apart from the Phaser sprites stuff. There's probably an array of arrays corresponding to your map, with each value being an index into another array describing what the terrain tiles are like? And each unit has an (x,y) position that corresponds to an element in one of those arrays?

Link to comment
Share on other sites

  • 2 weeks later...

I tried doing this:

Game.Board.prototype.tileEffect = function () {
    var targetTile = this.Board.tile.terrainAsset;
    var damageUnit = this.Unit.currUnit.data.health;
    var alterDef = this.Unit.currUnit.data.defense;
    var alterAtk = this.Unit.currUnit.data.attack;
    
    //magma effect
    while (this.targetTile === this.Board.tile.terrainAsset('magma')) {
        console.log('Magma. Damages units and weakens their Defense.');
        damageUnit = -5;
        alterDef = -5;
    }
    //Forest effect
    while (this.targetTile === this.Board.tile.terrainAsset('grasstrees2')) {
        console.log('Forest. Increases Defense but decreases Attack.');
        alterAtk = this.unit.data.attack - 3;
        alterDef = +5;

}

Link to comment
Share on other sites

I tried this yesterday, with no success.

Game.Board.prototype.tileEffect = function (effect) {
    var damageUnit, alterDef, alterAtk, targetTerrain = this.Unit.targetTile && this.terrains.asset;
    
    //plains
    if (targetTerrain === this.terrains.asset('grass')) {
        effect.events.alterAtk = this.Unit.data.attack + 1;
        effect.events.alterDef = this.Unit.data.defense - 1;
        console.log('Plains. Slightly increases Attack but weakens Defense.');
    }
    //rocks
    if (targetTerrain === this.terrains.asset('rocks')) {
        effect.events.alterAtk = this.Unit.data.attack - 1;
        effect.events.alterDef = this.Unit.data.defense + 1;
        console.log('Rocky Area. Slightly decreases Attack but increases Defense.');
    }
    //trees
    if (targetTerrain === this.terrains.asset('grasstrees')) {
        effect.events.alterAtk = this.Unit.data.attack - 2;
        effect.events.alterDef = this.Unit.data.defense + 3;
        console.log('Trees. Increases Defense but decreases Attack.');
    }
    //forest
    if (targetTerrain === this.terrains.asset('grasstrees2')) {
        effect.events.alterAtk = this.Unit.data.attack - 6;
        effect.events.alterDef = this.Unit.data.defense + 5;
        console.log('Forest. Increases Defense but severely weakens Attack.');
    }
    //magma
    if (targetTerrain === this.terrains.asset('magma')) {
        effect.events.damageUnit = this.Unit.data.health - 5;
        effect.events.alterDef = this.Unit.data.defense - 5;
        console.log('Magma. Damages units and decreases their Defense.');
    }
};

Link to comment
Share on other sites

Couple of different ways I can think of immediately, but they require a structure which meets the following:

* Units contain data about themselves, i.e. attack, defence, health

* Tiles contains data about their modifications to units stacked on to them, i.e. attack + 2, defence + 2

* Tile effects are temporary, only applicable while a unit is on a tile, this is critical to understand properly

With these simple requirements in hand (note you could code those however you like, its the concepts at this stage that are critical) you now have a few immediate options for achieving this, lets assume you have code for the following:

* Movement code to move a unit from one tile to another

* A list of entities

* A list of tiles

* A map of tiles representing your game board

Given that your tile effects are temporary you can either:

* 1) When a unit enters a tile (moves) you remove effects from the previous tile and apply the new effects

* 2) Just move the unit and calculate its properties, i.e. internal props plus tile effects, only when necessary

Of the two I hope its immediately clear that option 2 should always be your preference, I'll elaborate as to why anyway:

Option 2 requires you keep information about tiles and entities separate, separation of concerns is a goal worth achieving. By doing so you give yourself more options, when a user initiates an attack or if you display modifiers in some sort of view screen then you grab the unit object and you grab the tile it belongs on and perform any calculations you need to to display the data how you like, you are transforming the data from a logical state into a state ready to be presented to view or use, this is an important concept to consider. The only link between the two is not hard, it can be inferred from the position of the unit, which is likely data you are already storing on the unit anyway, there are no other links lying around, which is good.

Option 1 requires a few more things, firstly you need to keep track of not only where you are but also where you've been, this in itself isn't a great problem because its likely your movement code needs these two things (or calculate them) anyway, however, now you are tying unit property modification data into the movement code. It also means that you're mutating unit properties transiently, i.e. the attack property only defines current attack property, not its actual attack property, so you have to manage that and make sure effects get unapplied correctly as well as applied, already sounding nastier than necessary. Of course you could keep current modifiers as properties on the unit too, but now you're adding to your data model (which ideally you want as minimal as possible), you still have to manage the data modifiers properties but you might need them if you want to display both actual attack and current attack values (which option 1 handles because you can pass that data through separately to your rendering code, similarly to any code that initiates a battle between units).

Let's take the case of wanting to display to the screen, you might step through an algorithm that does the following:

* Grab the unit data for the unit you want to display

* Query its positional data and grab the tile it is currently on from your game board

* Grab the tile type from that tile data you just got

* If the tile contains data about its modifiers then use those, otherwise perform some sort of lookup on another structure to get them (I include this step because it looks like your code kind of does this)

* Let's say we're displaying the attack value, you now have unit.attack and a tile modifier for the tile it is on, so display them, either add them together or display them separately i.e. `Attack 2 (+1)`.

In code this might look something like:

// Master list of tile types
var tiles = [
  {
    id: 0,
    type: 'Grasslands',
    textureID: 0,
    modifiers: {
      attack: 0,
      defence: 0
    }
  },
  {
    id: 1,
    type: 'Hills',
    textureID: 4,
    modifiers: {
      attack: 0,
      defence: 1
    }
  }
]

// List of current units in the game
var units = [
  {
    type: 'Spearman',
    attack: 3,
    defence: 6,
    position: [0, 0]
  },
  { 
    type: 'Militia',
    attack: 1,
    defence: 1,
    position: [1, 0]
  }
]

// I've used a simple 2d tile map here for brevity, it only stores tile ids
var gamemap = [
  [0, 1],
  [0, 0]
]

// This function could be attached to a user input and should be passed the id
// of the unit to display, in this case id references array index for the units array
function displayUnit (id) {
  // Get the unit
  let unit = units[id]

  // Use the unit position to grab the tile it is currently on
  let tileid = gamemap[unit.position[1]][unit.position[0]]
  let tile = tiles[tileid]

  // We now have all the data we need to display the unit info
  console.log(`
    Unit Type: ${unit.type}
    Attack: ${unit.attack} (${tile.modifiers.attack})
    Defence: ${unit.defence} (${tile.modifiers.defence})
  `)
}

// Example usage
displayUnit(1)

// Output
Unit Type: Militia
Attack: 1 (0)
Defence: 1 (1)

So in this terse Civilisation example game all we've done is assumed some user interaction, a click, on the unit, we've done some code (not here) to work out the click happened on unit with the id of 1, we've then passed that 1 to the displayUnit function which grabs the unit (Militia), uses its positional data to perform a lookup on the game map to get the tile id, used that id to grab the tile from the master tile list and then displayed that information.

Let's say we now wanted to perform an attack between these two units, just perform very similar logic to get the data you want and perform your attack/battle operations to work out who won, who got injured, who died etc etc.

Link to comment
Share on other sites

Am I supposed to put the data for the tiles in my board.js file or somewhere else?

The data for the units are in playerUnits.json and enemyUnits.json, not in a normal .js file. 

I have the code fore everything else made except for the tile data stuff and the temporary application of the data modifiers.

I'm getting a message saying 'expected an identifier and instead saw 'let', and it isn't reading the code past it. 

Edited by Thunderfist
Link to comment
Share on other sites

3 hours ago, Thunderfist said:

I'm getting a message saying 'expected an identifier and instead saw 'let', and it isn't reading the code past it. 

Jeez, what are you running it with the doesn't understand let?? IE10 I guess or old Safari, update your browser :) Although from that error that isn't actually the issue (just use var in place of let or const).

Storing them in json data files is fine, you'll parse those into JS data structures and they'll be your 'master' or reference lists. In my example I used a reference list for the tile look up and let the map just hold references to that reference list, you can do it however you like, or, rather, whatever makes most sense to you and your application, if you want to store individual tile data in your map that is fine (as I've done in the units array), if you needed individual tiles (i.e. if one grassland tile can differ from another) then it would make sense to do so. Certainly, with the units list I'm presuming they are all unique, i.e. one Spearman unit has 12 health another maybe has 15 health points left, in which case you have an instantiated list of unique units, all created (presumably) from a Spearman unit template (again, there are many ways you can do that).

When you want to access a unit then you'll need to apply the modifiers, or, maybe you apply the modifiers when a movement occurs, in which case you'll have to unapply them when movement occurs again.

Link to comment
Share on other sites

  • 2 weeks later...
On 1/6/2017 at 11:31 AM, mattstyles said:

* Tile effects are temporary, only applicable while a unit is on a tile, this is critical to understand properly

 I'm kind of confused about the 'tile effects are only temporary' part. 

Got a message in the console saying:

'Phaser.cache.getText: invalid key: "effects"'.

I think that I'm missing the part above, but I have the code.

 

EDIT: I found what gave me the message! I forgot to preload the .json file!  

EDIT: Big problem! the game won't load! the console had some thing in it.

   Uncaught SyntaxError: Unexpected token / in JSON at position 0
    at JSON.parse (<anonymous>)
    at Object.create (game.js:14)
    at Phaser.StateManager.loadComplete (phaser.js:18360)
    at Phaser.StateManager.preUpdate (phaser.js:18138)
    at Phaser.Game.updateLogic (phaser.js:26379)
    at Phaser.Game.update (phaser.js:26327)
    at Phaser.RequestAnimationFrame.updateRAF (phaser.js:44881)
    at _onLoop (phaser.js:44865)

How do I fix this? I have to finish this by Friday!

Link to comment
Share on other sites

1 hour ago, Thunderfist said:

Uncaught SyntaxError: Unexpected token / in JSON at position 0

That implies your json is not valid, at position 0.

You could whack your json into something like jsonlint.com but it'll just highlight whats wrong with probably about the same error.

It sounds like maybe you haven't wrapped your json in [] or {}

Link to comment
Share on other sites

The code should be in a JS file, the data in a JSON file. a JSON file is a glorified js file that is basically just an object in a specific format. The issue with "let" is that it is ES6 syntax while if you are writing for ES5 you need to change "let" to "var"

Personally I would research JSON and how to write JSON as that is what you should put your data into, and then load that in separately. When you learn and start writing json use pro.jsonlint.com to validate it. JSON is a PURE data type. Meaning it requires the object keys to be quoted, no JavaScript syntax, and all that jazz. After that, preload the data, and then you can work with it. Below I have cleaned up the files.

I also changed the console.log to a format that might be easier to use. Both should work, but this one is easier to work with for beginners.

 

NOTE: All the code and such is from mattstyles post. Just slightly modified to be easier to understand.

 

Your JSON file:

{
    "tiles": [
        {
            "id": 0,
            "type": "Grasslands",
            "textureID": 0,
            "modifiers": {
                "attack": 0,
                "defence": 0
            }
        },
        {
            "id": 1,
            "type": "Hills",
            "textureID": 4,
            "modifiers": {
                "attack": 0,
                "defence": 1
            }
        }
    ],
    "units": [
        {
            "type": "Spearman",
            "attack": 3,
            "defence": 6,
            "position": [
                0,
                0
            ]
        },
        {
            "type": "Militia",
            "attack": 1,
            "defence": 1,
            "position": [
                1,
                0
            ]
        }
    ]
}

 

Your JS File:

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// PRELOAD YOUR JSON INTO A VARIABLE YOU CAN USE.
// Example used gamedata
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


// I've used a simple 2d tile map here for brevity, it only stores tile ids
var gamemap = [
  [0, 1],
  [0, 0]
]

// This function could be attached to a user input and should be passed the id
// of the unit to display, in this case id references array index for the units array
function displayUnit (id) {
  // Get the unit
  var unit = gamedata.units[id]

  // Use the unit position to grab the tile it is currently on
  var tileid = gamemap[unit.position[1]][unit.position[0]]
  var tile = gamedata.tiles[tileid]

  // We now have all the data we need to display the unit info
  console.log("Unit Type: %s\r\nAttack: %s ($s)\r\nDefence: %s (%s)",
    unit.type,
    unit.attack,
    tile.modifiers.attack,
    unit.defence,
    tile.modifiers.defence,
  );
}

 

Edited by phreaknation
Gave kudos to mattstyles as I didnt write the code. Just modified it
Link to comment
Share on other sites

2 minutes ago, mattstyles said:

That implies your json is not valid, at position 0.

You could whack your json into something like jsonlint.com but it'll just highlight whats wrong with probably about the same error.

It sounds like maybe you haven't wrapped your json in [] or {}

I feel he doesnt understand what json actually is and has put his code into the json and not the object data which is why I have explained your original post in a bit more detail. I also formatted it for es5 vs es6

Link to comment
Share on other sites

Quote

I have JSON data set up, but do I put the code in my game.js file, or in a new JS file?

Sounds like @phreaknation is correct and you havent yet got your head round the difference between JS files and JSON files. JSON literally stands for JavaScript Object Notation and is a text specification for describing data structures, it is quite literally a minimalist format for creating raw data structures and as its primarily a JS format there are well-defined language level functions for converting it into JS objects (and vice-versa). Notice the difference between where my post specified the data structures directly into JS and phreaknations (which is what you want) code specified it in JSON.

Typically you would load the JSON resource from a server and then parse that data into JS object/s so that you can use them (this can be done at build time depending on how you are building but its usually not ideal and mentioning it has probably complicated things for you). So long as you have well-formed JSON (use jsonlint if you're not sure) then JSON.parse() will do the heavy lifting of converting from a string (remember I said JSON is text, when you load it it will be a string) into a JS object, you can only specify one root JS object to create (it could be an array, but thats technically an object in JS, yes, JS is quirky!) but JSON.parse will work recursively over the structure which is often what you'd end up doing (so you could load all your tile data in an array rather than one by one).

If you do not want to load the data then you'd have to write them in JS files and include those in the page, note that you'd have to write them a JS objects and not as JSON. for example:

// data.js

var tiles = [
  {
    name: 'Grassland',
    textureID: 1
  }
]
// main.js

console.log(tiles[0])
// HTML
<script src='data.js'></script>
<script src='main.js'></script>

 

Link to comment
Share on other sites

Did you inline the data structures as JS or load them in via JSON?

The following is an example of loading json data from a data source (in this case its from the github api but its just an endpoint that is readily available and returns a list of objects):

fetch(' https://api.github.com/users/octocat/repos')
  .then(res => res.json())
  .then(data => console.log(data))

If you're using a new browser then just copy and paste that into the console, you should always be wary of copy-pasting stuff into the console so I'll briefly elaborate on what it does, feel free to check elsewhere too before copy-pasting:

Fetch works like an old-fashioned ajax request, but with a cleaner api, one that is promise based. I'm fetching (performing a GET) to the url specified, this returns a promise, you may have heard this referred to as a 'future' or a 'deferred', all a promise does is return an object that will 'resolve' some time in the future, it is for performing some tasks that occur asynchronously, you may have done this before using callbacks. When the promise resolves, i.e. when the over-the-network request completes, it executes the callback within the '.then' block, the first one shown here converts the body of the response into an object (likely using JSON.parse under the hood) and then that data gets passed to the next callback, which just logs the data out. The data you get back (as you can probably infer from the url structure) is a list of the repositories associated with the user 'octocat' (which is a github example user for running examples or tests against, so a good choice here).

Once you have that data list you can do what you like with it. In this case it'll return 7 objects. In your case it will return a list of your tiles.

If you are using the Phaser loader they work similarly, but I think they are callback based rather than promise based but the process is largely the same, you make your request, you parse the response and then you use those data structure.

The good thing here is that your data is separate from your code and you could update it without touching your code. The bad is that this happens asynchronously so you have to manage that, Phaser has a preload pattern for handling this already built in, so you'd do all this data loading in there, then move to the next lifecycle method (is it `create`?) and then use that data.

If you inlined your data structure then the good is that it is immediately available but the bad is that it is baked in to your code (hard-coded) meaning that data changes require code changes and rebuilding/redeploying your app. Up to you which you choose, pros/cons to either approach. Typically in a real-life app all the data required to run the app could be prohibitively large (requiring you to load pieces of it as and when you need), depending on the scale of your project this may or may not be a concern.

Link to comment
Share on other sites

Fetch is just used as an example here, its a browser api that can be accessed from JS, same as XHR but with a better api, fetch is understood by all modern browsers except safari. If you're more comfortable using XHR, or some sort of wrapper (like jquery's ajax helpers), then use that instead, the process is the same, GET the resource, parse it, use it. I mention sticking it in the console just so you can see it working, copy-pasting it into your code would work as well.

If by, "Okay. I just tried that, but it only displays the first set tile, which is grass", you were referring to `console.log(tiles[0])` then it'll only display the first tile because you've only told it to log the first tile using standard array index notation. `console.log(tiles)` would show the whole array, use that array of tiles as you will.

Link to comment
Share on other sites

On 1/17/2017 at 10:23 AM, mattstyles said:

Good idea showing it as ES5, although all modern (and many non-modern) browsers will happily run code with `let` and template strings.

I did this as he seems to be new to JavaScript in general. You are right but we do not know his target audience. Being a software engineer I still need to develop for multiple older version. With let, however, it isnt well supported in non modern browsers

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...