omanel Posted November 18, 2014 Share Posted November 18, 2014 Hi everyone. I have a weird situation. The thing is when I play sprite animation directly it is working fine. But when I make an object and call the animation of that objects sprite animation does not work. It only works when I call it from whithin update method. Can anyone help me out. Thanks! Link to comment Share on other sites More sharing options...
Sam Posted November 18, 2014 Share Posted November 18, 2014 please provide your code so that we can lookup to find the problem. Link to comment Share on other sites More sharing options...
omanel Posted November 18, 2014 Author Share Posted November 18, 2014 So this is the code for the button object:function MButtons(game, socket) { this.game = game;this.sprite = null;this.buttons = [];this.item = [];this.leftPos = 35;this.pipe_width = 828;this.pipe_height = 25;this.btn_height = 55;this.btn_width = 133;this.topPos = 300;this.step = 170;this.pipe_grey = 42;this.pipes = [];this.socket = socket;this.anim = 0;this.resetBtns = 0;this.anim_pipe = 0;this.anim_pipe_lock = 1;this.room = 0; this.playANIM = function(id){this.buttons[id].animations.play('sequence'); // <- this is the method I am calling} this.play_internal = function(id){console.log('Internal ID1: ' + id); this.buttons[id].animations.play('sequence');var self = this;if(this.anim_pipe_lock == 1)this.anim_pipe = 1;setTimeout(function() { self.play_anim(id, self.pipes, self.anim_pipe); }, 1200); }this.setRoom = function(room){this.room = room;}this.initButAnimParams = function(){this.anim_pipe = 1;this.anim = 1;this.anim_pipe_lock = 1;}this.preload = function(){this.game.load.spritesheet('left_buttons', 'assets/platform/left_buttons.png', this.btn_width, this.btn_height);this.game.load.spritesheet('right_buttons', 'assets/platform/right_buttons.png', this.btn_width, this.btn_height);this.game.load.spritesheet('platform', 'assets/platform/pipe.png', this.pipe_width, this.pipe_height);}this.create = function () {//this.sprite = this.game.add.sprite(32, this.game.world.height - 356, 'platform');//this.buttons = this.game.add.group(); for (var i = 0; i < 3; i++) { // pipes this.pipes = this.game.add.sprite(this.leftPos + this.btn_width - this.pipe_grey, this.topPos + this.step*i + this.btn_height/2-this.pipe_height/2, 'platform'); // Enable input. this.pipes.inputEnabled = true; //item.input.start(0, true); this.pipes.name = 'pipe_'+i; this.pipes.scale.setTo(1.9,1); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.pipes.animations.add('sequence', [0, 1], 6, false); this.pipes.animations.add('reset', [0], 3, false); this.pipes.events.onAnimationComplete.add(function(){ console.log("pipe complete"); this.anim_pipe = 0; this.anim_pipe_lock = 0; var me = this; if(this.resetBtns == 0){ this.resetBtns = 1; console.log('this.resetBtns_____: ' + this.resetBtns); //setTimeout(function() {console.log('me.resetBtns: ' + me.resetBtns + ', me.anim: ' + me.anim); me.resetBtns = 1;}, 2000); } else{ this.resetBtns = 0; console.log('this.resetBtns_____: ' + this.resetBtns); //setTimeout(function() {console.log('me.resetBtns: ' + me.resetBtns); me.resetBtns = 0;}, 2000); } //this.pipes[0].inputEnabled = false; //this.pipes[1].inputEnabled = false; //this.pipes[2].inputEnabled = false;}, this); // left buttons this.buttons = this.game.add.sprite(this.leftPos, this.topPos + this.step*i, 'left_buttons'); // Enable input. this.buttons.inputEnabled = true; //item.input.start(0, true); this.buttons.name = 'butt_'+i; var self = this; this.buttons.events.onInputDown.add(this.button_anim, {param1: 'Oljas', self: self}); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.buttons.animations.add('sequence', [0, 1, 2, 3, 4], 3, false); this.buttons.animations.add('reset', [0], 3, false); this.buttons.events.onAnimationComplete.add(function(){ console.log("but right complete"); this.anim = 0;}, this); // right buttons this.buttons[i+3] = this.game.add.sprite(this.pipe_width + this.leftPos + 850 - this.pipe_grey, this.topPos + this.step*i, 'right_buttons'); // Enable input. this.buttons[i+3].inputEnabled = true; //item.input.start(0, true); this.buttons[i+3].name = 'butt_'+(i+3); this.buttons[i+3].events.onInputDown.add(this.button_anim, {param1: 'Oljas', self: self}); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.buttons[i+3].animations.add('sequence', [0, 1, 2, 3, 4], 3, false); this.buttons[i+3].animations.add('reset', [0], 3, false); this.buttons[i+3].events.onAnimationComplete.add(function(){ console.log("but left complete"); this.anim = 0; //this.resetBtns = 0;}, this); }}this.update = function(){ }this.animH = function(){console.log('Pipe animation finished playing!');}this.disableButtons = function(){for (var i = 0; i < 3; i++) { // Disable input. this.buttons.inputEnabled = false; this.buttons[i+3].inputEnabled = false; }}this.enableButtons = function(){for (var i = 0; i < 3; i++) { // Disable input. this.buttons.inputEnabled = true; this.buttons[i+3].inputEnabled = true; }}this.button_anim = function(items) {var id = items.name.substr(5);var type = items.name.substr(0, 4);var self = this.self;//console.log('this.self.resetBtns: ' + this.self.resetBtns);//this.self.play_anim(13, this.self.buttons[id%3].name);//this.self. //console.log('items: '+id+', type: '+type + ', pipes: '+this.param1); this.self.socket.emit('buttons_pressed', {button_id: id, room: self.room}); items.play('sequence'); setTimeout(function() { self.play_anim(id, self.pipes, 1); }, 1200);}this.getGCTX = function(args){switch(args) { case 'pipe': return this.pipes; break; case 'button': return this.buttons; break; default: return this.pipes;}}this.play_anim = function(id, buts, mode){if(mode == 1){buts[id%3].animations.play('sequence'); // calling pipe animation//buts[id%3].scale.setTo(1.5,1);} //console.log('id: ' + id + ', but name: '+buts[id%3].name); }this.resetButAnim = function(){for(var i = 0; i < 3; i++){this.buttons.animations.play('reset');this.pipes.animations.play('reset');this.buttons[i+3].animations.play('reset');}}}; and I am trying to call it from this file: <!doctype html><html> <head> <meta charset="UTF-8" /> <title>hello phaser!</title> <script src="phaser.min.js"></script> <script src="socket.io.js"></script> <script src="jquery.min.js"></script> <script src="menu.js"></script> <script src="buttons.js"></script> <script src="player.js"></script> </head> <body> <script type="text/javascript"> //game.load.script('simon.js', 'simon.js'); window.onload = function() { var game = new Phaser.Game(1900, 1200, Phaser.AUTO, '', { preload: preload, create: create, update: update }); var player_proximity = 0; //var server_left_pos = 0; var player_width = 277; var player_count = 0; var player_loaded = 0; var item; var debuf_enabled = 1; var menu; var button_id = 0; var player = []; var buttons; var text_score; var player_name; var game_lock = 0; var room_id = 'ppnlahhfbn89179830'; var player_id = generateID(); var timer = 15;//15; var timer_value = 15;//15; var role = 'none'; var token = 0; //var resetButAnim = 0; var name = 'FingerTouch'; var level = 0; var timerStop = 0; var seconds = 0; //var socket = io('https://nodeserver-c9-omanel.c9.io' {'sync disconnect on unload' : true}); var socket = io('127.0.0.1:3001', {'sync disconnect on unload' : true}); socket.on('spawn', function(msg){ spawn(msg); // spawn players }); socket.on('player_moving', function(msg){ player_moving(msg); }); socket.on('player_left', function(msg){ player_left(msg); }); socket.on('buttons_pressed', function(msg){ buttons_pressed(msg); }); socket.on('initNextRound', function(msg){ initNextRound(msg); }); socket.on('full', function(msg){ debug('Server instructed: ' + msg.msg); }); function preload () { preloadGameObjects(); } function create () { createGameObjects(); } function update(){ countDown(); if(game_lock != 0){ for(var i = 0; i < player.length; i++) player.update(); if(buttons.anim == 1){ //buttons.play_internal(button_id); } if(buttons.resetBtns == 1){ //buttons.resetButAnim(); } } } function countDown(){ var secs = Math.floor(game.time.time / 1000) % 60; if(secs != seconds){ seconds = secs; if((timer >= 0)&&(timerStop == 0)){ var mtext; if(timer > 5){ mtext = game.add.text(game.world.centerX, 100, timer, { font: "40px Arial", fill: "#ffffff", align: "center" }); } else{ mtext = game.add.text(game.world.centerX, 100, timer, { font: "47px Arial", fill: "#ff0000", align: "center" }); } game.add.tween(mtext.scale).to( { x: 0, y: 0 }, 1000, Phaser.Easing.Back.Out, true, 500); }else{ // TODO kick out the player who is overdue with his attacks! } timer--; } } function preloadGameObjects(){ buttons = new MButtons(game, socket); buttons.preload(); debug('player_count_: ' + player_count); player.push(new Player(game, 0, socket)); player[player_count].preload(); } function createGameObjects(){ game_lock = 1; player[player_count].create(); buttons.create(); socket.emit('handshake', {room_id : room_id, y_pos : player[player_count].head.y, fy_pos : player[player_count].feet.y, player_id : player_id, mode : 'random game', fname : name}); } function pipe_click(item){ console.log(item.name); } function select(item, pointer) { item.alpha = 0; } function release(item, pointer) { item.alpha = 1; } function debug(text){ if(debuf_enabled){ console.log(text); } } function generateID(){ var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'l', 'm', 'n', 'o', 'p']; var index = Math.floor((Math.random() * 13) + 0); var digits = Math.floor((Math.random() * 90000000) + 1); var res = ''; for(var i = 0; i < 10; i++){ index = Math.floor((Math.random() * 13) + 0); res = res + letters[index]; } res = res + digits; return res; } function computeLocationByGrid(grid){ return grid * (player_width + player_proximity); } // This function returns the index of the player if pid matches the player id; Otherwise it retunrs -1 function check_player_existance(pid){ var res = -1; for(var i = 1; i < player.length; i++){ if(player.getPid() == pid) res = i; } return res; } function spawn(msg){ debug('From server: grid: ' + msg.grid + ', player_id: ' + msg.player_id + ', player_count: ' + player_count + ', room_id: ' + msg.room_id + ', token: ' + msg.token); if(msg.player_id == player_id){ player[0].setHeadXPos(computeLocationByGrid(msg.grid)); player[0].setHeadYPos(msg.y_pos); player[0].setFeetXPos(computeLocationByGrid(msg.grid)); player[0].setPid(msg.player_id); player[0].setName(msg.fname, ' Bobbittooo'); role = msg.role; token = msg.token; debug('Token received: ' + token); if(role == 'alpha'){ //initiateCountDown(); // let the player who created the random game start the count down } if(token != 1){ buttons.disableButtons(); }else{ buttons.enableButtons(); //player_name = game.add.text(20, 20, msg.fname, { font: "25px Arial", fill: "#fff094", align: "center" }); } debug('Me spawning: ' + player_count + ', role: ' + role); room_id = msg.room_id; player[0].setRoomID(room_id); buttons.setRoom(room_id); player_name = game.add.text(270, 20, "YourID: " + msg.player_id + ", Role: " + role, { font: "15px Arial", fill: "#ff0044", align: "center" }); }else{ if(check_player_existance(msg.player_id) == -1){ player_count = player.length; player.push(new Player(game, computeLocationByGrid(msg.grid), socket)); //player[player_count] = new Player(game, computeLocationByGrid(msg.grid), socket); player[player_count].preload(); player[player_count].create(); player[player_count].setPid(msg.player_id); player[player_count].head.inputEnabled = false; player[player_count].feet.inputEnabled = false; player[player_count].setHeadYPos(msg.y_pos); player[player_count].setRoomID(room_id); if(room_id == msg.player_id){ debug('msg.fy_pos: ' + msg.fy_pos); player[player_count].setFeetYPos(msg.fy_pos); } }else{ player[check_player_existance(msg.player_id)].setHeadXPos(computeLocationByGrid(msg.grid)); player[check_player_existance(msg.player_id)].setFeetXPos(computeLocationByGrid(msg.grid)); player[check_player_existance(msg.player_id)].setHeadYPos(msg.y_pos); } debug('player_count: ' + player_count); } } function player_moving(msg){ debug('Msg received from server: y_pos: ' + msg.y_pos + ', pid: ' + msg.player_id); var index = check_player_existance(msg.player_id); if(index != -1){ if(msg.type == 'head') player[index].setHeadYPos(msg.y_pos); else player[index].setFeetYPos(msg.y_pos); } } function player_left(msg){ debug('player left'); for(var i = 0; i<player.length;i++){ debug('player['+ i +'].pid: ' + player.pid + ' <=> ' + msg.player_id + ', length: ' + player.length); if(player.pid == msg.player_id){ player.eliminate(); player.splice(i, 1); debug('Player left with id: ' + msg.player_id + ', player num: ' + player.length); } } } function buttons_pressed(msg){ debug('Msg received from server: button_id: ' + msg.button_id); buttons.playANIM(msg.button_id); // <- this is where I am trying to call it // TODO: put setTiemout here as a callback so that clear animation is called with delay of 1 sec buttons.initButAnimParams(); // so that animation can be called from upate method buttons.play_internal(0); button_id = msg.button_id; timerStop = 1; seconds = 0; buttons.disableButtons(); socket.emit('sum_up', {room: room_id}); //debug('Button name: '+but[msg.button_id].name); } function initNextRound(msg){ // let us introduce a delay so that players have chance to see what is going on debug('Trying the timeout.......'); setTimeout(function() { nextRound(msg); }, 3200); } function nextRound(msg){ debug('Next round: ' + msg.pid); if((msg.role == role)&&(msg.pid == player_id)){ player_name = game.add.text(20, 25, player[0].getName() + ' MI TURN!!', { font: "25px Arial", fill: "#fff094", align: "center" }); //timer = timer_value; // timerStop = 0; //seconds = 0; //initiateTimer(); buttons.enableButtons(); //initiateCountDown(); debug('My turn'); }else if((role == 'alpha')&&(msg.pid == room_id)){ // if it is bots turn help him to run the countdown //timer = timer_value; //timerStop = 0; //seconds = 0; //initiateCountDown(); //initiateTimer(); debug('Bots turn'); } timer = timer_value; timerStop = 0; seconds = 0; } }; </script> </body></html> Link to comment Share on other sites More sharing options...
Sam Posted November 18, 2014 Share Posted November 18, 2014 a snipped had been enough..your structure is a bit uncommon to me. this.playANIM = function(id){this.buttons[id].animations.play('sequence'); // <- this is the method I am calling}I think this looses the game reference, or am I wrong?why not: function playANIM(id){ this.button[id].animations.play('sequence');} Link to comment Share on other sites More sharing options...
omanel Posted November 18, 2014 Author Share Posted November 18, 2014 I tried it, still not working. Here is the simplified version of the code: function MButtons(game, socket) { this.game = game;this.sprite = null;this.buttons = []; function playANIM(id){this.button[id].animations.play('sequience');} this.preload = function(){this.game.load.spritesheet('left_buttons', 'assets/platform/left_buttons.png', 133, 55);}this.create = function () {//this.sprite = this.game.add.sprite(32, this.game.world.height - 356, 'platform');//this.buttons = this.game.add.group(); for (var i = 0; i < 2; i++) { this.buttons = this.game.add.sprite(0, i*100, 'left_buttons'); // Enable input. this.buttons.inputEnabled = true; //item.input.start(0, true); this.buttons.name = 'butt_'+i; var self = this; this.buttons.events.onInputDown.add(this.button_anim, {param1: 'Oljas', self: self}); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.buttons.animations.add('sequence', [0, 1, 2, 3, 4], 3, false); this.buttons.animations.add('reset', [0], 3, false); this.buttons.events.onAnimationComplete.add(function(){ console.log("but right complete");}, this); }}this.button_anim = function(items) { var self = this.self; items.play('sequence'); }this.update = function(){ }}; And here i want to call it: <!doctype html><html> <head> <meta charset="UTF-8" /> <title>hello phaser!</title> <script src="phaser.min.js"></script> <script src="socket.io.js"></script> <script src="jquery.min.js"></script> <script src="menu.js"></script> <script src="buttons.js"></script> <script src="player.js"></script> </head> <body> <script type="text/javascript"> //game.load.script('simon.js', 'simon.js'); window.onload = function() { var game = new Phaser.Game(1900, 1200, Phaser.AUTO, '', { preload: preload, create: create, update: update }); var player_proximity = 0; //var server_left_pos = 0; var player_width = 277; var player_count = 0; var player_loaded = 0; var item; var debuf_enabled = 1; var menu; var button_id = 0; var player = []; var buttons; var text_score; var player_name; var game_lock = 0; var room_id = 'ppnlahhfbn89179830'; var player_id = generateID(); var timer = 15;//15; var timer_value = 15;//15; var role = 'none'; var token = 0; //var resetButAnim = 0; var name = 'FingerTouch'; var level = 0; var timerStop = 0; var seconds = 0; //var socket = io('https://nodeserver-c9-omanel.c9.io' {'sync disconnect on unload' : true}); var socket = io('127.0.0.1:3001', {'sync disconnect on unload' : true}); socket.on('buttons_pressed', function(msg){ buttons.playANIM(1); }); function preload () { preloadGameObjects(); } function create () { createGameObjects(); } function preloadGameObjects(){ buttons = new MButtons(game, socket); buttons.preload(); } function createGameObjects(){ buttons.create(); } function update(){ } }; </script> </body></html> Link to comment Share on other sites More sharing options...
omanel Posted November 18, 2014 Author Share Posted November 18, 2014 However if I put the code inside update function the animation works Link to comment Share on other sites More sharing options...
Sam Posted November 18, 2014 Share Posted November 18, 2014 what's about inside the create function? everything should be stored there.this.button[id].animations.play('sequience'); <- a typo (I did it above) Link to comment Share on other sites More sharing options...
omanel Posted November 18, 2014 Author Share Posted November 18, 2014 Sorry what do u mean by store it in create function?All I want is to be able to call playANIM function. Link to comment Share on other sites More sharing options...
Sam Posted November 18, 2014 Share Posted November 18, 2014 yeah, but normally you have 3 methods: preload, create and updatethe playANIM function is written into the create part.I do not want to say which structure is better etc. but the way your structure is organizes, I haven't seen so often before.common structures that looks familiar to me is : prototyping:preload: function(){ //preload your assets: images, sound, font ....},create: function(){ function playANIM(id){ //.. do animation stuff }}update: function(){ //.. do collision stuff here}I know that this isn't the solution for your question, but maybe it is because you are not doing the animation logic into the create part if phaser? Link to comment Share on other sites More sharing options...
omanel Posted November 19, 2014 Author Share Posted November 19, 2014 Ok. so you mean that in phaser the animation related functions need to be placed in create function? OK, I'll try. Thanks for the tip! Link to comment Share on other sites More sharing options...
omanel Posted November 19, 2014 Author Share Posted November 19, 2014 I totally restructered my code to use prototypes as you suggested, and put the function inside the create function. Still no result. I am at a loss (( This is how I did it:MButtons = function(game, socket) { this.game = game;this.sprite = null;this.buttons = [];this.item = [];this.leftPos = 35;this.pipe_width = 828;this.pipe_height = 25;this.btn_height = 55;this.btn_width = 133;this.topPos = 300;this.step = 170;this.pipe_grey = 42;this.pipes = [];this.socket = socket;this.anim = 0;this.resetBtns = 0;this.anim_pipe = 0;this.anim_pipe_lock = 1;this.room = 0;};MButtons.prototype = {/*this.play_internal = function(id){console.log('Internal ID1: ' + id); this.buttons[id].animations.play('sequence');var self = this;if(this.anim_pipe_lock == 1)this.anim_pipe = 1;setTimeout(function() { self.play_anim(id, self.pipes, self.anim_pipe); }, 1200); }*/setRoom: function(room){this.room = room;}, initButAnimParams: function(){this.anim_pipe = 1;this.anim = 1;this.anim_pipe_lock = 1;}, preload: function () {this.game.load.spritesheet('left_buttons', 'assets/platform/left_buttons.png', this.btn_width, this.btn_height);this.game.load.spritesheet('right_buttons', 'assets/platform/right_buttons.png', this.btn_width, this.btn_height);this.game.load.spritesheet('platform', 'assets/platform/pipe.png', this.pipe_width, this.pipe_height);},create: function () {//this.sprite = this.game.add.sprite(32, this.game.world.height - 356, 'platform');//this.buttons = this.game.add.group(); for (var i = 0; i < 3; i++) { // pipes this.pipes = this.game.add.sprite(this.leftPos + this.btn_width - this.pipe_grey, this.topPos + this.step*i + this.btn_height/2-this.pipe_height/2, 'platform'); // Enable input. this.pipes.inputEnabled = true; //item.input.start(0, true); this.pipes.name = 'pipe_'+i; this.pipes.scale.setTo(1.9,1); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.pipes.animations.add('sequence', [0, 1], 6, false); this.pipes.animations.add('reset', [0], 3, false); this.pipes.events.onAnimationComplete.add(function(){ console.log("pipe complete"); this.anim_pipe = 0; this.anim_pipe_lock = 0; var me = this; if(this.resetBtns == 0){ this.resetBtns = 1; console.log('this.resetBtns_____: ' + this.resetBtns); //setTimeout(function() {console.log('me.resetBtns: ' + me.resetBtns + ', me.anim: ' + me.anim); me.resetBtns = 1;}, 2000); } else{ this.resetBtns = 0; console.log('this.resetBtns_____: ' + this.resetBtns); //setTimeout(function() {console.log('me.resetBtns: ' + me.resetBtns); me.resetBtns = 0;}, 2000); } //this.pipes[0].inputEnabled = false; //this.pipes[1].inputEnabled = false; //this.pipes[2].inputEnabled = false;}, this); // left buttons this.buttons = this.game.add.sprite(this.leftPos, this.topPos + this.step*i, 'left_buttons'); // Enable input. this.buttons.inputEnabled = true; //item.input.start(0, true); this.buttons.name = 'butt_'+i; var self = this; this.buttons.events.onInputDown.add(this.button_anim, {param1: 'Oljas', self: self}); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.buttons.animations.add('sequence', [0, 1, 2, 3, 4], 3, false); this.buttons.animations.add('reset', [0], 3, false); this.buttons.events.onAnimationComplete.add(function(){ console.log("but right complete"); this.anim = 0;}, this); // right buttons this.buttons[i+3] = this.game.add.sprite(this.pipe_width + this.leftPos + 850 - this.pipe_grey, this.topPos + this.step*i, 'right_buttons'); // Enable input. this.buttons[i+3].inputEnabled = true; //item.input.start(0, true); this.buttons[i+3].name = 'butt_'+(i+3); this.buttons[i+3].events.onInputDown.add(this.button_anim, {param1: 'Oljas', self: self}); //item.events.onInputUp.add(release); //item.events.onInputOut.add(moveOff); //cubem.getAt(i).alpha = 0; this.buttons[i+3].animations.add('sequence', [0, 1, 2, 3, 4], 3, false); this.buttons[i+3].animations.add('reset', [0], 3, false); this.buttons[i+3].events.onAnimationComplete.add(function(){ console.log("but left complete"); this.anim = 0; //this.resetBtns = 0;}, this); } function playANIM(id){ this.pipes[id].animations.play('sequence'); } }, update: function(){ } }; Link to comment Share on other sites More sharing options...
Sam Posted November 20, 2014 Share Posted November 20, 2014 this.buttons[i+3].animations.add('sequence', [0, 1, 2, 3, 4], 3, false);add(name, frames, frameRate, loop, useNumericIndex) → {Phaser.Animation} name = correctframes = correctloop = error: false or true (you have put in the value 3)index = error: you have input falseMaybe it is solved by simply deleting the "3". Link to comment Share on other sites More sharing options...
Recommended Posts