Create a survival horror game in Flash tutorial – part 1

Read all posts about "" game

This is the beginning of a new and very funny tutorial serial that introduce some concepts I’ve never seen before in a Flash game.

We’ll learn how to create the engine for a survival horror game.

From Wikipedia: Survival horror is a video game genre in which the player has to survive against often undead or otherwise supernatural enemies, typically in claustrophobic environments and from a third-person perspective.

I am going to create a very dark, claustrophobic ambient. This tutorial is inspired by a spanish tutorial I found here, but I am going beyond the concepts explained there.

Before you continue, I suggest you to read Create a flash game like Security – part 1 tutorial.

Now, as usual, I introduce the objects involved in this project

Survival horror

environment: the obscure room where you found yourself abandoned with no hope to survive

player: the poor victim of evil vampires from outer space

ground: the ground where you’ll find death (note by myself: SHUT UP!!!)

ground.png: the texture mapping the ground

And now, in the first and only frame of our main scene, the actionscript:

walk_speed = 3;
radius = 8;
_root.attachMovie("ground", "ground", _root.getNextHighestDepth());
_root.attachMovie("environment", "environment", _root.getNextHighestDepth());
_root.attachMovie("player", "player", _root.getNextHighestDepth(), {_x:250, _y:200});
_root.createEmptyMovieClip("light", _root.getNextHighestDepth());
player.onEnterFrame = function() {
	if (Key.isDown(Key.LEFT)) {
		this._x -= walk_speed;
	}
	if (Key.isDown(Key.RIGHT)) {
		this._x += walk_speed;
	}
	if (Key.isDown(Key.UP)) {
		this._y -= walk_speed;
	}
	if (Key.isDown(Key.DOWN)) {
		this._y += walk_speed;
	}
	while (_root.environment.hitTest(this._x, this._y+radius, true)) {
		this._y--;
	}
	while (_root.environment.hitTest(this._x, this._y-radius, true)) {
		this._y++;
	}
	while (_root.environment.hitTest(this._x-radius, this._y, true)) {
		this._x++;
	}
	while (_root.environment.hitTest(this._x+radius, this._y, true)) {
		this._x--;
	}
	dist_x = this._x-_root._xmouse;
	dist_y = this._y-_root._ymouse;
	angle = -Math.atan2(dist_x, dist_y);
	this._rotation = angle/(Math.PI/180);
};

Line 1: Initializing player’s speed

Line 2: Player’s radius

Lines 3-6: Attaching movies to stage. The only one we won’t use in this first example is the empty movie clip called “light”, but you’ll discover later what are we doing with it.

Lines 7-31: Those lines manage player movements and are identical to the Create a flash game like Security – part 1 tutorial.

Lines 32-35: determining the angle formed by the line going from the player to mouse pointer, and rotating the player facing the mouse using trigonometry. Almost all my tutorials involve trigonometry, but I suggest you to read Create a flash draw game like Line Rider or others – part 3 if you are looking for more information about trigonometry.

That’s it… nothing new at the moment, and we have a walking player that can turn his “head” as you move the mouse.

Now it’s time to get things a little more complicated.

We are going to simulate a torch. A torch basically shots some rays of light.

Let’s do it.

torch_power = 100;
torch_step = 100;
torch_angle = 60;
torch_angle_step = 20;
walk_speed = 3;
radius = 8;
_root.attachMovie("ground", "ground", _root.getNextHighestDepth());
_root.attachMovie("environment", "environment", _root.getNextHighestDepth());
_root.attachMovie("player", "player", _root.getNextHighestDepth(), {_x:250, _y:200});
_root.createEmptyMovieClip("light", _root.getNextHighestDepth());
player.onEnterFrame = function() {
	if (Key.isDown(Key.LEFT)) {
		this._x -= walk_speed;
	}
	if (Key.isDown(Key.RIGHT)) {
		this._x += walk_speed;
	}
	if (Key.isDown(Key.UP)) {
		this._y -= walk_speed;
	}
	if (Key.isDown(Key.DOWN)) {
		this._y += walk_speed;
	}
	while (_root.environment.hitTest(this._x, this._y+radius, true)) {
		this._y--;
	}
	while (_root.environment.hitTest(this._x, this._y-radius, true)) {
		this._y++;
	}
	while (_root.environment.hitTest(this._x-radius, this._y, true)) {
		this._x++;
	}
	while (_root.environment.hitTest(this._x+radius, this._y, true)) {
		this._x--;
	}
	dist_x = this._x-_root._xmouse;
	dist_y = this._y-_root._ymouse;
	angle = -Math.atan2(dist_x, dist_y);
	this._rotation = angle/(Math.PI/180);
	light.clear();
	light.lineStyle(1, 0xffffff);
	for (x=0; x<=torch_angle; x += (torch_angle/torch_angle_step)) {
		light.moveTo(this._x, this._y);
		ray_angle = angle/(Math.PI/180)-90-(torch_angle/2)+x;
		ray_angle = ray_angle*(Math.PI/180);
		light.lineTo(this._x+(torch_power)*Math.cos(ray_angle), this._y+(torch_power)*Math.sin(ray_angle));
		light.lineTo(this._x, this._y);
	}
};

Line 1: Introducing the torch power. Torch power is the distance, in pixels, that the torch will reach.

Line 2: Torch step... I'll explain later this variable.

Line 3: Torch angle. It's the angle, in degrees, that the torch can reach.

Line 4: Torch angle step. Basically, the number of rays. In this case, I divide 60 degrees by 20 steps, having one ray every 3 degrees. The higher the number of steps, the more accurate the ray of light, the slower the game. It's up to you to balance these values.

Line 40: Clearing the movieclip called "light"

Line 41: Setting the line style to a white, one pixel line. For more information about Flash drawing, read Create a flash draw game like Line Rider or others - part 1.

Line 42: Beginning of the cycle to be executed torch_angle_step times (once per ray of light)

Line 43: Moving the pen to the center of the player

Line 44: Determining the angle (in degrees) of the x-th ray, according to player's angle, using trigonometry. In this case, if the player has an angle of 90, being the torch angle set to 60, rays will go from 60 (90-60/2) to 120 (90+60/2)

Line 45: Converting the angle in radians to be used by Flash Math functions

Line 46: Drawing a line from the actual pen position (player center, according to line 43) with a length of 100 pixels (torch_power) in the angle determined before, using trigonometry

Line 47: Moving the pen again to player center. This line is pretty useless because does the same of line 43 and it's inside the same loop, don't really know why I included in the script when I wrote it...

And that's it... now the player shots the rays of light from his torch.

Next problem is... my rays of light shouldn't go through walls. I have to follow every ray of light and make it stop when it reaches a wall.

torch_power = 100;
torch_step = 100;
torch_angle = 60;
torch_angle_step = 20;
walk_speed = 3;
radius = 8;
_root.attachMovie("ground", "ground", _root.getNextHighestDepth());
_root.attachMovie("environment", "environment", _root.getNextHighestDepth());
_root.attachMovie("player", "player", _root.getNextHighestDepth(), {_x:250, _y:200});
_root.createEmptyMovieClip("light", _root.getNextHighestDepth());
player.onEnterFrame = function() {
	if (Key.isDown(Key.LEFT)) {
		this._x -= walk_speed;
	}
	if (Key.isDown(Key.RIGHT)) {
		this._x += walk_speed;
	}
	if (Key.isDown(Key.UP)) {
		this._y -= walk_speed;
	}
	if (Key.isDown(Key.DOWN)) {
		this._y += walk_speed;
	}
	while (_root.environment.hitTest(this._x, this._y+radius, true)) {
		this._y--;
	}
	while (_root.environment.hitTest(this._x, this._y-radius, true)) {
		this._y++;
	}
	while (_root.environment.hitTest(this._x-radius, this._y, true)) {
		this._x++;
	}
	while (_root.environment.hitTest(this._x+radius, this._y, true)) {
		this._x--;
	}
	dist_x = this._x-_root._xmouse;
	dist_y = this._y-_root._ymouse;
	angle = -Math.atan2(dist_x, dist_y);
	this._rotation = angle/(Math.PI/180);
	light.clear();
	light.lineStyle(1, 0xffffff);
	for (x=0; x<=torch_angle; x += (torch_angle/torch_angle_step)) {
		light.moveTo(this._x, this._y);
		ray_angle = angle/(Math.PI/180)-90-(torch_angle/2)+x;
		ray_angle = ray_angle*(Math.PI/180);
		for (y=1; y<=torch_step; y++) {
			if (environment.hitTest(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle), true)) {
				break;
			}
		}
		light.lineTo(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle));
	}
};

Line 46: This is where I use the torch_step variable defined in line 2. I divide every ray in a number of steps, for every step I check if the ray of light is hitting a wall. If a ray of light hits a wall, it must stop. The higher the number of steps, the more accurate the simulation, the slower the game. Again, it's up to you to balance the game.

Line 47: If the x-th ray of light at its y-th step hits a wall, exit from the loop

Line 48: Draw the ray of light only until the position we found to hit the wall

Here it is our player with a real torch in his hands.

Now it's time to transform the rays of light in an "area of light" as if rays were infinite (like they are in real life). I can do this task only changing a line

torch_power = 100;
torch_step = 100;
torch_angle = 60;
torch_angle_step = 20;
walk_speed = 3;
radius = 8;
_root.attachMovie("ground", "ground", _root.getNextHighestDepth());
_root.attachMovie("environment", "environment", _root.getNextHighestDepth());
_root.attachMovie("player", "player", _root.getNextHighestDepth(), {_x:250, _y:200});
_root.createEmptyMovieClip("light", _root.getNextHighestDepth());
player.onEnterFrame = function() {
	if (Key.isDown(Key.LEFT)) {
		this._x -= walk_speed;
	}
	if (Key.isDown(Key.RIGHT)) {
		this._x += walk_speed;
	}
	if (Key.isDown(Key.UP)) {
		this._y -= walk_speed;
	}
	if (Key.isDown(Key.DOWN)) {
		this._y += walk_speed;
	}
	while (_root.environment.hitTest(this._x, this._y+radius, true)) {
		this._y--;
	}
	while (_root.environment.hitTest(this._x, this._y-radius, true)) {
		this._y++;
	}
	while (_root.environment.hitTest(this._x-radius, this._y, true)) {
		this._x++;
	}
	while (_root.environment.hitTest(this._x+radius, this._y, true)) {
		this._x--;
	}
	dist_x = this._x-_root._xmouse;
	dist_y = this._y-_root._ymouse;
	angle = -Math.atan2(dist_x, dist_y);
	this._rotation = angle/(Math.PI/180);
	light.clear();
	light.lineStyle(1, 0xffffff);
	light.moveTo(this._x, this._y);
	for (x=0; x<=torch_angle; x += (torch_angle/torch_angle_step)) {
		ray_angle = angle/(Math.PI/180)-90-(torch_angle/2)+x;
		ray_angle = ray_angle*(Math.PI/180);
		for (y=1; y<=torch_step; y++) {
			if (environment.hitTest(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle), true)) {
				break;
			}
		}
		light.lineTo(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle));
	}
	light.lineTo(this._x, this._y);
};

As you can see, I move the pen to player's centre only once, in line 42. Then I draw next lines from an end of the ray of light to another in line 51, and I return from the last ray to the player's centre at line 53. This allow me to define an area according to my rays of light.

Now that I have a "realistic" torch, it's time to solve the last problem: I don't have to see anything outside the light of the torch. In order to do it, I'll use a mask.

If you want to know more about masking, read Creation of realistic spheres in Flash with textures and masking.

torch_power = 100;
torch_step = 100;
torch_angle = 60;
torch_angle_step = 20;
walk_speed = 3;
radius = 8;
_root.attachMovie("ground", "ground", _root.getNextHighestDepth());
_root.attachMovie("environment", "environment", _root.getNextHighestDepth());
_root.attachMovie("player", "player", _root.getNextHighestDepth(), {_x:250, _y:200});
_root.createEmptyMovieClip("light", _root.getNextHighestDepth());
player.onEnterFrame = function() {
	if (Key.isDown(Key.LEFT)) {
		this._x -= walk_speed;
	}
	if (Key.isDown(Key.RIGHT)) {
		this._x += walk_speed;
	}
	if (Key.isDown(Key.UP)) {
		this._y -= walk_speed;
	}
	if (Key.isDown(Key.DOWN)) {
		this._y += walk_speed;
	}
	while (_root.environment.hitTest(this._x, this._y+radius, true)) {
		this._y--;
	}
	while (_root.environment.hitTest(this._x, this._y-radius, true)) {
		this._y++;
	}
	while (_root.environment.hitTest(this._x-radius, this._y, true)) {
		this._x++;
	}
	while (_root.environment.hitTest(this._x+radius, this._y, true)) {
		this._x--;
	}
	dist_x = this._x-_root._xmouse;
	dist_y = this._y-_root._ymouse;
	angle = -Math.atan2(dist_x, dist_y);
	this._rotation = angle/(Math.PI/180);
	light.clear();
	light.beginFill(0xffffff, 100);
	light.lineStyle(1, 0xffffff);
	light.moveTo(this._x, this._y);
	for (x=0; x<=torch_angle; x += (torch_angle/torch_angle_step)) {
		ray_angle = angle/(Math.PI/180)-90-(torch_angle/2)+x;
		ray_angle = ray_angle*(Math.PI/180);
		for (y=1; y<=torch_step; y++) {
			if (environment.hitTest(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle), true)) {
				break;
			}
		}
		light.lineTo(this._x+(torch_power/torch_step*y)*Math.cos(ray_angle), this._y+(torch_power/torch_step*y)*Math.sin(ray_angle));
	}
	light.lineTo(this._x, this._y);
	light.endFill();
	ground.setMask(light);
};

Line 41: Beginning to fill the shape I will create.

beginFill wants two arguments: the color to fill and the alpha. In my case, I am using a completely opaque white.

Line 55: Defining the end of filling. Basically you have to call beginFill before you start drawing the shape to fill, and endFill after you finished drawing the shape

Line 56: Masking the ground (the floor texture) with the shape just created. I don't need to mask walls because they are black already.

The result is very good in my opinion (it's a bit slow because we have 5 cpu expensive movies in one page)

The player is moving around the stage in a complete dark room, with only his torch protecting him from evil werepets from deep sea... this is a pretty new concept in Flash games, isn't it?

In next part, I'll introduce enemies and powerups, meanwhile download the source codes and give me feedback.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers