Jump to content

Changing entity appearance/behaviour by state


01271
 Share

Recommended Posts

What sort of framework would be used to switch the way that enemies function in an overworld/battle screen type game.

The design is as follows:

The player can see enemies on the game world screen and move around up, down, left, right. When the player touches an enemy both of them are transported to the fight screen. Intuitively, this screen should be a separate state. Now the problem arises: in the second fight screen state, the player and enemy behave very differently from normal. They stop moving up/down/left/right and take turns fighting. They are also shown from up close so their sprites are larger and more detailed. The battle can end without all of the combatants being defeated so values need to be persistent across both forms as the enemies & player should remain damaged post-combat.

 

I tried this before without too much success, I had the characters pull out of the pool and then "realize" which state they're in and instantiating with a different image/modifying their behaviour accordingly. The problems with that solution were that the code was super clunky (if/else everywhere) and it didn't have persistent values when returning from a fight.

 

Basically the enemies were managed by a director "class" that impersonated a real method of data storage.

They had a prototype Finite State Machine controlling them too.


var enemyDirector = {


	saveEnemyData: function() {
		var entList = me.game.world.getChildByName();
		for (var i = entList.length - 1; i >= 0; i--) {
			if (typeof entList[i].selfData !== 'undefined') {
				var essentialEnemyData = {
					selfData: {},
					pos: {}
				};
				essentialEnemyData.selfData.modelname = entList[i].selfData.modelname;
				essentialEnemyData.selfData.hp = entList[i].selfData.hp;
				essentialEnemyData.selfData.maxhp = entList[i].selfData.maxhp;
				essentialEnemyData.selfData.uniqEnemyNum = entList[i].selfData.uniqEnemyNum;

				game.enemyTracker.enemyStates.push(essentialEnemyData);
			}
		}
	},

	fightPositions: [],

	restoreEnemyFromSave: function(id) {
		for (var i = game.enemyTracker.enemyStates.length - 1; i >= 0; i--) {
			if (game.enemyTracker.enemyStates[i].selfData.uniqEnemyNum === id) {
				createPlaceEnemySprite(0, 0, game.enemyTracker.enemyStates[i], true);
			}
		}
	},
	restoreAllEnemies: function() {
		for (var i = game.enemyTracker.enemyStates.length - 1; i >= 0; i--) {
			enemyDirector.restoreEnemyFromSave(game.enemyTracker.enemyStates.selfData.uniqEnemyNum);
		}
	},
	restoreFightEnemies: function() {
		for (var i = game.data.enemyForFight.length - 1; i >= 0; i--) {
			enemyDirector.restoreEnemyFromSave(game.data.enemyForFight[i]);
		}
	}

};

 

There has to be a better way but though I'm experienced in Javascript I'm not experienced in solving data/interaction models for JS games.

 

I'm thinking for the enemy behaviour I can use my finite state machine to simply have the enemy move how I want him to in any situation but the sprites changing on state change & data persistence is more of a problem.

Link to comment
Share on other sites

I would probably use a single game state actually. Where the movement aspect would be one instance of me.Container, and the fight would be another me.Container. That way you can remove and add those as you please, leaving them in the previous state that they were in. The me.state stuff is fairly destructive, the idea is it removes everything. You could potentially setup an instance of me.ScreenObject to handle reloading the state  in onResetEvent, but personally i think managing separate containers might be easier :)

Link to comment
Share on other sites

I would just use a completely different class, if I was designing it. You inherently only have two states; map overworld screen, and fight screen. So each of these screens has some kind of entity base class, like game.MapEntity and game.FightingEntity; all NPCs, enemies, and player characters inherit from these base classes. So e.g. you have game.MapMonsterEntity and game.FightingMonsterEntity ...

In this way, you can keep the states and classes physically separated. As you switch between the different screen object states, just pass the list of existing entities so you can copy some state between them (positions, hp, items, etc.) But yeah, you could also just use containers to namespace everything, which is what agmcleod suggested. I've used both, TBH, and I find it cleaner to use the screen objects instead of swapping containers in and out.

Link to comment
Share on other sites

I now remember a year ago I used a bunch of hacks to make a similar situation happen.

I used

this.isPersistent = true;

which saves something from being removed by a state change and I used the following code I made up after analyzing melonjs to change sprite images.

I guess the only question I have left for this thread is if there's a better way to switch images on the fly that doesn't feel as if I'm trying to break the framework.

				this.renderable = new me.AnimationSheet(0, 0, {
					"image": me.loader.getImage('criminal'),
					"framewidth": 260,
					"frameheight": 129,
					"spacing": 0,
					"margin": 0
				});
				this.body.width = 260;
				this.width = 260;
				this.renderable.width = 260;
				this.renderable._width = 260;
				this.renderable._bounds._width = 260;
				this.renderable.resizeBounds(197, 260);
				this.updateBoundsPos(197, 260);

This worked in melonJS-2.1.3 and probably works now in v4.0.0 (if not it shouldn't be too hard to change)

Link to comment
Share on other sites

You're trying to change the images so that you don't have to rebuild the objects for each state transition? Is this just a performance concern, or you want the engine to maintain entity state, while you swap views? If your concern is the latter, I would recommend trying a clean setup between each state to see how it behaves; the performance should be very much adequate, especially if you are using object pooling.

On the other hand, if you want melonJS to maintain entity state while you swap views, then there is probably a better pattern. For example, you can wrap all of those properties into separate view classes, which will give you kind of a hybrid approach. So you don't have to recreate the entire entity for each state transition, but you do have to recreate the view objects. Then switching the view objects will manage updating all of those properties. TBH, I don't know if this hybrid approach is really any better than a "pure" separation and recreating the entities between the state transitions. Having the advantage that the engine maintains entity state seems nice at first. On the other hand, if you manage state transitions, then it will allow you to do application-specific optimizations and design patterns that are better suited to your workflow than what melonJS can provide out of the box.

But yeah, if I had any suggestion, it would be to try a full separation and see how it works for you. Whether you decide to do your state transitions with the melonJS screen objects or with container swaps, following the principle of separation of concerns is going to make your development cycles much easier. You could also frame the problem as an MVC pattern where the View and Controller are tightly coupled (they often have to be) and can be swapped at runtime, while the Model is shared. In more concrete terms, the example classes I described before are the "ViewControllers"; one for each screen state (separation of concerns), and you just swap between ViewControllers for each of your screen states. The Model (entity state like HP, carried items, etc.) can be shared between the ViewControllers either implicitly (because the ViewControllers directly reference the shared Model) or explicitly (because the Model reference gets passed between ViewControllers as part of their instantiation during a screen state transition.)

That's a lot to digest, but if you'll bear with me and trust that I speak from experience, these principles and design patterns will serve you well.

Link to comment
Share on other sites

  • 1 month later...

Sorry to be a bother

I decided that container swaps are my best bet for changing things out. That way I can have the enemies moved from container to container easily.

I don't know how to swap containers though. I tried making the container move 10000 pixels off the screen and I tried to change it by setting it all isRenderable = false; but neither worked, my other container just doesn't show up.

What do I do to swap containers?

Link to comment
Share on other sites

You can pull the existing container out with me.game.world.removeChild(container, true) The second arg instructs the container to keep its children alive. As long as you have a reference held on `container` e.g. in the global namespace called "game", the container won't be GC'd. You can add it later with me.game.world.addChild(container) Use these methods together to swap containers.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

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